SOURCE

"use strict";
window.onload = function() {

  const rayHexMin = 80;
  const rayHexMax = 160;
  const amplNoise = 30;
  const minPerX = 1;
  const maxPerX = 5;
  const minPerY = 1;
  const maxPerY = 5;

  let canv, ctx; // canvas and context
  let maxx, maxy;

  let grid;
  let nbx, nby;
  let rayHex;

  let tbSides;

/* for animation */
  let events = [];

// shortcuts for Math.…

  const mrandom = Math.random;
  const mfloor = Math.floor;
  const mround = Math.round;
  const mceil = Math.ceil;
  const mabs = Math.abs;
  const mmin = Math.min;
  const mmax = Math.max;

  const mPI = Math.PI;
  const mPIS2 = Math.PI / 2;
  const m2PI = Math.PI * 2;
  const msin = Math.sin;
  const mcos = Math.cos;
  const matan2 = Math.atan2;

  const mhypot = Math.hypot;
  const msqrt = Math.sqrt;

  const rac3   = msqrt(3);
  const rac3s2 = rac3 / 2;
  const mPIS3 = Math.PI / 3;

//-----------------------------------------------------------------
  function alea (min, max) {
// random number [min..max[ . If no max is provided, [0..min[

    if (typeof max == 'undefined') return min * mrandom();
    return min + (max - min) * mrandom();
  }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  function intAlea (min, max) {
// random integer number [min..max[ . If no max is provided, [0..min[

    if (typeof max == 'undefined') {
      max = min; min = 0;
    }
    return mfloor(min + (max - min) * mrandom());
  } // intAlea

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

function Noise1DOneShot (period, min = 0, max = 1, random) {
/* returns a 1D single-shot noise generator.
   the (optional) random function must return a value between 0 and 1
  the returned function has no parameter, and will return a new number every tiime it is called.
  If the random function provides reproductible values (and is not used elsewhere), this
  one will return reproductible values too.
  period should be > 1. The bigger period is, the smoother output noise is
*/
  random = random || Math.random;
  let currx = random(); // start with random offset
  let y0 = min + (max - min) * random(); // 'previous' value
  let y1 = min + (max - min) * random(); // 'next' value
  let dx = 1 / period;

  return function() {
    currx += dx;
    if (currx > 1) {
      currx -= 1;
      y0 = y1;
      y1 = min + (max - min) * random();
    }
    let z = (3 - 2 * currx) * currx * currx;
    return z * y1 + (1 - z) * y0;
  }
} // Noise1DOneShot


//------------------------------------------------------------------------
// class Hexagon
let Hexagon;
{ // scope for Hexagon

let orgx, orgy;

Hexagon = function (kx, ky) {

  this.kx = kx;
  this.ky = ky;
  this.neighbours = [];

  this.orient = intAlea(6);

} // function Hexagon

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/* static method */

Hexagon.dimensions = function () {
// coordinates of center of hexagon [0][0]
  orgx = (maxx - rayHex * (1.5 * nbx + 0.5)) / 2  + rayHex; // obvious, no ?
  orgy = (maxy - (rayHex * rac3 * (nby + 0.5))) / 2 + rayHex * rac3; // yet more obvious

} // Hexagon.dimensions

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.size = function() {
/* computes screen sizes / positions
*/
// centre
  this.xc = orgx + this.kx * 1.5 * rayHex;
  this.yc = orgy + this.ky * rayHex * rac3;
  if (this.kx & 1) this.yc -= rayHex * rac3s2; // odd columns

  this.vertices = [[],[],[],[],[],[]] ;

// x coordinates, from left to right
  this.vertices[3][0] = this.xc - rayHex;
  this.vertices[2][0] = this.vertices[4][0] = this.xc - rayHex  / 2;
  this.vertices[1][0] = this.vertices[5][0] = this.xc + rayHex / 2;
  this.vertices[0][0] = this.xc + rayHex;
// y coordinates, from top to bottom
  this.vertices[4][1] = this.vertices[5][1] = this.yc - rayHex * rac3s2;
  this.vertices[0][1] = this.vertices[3][1] = this.yc;
  this.vertices[1][1] = this.vertices[2][1] = this.yc + rayHex * rac3s2;
// get a 2nd copy of table to avoid many % 6 calculations later
  this.vertices = this.vertices.concat(this.vertices);

  this.extCenters = [[],[],[],[],[],[]] ;
  let dxc = rayHex;
  let dyc = rayHex / rac3;
  this.rad1 = dyc; // radius fir circles with center in extCenters

  for (let k = 0; k < 6; ++k) {
    this.extCenters[k][0] = this.xc + dxc * mcos(k * mPIS3) - dyc * msin(k * mPIS3);
    this.extCenters[k][1] = this.yc + dxc * msin(k * mPIS3) + dyc * mcos(k * mPIS3);
  }
// get a 2nd copy of table to avoid many % 6 calculations later
  this.extCenters = this.extCenters.concat(this.extCenters);

  this.extCentersB = [[],[],[],[],[],[]] ;
// x coordinates, from left to right
  this.extCentersB[3][0] = this.xc - 2 * rayHex;
  this.extCentersB[2][0] = this.extCentersB[4][0] = this.xc - rayHex;
  this.extCentersB[1][0] = this.extCentersB[5][0] = this.xc + rayHex;
  this.extCentersB[0][0] = this.xc + 2 * rayHex;

// y coordinates, from top to bottom
  this.extCentersB[4][1] = this.extCentersB[5][1] = this.yc - rayHex * rac3;
  this.extCentersB[3][1] = this.extCentersB[0][1] = this.yc;
  this.extCentersB[2][1] = this.extCentersB[1][1] = this.yc + rayHex * rac3;
// get a 2nd copy of table to avoid many % 6 calculations later
  this.extCentersB = this.extCentersB.concat(this.extCentersB);

} // Hexagon.prototype.size

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawHexagon = function(hue) {

  if (! this.vertices) this.size();
  let ctxGrid = ctx;
    ctxGrid.beginPath();
    ctxGrid.moveTo (this.vertices[0][0], this.vertices[0][1]);
    ctxGrid.lineTo (this.vertices[1][0], this.vertices[1][1]);
    ctxGrid.lineTo (this.vertices[2][0], this.vertices[2][1]);
    ctxGrid.lineTo (this.vertices[3][0], this.vertices[3][1]);
    ctxGrid.lineTo (this.vertices[4][0], this.vertices[4][1]);
    ctxGrid.lineTo (this.vertices[5][0], this.vertices[5][1]);
    ctxGrid.lineTo (this.vertices[0][0], this.vertices[0][1]);
    ctxGrid.strokeStyle = '#8FF';
    ctxGrid.lineWidth = 0.5;
    ctxGrid.strokeStyle = `hsl(${hue},100%,60%)`;
    ctxGrid.stroke();
} // Hexagon.prototype.drawHexagon

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawArc1 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R;

  xc = x0 = rayHex * (1 - alpha / 2);
  y0 = alpha * rayHex * rac3s2;
  yc = xc / rac3
  R = rayHex * (1 - 2 * alpha) / rac3;

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);
  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R, (2 * vert - 3) * mPI / 6,(2 * vert + 5) * mPI / 6 , true);

}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawArc6 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R;

  xc = x0 = rayHex * (1 - alpha / 2);
  y0 = -alpha * rayHex * rac3s2;
  yc = -xc / rac3
  R = rayHex * (1 - 2 * alpha) / rac3;

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);
  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R, (2 * vert + 3) * mPI / 6,(2 * vert - 5) * mPI / 6);

}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawArc2 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R, angle;

  xc = x0 = rayHex;
  y0 = 0;
  yc = ((0.5 + alpha) * (0.5 + alpha) + 0.75) / rac3 * rayHex;
  R = yc;
  angle = matan2(rayHex * ( 1/2 + alpha), yc - rayHex * rac3s2);

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);
  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R, (2 * vert - 3) * mPI / 6,(2 * vert - 3) * mPI / 6 - angle , true);

}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawArc7 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R, angle;

  x0 = rayHex * (0.5 - alpha);
  y0 = rayHex * rac3s2;
  xc = rayHex;
  yc = ((0.5 + alpha) * (0.5 + alpha) + 0.75) / rac3 * rayHex;
  R = yc;
  angle = matan2(rayHex * ( 1/2 + alpha), yc - rayHex * rac3s2);

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);
  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R,(2 * vert - 3) * mPI / 6 - angle , (2 * vert - 3) * mPI / 6);

}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawArc3 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R, xb, yb, angle;

  xc = x0 = rayHex;
  y0 = 0;
  xb = rayHex * (- 1 / 2 - alpha / 2);
  yb = (1 - alpha) * rac3s2 * rayHex;

  yc = (yb * yb + xb * xb - x0 * x0 + 2 * x0 * xc  - 2 * xb * xc) / 2 / yb;
  R = yc;

  angle = matan2(rayHex - xb, yc - yb);

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);

  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R, (2 * vert - 3) * mPI / 6,(2 * vert - 3) * mPI / 6 - angle , true);

}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawArc8 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R, xb, yb, angle;

  x0 = -(1 + alpha) / 2 * rayHex;
  y0 = (1 - alpha) * rac3s2 * rayHex;
  xc = rayHex;
  xb = rayHex * (- 1 / 2 - alpha / 2);
  yb = (1 - alpha) * rac3s2 * rayHex;

  yc = (yb * yb + xb * xb - rayHex * rayHex + 2 * rayHex * xc  - 2 * xb * xc) / 2 / yb;
  R = yc;

  angle = matan2(rayHex - xb, yc - yb);

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);

  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R, (2 * vert - 3) * mPI / 6 - angle , (2 * vert - 3) * mPI / 6);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawArc4 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R, xb, yb, angle;

  xc = x0 = rayHex;
  y0 = 0;
  yc = - ((0.5 + alpha) * (0.5 + alpha) + 0.75) / rac3 * rayHex;
  R = - yc;
  angle = matan2(rayHex * ( 1/2 + alpha),  R - rayHex * rac3s2);

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);
  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R, (2 * vert + 3) * mPI / 6,(2 * vert + 3) * mPI / 6 + angle);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Hexagon.prototype.drawArc9 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R, angle;

  x0 = (0.5 - alpha) * rayHex;
  y0 = - rayHex * rac3s2;
  xc = rayHex;
  yc = - ((0.5 + alpha) * (0.5 + alpha) + 0.75) / rac3 * rayHex;
  R = - yc;
  angle = matan2(rayHex * ( 1/2 + alpha),  R - rayHex * rac3s2);

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);
  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R,(2 * vert + 3) * mPI / 6 + angle, (2 * vert + 3) * mPI / 6, true);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hexagon.prototype.drawArc5 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R, xb, yb, angle;

  xc = x0 = rayHex;
  y0 = 0;
  xb = rayHex * (- 1 / 2 - alpha / 2);
  yb = (alpha - 1) * rac3s2 * rayHex;

  yc = (yb * yb + xb * xb - x0 * x0 + 2 * x0 * xc  - 2 * xb * xc) / 2 / yb;
  R = - yc;

  angle = matan2(rayHex - xb, R + yb);

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);

  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R, (2 * vert + 3) * mPI / 6,(2 * vert + 3) * mPI / 6 + angle);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hexagon.prototype.drawArc10 = function (vert, alpha, first = false) {

  let x0, y0, xc, yc, R, xb, yb, angle;

  x0 = -(1 + alpha) / 2 * rayHex;
  y0 = -(1 - alpha) * rayHex * rac3s2;
  xc = rayHex;
  xb = rayHex * (- 1 / 2 - alpha / 2);
  yb = (alpha - 1) * rac3s2 * rayHex;

  yc = (yb * yb + xb * xb - rayHex * rayHex + 2 * rayHex * xc  - 2 * xb * xc) / 2 / yb;
  R = - yc;

  angle = matan2(rayHex - xb, R + yb);

  [x0, y0] = rotate([x0, y0], 2 * vert);
  [xc, yc] = rotate([xc, yc], 2 * vert);

  if (first) ctx.moveTo (this.xc + x0, this.yc + y0);
  ctx.arc (this.xc + xc, this.yc + yc, R,(2 * vert + 3) * mPI / 6 + angle, (2 * vert + 3) * mPI / 6, true);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

/* returns a cell's neighbour
  keep track of it for future request
  defines itself as its neighbour's neighbour to reduce calculations

  returns false if no neighbour
*/

Hexagon.prototype.neighbour = function(side) {

  let neigh = this.neighbours[side];
  if (neigh instanceof(Hexagon)) return neigh; // known neighbour
  if (neigh === false) return false; // known for no neighbour
//  do not know yet

  if (this.kx & 1) {
    neigh =  {kx: this.kx + [1, 0, -1, -1, 0, 1][side],
              ky: this.ky + [0, 1, 0, -1, -1, -1][side]};
  } else {
    neigh = {kx: this.kx + [1, 0, -1, -1, 0, 1][side],
             ky: this.ky + [1, 1, 1, 0, -1, 0][side]};
  }
  if (neigh.kx < 0 || neigh.ky <0 || neigh.kx >= nbx || neigh.ky >= nby) {
    this.neighbours[side] = false;
    return false;
  }
  neigh = grid[neigh.ky][neigh.kx];
  this.neighbours[side] = neigh;
  neigh.neighbours[(side + 3) % 6] = this;
  return neigh;

} // Hexagon.prototype.neighbour

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hexagon.prototype.drawSide = function(side, alpha, first){

  switch (this.styles[side]) {
    case 'a' :
      this.drawArc6((side + 1) % 6, alpha, first);
      break;
    case 'b' :
      this.drawArc7((side + 5) % 6, 1 - alpha, first);
      this.drawArc2((side + 5) % 6, alpha);
      break;
    case 'c' :
      this.drawArc8((side + 4) % 6, 1 - alpha, first);
      this.drawArc3((side + 4) % 6, alpha);
      break;
    case 'd' :
      this.drawArc9((side + 2) % 6, alpha, first);
      this.drawArc4((side + 2) % 6, 1 - alpha);
      break;
    case 'e' :
      this.drawArc10((side + 3) % 6, alpha, first);
      this.drawArc5((side + 3) % 6, 1 - alpha);
      break;
  }
} // Hexagon.prototype.drawSide

} // scope for Hexagon

