<input type="checkbox" name="play" id="play">
  <input type="checkbox" name="faceVis" id="faceVis">

  <div class="scene">
    <div class="cube">
      <div class="wall horizontal"></div>
      <div class="wall horizontal"></div>
      <div class="wall vertical"></div>
      <div class="wall vertical"></div>
      <div class="wall applicate"></div>
      <div class="wall applicate"></div>
    </div>
    <label for="play" class="playBtn"></label>
  </div>
  <label for="faceVis" class="faceOff">
    <span>backface visibility:</span>
    <div class="smiley"></div>
    <span></span>
  </label>
body {
  margin: 0; /* this overrides the default margin - which is crucial if your background isn't white/blank/undefined */
  box-sizing: border-box; /* another one of those standard overrides that you'll need to put in every darn time */
  background-color: black; /* the hex code for dark gray, the equivalent of rgb(48,48,48) or hsl(0deg 0% calc( 100% / 16 *3)), and a shorthard for #333333 )*/
  min-height: 100vh; /* we need the viewport (the browser window) to be 100vh (100 viewport height units) so that there is enough space to suspend the object in */
  font-size: .3vw; /* sets a fontsize dependent on the width of the viewport */
  display: flex;
  justify-content: center; /* centers every object horizontally */
  align-items: center; /* centers every object vertically */
  flex-direction: row; /* contents are arranged side by side */
}

.scene {
  width: 100em; /* sets the width to be 100 units of the font size, thus ensuring responsive scaling that mimics percentages */
  aspect-ratio: 1; /* makes sure the height matches the width of the container, making it a square*/
  position: relative; /*see absove*/
  display: grid;
  place-items: center;
  perspective: 250em; /* sets the distance of the perspective */
}

.scene *:not(label) {
  inset: 0; /* gives the containers nested in the .scene tag a margin of 0, thus making them fill it 100% in width and height; this is shorthand for top, right, bottom, left */
  transform-style: preserve-3d;
  position: absolute; /* makes sure that each container nested inside the .scene tag treats its nested content as a 3D object rather than a 2D projection */
}

.cube {
  animation: spin 6s linear infinite; /* adds a slow spinning animation */
}

.wall:nth-child(odd) {
  --direction: 1; /* variable for the direction of the .wall transforms; this one keeps the value the same */
}

.wall:nth-child(even) {
  --direction: -1; /* variable for the direction of the .wall transforms; this one turns value to its negative */
  filter: invert(); /* inverts the colors */
}

.horizontal {
  background-color: red;
  transform: rotateY( calc( 90deg*var(--direction) ) ) translateZ(50em); /* rotate the wall left or right, depending on the variable, then go forward by 50em */
}

.vertical {
  background-color: green;
  transform: rotateX( calc( 90deg*var(--direction) ) ) translateZ(50em); /* rotate the wall up or down, depending on the variable, then go forward by 50em */
}

.applicate {
  background-color:blue;
  transform: translateZ( calc( 50em*var(--direction) ) ); /* move the wall forward - or back, depending on the variable - by 50em */
}

@keyframes spin { /* the details of the cube's spinning animation: */

  100% { /* in the course of the animation, at the end of it... */
    rotate: y 360deg; /* ...the cube will make a full 360 degree turn, adding to the the 20 degrees that it was already rotated */
  }

}


/* OPTIONAL READING

This section details the appearance and the functioning of the play/pause toggler. It is not a part of the cube - but still a fun thing to learn if you're up for it.

*/

input {
  display: none; /* hides the input, we'll have a label for it instead that is nicer than a tick-box */
}

.playBtn { /* that's the label */
  background-color: green;
  position: absolute;
  width: 40em;
  aspect-ratio: 1; /* makes sure the height matches the width of the container, making it a square*/
  z-index: 1; /* places the label on a layer above the cube - as otherwise it'd be inside the 3D solid, and you wouldn't be able to interact with it */
  display: grid; /* this label will have nested content, and we want it centered */
  place-items: center; /* all children are placed at the center of the container */
  transition: all .5s ease-in-out; /* when you interact with the button - hover, check/uncheck the input - it will change appearance. This makes it switch between each look smoothly rather than instantly */
  --dist: calc(100%/6); /* gap between the two vertical bars of the pause button */
  clip-path: polygon(0 0, 0 50%, 100% 50%, 100% 50%, 0 50%, 0 100%, 100% 50%, 100% 50%); /* cutour for the PLAY button*/
}

.playBtn:hover { /* when you mouse over the label... */
  scale: 1.25; /* ...it gets a little bit larger */
  cursor: grab;
}

#play:not(:checked) ~ .scene .cube { /* if the PLAY button hasn't been pressed */
  animation-play-state: paused; /* ...the animation is paued */
}

#play:checked ~ .playBtn { /* when you hit the PLAY button */
  rotate: -90deg; /* ...it turns to its side */
  clip-path: polygon(0 0, 0 calc(50% - var(--dist)), 100% calc(50% - var(--dist)), 100% calc(50% + var(--dist)), 0 calc(50% + var(--dist)), 0 100%, 100% 100%, 100% 0);
}

.faceOff { /* the backface-visibility toggle */
  position: relative;
  height: 100em;
  aspect-ratio: 1; /* makes sure the height matches the width of the container, making it a square*/
  margin: 30em;
  display: flex;
  flex-direction: column; /* items are stacked on top of each other */
  align-items: center; /* centers the items in the axis perpendicular to the flex-direction: here that's centering horizontally - but will change to vertical centering if direction is switched to "row" */
}

.faceOff span { /* the text inside the backface-visibility toggle */
  color: #ff0;
  font-size: 5vh;
  font-family: 'Courier New';
  font-weight: bold;
  text-align: center;
}

.smiley {
  position: relative;
  width: 50%;
  aspect-ratio: 1;
  background-color: #ff0;
  border-radius: 50%; /* makes the container a perfect ellipse - or circle, if the aspect-ratio is 1 */
  margin: 10em;
}

.smiley, .smiley::before, .smiley::after {
  transition: all .3s;
}

.smiley::before, .smiley::after {
  content: '';
  position: absolute;
  border-radius: 50%;
}

.smiley::before { /* the smile of the smiley */
  inset: 15%;
  border-bottom: 4em solid;
}

.smiley::after { /* the eyes of the smiley */
  inset: 30% 60% 60% 30%;
  background: #000; 
  box-shadow: 15em 0 0 #000;
}

span:nth-of-type(2)::before { /* the text below the smiley, denoting in writing the current value of backface-visibility */
  content: 'visible';
}

#faceVis:checked ~ .faceOff .smiley::before { /* the smiley's smile vanishes when the input is checked */
  border-width: 0;
}

#faceVis:checked ~ .faceOff .smiley::after { /* the smiley's eyes close when the input is checked */
  inset: 40% 60% 60% 30%;
}

#faceVis:checked ~ .faceOff span:nth-of-type(2)::before { /* the text declaring the current value of the backface-visibility changes to "hidden" when the input is checked */
  content: 'hidden';
}

#faceVis:checked ~ .scene .wall {
  backface-visibility: hidden; /* accordingly, the backfaces on all .walls are hidden */
}


@media (orientation: portrait) { /* for mobile screens displaying in portait mode (a tall, narrow rectangle) */
  body {
    font-size: .3vh; /* the font size is now depended on the viewport height - rather than width - since now it's the narrower of the two dimensions */
    flex-direction: column; /* contents are stacked vertically */
  }
}
// This pen is used for demonstration in my tutorial on 3D CSS cubes at https://mackfitz.hashnode.dev/css-3d-the-cube-part-2

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.