Back to the library

Liquid sync status, ghost

Variant: Ghost

A near-transparent rung for backgrounded or low-priority work — the rings are thin and dropped to a fraction of full opacity, the core barely lifts, and the halo is absent, so the indicator registers in peripheral vision without competing with content. Every duration and curve matches the standard; only opacity and stroke shrink, which is what keeps it legible as the same component in a quieter state. Useful as the resting appearance of an indicator that swaps to the standard rung when the sync becomes user-initiated.

loaderstatusambientsvgsubtle

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>Liquid sync status — ghost</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-in-out: cubic-bezier(0.77, 0, 0.175, 1);

  /* File-specific: weight carried almost entirely by opacity here. */
  --_stroke-a: 2;
  --_stroke-b: 1.4;
  --_lobe: 0.94;
  --_core: 5.5;
  --_glow: 0;
  --_swell: 1.04;
  --_ringspin: calc(var(--t) * 2.5);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  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);
}

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

.dial {
  width: clamp(64px, 18vw, 78px);
  height: clamp(64px, 18vw, 78px);
  display: block;
  overflow: visible;
}

.ring {
  fill: none;
  stroke-linecap: round;
  transform-origin: 50% 50%;
}

.ring-a {
  stroke: var(--clay);
  stroke-width: var(--_stroke-a);
  opacity: 0.32;
  animation: spin-cw var(--_ringspin) linear infinite;
}

.ring-b {
  stroke: var(--olive);
  stroke-width: var(--_stroke-b);
  opacity: 0.22;
  animation: spin-ccw calc(var(--_ringspin) * 1.5) linear infinite;
}

.lobes { transform-origin: 50% 50%; transform: scale(var(--_lobe)); }

.core {
  fill: var(--rust);
  transform-origin: 50% 50%;
  opacity: 0.5;
  animation: breathe var(--t) var(--ease-in-out) infinite;
}

.halo {
  fill: var(--clay);
  transform-origin: 50% 50%;
  opacity: var(--_glow);
  animation: halo var(--t) var(--ease-in-out) infinite;
}

@keyframes spin-cw  { to { transform: rotate(360deg); } }
@keyframes spin-ccw { to { transform: rotate(-360deg); } }

/* Opacity ceiling is lower than the other rungs; the curve and phase are not. */
@keyframes breathe {
  0%, 100% { transform: scale(0.94); opacity: 0.3; }
  46%      { transform: scale(var(--_swell)); opacity: 0.62; }
}

@keyframes halo {
  0%, 100% { transform: scale(0.9); opacity: 0; }
  40%      { opacity: var(--_glow); }
  100%     { transform: scale(1.55); }
}

.copy { display: grid; gap: 4px; min-width: 0; }

.label {
  font-size: clamp(13px, 3.6vw, 15px);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.sub {
  font-size: clamp(11px, 3vw, 12.5px);
  color: color-mix(in oklab, var(--slate) 55%, var(--ivory));
}

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

@media (prefers-reduced-motion: reduce) {
  .ring-a, .ring-b, .halo { animation: none; }
  .halo { opacity: 0; }
  .core {
    opacity: 1;
    animation: breathe-soft calc(var(--t) * 2) var(--ease-in-out) infinite;
  }
  @keyframes breathe-soft {
    0%, 100% { opacity: 0.3;  transform: scale(1); }
    50%      { opacity: 0.62; transform: scale(1); }
  }
}
</style>
</head>
<body>
  <div class="card">
    <svg class="dial" viewBox="0 0 100 100" aria-hidden="true">
      <g class="lobes">
        <path class="ring ring-a"
              d="M50 8 C72 8 92 28 92 50 C92 72 72 92 50 92 C28 92 8 72 8 50 C8 28 28 8 50 8 Z"
              pathLength="100" stroke-dasharray="17 8" />
        <path class="ring ring-b"
              d="M50 20 C64 26 74 36 80 50 C74 64 64 74 50 80 C36 74 26 64 20 50 C26 36 36 26 50 20 Z"
              pathLength="100" stroke-dasharray="11 6" />
      </g>
      <circle class="halo" cx="50" cy="50" r="11" />
      <circle class="core" cx="50" cy="50" r="5.5" />
    </svg>
    <div class="copy">
      <div class="label">Syncing library</div>
      <div class="sub">1,284 items · keeping up</div>
    </div>
  </div>
</body>
</html>

The variant family

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