Text edit — suggestion accepted
Variant: Suggestion accepted
The quietest member of the family, and the only one where nothing travels: the suggested phrase was already typeset in its final position, so acceptance is pure subtraction — the wash and the dotted rule leave in about 300ms, the interface withdrawing its claim on the words, and the olive ink keeps settling for almost 400ms after they are gone, because a phrase stops being highlighted before it stops being new. The tick chip is the event rather than the payoff: it arrives at 92% scale, sits through the settle so cause and effect share the frame, and leaves faster than it came once the text is plain. Reach for it wherever an agent or a reviewer proposes copy inline and the moment of acceptance deserves to be seen.
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>Text edit — suggestion accepted</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)
);
/* Clock — 5.6s: the suggestion is read until 28%, the tick lands by
31%, the subtraction starts at 34%, and the ink has settled by 47%. */
--cycle: 5.6s;
--t: calc(var(--cycle) / var(--speed));
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
--_read: color-mix(in srgb, var(--slate) 78%, transparent);
--_olive-ink: color-mix(in oklab, var(--olive) 72%, var(--slate));
--_wash: color-mix(in srgb, var(--olive) 11%, transparent);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
padding: clamp(12px, 3vw, 28px);
background: var(--ivory);
color: var(--slate);
font-family:
system-ui,
-apple-system,
'Segoe UI',
Roboto,
sans-serif;
}
.card {
width: min(360px, 92vw);
padding: clamp(14px, 3.4vw, 22px) clamp(15px, 3.6vw, 24px);
border: 1px solid var(--oat);
border-radius: calc(14px * var(--round));
background: var(--paper);
}
.eyebrow {
font-size: clamp(11px, 2.4vw, 12.5px);
font-weight: 600;
letter-spacing: 0.09em;
text-transform: uppercase;
color: color-mix(in srgb, var(--slate) 55%, transparent);
}
.copy {
margin-top: clamp(7px, 1.4vw, 11px);
display: grid;
justify-items: start;
animation: seam var(--t) infinite;
}
.line {
white-space: nowrap;
font-size: clamp(13px, 3.2vw, 17.5px);
line-height: 1.55;
letter-spacing: -0.005em;
color: var(--_read);
}
/* The suggested phrase is already typeset in its final position — the
only things that are the suggestion's are its markup: olive ink, a
dotted rule, a faint wash. Nothing in this file ever translates. */
.sug {
position: relative;
isolation: isolate; /* keeps the z:-1 wash above the card without leaning on the seam animation's stacking context */
font-weight: 500;
animation: ink var(--t) infinite;
}
.sug::before {
content: '';
position: absolute;
inset: -0.08em -0.14em;
z-index: -1;
border-radius: calc(4px * var(--round));
background: var(--_wash);
animation: wash var(--t) infinite;
}
.sug::after {
content: '';
position: absolute;
left: -0.02em;
right: -0.02em;
bottom: -0.18em;
border-bottom: max(2px, 0.08em) dotted var(--olive);
animation: dots var(--t) infinite;
}
.mark {
position: absolute;
left: calc(100% + 0.55em);
top: 50%;
display: grid;
place-items: center;
width: 1.35em;
height: 1.35em;
border-radius: 50%;
background: var(--olive);
color: var(--paper);
font-size: 0.78em;
font-weight: 700;
animation: chip var(--t) infinite;
}
/* Acceptance is subtraction. The wash and the dotted rule leave first
and quickly — the interface withdrawing its claim on the words — and
the ink keeps settling for almost 400ms after they are gone, because
a phrase stops being highlighted before it stops being new. */
@keyframes ink {
0%,
36% {
color: var(--_olive-ink);
animation-timing-function: ease;
}
47%,
94% {
color: var(--_read);
}
95%,
100% {
color: var(--_olive-ink);
}
}
@keyframes wash {
0%,
34% {
opacity: 1;
animation-timing-function: ease;
}
40%,
94% {
opacity: 0;
}
95%,
100% {
opacity: 1;
}
}
@keyframes dots {
0%,
34% {
opacity: 1;
animation-timing-function: ease;
}
39%,
94% {
opacity: 0;
}
95%,
100% {
opacity: 1;
}
}
/* The tick is the event, not the payoff: it arrives at 92% scale — never
from nothing — sits through the settle so cause and effect share the
frame, and leaves faster than it came once the phrase is plain text. */
@keyframes chip {
0%,
28% {
opacity: 0;
transform: translateY(-50%) scale(0.92);
animation-timing-function: var(--ease-out);
}
31%,
52% {
opacity: 1;
transform: translateY(-50%) scale(1);
animation-timing-function: ease;
}
54.5%,
94% {
opacity: 0;
transform: translateY(-50%) scale(1);
}
95%,
100% {
opacity: 0;
transform: translateY(-50%) scale(0.92);
}
}
/* The rewind hides in a dark window at the seam — a suggestion visibly
reappearing would read as the acceptance being undone. */
@keyframes seam {
0%,
90% {
opacity: 1;
animation-timing-function: ease;
}
93.5%,
96.5% {
opacity: 0;
animation-timing-function: ease;
}
100% {
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
/* Already a colour-and-opacity piece; it keeps its meaning with the
chip's scale dropped and the clock slowed. */
.copy,
.sug,
.sug::before,
.sug::after,
.mark {
animation-duration: calc(var(--t) * 1.7);
}
.mark {
animation-name: chip-rm;
}
@keyframes chip-rm {
0%,
28% {
opacity: 0;
animation-timing-function: ease;
}
31%,
52% {
opacity: 1;
animation-timing-function: ease;
}
54.5%,
100% {
opacity: 0;
}
}
}
</style>
</head>
<body>
<div class="card">
<p class="eyebrow">Doc review</p>
<div
class="copy"
role="img"
aria-label="A sentence with the suggested phrase 'double each time' shown in olive, then accepted into plain text."
>
<p class="line">Retry the fetch, but make</p>
<p class="line">the delay <span class="sug">double each time<span class="mark">✓</span></span></p>
<p class="line">so the queue can drain.</p>
</div>
</div>
</body>
</html>
The variant family
Same idea, one axis moved. Compare them side by side before you commit.