Pure CSS Shapes & solids #loop #shape

Crystal cluster in pure CSS

Five hexagonal prisms of different sizes growing from one rock at different tilts. Each side panel carries its own tip facet as a ::before, folded in so the six meet in a point.

  • 103 lines of CSS
  • 50 lines of HTML
  • No JavaScript
  • No dependencies
  • MIT licensed

How it works

  1. Every crystal is a hexagonal prism: six panels --w wide in a closed ring, rotateY(i × 60deg) translateZ(w × 0.866). 0.866 is √3 / 2, the apothem of a hexagon with side w.
  2. The pointed tip needs no extra elements: each panel carries its own facet as a ::before. The panel gets transform-style: preserve-3d, so the triangle can fold in along the panel's top edge by 40°. Its height, apothem / sin(40°) = 1.347 × w, makes the six points meet on the axis.
  3. A crystal leans by walking its transforms: stand on the floor, rotateY to face its direction, translateZ out from the centre, then rotateX to lean outward. The same four numbers in a style attribute place all five.
  4. The inner glow is two crossed planes of radial gradient, so it has body from every side. Its opacity breathes on a pseudo-element: on the element itself, opacity would flatten its 3D children.

Key techniques

  • hexagon ring: apothem = side × √3 / 2
  • tip facet as a folded ::before
  • rotateY · translateZ · rotateX to lean out
  • crossed glow planes

Copy-paste code

HTML 50 lines

<div class="scene">
  <div class="cluster">
    <b class="rock"></b>
    <div class="crystal" style="--az:0deg; --off:0px; --tilt:0deg; --w:22px; --h:86px; --c:#8b6cff; --c2:#ff4d9d; --d:0s">
      <i style="--i:0"></i>
      <i style="--i:1"></i>
      <i style="--i:2"></i>
      <i style="--i:3"></i>
      <i style="--i:4"></i>
      <i style="--i:5"></i>
      <b></b>
    </div>
    <div class="crystal" style="--az:30deg; --off:16px; --tilt:30deg; --w:15px; --h:60px; --c:#2ee6d6; --c2:#8b6cff; --d:-1.2s">
      <i style="--i:0"></i>
      <i style="--i:1"></i>
      <i style="--i:2"></i>
      <i style="--i:3"></i>
      <i style="--i:4"></i>
      <i style="--i:5"></i>
      <b></b>
    </div>
    <div class="crystal" style="--az:115deg; --off:14px; --tilt:26deg; --w:14px; --h:50px; --c:#ff4d9d; --c2:#8b6cff; --d:-2.4s">
      <i style="--i:0"></i>
      <i style="--i:1"></i>
      <i style="--i:2"></i>
      <i style="--i:3"></i>
      <i style="--i:4"></i>
      <i style="--i:5"></i>
      <b></b>
    </div>
    <div class="crystal" style="--az:205deg; --off:16px; --tilt:34deg; --w:16px; --h:66px; --c:#8b6cff; --c2:#2ee6d6; --d:-0.6s">
      <i style="--i:0"></i>
      <i style="--i:1"></i>
      <i style="--i:2"></i>
      <i style="--i:3"></i>
      <i style="--i:4"></i>
      <i style="--i:5"></i>
      <b></b>
    </div>
    <div class="crystal" style="--az:290deg; --off:14px; --tilt:30deg; --w:12px; --h:40px; --c:#2ee6d6; --c2:#ff4d9d; --d:-3s">
      <i style="--i:0"></i>
      <i style="--i:1"></i>
      <i style="--i:2"></i>
      <i style="--i:3"></i>
      <i style="--i:4"></i>
      <i style="--i:5"></i>
      <b></b>
    </div>
  </div>
</div>

CSS 103 lines

.scene {
  perspective: 800px;
}

.cluster {
  position: relative;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  animation: spin 30s linear infinite;
}

/* stand on the floor (44px down), face a direction, step out, lean outward */
.crystal {
  position: absolute;
  transform-style: preserve-3d;
  transform: translateY(44px) rotateY(var(--az)) translateZ(var(--off)) rotateX(calc(var(--tilt) * -1));
}

