Back to the library
Threshold handoff loader, held return
Variant: Held return
One crossing per cycle, but the payload lands early and the far side holds lit for nearly half the loop before the next item enters. The long empty beat suits screens where the crossing is rare and consequential and you want the committed state, not the travel, to be the resting image. Only the keyframe percentages and the cycle length differ from the single beat.
loadertransitionprogressholdambient
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>Threshold handoff loader — held return</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-in-out: cubic-bezier(0.77, 0, 0.175, 1);
--_rail: 76%;
}
* { 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);
}
.card {
width: min(420px, 92vw);
padding: clamp(18px, 5vw, 26px) clamp(16px, 5vw, 24px);
background: var(--paper);
border: 1px solid var(--oat);
border-radius: calc(14px * var(--round));
display: grid;
gap: clamp(14px, 4vw, 18px);
}
.head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
}
.title {
font-size: clamp(12px, 3.4vw, 13.5px);
font-weight: 600;
letter-spacing: 0.01em;
}
.meta {
font-size: clamp(11px, 3vw, 12px);
color: color-mix(in oklab, var(--slate) 48%, var(--paper));
font-variant-numeric: tabular-nums;
}
.stage {
position: relative;
height: clamp(72px, 22vw, 88px);
}
.rail {
position: absolute;
left: 0;
right: 0;
top: 50%;
height: 1px;
background: var(--oat);
}
.gate {
position: absolute;
left: var(--_rail);
top: 50%;
width: 1px;
height: 100%;
transform: translate(-50%, -50%);
background: linear-gradient(to bottom,
transparent,
color-mix(in oklab, var(--slate) 30%, var(--paper)) 22%,
color-mix(in oklab, var(--slate) 30%, var(--paper)) 78%,
transparent);
}
.gate::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
width: 7px;
height: 7px;
margin: -3.5px 0 0 -3.5px;
border-radius: 50%;
background: var(--paper);
border: 1px solid color-mix(in oklab, var(--slate) 34%, var(--paper));
animation: gate-mark var(--t) var(--ease-in-out) infinite;
}
.far {
position: absolute;
left: var(--_rail);
right: 0;
top: 50%;
height: 1px;
background: var(--olive);
animation: far-warm var(--t) var(--ease-in-out) infinite;
}
.travellers {
position: absolute;
inset: 0;
}
.payload,
.guide {
position: absolute;
top: 50%;
left: 0;
}
.payload {
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
border-radius: calc(4px * var(--round));
background: var(--clay);
animation: cross var(--t) var(--ease-in-out) infinite;
}
.payload::after {
content: '';
position: absolute;
inset: 0;
border-radius: calc(4px * var(--round));
background: var(--olive);
opacity: 0;
animation: commit var(--t) var(--ease-in-out) infinite;
}
.guide {
width: 8px;
height: 8px;
margin: -4px 0 0 -4px;
border-radius: 50%;
border: 1.5px solid var(--slate);
animation: escort var(--t) var(--ease-in-out) infinite;
}
.legend {
display: flex;
justify-content: space-between;
font-size: clamp(10.5px, 2.9vw, 11.5px);
color: color-mix(in oklab, var(--slate) 46%, var(--paper));
}
.legend b {
font-weight: 600;
color: color-mix(in oklab, var(--slate) 72%, var(--paper));
}
/* Travel is compressed into the first 55% of the loop; the rest is the hold. */
@keyframes cross {
0% { transform: translateX(6%) scale(0.97); opacity: 0; }
8% { transform: translateX(8%) scale(1); opacity: 1; }
14% { transform: translateX(10%); }
46% { transform: translateX(90%); opacity: 1; }
62% { transform: translateX(92%); opacity: 1; }
76% { transform: translateX(96%) scale(0.97); opacity: 0; }
100% { transform: translateX(96%) scale(0.97); opacity: 0; }
}
@keyframes commit {
0%, 36% { opacity: 0; }
50%, 100% { opacity: 1; }
}
@keyframes escort {
0% { transform: translateX(0%) scale(0.9); opacity: 0; }
9% { transform: translateX(2%) scale(1); opacity: 1; }
15% { transform: translateX(16%); opacity: 1; }
46% { transform: translateX(80%); opacity: 1; }
60% { transform: translateX(72%); opacity: 0.45; }
72% { transform: translateX(60%) scale(0.9); opacity: 0; }
100% { transform: translateX(60%) scale(0.9); opacity: 0; }
}
@keyframes gate-mark {
0%, 28% { transform: scale(1); background: var(--paper); }
40% { transform: scale(1.45); background: var(--olive); }
58%, 100% { transform: scale(1); background: var(--paper); }
}
/* Stays lit through the empty beat, so the resting frame is "done". */
@keyframes far-warm {
0%, 32% { opacity: 0.18; }
48%, 84% { opacity: 1; }
100% { opacity: 0.18; }
}
@media (max-width: 380px) {
.card { padding: 16px 14px; gap: 12px; }
.stage { height: 68px; }
}
@media (prefers-reduced-motion: reduce) {
.payload, .guide, .gate::after { animation: none; }
.payload { transform: translateX(42%); opacity: 1; }
.guide { transform: translateX(30%); opacity: 1; }
.payload::after { animation: commit calc(var(--t) * 1.8) var(--ease-in-out) infinite; }
.far { animation: far-warm calc(var(--t) * 1.8) var(--ease-in-out) infinite; }
}
</style>
</head>
<body>
<div class="card">
<div class="head">
<span class="title">Finalising transfer</span>
<span class="meta">1 of 1</span>
</div>
<div class="stage">
<div class="rail"></div>
<div class="far"></div>
<div class="gate"></div>
<div class="travellers">
<div class="payload"></div>
<div class="guide"></div>
</div>
</div>
<div class="legend">
<span>Source</span>
<span><b>Committed</b></span>
</div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.