//------------------------------------------------------------------------
function rotate (p, k) {

// turn the given point after a rotation of k / 12 turns (k * PI / 6) around the origin
  let s = msin (k * mPI / 6);
  let c = mcos (k * mPI / 6);
  return [p[0] * c - p[1] * s,
          p[0] * s + p[1] * c];
} // rotate

//------------------------------------------------------------------------

function createGrid() {
/* create the grid of Hexagons
  but does NOT define the crossings between dots inside an hexagon
*/
  let hexa;

  let tbPer = [];
  let tbKind = [];
  let perx = intAlea(minPerX, maxPerX + 1);
  let pery = intAlea(minPerY, maxPerY + 1);

  if (intAlea(3) == 0) { // aperiodic
    perx = nbx;
    pery = nby;
  }

  for (let ky = 0; ky < pery; ++ky) {
    tbPer[ky] = [];
    tbKind[ky] = [];
    for (let kx = 0; kx < perx; ++kx) {
      tbPer[ky][kx] = intAlea(6); // random orientation
      tbKind[ky][kx] = intAlea(3);
    } // for kx
  } // for ky

  grid = [];

  for (let ky = 0; ky < nby; ++ky) {
    grid[ky] = []
    for (let kx = 0; kx < nbx; ++kx) {
      hexa = new Hexagon(kx, ky);
      grid[ky][kx] = hexa;
      grid[ky][kx].orient = tbPer[ky % pery][kx % perx];
      grid[ky][kx].kind = tbKind[ky % pery][kx % perx];
    } // for kx
  } // for ky
} // createGrid