/* six side panels in a closed ring */
.crystal i {
  position: absolute;
  top: calc(var(--h) * -1);
  left: calc(var(--w) / -2);
  width: var(--w);
  height: var(--h);
  transform-style: preserve-3d; /* lets the tip fold in 3D */
  transform: rotateY(calc(var(--i) * 60deg)) translateZ(calc(var(--w) * 0.866));
  background:
    linear-gradient(90deg, transparent 30%, rgb(255 255 255 / 0.2) 42%, transparent 54%),
    linear-gradient(to top, color-mix(in srgb, var(--c) 62%, transparent), color-mix(in srgb, var(--c) 14%, transparent) 70%, color-mix(in srgb, var(--c) 28%, transparent));
  box-shadow:
    inset 1px 0 color-mix(in srgb, var(--c) 80%, transparent),
    inset -1px 0 color-mix(in srgb, var(--c) 80%, transparent),
    inset 0 1px color-mix(in srgb, var(--c) 85%, #fff);
}

.crystal i:nth-child(even) {
  --c: var(--c2);
}

/* the tip facet: hinged on the panel's top edge, folded in by 40deg */
.crystal i::before {
  --edge: color-mix(in srgb, var(--c) 85%, #fff);
  content: '';
  position: absolute;
  bottom: 100%;
  left: 0;
  width: 100%;
  height: calc(var(--w) * 1.347); /* apothem / sin(40deg) */
  transform-origin: 50% 100%;
  transform: rotateX(40deg);
  clip-path: polygon(50% 0, 0 100%, 100% 100%);
  background:
    linear-gradient(to top left, transparent calc(50% - 1px), var(--edge) calc(50% - 1px) 50%, transparent 50%) left / 50% 100% no-repeat,
    linear-gradient(to top right, transparent calc(50% - 1px), var(--edge) calc(50% - 1px) 50%, transparent 50%) right / 50% 100% no-repeat,
    linear-gradient(color-mix(in srgb, var(--c) 20%, #fff 30%), color-mix(in srgb, var(--c) 40%, transparent));
}

/* the inner glow: two crossed planes */
.crystal b {
  position: absolute;
  top: calc(var(--h) * -1);
  left: calc(var(--w) * -0.8);
  width: calc(var(--w) * 1.6);
  height: var(--h);
  transform-style: preserve-3d;
}

.crystal b::before,
.crystal b::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(50% 60% at 50% 72%, color-mix(in srgb, #fff 60%, var(--c)), color-mix(in srgb, var(--c) 45%, transparent) 40%, transparent);
  animation: glow 3.6s ease-in-out var(--d) infinite alternate;
}

.crystal b::after {
  transform: rotateY(90deg);
}

/* the rock: an uneven slab laid flat */
.rock {
  position: absolute;
  top: -24px;
  left: -68px;
  width: 136px;
  height: 136px;
  clip-path: polygon(22% 4%, 62% 0, 92% 18%, 100% 55%, 84% 90%, 46% 100%, 12% 86%, 0 48%);
  background: radial-gradient(circle, #ae98ff 0 6%, #4a3e8d 24%, #2e2a5d 60%, #3c3576);
  transform: translateY(1px) rotateX(90deg);
}

@keyframes spin {
  from { transform: rotateX(-22deg) rotateY(0deg); }
  to   { transform: rotateX(-22deg) rotateY(360deg); }
}

@keyframes glow {
  from { opacity: 0.35; }
  to   { opacity: 1; }
}

Sass source 135 lines

$floor: 44px; // the rock's surface, below the centre so the cluster turns round its middle
$tip: 40deg; // lean of a tip facet from upright
$spin: 30s;
$edge: color-mix(in srgb, var(--c) 85%, #fff);

// Five hexagonal prisms growing out of one rock. Every crystal is set up by the same numbers
// (--w width of a side, --h height, --az / --tilt / --off where and how it leans) and built the
// same way: 6 side panels, each carrying its own tip facet as a ::before.
.d-crystal {
  position: relative;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  animation: d-crystal-spin $spin linear infinite;

  // one crystal: stand on the floor, face its direction, step out from the centre, lean outward
  &__c {
    position: absolute;
    transform-style: preserve-3d;
    transform: translateY($floor) rotateY(var(--az)) translateZ(var(--off)) rotateX(calc(var(--tilt) * -1));

    // side panels: a closed hexagonal ring. Apothem = side × √3 / 2.
    i {
      position: absolute;
      top: calc(var(--h) * -1);
      left: calc(var(--w) / -2);
      width: var(--w);
      height: var(--h);
      transform-style: preserve-3d; // so the tip on top can fold in 3D
      transform: rotateY(calc(var(--i) * 60deg)) translateZ(calc(var(--w) * 0.866));
      background:
        linear-gradient(90deg, transparent 30%, rgb(255 255 255 / 0.2) 42%, transparent 54%),
        linear-gradient(
          to top,
          color-mix(in srgb, var(--c) 62%, transparent),
          color-mix(in srgb, var(--c) 14%, transparent) 70%,
          color-mix(in srgb, var(--c) 28%, transparent)
        );
      box-shadow:
        inset 1px 0 color-mix(in srgb, var(--c) 80%, transparent),
        inset -1px 0 color-mix(in srgb, var(--c) 80%, transparent),
        inset 0 1px $edge;

      // the tip facet: a triangle hinged on the panel's top edge, leaned in by 40deg.
      // Its slant height is apothem / sin(40deg) = 1.347 × side, so all six tips meet on the axis.
      &::before {
        content: '';
        position: absolute;
        bottom: 100%;
        left: 0;
        width: 100%;
        height: calc(var(--w) * 1.347);
        transform-origin: 50% 100%;
        transform: rotateX($tip);
        clip-path: polygon(50% 0, 0 100%, 100% 100%);
        background:
          linear-gradient(to top left, transparent calc(50% - 1px), $edge calc(50% - 1px), $edge 50%, transparent 50%) left /
            50% 100% no-repeat,
          linear-gradient(to top right, transparent calc(50% - 1px), $edge calc(50% - 1px), $edge 50%, transparent 50%) right /
            50% 100% no-repeat,
          linear-gradient(color-mix(in srgb, var(--c) 20%, #fff 30%), color-mix(in srgb, var(--c) 40%, transparent));
      }

      &:nth-child(even) {
        --c: var(--c2);
      }
    }

    // the inner glow: two crossed planes of soft light up the middle, breathing on their own beat
    b {
      position: absolute;
      top: calc(var(--h) * -1);
      left: calc(var(--w) * -0.8);
      width: calc(var(--w) * 1.6);
      height: var(--h);
      transform-style: preserve-3d;

      &::before,
      &::after {
        content: '';
        position: absolute;
        inset: 0;
        background: radial-gradient(
          50% 60% at 50% 72%,
          color-mix(in srgb, #fff 60%, var(--c)),
          color-mix(in srgb, var(--c) 45%, transparent) 40%,
          transparent 100%
        );
        animation: d-crystal-glow 3.6s ease-in-out var(--d) infinite alternate;
      }

      &::after {
        transform: rotateY(90deg);
      }
    }
  }

  // the rock: an uneven slab laid flat, lit from the crystals' roots
  &__rock {
    position: absolute;
    top: $floor - 68px;
    left: -68px;
    width: 136px;
    height: 136px;
    clip-path: polygon(22% 4%, 62% 0, 92% 18%, 100% 55%, 84% 90%, 46% 100%, 12% 86%, 0 48%);
    background: radial-gradient(
      circle,
      color-mix(in srgb, var(--accent) 70%, #fff) 0 6%,
      color-mix(in srgb, var(--accent) 45%, var(--surface-solid)) 24%,
      color-mix(in srgb, var(--accent) 22%, var(--surface-solid)) 60%,
      color-mix(in srgb, var(--accent) 34%, var(--surface-solid))
    );
    transform: translateY(1px) rotateX(90deg);
  }
}

@keyframes d-crystal-spin {
  from {
    transform: rotateX(-22deg) rotateY(0deg);
  }

  to {
    transform: rotateX(-22deg) rotateY(360deg);
  }
}

@keyframes d-crystal-glow {
  from {
    opacity: 0.35;
  }

  to {
    opacity: 1;
  }
}
Standalone snippet · plain CSS, no build step, no dependencies.