Zonal

A pure CSS zonal spinner featuring two coloured orbs that flash in and out at randomised positions using CSS custom properties, offset by half a cycle.

Generated using Grepped's AI UI component generator — created from scratch, not pulled from a library.

spinnerzonalloaderorbteleportcss-variables

Live Preview — customize or regenerate this in the workspace

Loading preview…

Design Intent

Two orbs that flash between random positions using CSS custom property coordinate offsets, staggered by half a cycle.

CSS

css
body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #0f0f0f; }
    :root { --primary: #6366f1; --secondary: #ec4899; }

    .zonal {
      --size: 20;
      height: calc(var(--size) * 1px);
      width: calc(var(--size) * 1px);
      position: relative;
    }

    .zonal:after,
    .zonal:before {
      animation: zonal-flash 4s infinite ease;
      border-radius: 100%;
      content: '';
      position: absolute;
      height: 100%;
      width: 100%;
    }

    .zonal:after {
      background-color: var(--primary);
      --x1: 55;  --x2: -196; --x3: -122; --x4: 2;   --x5: 198;
      --y1: 113; --y2: 221;  --y3: -141; --y4: -164; --y5: -44;
    }

    .zonal:before {
      animation-delay: calc(4s * -0.5);
      background-color: var(--secondary);
      --x1: -151; --x2: -192; --x3: -112; --x4: -109; --x5: 155;
      --y1: 222;  --y2: -121; --y3: -227; --y4: -115; --y5: 129;
    }

    @keyframes zonal-flash {
      0%   { transform: scale(0) translate(calc(var(--x1) * 1%), calc(var(--y1) * 1%)); }
      10%  { transform: scale(1) translate(calc(var(--x1) * 1%), calc(var(--y1) * 1%)); }
      20%  { transform: scale(0) translate(calc(var(--x2) * 1%), calc(var(--y2) * 1%)); }
      30%  { transform: scale(1) translate(calc(var(--x2) * 1%), calc(var(--y2) * 1%)); }
      40%  { transform: scale(0) translate(calc(var(--x3) * 1%), calc(var(--y3) * 1%)); }
      50%  { transform: scale(1) translate(calc(var(--x3) * 1%), calc(var(--y3) * 1%)); }
      60%  { transform: scale(0) translate(calc(var(--x4) * 1%), calc(var(--y4) * 1%)); }
      70%  { transform: scale(1) translate(calc(var(--x4) * 1%), calc(var(--y4) * 1%)); }
      80%  { transform: scale(0) translate(calc(var(--x5) * 1%), calc(var(--y5) * 1%)); }
      90%  { transform: scale(1) translate(calc(var(--x5) * 1%), calc(var(--y5) * 1%)); }
      100% { transform: scale(0) translate(calc(var(--x1) * 1%), calc(var(--y1) * 1%)); }
    }

HTML

html
<div class="zonal"></div>

Full Source

html
<!DOCTYPE html>
<html>
<head>
  <style>
    body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #0f0f0f; }
    :root { --primary: #6366f1; --secondary: #ec4899; }

    .zonal {
      --size: 20;
      height: calc(var(--size) * 1px);
      width: calc(var(--size) * 1px);
      position: relative;
    }

    .zonal:after,
    .zonal:before {
      animation: zonal-flash 4s infinite ease;
      border-radius: 100%;
      content: '';
      position: absolute;
      height: 100%;
      width: 100%;
    }

    .zonal:after {
      background-color: var(--primary);
      --x1: 55;  --x2: -196; --x3: -122; --x4: 2;   --x5: 198;
      --y1: 113; --y2: 221;  --y3: -141; --y4: -164; --y5: -44;
    }

    .zonal:before {
      animation-delay: calc(4s * -0.5);
      background-color: var(--secondary);
      --x1: -151; --x2: -192; --x3: -112; --x4: -109; --x5: 155;
      --y1: 222;  --y2: -121; --y3: -227; --y4: -115; --y5: 129;
    }

    @keyframes zonal-flash {
      0%   { transform: scale(0) translate(calc(var(--x1) * 1%), calc(var(--y1) * 1%)); }
      10%  { transform: scale(1) translate(calc(var(--x1) * 1%), calc(var(--y1) * 1%)); }
      20%  { transform: scale(0) translate(calc(var(--x2) * 1%), calc(var(--y2) * 1%)); }
      30%  { transform: scale(1) translate(calc(var(--x2) * 1%), calc(var(--y2) * 1%)); }
      40%  { transform: scale(0) translate(calc(var(--x3) * 1%), calc(var(--y3) * 1%)); }
      50%  { transform: scale(1) translate(calc(var(--x3) * 1%), calc(var(--y3) * 1%)); }
      60%  { transform: scale(0) translate(calc(var(--x4) * 1%), calc(var(--y4) * 1%)); }
      70%  { transform: scale(1) translate(calc(var(--x4) * 1%), calc(var(--y4) * 1%)); }
      80%  { transform: scale(0) translate(calc(var(--x5) * 1%), calc(var(--y5) * 1%)); }
      90%  { transform: scale(1) translate(calc(var(--x5) * 1%), calc(var(--y5) * 1%)); }
      100% { transform: scale(0) translate(calc(var(--x1) * 1%), calc(var(--y1) * 1%)); }
    }
  </style>
</head>
<body>
  <div class="zonal"></div>
</body>
</html>

More Spinners Animations