Back to the library
Satellite fix lock, linear
Variant: Linear
The same acquisition indicator with every curve replaced by a flat linear pass, giving the ticks and the fill a machine-metered, instrument-panel character. Use it when the component should read as raw telemetry rather than as UI feedback — constant-rate motion implies a measured quantity. Geometry, colour, and the 3.4s loop are identical to the eased sibling; only the rate of change differs, which is enough to make the arrival feel reported instead of confirmed.
loaderstatusgpshigh-contrastloop
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>Satellite fix lock — 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.4s;
--t: calc(var(--cycle) / var(--speed));
/* File-specific scratch tokens. */
--_tick-in: calc(0.42s / var(--speed));
--_stagger: calc(0.06s / var(--speed));
--_plate: calc(0.26s / var(--speed));
}
* { 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);
}
.panel {
width: min(360px, 92vw);
padding: clamp(16px, 5vw, 24px);
background: var(--paper);
border: 3px solid var(--slate);
border-radius: calc(6px * var(--round));
display: grid;
gap: clamp(12px, 3.5vw, 18px);
justify-items: center;
}
.stage {
position: relative;
width: clamp(132px, 40vw, 156px);
aspect-ratio: 1;
}
svg { display: block; width: 100%; height: 100%; overflow: visible; }
.grid-line { stroke: var(--oat); stroke-width: 5; }
.frame { stroke: var(--slate); stroke-width: 6; fill: none; }
.tick {
stroke: var(--slate);
stroke-width: 9;
stroke-linecap: butt;
transform-box: fill-box;
transform-origin: 50% 50%;
animation: tick-drop var(--t) linear infinite;
}
.tick--a { animation-delay: 0s; }
.tick--b { animation-delay: var(--_stagger); }
.tick--c { animation-delay: calc(var(--_stagger) * 2); }
@keyframes tick-drop {
0% { opacity: 0; transform: translate(var(--_dx), var(--_dy)) scale(0.94); }
12%, 78% { opacity: 1; transform: translate(0, 0) scale(1); }
90%, 100% { opacity: 0; transform: translate(var(--_dx), var(--_dy)) scale(0.94); }
}
.core {
fill: var(--clay-d);
transform-box: fill-box;
transform-origin: 50% 50%;
animation: core-lock var(--t) linear infinite;
animation-delay: calc(var(--_stagger) * 3);
}
@keyframes core-lock {
0%, 8% { opacity: 0; transform: scale(0.93); }
20%, 78% { opacity: 1; transform: scale(1); }
88%, 100% { opacity: 0; transform: scale(0.93); }
}
.ring {
fill: none;
stroke: var(--rust);
stroke-width: 8;
stroke-dasharray: 232;
animation: ring-draw var(--t) linear infinite;
}
@keyframes ring-draw {
0%, 10% { stroke-dashoffset: 232; opacity: 1; }
34%, 80% { stroke-dashoffset: 0; opacity: 1; }
92%, 100% { stroke-dashoffset: 0; opacity: 0; }
}
.readout {
width: 100%;
display: flex;
align-items: center;
gap: 10px;
}
.bar {
position: relative;
flex: 1;
height: 16px;
border: 3px solid var(--slate);
border-radius: calc(3px * var(--round));
overflow: hidden;
}
.bar__fill {
position: absolute;
inset: 0;
background: var(--clay);
transform-origin: 0 50%;
animation: bar-fill var(--t) linear infinite;
}
@keyframes bar-fill {
0%, 10% { transform: scaleX(0.04); }
40%, 80% { transform: scaleX(1); }
92%, 100% { transform: scaleX(0.04); }
}
.label {
font-size: clamp(11px, 3.2vw, 13px);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
white-space: nowrap;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--olive);
animation: dot-hold var(--t) linear infinite;
}
@keyframes dot-hold {
0%, 30% { opacity: 0.22; }
40%, 82% { opacity: 1; }
94%, 100% { opacity: 0.22; }
}
@media (max-width: 380px) {
.panel { padding: 14px; gap: 12px; }
.bar { height: 14px; }
}
@media (prefers-reduced-motion: reduce) {
/* Movement drops; the state cadence survives as a slow opacity breath. */
.tick, .core, .ring, .bar__fill {
animation: none;
opacity: 1;
transform: none;
stroke-dashoffset: 0;
}
.bar__fill { transform: scaleX(1); }
.dot {
animation: dot-hold calc(var(--t) * 2.2) linear infinite;
}
}
</style>
</head>
<body>
<div class="panel">
<div class="stage">
<svg viewBox="0 0 120 120" role="img" aria-label="Acquiring position fix">
<path class="grid-line" d="M60 8 V112" />
<path class="grid-line" d="M8 60 H112" />
<rect class="frame" x="8" y="8" width="104" height="104" rx="3" />
<circle class="ring" cx="60" cy="60" r="37" />
<g>
<line class="tick tick--a" style="--_dx:-30px; --_dy:-22px;" x1="26" y1="40" x2="44" y2="40" />
<line class="tick tick--b" style="--_dx:30px; --_dy:-10px;" x1="76" y1="52" x2="94" y2="52" />
<line class="tick tick--c" style="--_dx:-14px; --_dy:32px;" x1="38" y1="86" x2="56" y2="86" />
</g>
<rect class="core" x="48" y="48" width="24" height="24" rx="2" />
</svg>
</div>
<div class="readout">
<span class="dot"></span>
<span class="label">Fix</span>
<span class="bar"><span class="bar__fill"></span></span>
</div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.