- var i = 0;
- var j = 0;
- var planes = 12;
- var spokes = 36;

// Sphere
.main-wrapper
  .sphere-wrapper
    while i < planes
      - i++
      .plane(class="plane-"+ i)
        - j = 0
        while j < spokes
          - j++
          .spoke(class="spoke-"+ j)
            .dot
            
// Animista.net         
.animista-badge
  | Into CSS animation? #[a(href="http://animista.net" rel="noopeneer noreferrer" target="_blank") Check Animista]
View Compiled
// -----------------------------------------------------
// If you like this, be sure to also check my Jellyfish
// pen that was inspired by this one:
// https://codepen.io/iamlark/pen/opyBNy
// -----------------------------------------------------

@radius: 300px;
@dotSize: 6px;
@spokesNum: 36;
@planesNum: 12;

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: #333;
}

.main-wrapper {
  display: flex;
  position: absolute;
	transform-style: preserve-3d;
	perspective: 400px;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;
  align-items: center;
  justify-content: center;
}

.sphere-wrapper {
	transform-style: preserve-3d;
  width: @radius;
  height: @radius;
  position: relative;
  // background: rgba(255,255,255,.1);
  // border-radius: 50%;
  animation: rotate3d 10s linear infinite;
}

.plane {
  position: absolute;
  transform-style: preserve-3d;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

.spoke {
  transform-origin: 0 0;
  transform-style: preserve-3d;
  position: absolute;
  left: 50%;
  top: 50%;
  height: @radius/2;
  width: 0px;
  // background: rgba(255,255,255,.05);
}

.dot {
  position: absolute;
  width: @dotSize;
  height: @dotSize;
  border-radius: 50%;
  background: rgba(255,255,255,1);
  left: -@dotSize/2;
  top: 100%;
  transform: rotateX(90deg);
}

.place-spokes(@spokes: @spokesNum; @i: 1) when (@i < @spokes) {
  @currDeg: unit((360 / @spokes * @i), deg);
  .spoke.spoke-@{i} {
    transform: rotateZ(@currDeg);
  }
  .place-spokes(@spokes; @i + 1);
}

.place-planes(@planes: @planesNum; @i: 1) when (@i < @planes) {
  @currDeg: unit(180 / @planes * @i, deg);
  .plane.plane-@{i} {
    transform: rotateY(@currDeg);
  }
  .place-planes(@planes; @i + 1);
}

.animate-dots(@spokes: @spokesNum/2; @i: 0) when (@i <= @spokes) {
  @delay: unit(@i/@spokes, s);
  @j: @spokesNum - @i;
  @colorDeg: unit(360/@spokesNum*@i, deg);
  .spoke-@{i} .dot, .spoke-@{j} .dot  {
    animation: pulsate .5s infinite @delay alternate both;
    background-color: spin(#f95, @colorDeg);
  }
  .animate-dots(@spokes, @i + 1);
}

.place-spokes();
.place-planes();
.animate-dots();

@keyframes rotate3d {
	  0% { transform: rotate3d(1,1,1,0deg)};
   25% { transform: rotate3d(1,1,1,90deg)};
   50% { transform: rotate3d(1,1,1,180deg)};
   75% { transform: rotate3d(1,1,1,270deg)};
	100% { transform: rotate3d(1,1,1,360deg)};
}

@keyframes pulsate {
    0% { transform: rotateX(90deg) scale(.3) translateZ(20px); }
  100% { transform: rotateX(90deg) scale(1) translateZ(0px); }
}

.animista-badge {
  font: normal 15px/1.5 sans-serif;
  position: absolute;
  right: 10px;
  bottom: 10px;
  color: rgba(255,255,255,.8);
  padding: .5em 1em;
}

a {
  color: #ff6060;
  text-decoration: none;
  &:hover { text-decoration: underline; }
}
View Compiled
// No JS here ;)
Rerun