Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
              
            
!

CSS

              
                body {
  font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
  background-color: #000;
  color: #fff;
  margin: 0;
  padding: 0;
  border-width:0;
  cursor: pointer;
}

              
            
!

JS

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

  const rayHexMin = 80;
  const rayHexMax = 160;
  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

              
            
!
999px

Console