slop: motion
Motion is information or it is removed. Each section puts the common animated default (A) next to the still or composited version (B) with the same content, then states what the animation costs. Patterns are collected from impeccable.style/slop; the reasoning, demos and samples are ours. Hover the demos: they are live.
pulsing dot
The slop: every status dot on the page pulses forever. The rule: a dot is a state, not an alarm; color carries the state and motion marks only the moment the state changes.
An animation that never stops has no off position, so the reader cannot tell "live" from "decorated". When everything pulses, a genuinely failing service has nothing left to escalate with.
A: everything pulses
- apioperational
- workeroperational
- edgeoperational
three healthy services demand attention every two seconds; the animation carries no information.
B: color carries the state
- apioperational
- workeroperational
- edgeoperational
same rows, still. the reader scans color, not motion; a ping plays once, on the transition.
<span class="dot is-ok"></span>.dot { size-2; rounded-full }.dot.is-ok { bg-emerald-400 }.dot.is-degraded { bg-amber-400 }/* motion only on the change itself */.dot.just-changed { animation: ping 600ms ease-out 1 }
blinking cursor
The slop: a fake terminal hero whose caret blinks forever on a marketing page. The rule: a caret blinks to mark focus in an input the reader can type into; in a display the reader cannot type into, it is drawn solid or not at all.
The blink borrows the one signal a terminal earns from real input and spends it as decoration, so the eye keeps checking a cursor that will never accept typing.
A: blinking forever
~ deploy $ vercel --prod
▌the caret pulses with no input behind it; the page flickers at the reader.
B: solid caret
~ deploy $ vercel --prod
▌same line, no animation. the block reads as a caret without promising focus.
<pre class="term">~ deploy $ vercel --prod<span class="caret"></span></pre>.caret { inline-block; w-2; h-[1em]; bg-zinc-500 }/* no animation: a display the readercannot type into has no focus to show */
marquee
The slop: logos or quotes drifting across the page in an infinite loop. The rule: if the content is worth showing, it is worth reading; motion never competes with reading itself.
A marquee makes the reader chase its content: anything past the fold has already moved, and stopping to read one name means watching the rest queue. The loop also burns a composited layer forever to say what a static row says for free.
A: the loop
the same six names, duplicated and scrolling; read one and you lose the rest.
B: still and readable
same six names, wrapped once; the reader reads at their own pace.
<div class="logo-row"><span>acme</span><span>bolta</span> ...</div>.logo-row { flex; flex-wrap; items-center;gap-x-8; gap-y-3 }/* the loop is gone: the reader readsat their own pace */
bounce easing
The slop: an overshoot curve (spring, elastic, back) on interface elements, stretched over half a second. The rule: easing starts fast and ends quiet; interface elements are not balls.
The overshoot says "physical object settling", but nothing on a screen has weight, so the reader sees a wobble with no meaning. Long spring durations also queue: hover away mid-bounce and the element keeps traveling after the pointer has left.
A: spring and wobble
700ms overshoot on hover: playful once, exhausting on every element, every time.
B: fast in, quiet out
same button, 150ms ease-out on a color shift; the state change is confirmed, not performed.
<button class="btn">deploy</button>.btn { transition: background-color 150ms ease-out }.btn:hover { background: indigo-500 }/* transform feedback, if wanted: scale(1.02),same 150ms ease-out. no overshoot curve,nothing past 300ms */
layout animation
The slop: animating width, height or margins, so the browser reflows the page on every frame. The rule: animate only transform and opacity; they composite on the GPU while layout properties reflow everything after them.
The cost is not only jank: a height animation pushes the whole page below it every frame, so the reader's eye loses its anchor mid-motion. The reveal lands as an overlay instead, and nothing the reader was reading moves.
A: the page reflows
hover: height interpolates and the row below jumps for 300ms; every frame is a layout pass.
B: transform and opacity
same content as an overlay; the row below never moves and the animation composites.
<div class="row">deploy log</div><div class="panel">step 1 · step 2</div><div class="below">row below: never moves</div>.panel { position-absolute; opacity-0; -translate-y-1;transition: opacity 150ms ease-out,transform 150ms ease-out }.row:hover + .panel { opacity-1; translate-y-0 }/* never animate width/height/margin: theyreflow the page every frame */
hover zoom
The slop: a card whose cover zooms to 1.5x over 700ms while the whole card scales too, often without a mask, so the image bleeds past its own radius. The rule: a hover state confirms; it does not perform.
The zoom destroys the image the card exists to show: at 1.5x the cover is a crop, the text next to it loses its neighbor, and the unmasked edges turn a contained component into spillover. A 3% drift inside a mask keeps the "this responds" signal at a fraction of the cost.
A: zoom and bleed
sunset over the lake
shot at golden hour from the east pier.
hover: the card scales, the cover crops to 1.5x and escapes its own radius.
B: masked and modest
sunset over the lake
shot at golden hour from the east pier.
same card: the cover drifts 3% inside its mask over 200ms; the card itself stays put.
<figure class="card"><div class="mask"><div class="cover"></div></div><figcaption class="body">...</figcaption></figure>.card { overflow-hidden; rounded-xl }.mask { overflow-hidden } <!-- the zoom stays inside -->.cover { transition: transform 200ms ease-out }.card:hover .cover { transform: scale(1.03) }/* the card itself never scales */