Edit lease — contested
Variant: Contested
Two edit requests for an unlocked file, 18ms apart, and the photo finish that settles them. The requests enter the log 64ms apart in arrival order, timestamps in tabular figures so the deciding digits line up. Then the winner’s avatar rises out of its row into the lock’s empty seat — a shared element, not a new one appearing, so the eye follows the request to the lock it took — on 320ms of ease-in-out, because it is already on screen travelling from A to B. Everything the grant decides waits for the landing: the shackle shuts, the lease is issued on ease-out, the label names the holder. The loser does not shake or flash. Being second is not an error, it is a place in line, so the row steps back to 55% and reads "queued" — that one word set in full ink, since muted type inside a dimmed row stops being legible. Use it anywhere a claim can race: seat holds, ticket assignment, collaborative locks.
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>Edit lease — contested</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. */
--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)
);
--_muted: color-mix(in srgb, var(--slate) 50%, transparent);
--_hair: color-mix(in srgb, var(--slate) 9%, transparent);
--_ink: color-mix(in srgb, var(--slate) 72%, transparent);
/* Clock — 6.4s: two requests stamped 18ms apart (shown entering 64ms
apart, in arrival order), the first granted at 20–25%, its holder
riding the lease until the re-arm at 90%. */
--cycle: 6.4s;
--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);
/* Hold-row centre to first log-row centre: 16 + 8 + 4 + 14 + 14. Every
height on that path is a fixed px below, so this holds at any width. */
--_rise: -56px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 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(300px, calc(100vw - 32px));
padding: clamp(14px, 4.5vw, 18px);
border-radius: calc(14px * var(--round));
background: var(--paper);
box-shadow:
0 0 0 1px var(--_hair),
0 1px 2px var(--_hair);
}
.doc {
display: flex;
align-items: center;
gap: 7px;
padding-bottom: 12px;
margin-bottom: 12px;
border-bottom: 1px solid var(--_hair);
font-size: 12.5px;
font-weight: 500;
letter-spacing: -0.005em;
}
.doc svg {
flex: none;
width: 14px;
height: 14px;
fill: none;
stroke: var(--_muted);
stroke-width: 1.4;
stroke-linejoin: round;
}
.stage {
animation: rearm var(--t) var(--ease-out) infinite;
}
.hold {
display: flex;
align-items: center;
gap: 8px;
height: 32px;
}
.lock {
flex: none;
width: 16px;
height: 16px;
fill: none;
stroke: var(--_ink);
stroke-width: 1.6;
stroke-linecap: round;
}
.lock rect {
fill: var(--_ink);
stroke: none;
}
.open {
animation: open var(--t) infinite;
}
.shut {
opacity: 0;
animation: shut var(--t) infinite;
}
.slot {
position: relative;
flex: none;
width: 24px;
height: 24px;
}
.empty {
position: absolute;
inset: 0;
border-radius: 50%;
border: 1.5px dashed color-mix(in srgb, var(--slate) 26%, transparent);
}
.avatar {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 50%;
background: color-mix(in srgb, var(--_c) 18%, var(--paper));
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--_c) 34%, transparent);
font-size: 11px;
font-weight: 600;
color: color-mix(in srgb, var(--_c) 70%, var(--slate));
}
.jun {
--_c: var(--olive);
}
.you {
--_c: var(--clay);
}
/* Only used when motion is reduced: the winner appears in the seat
instead of travelling to it. */
.seated {
opacity: 0;
}
.runner {
z-index: 1;
animation: grant var(--t) infinite;
}
.stack {
flex: 1;
min-width: 0;
display: grid;
font-size: 13px;
font-weight: 500;
letter-spacing: -0.01em;
}
.stack span {
grid-area: 1 / 1;
white-space: nowrap;
}
.avail {
color: var(--_muted);
animation: was-free var(--t) var(--ease-out) infinite;
}
.taken {
opacity: 0;
animation: now-held var(--t) var(--ease-out) infinite;
}
.lease {
position: relative;
height: 4px;
margin-top: 8px;
border-radius: 999px;
background: var(--oat);
}
.fill {
position: absolute;
inset: 0;
border-radius: 999px;
background: var(--olive);
transform-origin: left center;
transform: scaleX(0);
animation: issue var(--t) infinite;
}
.log {
display: grid;
gap: 4px;
margin-top: 14px;
list-style: none;
}
.log li {
display: flex;
align-items: center;
gap: 8px;
height: 28px;
font-size: 12.5px;
opacity: 0;
}
.log li:nth-child(1) {
animation: arrive-1 var(--t) var(--ease-out) infinite;
}
.log li:nth-child(2) {
animation: arrive-2 var(--t) var(--ease-out) infinite;
}
.n {
flex: none;
width: 16px;
text-align: center;
font-size: 11px;
color: var(--_muted);
font-variant-numeric: tabular-nums;
}
.ghost {
position: absolute;
inset: 0;
border-radius: 50%;
border: 1.5px dashed color-mix(in srgb, var(--slate) 18%, transparent);
}
.name {
flex: 1;
font-weight: 500;
}
.at {
font-size: 11.5px;
color: var(--_muted);
font-variant-numeric: tabular-nums;
}
.verdict {
display: grid;
width: 66px;
font-size: 11.5px;
text-align: right;
}
.verdict span {
grid-area: 1 / 1;
}
.asked {
color: var(--_muted);
animation: was-asked var(--t) var(--ease-out) infinite;
}
.won,
.wait {
opacity: 0;
animation: ruled var(--t) var(--ease-out) infinite;
}
.won {
color: color-mix(in srgb, var(--olive) 78%, var(--slate));
font-weight: 500;
}
/* Full ink inside a row already stepped back to 55% — muted on muted
would leave the one word the loser needs barely legible. */
.wait {
color: var(--_ink);
}
/* The loser does not shake or flash: being second is not an error, it is
a place in line. It steps back to 55% and says so. */
.loser {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
animation: step-back var(--t) var(--ease-out) infinite;
}
@keyframes arrive-1 {
0%,
8% {
opacity: 0;
transform: translateY(4px);
}
11.5%,
94% {
opacity: 1;
transform: translateY(0);
}
94.5%,
100% {
opacity: 0;
transform: translateY(4px);
}
}
@keyframes arrive-2 {
0%,
9% {
opacity: 0;
transform: translateY(4px);
}
12.5%,
94% {
opacity: 1;
transform: translateY(0);
}
94.5%,
100% {
opacity: 0;
transform: translateY(4px);
}
}
/* The winner's avatar is the element that moves into the seat — a shared
element, not a new one appearing — so the eye follows the request to
the lock it took. 320ms on ease-in-out: it is already on screen, going
from A to B. */
@keyframes grant {
0%,
20% {
transform: translateY(0);
animation-timing-function: var(--ease-in-out);
}
25%,
94% {
transform: translateY(var(--_rise));
}
94.5%,
100% {
transform: translateY(0);
}
}
/* Everything that the grant decides happens on the frame the avatar
lands, never before it: the lock shuts, the lease is issued, the label
names the holder. */
@keyframes open {
0%,
25% {
opacity: 1;
}
25.25%,
94% {
opacity: 0;
}
94.5%,
100% {
opacity: 1;
}
}
@keyframes shut {
0%,
25% {
opacity: 0;
}
25.25%,
94% {
opacity: 1;
}
94.5%,
100% {
opacity: 0;
}
}
@keyframes issue {
0%,
25% {
transform: scaleX(0);
animation-timing-function: var(--ease-out);
}
30% {
transform: scaleX(1);
animation-timing-function: linear;
}
90%,
94% {
transform: scaleX(0.72);
}
94.5%,
100% {
transform: scaleX(0);
}
}
@keyframes was-free {
0%,
25% {
opacity: 1;
}
26.5%,
94% {
opacity: 0;
}
94.5%,
100% {
opacity: 1;
}
}
@keyframes now-held {
0%,
26.5% {
opacity: 0;
}
29.5%,
94% {
opacity: 1;
}
94.5%,
100% {
opacity: 0;
}
}
@keyframes was-asked {
0%,
25% {
opacity: 1;
}
26.5%,
94% {
opacity: 0;
}
94.5%,
100% {
opacity: 1;
}
}
@keyframes ruled {
0%,
26.5% {
opacity: 0;
}
29.5%,
94% {
opacity: 1;
}
94.5%,
100% {
opacity: 0;
}
}
@keyframes step-back {
0%,
25% {
opacity: 1;
}
29.5%,
94% {
opacity: 0.55;
}
94.5%,
100% {
opacity: 1;
}
}
/* Re-armed in the dark: everything snaps to its first frame at 94–94.5%
while the stage is out. */
@keyframes rearm {
0%,
90% {
opacity: 1;
}
93%,
95.5% {
opacity: 0;
}
99.5%,
100% {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
/* Nothing travels: the requests fade in where they sit, and the winner
fades out of its row as it fades into the seat. The bar stays — it is
the lease. One shared 1.6 keeps every cue on the grant frame. */
.stage,
.open,
.shut,
.avail,
.taken,
.fill,
.asked,
.won,
.wait,
.loser {
animation-duration: calc(var(--t) * 1.6);
}
.log li:nth-child(1) {
animation: arrive-still-1 calc(var(--t) * 1.6) var(--ease-out) infinite;
}
.log li:nth-child(2) {
animation: arrive-still-2 calc(var(--t) * 1.6) var(--ease-out) infinite;
}
.runner {
animation: leave-row calc(var(--t) * 1.6) var(--ease-out) infinite;
}
.seated {
animation: take-seat calc(var(--t) * 1.6) var(--ease-out) infinite;
}
@keyframes arrive-still-1 {
0%,
8% {
opacity: 0;
}
11.5%,
94% {
opacity: 1;
}
94.5%,
100% {
opacity: 0;
}
}
@keyframes arrive-still-2 {
0%,
9% {
opacity: 0;
}
12.5%,
94% {
opacity: 1;
}
94.5%,
100% {
opacity: 0;
}
}
@keyframes leave-row {
0%,
20% {
opacity: 1;
}
23%,
94% {
opacity: 0;
}
94.5%,
100% {
opacity: 1;
}
}
@keyframes take-seat {
0%,
22% {
opacity: 0;
}
25%,
94% {
opacity: 1;
}
94.5%,
100% {
opacity: 0;
}
}
}
</style>
</head>
<body>
<div
class="card"
role="img"
aria-label="Two edit requests for an unlocked document arrive 18 milliseconds apart. Jun's lands first: that avatar rises out of the request log into the lock's seat, the lock shuts and a lease is issued. Your request steps back and reads queued"
>
<div class="doc">
<svg viewBox="0 0 14 14" aria-hidden="true">
<path d="M3 1.5h5.2L11 4.3v8.2H3z M8 1.5v3h3" />
</svg>
<span>pricing-q4.md</span>
</div>
<div class="stage">
<div class="hold">
<svg class="lock" viewBox="0 0 16 16" aria-hidden="true">
<path class="open" d="M11 7.5V4a3 3 0 0 0-6 0v.4" />
<path class="shut" d="M5 7.5V5.2a3 3 0 0 1 6 0v2.3" />
<rect x="3" y="7" width="10" height="7.5" rx="1.8" />
</svg>
<span class="slot">
<span class="empty"></span>
<span class="avatar jun seated">JU</span>
</span>
<span class="stack">
<span class="avail">Available</span>
<span class="taken">Jun is editing</span>
</span>
</div>
<div class="lease"><i class="fill"></i></div>
<ol class="log">
<li>
<span class="n">1</span>
<span class="slot">
<span class="ghost"></span>
<span class="avatar jun runner">JU</span>
</span>
<span class="name">Jun</span>
<span class="at">:07.412</span>
<span class="verdict">
<span class="asked">requested</span>
<span class="won">granted</span>
</span>
</li>
<li>
<span class="n">2</span>
<span class="loser">
<span class="slot"><span class="avatar you">AK</span></span>
<span class="name">You</span>
<span class="at">:07.430</span>
<span class="verdict">
<span class="asked">requested</span>
<span class="wait">queued</span>
</span>
</span>
</li>
</ol>
</div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.