Aurora drift
Two blurred colour blobs drifting on mismatched periods, so the composition never visibly loops. Two pseudo-elements, no images, no canvas.
How it works
- Two
::before/::aftercircles, each aradial-gradientfading to transparent at 70%, thenfilter: blur(90px)over the top. The blur is what turns two hard circles into light. - Periods are 18s and 22s. Equal durations — or one a multiple of the other — make the pair return to the same arrangement every cycle, and the loop becomes visible. Mismatched periods take minutes to repeat.
- Only
transformis animated. Animatingtop/leftwould re-run a 90px blur every frame; a transform hands the already-blurred layer to the compositor and just moves it. overflow: hiddenon the container is load-bearing: the blobs are positioned outside the box on purpose, and without it they add scrollbars.
Numbers
- Blob size
- 420px / 460px
- Blur
- 90px
- Opacity
- 0.55
- Periods
- 18s / 22s, ease-in-out alternate
- Travel
- ≤80px, scale ≤1.12
Prompt
Paste this into any coding model to get the effect from scratch. It states every number explicitly — vague words like “smooth” or “modern” produce a different result every time.
Create an ambient background using two pseudo-elements on a container. Each is a circle (420px and 460px) filled with a radial-gradient from a pastel colour to transparent at 70%, with filter: blur(90px) and opacity 0.55, positioned so they overflow opposite corners of the container. Animate each with a different period — 18s and 22s, ease-in-out, infinite alternate — moving them by no more than 80px with translate() and scaling to at most 1.12. Animate transform only, never top/left. Set overflow: hidden and isolation: isolate on the container, put the blobs at z-index: -1, and disable both animations under prefers-reduced-motion: reduce.Source
The whole file. It is what the sample above is running — copy it into an .html file and it works with nothing else.
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aurora drift</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 100vh;
display: grid; place-items: center;
background: #fbfbfd;
font: 600 clamp(22px, 5vw, 34px)/1.1 -apple-system, BlinkMacSystemFont, "SF Pro Display", sans-serif;
letter-spacing: -0.025em;
color: #1d1d1f;
/* The blobs deliberately overflow the viewport. Without this the page
grows scrollbars around them. */
overflow: hidden;
position: relative;
isolation: isolate;
}
/* Two large blurred blobs, each drifting on its own period.
The periods are 18s and 22s — deliberately not equal, and not
multiples of one another. Give both the same duration and the whole
composition visibly repeats every cycle. */
body::before, body::after {
content: "";
position: absolute; z-index: -1;
border-radius: 50%;
filter: blur(90px);
opacity: 0.55;
pointer-events: none;
}
body::before {
width: 420px; height: 420px; top: -12%; left: -10%;
background: radial-gradient(circle, #a6dcf5, transparent 70%);
animation: drift-a 18s ease-in-out infinite alternate;
}
body::after {
width: 460px; height: 460px; bottom: -14%; right: -12%;
background: radial-gradient(circle, #f5cde8, transparent 70%);
animation: drift-b 22s ease-in-out infinite alternate;
}
/* Animate transform only. Moving the blob by changing top/left would
re-run the 90px blur every frame; a transform hands the already-blurred
layer to the compositor and merely moves it. */
@keyframes drift-a { to { transform: translate(70px, 50px) scale(1.12); } }
@keyframes drift-b { to { transform: translate(-80px, -60px) scale(1.08); } }
@media (prefers-reduced-motion: reduce) {
body::before, body::after { animation: none; }
}
</style>
<p>Quiet light, moving slowly.</p>Where it breaks
blur(90px)over a large area is genuinely expensive on low-end Android. If the page also scrolls, check it on a real device before shipping — the cost shows up as scroll jank, not as a dropped animation.- Content sitting on top needs its own stacking context (
isolation: isolate), or a child with a negative z-index elsewhere can slip behind the blobs.