//------------------------------------------------------------------------
/* creates a Side and initialize it with a first cell
in most cases, a second cell will be added later, excepts on the edges of the grid.
*/

function Side(cell, side, styl) {

  let fdHue;
  this.cell1 = cell;
  this.side1 = side;

  this.hue = intAlea(360);
  this.satur = intAlea(90,100);
  if (! cell.styles) cell.styles = [];
  cell.styles[side] = styl;
  tbSides.push (this);
}

Side.prototype.addCell = function (cell, side, styl) {
  this.cell2 = cell;
  this.side2 = side; // normally useless, but can make things more easy
  if (! cell.styles) cell.styles = [];
  cell.styles[side] = styl;
}

Side.prototype.drawSide = function(alpha) {
/* alpha in range [0..0.5]
*/

  ctx.beginPath();

  this.cell1.drawSide(this.side1, alpha, true);
  if (this.cell2) {
    this.cell2.drawSide(this.side2, alpha);
  }
  ctx.closePath();
  ctx.strokeStyle = `hsl(${this.hue},${this.satur}%,${20+120*alpha}%)`;
  ctx.lineWidth = 1;
  ctx.stroke();
}

//------------------------------------------------------------------------

function drawAll (alpha) {
  tbSides.forEach(side => {
    side.drawSide(alpha);
  });
}

