dot-spinner {
  display: inline-block;
  position: relative;
  width: var(--spinner-size, 200px);
  max-width: 100%;
  aspect-ratio: 1;
  vertical-align: middle;
  flex: none;
}

.dot-spinner-sm {
  --spinner-size: 80px;
}

.dot-spinner__dot {
  position: absolute;
  width: var(--spinner-max-dot-diameter);
  aspect-ratio: 1;
  border-radius: 50%;
  background-color: var(--spinner-base-color);
  transform: translate(-50%, -50%) scale(var(--spinner-base-dot-scale));
  animation: dot-spinner-pulse var(--spinner-loop-duration) linear infinite;
  animation-delay: calc(var(--spinner-loop-duration) * var(--phase));
}

dot-spinner[data-paused="true"] .dot-spinner__dot {
  animation-play-state: paused;
}

@keyframes dot-spinner-pulse {
  0%, 100% {
    background-color: var(--spinner-highlight-color);
    transform: translate(-50%, -50%) scale(1);
  }
  25%, 87.5% {
    background-color: var(--spinner-base-color);
    transform: translate(-50%, -50%) scale(var(--spinner-base-dot-scale));
  }
}

@supports (animation-timing-function: linear(0, 1)) {
  .dot-spinner__dot {
    /* Keep the fade twice as long as the rise. */
    animation-name: dot-spinner-adjustable-pulse;
    animation-timing-function: linear(
      1,
      0 calc(var(--spinner-pulse-width) * 2 / 3) calc(100% - var(--spinner-pulse-width) / 3),
      1
    );
  }
}

@keyframes dot-spinner-adjustable-pulse {
  from {
    background-color: var(--spinner-base-color);
    transform: translate(-50%, -50%) scale(var(--spinner-base-dot-scale));
  }
  to {
    background-color: var(--spinner-highlight-color);
    transform: translate(-50%, -50%) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  dot-spinner:not([data-paused="false"]) .dot-spinner__dot {
    animation-play-state: paused;
  }
}
