Back to the library
Horizon draft loader
Variant: Bold
An ambient progress indicator for long generative work: a dashed outline is traced ahead of the solid path that follows it, so the interface shows the intended shape before the filled state catches up. The trace runs linear because it is constant motion; the fill uses ease-in-out so it reads as mass moving between two rest points. This bold sibling pushes stroke to 3.5px and the ghost to near-full opacity so the pair stays legible as the primary element of an empty state or a full-screen wait.
loaderprogressambientsvgbold
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>Horizon draft loader — bold</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.6s;
--t: calc(var(--cycle) / var(--speed));
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
/* Weight tokens — the only thing that moves across this family. */
--_stroke: 3.5px;
--_ghost-op: 0.72;
--_bar-h: 10px;
--_rise: 7px;
--_dot: 9px;
}
* { 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;
}
.card {
width: min(400px, 90vw);
padding: clamp(16px, 5vw, 26px);
background: var(--paper);
border: 1px solid var(--oat);
border-radius: calc(14px * var(--round));
display: grid;
gap: clamp(12px, 3.4vw, 18px);
}
.head {
display: flex;
align-items: center;
gap: 8px;
font-size: clamp(11px, 3vw, 12.5px);
letter-spacing: 0.06em;
text-transform: uppercase;
color: color-mix(in oklch, var(--slate) 58%, var(--ivory));
}
.pip {
width: var(--_dot);
height: var(--_dot);
border-radius: 50%;
background: var(--clay);
animation: pip var(--t) linear infinite;
}
/* The sketch: a dashed outline drawn ahead of the solid form. */
.sketch { display: block; width: 100%; height: auto; overflow: visible; }
.ghost {
fill: none;
stroke: var(--clay);
stroke-width: var(--_stroke);
stroke-linecap: round;
stroke-dasharray: 5 5;
stroke-dashoffset: 0;
opacity: var(--_ghost-op);
/* linear: this is constant motion, not a response to an action. */
animation: trace var(--t) linear infinite;
}
.solid {
fill: none;
stroke: var(--clay-d);
stroke-width: calc(var(--_stroke) * 1.6);
stroke-linecap: round;
stroke-dasharray: 300;
stroke-dashoffset: 300;
animation: settle var(--t) var(--ease-in-out) infinite;
}
.track {
position: relative;
height: var(--_bar-h);
border-radius: 999px;
background: color-mix(in oklch, var(--oat) 72%, var(--paper));
overflow: hidden;
}
.track::after {
content: '';
position: absolute;
inset: 0;
border-radius: 999px;
background: var(--clay);
transform-origin: left center;
transform: scaleX(0.06);
animation: fill var(--t) var(--ease-in-out) infinite;
}
.legend {
display: flex;
justify-content: space-between;
font-size: clamp(10.5px, 2.8vw, 12px);
color: color-mix(in oklch, var(--slate) 50%, var(--ivory));
}
.legend b {
font-weight: 600;
color: var(--clay-d);
display: inline-block;
animation: lift var(--t) var(--ease-in-out) infinite;
}
@keyframes trace {
/* one dash period per cycle keeps the crawl seamless at any --speed */
to { stroke-dashoffset: -60; }
}
@keyframes settle {
0% { stroke-dashoffset: 300; opacity: 0; }
12% { opacity: 1; }
62% { stroke-dashoffset: 0; opacity: 1; }
86% { stroke-dashoffset: 0; opacity: 1; }
100% { stroke-dashoffset: 300; opacity: 0; }
}
@keyframes fill {
0% { transform: scaleX(0.06); }
62% { transform: scaleX(1); }
86% { transform: scaleX(1); }
100% { transform: scaleX(0.06); }
}
@keyframes lift {
0%, 100% { transform: translateY(0); opacity: 0.6; }
62%, 86% { transform: translateY(calc(-1 * var(--_rise))); opacity: 1; }
}
@keyframes pip {
0%, 100% { opacity: 0.35; }
50% { opacity: 1; }
}
@media (max-width: 380px) {
.card { padding: 14px; gap: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.ghost {
animation: ghost-breathe calc(var(--t) * 2.2) ease-in-out infinite;
stroke-dashoffset: 0;
}
.solid {
stroke-dashoffset: 0;
animation: solid-breathe calc(var(--t) * 2.2) ease-in-out infinite;
}
.track::after {
transform: scaleX(1);
animation: solid-breathe calc(var(--t) * 2.2) ease-in-out infinite;
}
.legend b { transform: none; animation: solid-breathe calc(var(--t) * 2.2) ease-in-out infinite; }
.pip { animation: solid-breathe calc(var(--t) * 2.2) ease-in-out infinite; }
}
@keyframes ghost-breathe {
0%, 100% { opacity: calc(var(--_ghost-op) * 0.6); }
50% { opacity: var(--_ghost-op); }
}
@keyframes solid-breathe {
0%, 100% { opacity: 0.62; }
50% { opacity: 1; }
}
</style>
</head>
<body>
<div class="card" role="status" aria-label="Drafting">
<div class="head"><span class="pip"></span> Drafting</div>
<svg class="sketch" viewBox="0 0 300 74" preserveAspectRatio="none" aria-hidden="true">
<path class="ghost" d="M6 62 L74 30 L150 46 L222 14 L294 34" />
<path class="solid" d="M6 62 L74 30 L150 46 L222 14 L294 34" />
</svg>
<div class="track"></div>
<div class="legend"><span>outline</span><b>resolved</b></div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.