Back to the library
Console warm-up ready (stage stagger)
Variant: Stage stagger
The warm-up broken into an offset pass: the arc, the needle, the fill bar and each lamp start on their own delays rather than moving as one block, so the panel reads as a sequence of subsystems coming up in order. Choose it when the wait has genuine named stages — link, auth, stream — and you want the eye to be able to count them. Delays sit in the 90–140ms band so the cascade stays inside one perceptual beat instead of feeling like four separate events.
statusloaderstaggerdialpanel
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>Console warm-up ready — stage stagger</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: 4.4s; /* the authored master loop */
--t: calc(var(--cycle) / var(--speed));
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
/* Stage offsets: arc first, needle, bar, then lamps latch in sequence. */
--_arc: 0s;
--_needle: calc(0.1s / var(--speed));
--_bar: calc(0.2s / var(--speed));
--_lamp-1: calc(0.34s / var(--speed));
--_lamp-2: calc(0.46s / var(--speed));
--_lamp-3: calc(0.58s / var(--speed));
--_chrome: #cfd2d4;
}
* { box-sizing: border-box; }
html, body { margin: 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);
}
.console {
width: min(360px, 90vw);
padding: clamp(14px, 4.4vw, 20px);
background: var(--paper);
border: 1px solid var(--oat);
border-radius: calc(14px * var(--round));
box-shadow: 0 1px 0 var(--oat) inset;
display: grid;
gap: clamp(12px, 3.6vw, 16px);
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.label {
font-size: clamp(11px, 3vw, 12px);
letter-spacing: 0.16em;
text-transform: uppercase;
font-weight: 600;
color: var(--slate);
}
.lamps { display: flex; gap: 6px; }
.lamp {
width: 20px;
height: 7px;
border-radius: 999px;
background: var(--oat);
opacity: 0.55;
transform-origin: left center;
animation: lamp-latch var(--t) var(--ease-out) infinite;
}
.lamp:nth-child(1) { animation-delay: var(--_lamp-1); }
.lamp:nth-child(2) { animation-delay: var(--_lamp-2); }
.lamp:nth-child(3) { animation-delay: var(--_lamp-3); }
@keyframes lamp-latch {
0% { opacity: 0.35; background: var(--oat); transform: scaleX(0.92); }
9%, 80% { opacity: 1; background: var(--clay); transform: scaleX(1); }
100% { opacity: 0.35; background: var(--oat); transform: scaleX(0.92); }
}
.face {
position: relative;
border-radius: calc(10px * var(--round));
background: linear-gradient(180deg, var(--ivory), var(--paper));
border: 1px solid var(--oat);
padding: clamp(10px, 3vw, 14px);
overflow: hidden;
}
svg { display: block; width: 100%; height: auto; }
.arc { stroke: var(--oat); }
.arc-live {
stroke: var(--clay);
stroke-dasharray: 150;
stroke-dashoffset: 150;
animation: arc-fill var(--t) var(--ease-out) infinite;
animation-delay: var(--_arc);
}
@keyframes arc-fill {
0% { stroke-dashoffset: 150; }
40% { stroke-dashoffset: 42; }
84% { stroke-dashoffset: 42; }
100% { stroke-dashoffset: 150; }
}
.needle {
transform-origin: 110px 78px;
animation: needle-swing var(--t) var(--ease-in-out) infinite;
animation-delay: var(--_needle);
}
@keyframes needle-swing {
0% { transform: rotate(-58deg); }
40% { transform: rotate(34deg); }
84% { transform: rotate(34deg); }
100% { transform: rotate(-58deg); }
}
.pip { fill: var(--oat); }
.hub { fill: var(--slate); }
.sweep {
position: absolute;
inset: 0;
background: linear-gradient(105deg, transparent 38%, var(--_chrome) 50%, transparent 62%);
opacity: 0.5;
transform: translateX(-120%);
animation: sweep var(--t) linear infinite;
pointer-events: none;
}
@keyframes sweep {
0%, 14% { transform: translateX(-120%); }
46% { transform: translateX(120%); }
100% { transform: translateX(120%); }
}
.foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
font-size: clamp(10.5px, 2.9vw, 11.5px);
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--clay-d);
}
.foot .right { color: var(--olive); }
.bar {
height: 4px;
border-radius: 999px;
background: var(--oat);
overflow: hidden;
}
.bar i {
display: block;
height: 100%;
border-radius: 999px;
background: var(--rust);
transform-origin: left center;
transform: scaleX(0.06);
animation: bar-fill var(--t) var(--ease-out) infinite;
animation-delay: var(--_bar);
}
@keyframes bar-fill {
0% { transform: scaleX(0.06); }
40% { transform: scaleX(1); }
84% { transform: scaleX(1); }
100% { transform: scaleX(0.06); }
}
@media (max-width: 380px) {
.console { padding: 13px; gap: 11px; }
.face { padding: 9px; }
.lamp { width: 17px; }
}
@media (prefers-reduced-motion: reduce) {
.needle, .sweep, .bar i, .arc-live { animation: none; }
.needle { transform: rotate(34deg); }
.bar i { transform: scaleX(1); }
.arc-live { stroke-dashoffset: 42; }
.sweep { display: none; }
/* Stagger survives as a phase offset on the surviving opacity loop. */
.lamp {
transform: none;
animation: lamp-dim calc(var(--t) * 2) ease-in-out infinite;
}
.lamp:nth-child(1) { animation-delay: var(--_lamp-1); }
.lamp:nth-child(2) { animation-delay: var(--_lamp-2); }
.lamp:nth-child(3) { animation-delay: var(--_lamp-3); }
@keyframes lamp-dim {
0%, 100% { opacity: 0.6; background: var(--clay); }
50% { opacity: 1; background: var(--clay); }
}
}
</style>
</head>
<body>
<div class="console" role="img" aria-label="Device warming up to ready state">
<div class="head">
<span class="label">Link status</span>
<div class="lamps">
<span class="lamp"></span>
<span class="lamp"></span>
<span class="lamp"></span>
</div>
</div>
<div class="face">
<svg viewBox="0 0 220 96" aria-hidden="true">
<path class="arc" d="M 42 78 A 68 68 0 0 1 178 78" fill="none" stroke-width="6" stroke-linecap="round" />
<path class="arc-live" d="M 42 78 A 68 68 0 0 1 178 78" fill="none" stroke-width="6" stroke-linecap="round" />
<g class="pip">
<rect x="54" y="44" width="2.5" height="8" rx="1.2" transform="rotate(-52 55 48)" />
<rect x="108.5" y="22" width="2.5" height="9" rx="1.2" />
<rect x="163" y="44" width="2.5" height="8" rx="1.2" transform="rotate(52 164 48)" />
</g>
<g class="needle">
<rect x="108.6" y="30" width="2.8" height="50" rx="1.4" fill="currentColor" />
</g>
<circle class="hub" cx="110" cy="78" r="5" />
</svg>
<span class="sweep"></span>
</div>
<div class="bar"><i></i></div>
<div class="foot">
<span>Warming</span>
<span class="right">Ready</span>
</div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.