Back to the library

Job complete, quiet

Variant: Quiet

A build row on an ops dashboard: the clay rail reaches full, a tick draws in beside the label, and the rail dims to a third of its weight — because finishing means it stops being live. Every beat lands in 140-180ms and nothing scales, glows or celebrates. Reach for it where the same event fires hundreds of times a day and the only correct behaviour is to acknowledge and get out of the way.

successstatuscontrolsvg

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 — understated</title>
    <style>
      :root {
        --ivory: #faf9f5;
        --slate: #141413;
        --clay: #d97757;
        --oat: #e3dacc;
        /* One cycle. The event itself occupies ~400ms of it; the rest is the
           row simply sitting there, which is what this row does all day. */
        --cycle: 6s;
        --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);
      }

      .row {
        /* Grows with the stage instead of sitting at 200px in a 1100px frame.
           Floors at the original width, so the two mobile sizes are unchanged. */
        width: clamp(200px, 34vw, 340px);
        display: grid;
        gap: 10px;
        /* The loop restarts from opacity 0, so the reset is never seen. */
        animation: card var(--cycle) linear infinite;
      }

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

      .head {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        font-size: clamp(12px, 1.85vw, 18px);
        letter-spacing: -0.01em;
      }

      .head .name {
        font-weight: 550;
      }

      .head .id {
        font-size: clamp(11px, 1.8vw, 17px);
        color: color-mix(in srgb, var(--slate) 45%, transparent);
        font-variant-numeric: tabular-nums;
      }

      .rail {
        height: 2px;
        border-radius: 1px;
        background: var(--oat);
        overflow: hidden;
      }

      .rail i {
        display: block;
        height: 100%;
        background: var(--clay);
        transform-origin: left center;
        animation: fill var(--cycle) linear infinite;
      }

      /* The only "event" gesture: the last sliver of the bar lands (180ms),
         then the bar steps back to a resting tint. No pop, no colour flood. */
      @keyframes fill {
        0%,
        8% {
          transform: scaleX(0.93);
          opacity: 1;
          animation-timing-function: var(--out);
        }
        11% {
          transform: scaleX(1);
          opacity: 1;
          animation-timing-function: ease;
        }
        12.5% {
          transform: scaleX(1);
          opacity: 1;
        }
        15%,
        100% {
          transform: scaleX(1);
          opacity: 0.32;
        }
      }

      .foot {
        display: grid;
        grid-template-columns: 1fr auto;
        align-items: center;
        font-size: clamp(11px, 1.8vw, 17px);
        font-variant-numeric: tabular-nums;
      }

      .states {
        display: grid;
      }

      .states > span {
        grid-area: 1 / 1;
        display: flex;
        align-items: center;
        gap: 5px;
        line-height: 1.2;
      }

      .running {
        color: color-mix(in srgb, var(--slate) 55%, transparent);
        animation: state-out var(--cycle) linear infinite;
      }

      @keyframes state-out {
        0%,
        8.5% {
          opacity: 1;
          transform: translateY(0);
          animation-timing-function: ease;
        }
        10.5%,
        100% {
          opacity: 0;
          transform: translateY(-2px);
        }
      }

      .done {
        font-weight: 550;
        animation: done var(--cycle) linear infinite;
      }

      @keyframes done {
        0%,
        10% {
          opacity: 0;
          transform: translateY(3px);
          animation-timing-function: var(--out);
        }
        12.3%,
        100% {
          opacity: 1;
          transform: translateY(0);
        }
      }

      .tick {
        /* Tracks the foot type size so the mark stays cap-height next to it. */
        width: clamp(11px, 1.8vw, 17px);
        height: clamp(11px, 1.8vw, 17px);
        flex: none;
        overflow: visible;
      }

      .tick path {
        fill: none;
        stroke: var(--clay);
        stroke-width: 2;
        stroke-linecap: round;
        stroke-linejoin: round;
        stroke-dasharray: 16;
        animation: draw var(--cycle) linear infinite;
      }

      @keyframes draw {
        0%,
        10.5% {
          stroke-dashoffset: 16;
          animation-timing-function: var(--out);
        }
        13.2%,
        100% {
          stroke-dashoffset: 0;
        }
      }

      .time {
        color: color-mix(in srgb, var(--slate) 45%, transparent);
      }

      @media (prefers-reduced-motion: reduce) {
        /* Gentler, not dead: the state still changes, it just crossfades. */
        .rail i {
          animation: fill-rm var(--cycle) linear infinite;
          transform: scaleX(1);
        }
        @keyframes fill-rm {
          0%,
          10% {
            opacity: 1;
          }
          16%,
          100% {
            opacity: 0.32;
          }
        }
        .running {
          animation: running-rm var(--cycle) linear infinite;
        }
        @keyframes running-rm {
          0%,
          9% {
            opacity: 1;
          }
          13%,
          100% {
            opacity: 0;
          }
        }
        .done {
          animation: done-rm var(--cycle) linear infinite;
          transform: none;
        }
        @keyframes done-rm {
          0%,
          10% {
            opacity: 0;
          }
          15%,
          100% {
            opacity: 1;
          }
        }
        .tick path {
          animation: none;
          stroke-dashoffset: 0;
        }
      }
    </style>
  </head>
  <body>
    <div class="row" role="status" aria-label="Deploy to production finished successfully">
      <div class="head">
        <span class="name">deploy · production</span>
        <span class="id">#4912</span>
      </div>
      <div class="rail"><i></i></div>
      <div class="foot">
        <div class="states">
          <span class="running">running</span>
          <span class="done">
            <svg class="tick" viewBox="0 0 16 16" aria-hidden="true">
              <path d="M3.5 8.5 L6.5 11.5 L12.5 4.5" />
            </svg>
            passed
          </span>
        </div>
        <span class="time">3m 14s</span>
      </div>
    </div>
  </body>
</html>

The variant family

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