Back to the library

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.

textdiffsuccessloop

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>