Back to the library

Job complete, delight

Variant: Delight

The rare one: the mark becomes a medal, an arc and burst fire off it, the headline rises, and a light sweep crosses 2.7s later once the eye has settled. The dashed outer halo drifts exactly one dash period per cycle, so the loop closes on itself with no seam. Earns its length by being seen once or twice ever — a first deploy, a millionth record.

successcontrolsvgloop

The source

A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Job complete — delight</title>
    <style>
      :root {
        --ivory: #faf9f5;
        --slate: #141413;
        --clay: #d97757;
        --oat: #e3dacc;
        /* WHY THIS ONE IS LONG (8.4s cycle, ~3.5s of staged motion):
           this is a once-ever milestone, not a status change. It is allowed to
           be read rather than glanced at, so it plays as a short sentence in
           five clauses — the arc closes (670ms), the medal lands (500ms), the
           mark is drawn (550ms), the burst throws (670ms), the words arrive
           (590ms) — and then, after a beat of stillness, a single light sweeps
           across the medal at 2.7s to say "still here". Compressing that into
           the 400ms budget of the dashboard file would turn five clauses into
           one shout. The other two files in this pack deliberately do not
           spend this. */
        --cycle: 8.4s;
        --out: cubic-bezier(0.16, 0.84, 0.3, 1);
      }

      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }

      body {
        min-height: 100vh;
        display: grid;
        place-items: center;
        background: var(--ivory);
        font-family:
          system-ui,
          -apple-system,
          'Segoe UI',
          Roboto,
          sans-serif;
        color: var(--slate);
      }

      .milestone {
        /* Grows with the stage instead of sitting at 214px in a 1100px frame.
           Floors at the original width, so the two mobile sizes are unchanged. */
        width: clamp(214px, 36vw, 364px);
        display: grid;
        justify-items: center;
        text-align: center;
        gap: 12px;
        animation: card var(--cycle) linear infinite;
      }

      @keyframes card {
        0% {
          opacity: 0;
        }
        2% {
          opacity: 1;
        }
        96% {
          opacity: 1;
          animation-timing-function: ease;
        }
        100% {
          opacity: 0;
        }
      }

      .medal {
        /* Same ramp as the card, so the medal holds its 0.54 proportion. */
        width: clamp(116px, 19vw, 197px);
        height: clamp(116px, 19vw, 197px);
        display: block;
        overflow: visible;
      }

      .medal * {
        transform-box: view-box;
        transform-origin: 50% 50%;
      }

      /* Slow drift on the outer dashes. Exactly one dash period (30°) per
         cycle, so the loop closes on itself with no seam. */
      .halo {
        fill: none;
        stroke: var(--oat);
        stroke-width: 1.4;
        stroke-dasharray: 2.6 27.77;
        stroke-linecap: round;
        animation: drift var(--cycle) linear infinite;
      }

      @keyframes drift {
        0% {
          transform: rotate(0deg);
        }
        100% {
          transform: rotate(30deg);
        }
      }

      .sweep {
        fill: none;
        stroke: var(--clay);
        stroke-width: 2.4;
        stroke-linecap: round;
        stroke-dasharray: 238.76;
        transform: rotate(-90deg);
        animation: sweep var(--cycle) linear infinite;
      }

      @keyframes sweep {
        0%,
        3% {
          stroke-dashoffset: 238.76;
          opacity: 1;
          animation-timing-function: var(--out);
        }
        11% {
          stroke-dashoffset: 0;
          opacity: 1;
        }
        16%,
        100% {
          stroke-dashoffset: 0;
          opacity: 0.25;
        }
      }

      .pulse {
        fill: none;
        stroke: var(--clay);
        stroke-width: 1.6;
        animation: pulse var(--cycle) linear infinite;
      }

      @keyframes pulse {
        0%,
        13% {
          transform: scale(1);
          opacity: 0;
          animation-timing-function: var(--out);
        }
        14% {
          transform: scale(1.08);
          opacity: 0.4;
        }
        23%,
        100% {
          transform: scale(1.8);
          opacity: 0;
        }
      }

      .disc {
        fill: var(--clay);
        animation: disc var(--cycle) linear infinite;
      }

      @keyframes disc {
        0%,
        8% {
          transform: scale(0.9);
          opacity: 0;
          animation-timing-function: var(--out);
        }
        14%,
        100% {
          transform: scale(1);
          opacity: 1;
        }
      }

      .tick {
        fill: none;
        stroke: var(--ivory);
        stroke-width: 4;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-dasharray: 50;
        animation: draw var(--cycle) linear infinite;
      }

      @keyframes draw {
        0%,
        12.5% {
          stroke-dashoffset: 50;
          animation-timing-function: var(--out);
        }
        19%,
        100% {
          stroke-dashoffset: 0;
        }
      }

      /* Twelve spokes thrown outward on a 22ms stagger. They start and end at
         opacity 0, so the animation-delay never catches one mid-pose. */
      .ray {
        stroke: var(--clay);
        stroke-width: 2.2;
        stroke-linecap: round;
        opacity: 0;
        transform: rotate(var(--a)) scale(0.78);
        animation: ray var(--cycle) linear infinite;
        animation-delay: calc(var(--i) * 22ms);
      }

      @keyframes ray {
        0%,
        13% {
          transform: rotate(var(--a)) scale(0.78);
          opacity: 0;
          animation-timing-function: var(--out);
        }
        14.5% {
          transform: rotate(var(--a)) scale(0.95);
          opacity: 0.95;
          animation-timing-function: ease-out;
        }
        21%,
        100% {
          transform: rotate(var(--a)) scale(1.12);
          opacity: 0;
        }
      }

      .sheen {
        fill: url(#sheenGrad);
        opacity: 0;
        animation: sheen var(--cycle) linear infinite;
      }

      @keyframes sheen {
        0%,
        32% {
          transform: translateX(-46px) rotate(16deg);
          opacity: 0;
          animation-timing-function: cubic-bezier(0.4, 0, 0.35, 1);
        }
        34% {
          opacity: 1;
        }
        40% {
          opacity: 1;
        }
        42%,
        100% {
          transform: translateX(46px) rotate(16deg);
          opacity: 0;
        }
      }

      .copy {
        display: grid;
        gap: 4px;
      }

      .line {
        overflow: hidden;
        display: block;
        /* Padding tracks the type size so the mask never clips a descender. */
        padding-bottom: 0.3em;
        margin-bottom: -0.3em;
      }

      .line > span {
        display: block;
      }

      .title {
        font-size: clamp(16px, 2.6vw, 26px);
        font-weight: 620;
        letter-spacing: -0.02em;
      }

      .title > span {
        animation: lift var(--cycle) linear infinite;
      }

      .sub {
        font-size: clamp(12px, 1.85vw, 18px);
        color: color-mix(in srgb, var(--slate) 55%, transparent);
        font-variant-numeric: tabular-nums;
      }

      .sub > span {
        animation: lift-2 var(--cycle) linear infinite;
      }

      @keyframes lift {
        0%,
        17% {
          transform: translateY(115%);
          opacity: 0;
          animation-timing-function: var(--out);
        }
        24%,
        100% {
          transform: translateY(0);
          opacity: 1;
        }
      }

      @keyframes lift-2 {
        0%,
        20.5% {
          transform: translateY(115%);
          opacity: 0;
          animation-timing-function: var(--out);
        }
        27.5%,
        100% {
          transform: translateY(0);
          opacity: 1;
        }
      }

      @media (prefers-reduced-motion: reduce) {
        /* Still a sequence, still celebratory — but nothing travels: the arc
           still draws, everything else crossfades in place. */
        .halo {
          animation: none;
        }
        .pulse {
          animation: pulse-rm var(--cycle) linear infinite;
          transform: scale(1.45);
        }
        @keyframes pulse-rm {
          0%,
          13% {
            opacity: 0;
          }
          20% {
            opacity: 0.35;
          }
          32%,
          100% {
            opacity: 0;
          }
        }
        .disc {
          animation: fade-rm var(--cycle) linear infinite;
          transform: scale(1);
        }
        .ray {
          animation: ray-rm var(--cycle) linear infinite;
          transform: rotate(var(--a)) scale(1);
        }
        @keyframes ray-rm {
          0%,
          15% {
            opacity: 0;
          }
          22% {
            opacity: 0.9;
          }
          34%,
          100% {
            opacity: 0;
          }
        }
        .sheen {
          animation: sheen-rm var(--cycle) linear infinite;
          transform: translateX(0) rotate(16deg);
        }
        @keyframes sheen-rm {
          0%,
          33% {
            opacity: 0;
          }
          37% {
            opacity: 0.7;
          }
          43%,
          100% {
            opacity: 0;
          }
        }
        .title > span {
          animation: fade-rm var(--cycle) linear infinite;
          transform: none;
        }
        .sub > span {
          animation: fade-rm-2 var(--cycle) linear infinite;
          transform: none;
        }
        @keyframes fade-rm {
          0%,
          13% {
            opacity: 0;
          }
          21%,
          100% {
            opacity: 1;
          }
        }
        @keyframes fade-rm-2 {
          0%,
          17% {
            opacity: 0;
          }
          25%,
          100% {
            opacity: 1;
          }
        }
      }
    </style>
  </head>
  <body>
    <div
      class="milestone"
      role="status"
      aria-label="Milestone reached: one million records processed"
    >
      <svg class="medal" viewBox="0 0 140 140" aria-hidden="true">
        <defs>
          <linearGradient id="sheenGrad" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0%" stop-color="#faf9f5" stop-opacity="0" />
            <stop offset="50%" stop-color="#faf9f5" stop-opacity="0.6" />
            <stop offset="100%" stop-color="#faf9f5" stop-opacity="0" />
          </linearGradient>
          <clipPath id="discClip">
            <circle cx="70" cy="70" r="30" />
          </clipPath>
        </defs>

        <circle class="halo" cx="70" cy="70" r="58" />
        <circle class="pulse" cx="70" cy="70" r="34" />

        <line class="ray" style="--a: 0deg; --i: 0" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 30deg; --i: 1" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 60deg; --i: 2" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 90deg; --i: 3" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 120deg; --i: 4" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 150deg; --i: 5" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 180deg; --i: 6" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 210deg; --i: 7" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 240deg; --i: 8" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 270deg; --i: 9" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 300deg; --i: 10" x1="70" y1="26" x2="70" y2="17" />
        <line class="ray" style="--a: 330deg; --i: 11" x1="70" y1="26" x2="70" y2="17" />

        <circle class="sweep" cx="70" cy="70" r="38" />
        <circle class="disc" cx="70" cy="70" r="30" />
        <path class="tick" d="M55 71 L66 82 L87 57" />

        <g clip-path="url(#discClip)">
          <rect class="sheen" x="56" y="20" width="28" height="100" />
        </g>
      </svg>

      <div class="copy">
        <span class="line title"><span>One million records</span></span>
        <span class="line sub"><span>your first full corpus, processed</span></span>
      </div>
    </div>
  </body>
</html>

The variant family

Same idea, one axis moved. Compare them side by side before you commit.