//------------------------------------------------------------------------

function createSides() {
  let cellneigh, side, styl, styleList;

  tbSides = [];

  grid.forEach ((line, ky) => {
    line.forEach ((cell, kx) => {
      cell.sides = [];
      styleList = ['abceda','abcabc', 'edaeda'][cell.kind];
      for (let kside = 0; kside < 6 ; ++ kside) {
        styl = styleList [(kside + cell.orient) % 6];
        cellneigh = cell.neighbour(kside);
        if (!cellneigh) cell.sides[kside] = new Side(cell, kside, styl);
        else if (cellneigh.sides && cellneigh.sides [(kside + 3) % 6]) {
          side = cellneigh.sides [(kside + 3) % 6];
          side.addCell(cell, kside, styl);
          cell.sides[kside] = side;
        } else cell.sides[kside] = new Side(cell, kside, styl);
      } // for side
    }); // grid.forEach
  }); // grid.forEach
}

//-----------------------------------------------------------------------------

let animate;

{ // scope for animate
  let animState = 0;
  let alpha = 0;

  animate = function(tStamp) {
    let event = events.pop();
    requestAnimationFrame(animate)
    if (event) {
      switch (event.event) {
        case 'reset' :
          animState = 0;
          break;
      } // switch (event)
    } // if (event)

    let tinit = performance.now();

    switch (animState) {
      case 0:
        if (startOver()) {
          alpha = 0.499;
          ++animState;
        }
        break;

      case 1:
        drawAll(alpha);
        alpha -= 0.5/rayHex;
        if (alpha <= 0) {
          ++animState;
          alpha = 0.001;
        }
        break;

      case 2:
        drawAll(alpha);
        alpha += 0.5/rayHex;
        if (alpha >= 0.5) {
          alpha = 0.499;
          ++animState;
        }
        break;

    } // switch (animState)

  } // animate

} // scope for animate

