JSFiddle - React, Tailwind, and code Playground

by Graham Dixon

CSS

body {
  font-family: "Roboto", sans-serif;
  margin: 0 auto;
  text-align: center;
}

.hidden {
  display: none;
}

.outer {
  display: flex;
  width: 100%;
}

.inner {
  width: 50%;
  flex-direction: column;
}

.cube {
  fill-opacity: 0.9;
  stroke-miterlimit: 0;
}

.blue-cube, .blue-cube-generated, .pink-cube > .move-right {
  --mainColor: #009cde;
  --strokeColor: #0079ad;
  --lightColor: #00affa;
  --darkColor: #008bc7;
}

.pink-cube, .pink-cube-generated, .move-right, .move-right-right {
  --mainColor: #de0063;
  --strokeColor: #ad004e;
  --lightColor: #fa0070;
  --darkColor: #c7005a;
}

.green-cube, .move-left-left {
  --mainColor: #00de42;
  --strokeColor: #00ad34;
  --lightColor: #00fa4b;
  --darkColor: #00c73c;
}

.orange-cube, .move-left {
  --mainColor: #9cde00;
  --strokeColor: #79ad00;
  --lightColor: #affa00;
  --darkColor: #8bc700;
}

.move-left {
  --translate: -50px;
}

.move-left-left {
  --translate: -100px;
}

.move-right {
  --translate: 50px;
}

.move-right-right {
  --translate: 100px;
}

svg * {
  transition: 0.2s transform;
}

.cube {
  transform: translateX(0px);
}

svg:hover .cube,
svg.isIntersecting .cube {
  transform: translateX(var(--translate));
}

JavaScript

// Credit to Mariana Beldi for the base implementation (https://codepen.io/marianab/pen/KKKYdxE)

// example...
generateCube(8, [1, 20, 10, 5, 5], "blue-cube");
generateCube(6, [1000, 200, 2000, 100], "blue-cube");
generateCube(3, [1000, 1, 1000], "pink-cube");
generateCube(3, [1, 20, 1], "blue-cube");

// generate a n*3 cube and seperate into groups (eg, [left, middle, right] upto a max of 5)
function generateCube(n, values = [1, 1, 1], className = "blue-cube") {
  // fill with cubes before printing to screen
  const cubes = [];

  // end early on too many values
  if (values.length > 5) {
    throw new Error("Too many values provided, max of 5.");
  }

  // define starting position
  const startX = 32;
  const startY = 64;
  
  // define dimensions and spacing
  const cubeHeight = 12;
  const cubeWidth = 21;
  const verticalSpacing = 12;
  const horizontalSpacing = 21;

  // produce the matrix and rotate for layout positions
  const cubeMatrix = generateSquareMatrix(n);
  const cubePositions = rotateMatrix45Deg(cubeMatrix);

  // create and position cubes
  cubePositions.flat().forEach((cube) => {
    // extract row/col
    const {
      x: col,
      y: row
    } = cube;

    // the true x/col pos is taken from the diagonal in the cubePos, we need to know its depth to use in the cols place
    const depth = cubePositions.findIndex((cubes, indx) => (
      cubes.indexOf(cube) !== -1
    ));

    // we set the 3d depth for each x/y vector using layer (column at a time)
    for (let layer = 0; layer < n; layer++) {
      // calc the x pos using the hoz and width vals for the col and depth for hoz offsets
      const x = startX + (depth * horizontalSpacing) + (row * (cubeWidth + horizontalSpacing));
      // vert position is taken from the diagonal depth spacing with shift based on layer to simulate 3d depth
      const y = startY + (depth * verticalSpacing) - ((layer * (cubeHeight + verticalSpacing)));

      // record the cube
      cubes.push({
        screenX:...