Back to the library

Pour fill confirm

Variant: Linear

The same vessel meter, filled at a constant rate instead of a decelerating one. Linear reads as a measured, machine-paced transfer — useful when the underlying process really is steady and you do not want the fill to imply it is nearly done. Geometry, colour, and the 3.2s cycle are identical to the ease-out sibling; only the curve on the rise and the tick marks changes.

progressstatusfilllooplinear

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>Pour fill confirm — linear</title>
<style>
:root {
  /* Neutrals — fixed, never remixed. */
  --ivory: #faf9f5;
  --paper: #ffffff;
  --oat: #e3dacc;
  --slate: #141413;

  /* Knobs — the remix API. A knob exists iff declared here and used below. */
  --hue: 38.8;
  --lift: 0;
  --chroma: 1;
  --round: 1;
  --speed: 1;

  /* Accents — derived from the knobs with the family's offsets baked in,
     so one hue roll moves them together. Defaults reproduce the original hex
     (#d97757 / #b85c3e / #788c5d / #b04a3f). */
  --clay: oklch(calc(0.6724 + var(--lift)) calc(0.1308 * var(--chroma)) var(--hue));
  --clay-d: oklch(calc(0.5797 + var(--lift)) calc(0.127 * var(--chroma)) var(--hue));
  --olive: oklch(calc(0.6118 + var(--lift)) calc(0.0713 * var(--chroma)) calc(var(--hue) + 88.3));
  --rust: oklch(calc(0.5408 + var(--lift)) calc(0.1357 * var(--chroma)) calc(var(--hue) - 10.3));

  /* Clock — every duration in the document runs off --t. */
  --cycle: 3.2s;
  --t: calc(var(--cycle) / var(--speed));

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);

  /* File-specific: the level line trails the body by one small beat. */
  --_lag: calc(60ms / var(--speed));
  --_label-in: calc(220ms / var(--speed));
}

* { box-sizing: border-box; }

html, body { margin: 0; }

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

.card {
  width: min(380px, 90vw);
  padding: clamp(16px, 5vw, 24px);
  background: var(--paper);
  border: 1px solid var(--oat);
  border-radius: calc(14px * var(--round));
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: clamp(14px, 4vw, 20px);
}

.vessel {
  position: relative;
  width: clamp(56px, 17vw, 68px);
  height: clamp(76px, 23vw, 92px);
  border-radius: calc(10px * var(--round)) calc(10px * var(--round)) calc(26px * var(--round)) calc(26px * var(--round));
  background: var(--ivory);
  border: 1.5px solid var(--oat);
  overflow: hidden;
  flex: none;
}

.body-fill,
.level {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 100%;
}

.body-fill {
  background: linear-gradient(180deg, var(--clay) 0%, var(--clay-d) 100%);
  clip-path: inset(100% 0 0 0);
  animation: rise var(--t) linear infinite;
}

.level {
  background: linear-gradient(180deg, color-mix(in oklch, var(--paper) 62%, var(--clay)) 0 2.5px, transparent 2.5px);
  clip-path: inset(100% 0 0 0);
  animation: rise var(--t) linear var(--_lag) infinite;
}

@keyframes rise {
  0%      { clip-path: inset(100% 0 0 0); }
  46%     { clip-path: inset(22% 0 0 0); }
  82%     { clip-path: inset(22% 0 0 0); }
  100%    { clip-path: inset(100% 0 0 0); }
}

.copy { min-width: 0; }

.title {
  font-size: clamp(13px, 3.6vw, 15px);
  font-weight: 550;
  letter-spacing: 0.01em;
  margin: 0 0 6px;
}

.sub {
  font-size: clamp(11px, 3.1vw, 12.5px);
  line-height: 1.35;
  margin: 0;
  color: color-mix(in oklch, var(--slate) 55%, var(--ivory));
}

.ticks {
  margin-top: 12px;
  display: flex;
  gap: 6px;
}

.ticks i {
  display: block;
  width: clamp(16px, 5vw, 22px);
  height: 3px;
  border-radius: 999px;
  background: var(--oat);
  transform-origin: left center;
  transform: scaleX(0.35);
  animation: tick var(--t) linear infinite;
}

.ticks i:nth-child(1) { animation-delay: calc(0ms / var(--speed)); }
.ticks i:nth-child(2) { animation-delay: calc(90ms / var(--speed)); }
.ticks i:nth-child(3) { animation-delay: calc(180ms / var(--speed)); }

@keyframes tick {
  0%   { transform: scaleX(0.35); background: var(--oat); }
  40%  { transform: scaleX(1); background: var(--olive); }
  82%  { transform: scaleX(1); background: var(--olive); }
  100% { transform: scaleX(0.35); background: var(--oat); }
}

/* The confirmation glyph stays ease-out in every sibling: it enters, it does
   not transfer, so a constant-rate entrance would read as a glitch. */
.mark {
  display: inline-block;
  margin-left: 6px;
  color: var(--olive);
  opacity: 0;
  transform: translateY(2px);
  animation: mark var(--t) var(--ease-out) var(--_label-in) infinite;
}

@keyframes mark {
  0%, 14%   { opacity: 0; transform: translateY(2px); }
  40%, 82%  { opacity: 1; transform: translateY(0); }
  92%, 100% { opacity: 0; transform: translateY(0); }
}

@media (max-width: 380px) {
  .card { padding: 14px; gap: 12px; }
}

@media (prefers-reduced-motion: reduce) {
  .body-fill {
    clip-path: inset(22% 0 0 0);
    animation: hold-breathe calc(var(--cycle) * 2.2 / var(--speed)) ease-in-out infinite;
  }
  .level {
    clip-path: inset(22% 0 0 0);
    animation: hold-breathe calc(var(--cycle) * 2.2 / var(--speed)) ease-in-out calc(60ms / var(--speed)) infinite;
  }
  .ticks i {
    transform: scaleX(1);
    background: var(--olive);
    animation: none;
  }
  .mark { opacity: 1; transform: none; animation: none; }

  @keyframes hold-breathe {
    0%, 100% { opacity: 0.72; }
    50%      { opacity: 1; }
  }
}
</style>
</head>
<body>
  <div class="card">
    <div class="vessel" aria-hidden="true">
      <div class="body-fill"></div>
      <div class="level"></div>
    </div>
    <div class="copy">
      <p class="title">Archive synced<span class="mark">&#10003;</span></p>
      <p class="sub">Local changes written to the vault.</p>
      <div class="ticks" aria-hidden="true"><i></i><i></i><i></i></div>
    </div>
  </div>
</body>
</html>

The variant family

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