//-----------------------------------------------------------------
function startOver() {
// canvas dimensions

  maxx = window.innerWidth;
  maxy = window.innerHeight;

  let orgLeft = mmax (((window.innerWidth ) - maxx) / 2, 0);
  let orgTop = mmax (((window.innerHeight ) - maxy) / 2, 0);

  canv.style.left = orgLeft + 'px';
  canv.style.top = orgTop + 'px';

  if (maxx != canv.width) canv.width = maxx;
  if (maxy != canv.height) canv.height = maxy;

  canv.width = maxx;
  canv.height = maxy;
  ctx.lineCap = 'round';   // placed here because reset when canvas resized
  ctx.lineJoin = 'round';

// number of columns / rows
// computed to have (0,0) in top leftmost corner
// and for all hexagons to be fully contained in canvas

  rayHex = alea(rayHexMin, rayHexMax);

  nbx = mfloor(((maxx / rayHex) - 0.5) / 1.5);
  nby = mfloor(maxy / rayHex / rac3 - 0.5); //

  nbx += 3; // to have canvas fully covered by hexagons
  nby += 3;

  if (nbx <= 3 || nby <= 3) return false; // nothing to do

  Hexagon.dimensions();
  createGrid();

  createSides();

  grid.forEach(line => {
    line.forEach(cell => {
      cell.size();
    });
  });

  return true;
} // startOver

//-----------------------------------------------------------------
// beginning of execution

  {
    canv = document.createElement('canvas');
    canv.style.position="absolute";
    document.body.appendChild(canv);
    ctx = canv.getContext('2d');
    canv.addEventListener('click', ()=> {events.push({event:'reset'})});
    canv.setAttribute ('title', 'click me');
  } // canvas creation

  events.push({event:'reset'});
  requestAnimationFrame(animate)
} // window.onload
body {
  font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
  background-color: #000;
  color: #fff;
  margin: 0;
  padding: 0;
  border-width:0;
  cursor: pointer;
}
console 命令行工具 X clear

                    
>
console