Back to the library
Guest arrival ring (ghost)
Variant: Ghost
A near-silent version for surfaces that must stay quiet: hairline rings at low alpha, an outlined chip that barely travels, and companion dots that only lift to about half opacity. Reach for it when presence is background information on an already busy screen. Everything below full strength is opacity and stroke; the 90ms ring-before-guest lead and the 60ms dot stagger are untouched so it still reads as the same gesture.
loaderpresenceambientghostsubtle
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>Guest arrival ring — 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.6s;
--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);
/* File-specific — weight axis only. */
--_ring-w: 1px;
--_ring-open: 0.93;
--_ring-wide: 1.0;
--_chip-travel: 10%;
--_dot-size: 5px;
--_dot-dim: 0.1;
--_stage: min(300px, 82vw);
}
* { 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(360px, 92vw);
padding: clamp(18px, 5vw, 26px);
border-radius: calc(18px * var(--round));
background: var(--paper);
border: 1px solid var(--oat);
display: grid;
justify-items: center;
gap: clamp(12px, 3.5vw, 18px);
}
.stage {
position: relative;
width: var(--_stage);
height: calc(var(--_stage) * 0.52);
display: grid;
place-items: center;
}
.ring {
position: absolute;
width: calc(var(--_stage) * 0.42);
height: calc(var(--_stage) * 0.42);
border-radius: 50%;
border: var(--_ring-w) solid var(--clay);
opacity: 0;
transform: scale(var(--_ring-open));
animation: ring-open var(--t) var(--ease-out) infinite;
}
.ring.echo {
border-color: var(--olive);
animation-delay: calc(140ms / var(--speed));
}
@keyframes ring-open {
0% { transform: scale(var(--_ring-open)); opacity: 0; }
14% { transform: scale(var(--_ring-open)); opacity: 0.4; }
46% { transform: scale(var(--_ring-wide)); opacity: 0.24; }
74% { transform: scale(var(--_ring-wide)); opacity: 0.18; }
92% { transform: scale(var(--_ring-open)); opacity: 0; }
100% { transform: scale(var(--_ring-open)); opacity: 0; }
}
.chip {
position: absolute;
width: calc(var(--_stage) * 0.14);
height: calc(var(--_stage) * 0.14);
border-radius: 50%;
background: transparent;
border: var(--_ring-w) solid var(--clay-d);
display: grid;
place-items: center;
opacity: 0;
animation: chip-arrive var(--t) var(--ease-out) calc(90ms / var(--speed)) infinite;
}
.chip svg { width: 56%; height: 56%; display: block; }
.chip path { fill: none; stroke: var(--clay-d); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
@keyframes chip-arrive {
0% { transform: translateX(calc(-1 * var(--_chip-travel))) scale(0.96); opacity: 0; }
22% { transform: translateX(0) scale(1); opacity: 0.6; }
76% { transform: translateX(0) scale(1); opacity: 0.6; }
90% { transform: translateX(0) scale(0.97); opacity: 0; }
100% { transform: translateX(0) scale(0.97); opacity: 0; }
}
.seats {
position: absolute;
inset: 0;
display: grid;
place-items: center;
pointer-events: none;
}
.seat {
position: absolute;
width: var(--_dot-size);
height: var(--_dot-size);
border-radius: 50%;
background: var(--rust);
opacity: var(--_dot-dim);
transform: rotate(var(--_a)) translateX(calc(var(--_stage) * 0.3)) rotate(calc(-1 * var(--_a)));
animation: seat-fill var(--t) var(--ease-in-out) var(--_d) infinite;
}
.seat:nth-child(1) { --_a: -46deg; --_d: calc(360ms / var(--speed)); }
.seat:nth-child(2) { --_a: 0deg; --_d: calc(420ms / var(--speed)); }
.seat:nth-child(3) { --_a: 46deg; --_d: calc(480ms / var(--speed)); }
@keyframes seat-fill {
0% { opacity: var(--_dot-dim); }
16% { opacity: 0.5; }
70% { opacity: 0.5; }
88% { opacity: var(--_dot-dim); }
100% { opacity: var(--_dot-dim); }
}
.label {
font-size: clamp(11px, 3vw, 13px);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--slate);
opacity: 0.4;
}
@media (max-width: 380px) {
.card { padding: 14px; gap: 10px; }
}
@media (prefers-reduced-motion: reduce) {
.ring {
transform: scale(1);
animation: ring-hold calc(var(--t) * 2.2) var(--ease-in-out) infinite;
}
.ring.echo { animation-delay: calc(300ms / var(--speed)); }
.chip {
transform: none;
animation: soft-hold calc(var(--t) * 2.2) var(--ease-in-out) calc(200ms / var(--speed)) infinite;
}
.seat {
transform: rotate(var(--_a)) translateX(calc(var(--_stage) * 0.3)) rotate(calc(-1 * var(--_a)));
animation: seat-fill calc(var(--t) * 2.2) var(--ease-in-out) var(--_d) infinite;
}
@keyframes ring-hold {
0%, 100% { opacity: 0.16; }
50% { opacity: 0.4; }
}
@keyframes soft-hold {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.62; }
}
}
</style>
</head>
<body>
<div class="card">
<div class="stage">
<div class="ring"></div>
<div class="ring echo"></div>
<div class="seats">
<span class="seat"></span>
<span class="seat"></span>
<span class="seat"></span>
</div>
<div class="chip">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 4.5a3.4 3.4 0 1 1 0 6.8 3.4 3.4 0 0 1 0-6.8Z" />
<path d="M4.8 19.4c0-3.3 3.2-5.3 7.2-5.3s7.2 2 7.2 5.3" />
</svg>
</div>
</div>
<div class="label">Someone is joining</div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.