16 CSS Gradient Animations 12 / 16
CSS Retro Vaporwave Cyberpunk Mesh
A high-contrast, fast-rotating mesh gradient in neon magenta, cyan, and violet layered with a CSS grid overlay and CRT scanlines, creating a brutalist retro-futuristic aesthetic popular in vaporwave and cyberpunk design.
The code
<div class="ga-12">
<div class="ga-12__grid"></div>
<div class="ga-12__scan"></div>
<div class="ga-12__content">
<span class="ga-12__year">∴ 1989 — 2089 ∴</span>
<h1 class="ga-12__title">VAPORWAVE</h1>
<p class="ga-12__sub">Aesthetics · Interface · Future · Retro</p>
<div class="ga-12__divider"></div>
<div class="ga-12__pills">
<span class="ga-12__pill ga-12__pill--m">Neon Magenta</span>
<span class="ga-12__pill ga-12__pill--c">Cyber Cyan</span>
<span class="ga-12__pill ga-12__pill--p">Deep Violet</span>
</div>
</div>
<div class="ga-12__ctrl">
<button class="ga-12__ctrl-btn" data-dur="12s">Slow</button>
<button class="ga-12__ctrl-btn active" data-dur="6s">Normal</button>
<button class="ga-12__ctrl-btn" data-dur="2.5s">Fast</button>
</div>
</div> <div class="ga-12">
<div class="ga-12__grid"></div>
<div class="ga-12__scan"></div>
<div class="ga-12__content">
<span class="ga-12__year">∴ 1989 — 2089 ∴</span>
<h1 class="ga-12__title">VAPORWAVE</h1>
<p class="ga-12__sub">Aesthetics · Interface · Future · Retro</p>
<div class="ga-12__divider"></div>
<div class="ga-12__pills">
<span class="ga-12__pill ga-12__pill--m">Neon Magenta</span>
<span class="ga-12__pill ga-12__pill--c">Cyber Cyan</span>
<span class="ga-12__pill ga-12__pill--p">Deep Violet</span>
</div>
</div>
<div class="ga-12__ctrl">
<button class="ga-12__ctrl-btn" data-dur="12s">Slow</button>
<button class="ga-12__ctrl-btn active" data-dur="6s">Normal</button>
<button class="ga-12__ctrl-btn" data-dur="2.5s">Fast</button>
</div>
</div>.ga-12, .ga-12 *, .ga-12 *::before, .ga-12 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ga-12 ::selection { background: rgba(236,72,153,.5); color: #fff; }
.ga-12 {
--bg: #06010f;
--c1: #ff00ff;
--c2: #00ffff;
--c3: #bf00ff;
--c4: #ff006e;
--dur: 6s;
position: relative;
width: 100%;
min-height: 100vh;
overflow: hidden;
background: var(--bg);
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
padding: 48px 24px;
}
/* ── Fast-moving high-contrast mesh ── */
.ga-12::before {
content: '';
position: absolute;
inset: -20%;
background:
radial-gradient(ellipse 50% 40% at 20% 30%, color-mix(in srgb, var(--c1) 35%, transparent), transparent 60%),
radial-gradient(ellipse 45% 45% at 80% 70%, color-mix(in srgb, var(--c2) 30%, transparent), transparent 60%),
radial-gradient(ellipse 40% 50% at 60% 20%, color-mix(in srgb, var(--c3) 28%, transparent), transparent 55%),
radial-gradient(ellipse 55% 35% at 30% 80%, color-mix(in srgb, var(--c4) 25%, transparent), transparent 55%);
animation: ga-12-mesh var(--dur) linear infinite;
mix-blend-mode: screen;
}
/* Second mesh layer — faster, different direction */
.ga-12::after {
content: '';
position: absolute;
inset: -20%;
background:
radial-gradient(ellipse 30% 50% at 70% 40%, color-mix(in srgb, var(--c2) 20%, transparent), transparent 55%),
radial-gradient(ellipse 40% 30% at 15% 60%, color-mix(in srgb, var(--c1) 18%, transparent), transparent 50%);
animation: ga-12-mesh2 calc(var(--dur) * .7) linear infinite reverse;
mix-blend-mode: screen;
}
@keyframes ga-12-mesh {
0% { transform: rotate(0deg) scale(1); }
100% { transform: rotate(360deg) scale(1.05); }
}
@keyframes ga-12-mesh2 {
0% { transform: rotate(0deg) translate(0, 0); }
33% { transform: rotate(120deg) translate(5%, -5%); }
66% { transform: rotate(240deg) translate(-5%, 5%); }
100% { transform: rotate(360deg) translate(0, 0); }
}
/* Scan-line grid overlay */
.ga-12__grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(0,255,255,.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0,255,255,.03) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
z-index: 1;
}
/* CRT scanline overlay */
.ga-12__scan {
position: absolute;
inset: 0;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 2px,
rgba(0,0,0,.08) 2px,
rgba(0,0,0,.08) 4px
);
pointer-events: none;
z-index: 2;
}
/* Content */
.ga-12__content {
position: relative;
z-index: 3;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.ga-12__year {
font-size: .72rem;
font-weight: 900;
letter-spacing: .3em;
text-transform: uppercase;
color: var(--c2);
text-shadow: 0 0 10px var(--c2), 0 0 20px var(--c2);
animation: ga-12-flicker 4s ease-in-out infinite;
}
@keyframes ga-12-flicker {
0%, 95%, 100% { opacity: 1; }
96% { opacity: .6; }
97% { opacity: 1; }
98% { opacity: .4; }
}
.ga-12__title {
font-size: clamp(2.4rem, 7vw, 5rem);
font-weight: 900;
line-height: 1;
letter-spacing: -.02em;
text-transform: uppercase;
background: linear-gradient(180deg, var(--c1) 0%, var(--c2) 55%, var(--c3) 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
filter: drop-shadow(0 0 16px rgba(255,0,255,.6)) drop-shadow(0 0 32px rgba(0,255,255,.3));
}
.ga-12__sub {
font-size: .95rem;
font-weight: 600;
color: rgba(0,255,255,.7);
letter-spacing: .08em;
text-shadow: 0 0 8px rgba(0,255,255,.5);
}
.ga-12__divider {
width: 200px;
height: 1px;
background: linear-gradient(90deg, transparent, var(--c1), var(--c2), transparent);
position: relative;
}
.ga-12__divider::after {
content: '';
position: absolute;
inset: 0;
background: inherit;
filter: blur(4px);
opacity: .7;
}
/* Stat pills */
.ga-12__pills {
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
margin-top: 4px;
}
.ga-12__pill {
padding: 6px 16px;
border-radius: 4px;
font-size: .72rem;
font-weight: 800;
letter-spacing: .1em;
text-transform: uppercase;
border: 1px solid;
cursor: pointer;
transition: all .2s;
}
.ga-12__pill--m { color: var(--c1); border-color: rgba(255,0,255,.35); background: rgba(255,0,255,.07); }
.ga-12__pill--m:hover { background: rgba(255,0,255,.15); box-shadow: 0 0 12px rgba(255,0,255,.3); }
.ga-12__pill--c { color: var(--c2); border-color: rgba(0,255,255,.35); background: rgba(0,255,255,.06); }
.ga-12__pill--c:hover { background: rgba(0,255,255,.12); box-shadow: 0 0 12px rgba(0,255,255,.25); }
.ga-12__pill--p { color: var(--c3); border-color: rgba(191,0,255,.3); background: rgba(191,0,255,.06); }
.ga-12__pill--p:hover { background: rgba(191,0,255,.13); box-shadow: 0 0 12px rgba(191,0,255,.25); }
/* Speed control */
.ga-12__ctrl {
position: absolute;
bottom: 14px;
right: 14px;
z-index: 10;
display: flex;
gap: 5px;
}
.ga-12__ctrl-btn {
padding: 4px 10px;
font-size: .68rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: .07em;
border-radius: 3px;
border: 1px solid rgba(0,255,255,.2);
background: rgba(0,0,0,.4);
color: rgba(0,255,255,.4);
cursor: pointer;
transition: all .15s;
}
.ga-12__ctrl-btn.active,
.ga-12__ctrl-btn:hover {
border-color: var(--c2);
color: var(--c2);
text-shadow: 0 0 6px var(--c2);
}
@media (prefers-reduced-motion: reduce) {
.ga-12::before, .ga-12::after { animation: none; }
.ga-12__year { animation: none; }
} .ga-12, .ga-12 *, .ga-12 *::before, .ga-12 *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
.ga-12 ::selection { background: rgba(236,72,153,.5); color: #fff; }
.ga-12 {
--bg: #06010f;
--c1: #ff00ff;
--c2: #00ffff;
--c3: #bf00ff;
--c4: #ff006e;
--dur: 6s;
position: relative;
width: 100%;
min-height: 100vh;
overflow: hidden;
background: var(--bg);
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
padding: 48px 24px;
}
/* ── Fast-moving high-contrast mesh ── */
.ga-12::before {
content: '';
position: absolute;
inset: -20%;
background:
radial-gradient(ellipse 50% 40% at 20% 30%, color-mix(in srgb, var(--c1) 35%, transparent), transparent 60%),
radial-gradient(ellipse 45% 45% at 80% 70%, color-mix(in srgb, var(--c2) 30%, transparent), transparent 60%),
radial-gradient(ellipse 40% 50% at 60% 20%, color-mix(in srgb, var(--c3) 28%, transparent), transparent 55%),
radial-gradient(ellipse 55% 35% at 30% 80%, color-mix(in srgb, var(--c4) 25%, transparent), transparent 55%);
animation: ga-12-mesh var(--dur) linear infinite;
mix-blend-mode: screen;
}
/* Second mesh layer — faster, different direction */
.ga-12::after {
content: '';
position: absolute;
inset: -20%;
background:
radial-gradient(ellipse 30% 50% at 70% 40%, color-mix(in srgb, var(--c2) 20%, transparent), transparent 55%),
radial-gradient(ellipse 40% 30% at 15% 60%, color-mix(in srgb, var(--c1) 18%, transparent), transparent 50%);
animation: ga-12-mesh2 calc(var(--dur) * .7) linear infinite reverse;
mix-blend-mode: screen;
}
@keyframes ga-12-mesh {
0% { transform: rotate(0deg) scale(1); }
100% { transform: rotate(360deg) scale(1.05); }
}
@keyframes ga-12-mesh2 {
0% { transform: rotate(0deg) translate(0, 0); }
33% { transform: rotate(120deg) translate(5%, -5%); }
66% { transform: rotate(240deg) translate(-5%, 5%); }
100% { transform: rotate(360deg) translate(0, 0); }
}
/* Scan-line grid overlay */
.ga-12__grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(0,255,255,.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0,255,255,.03) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
z-index: 1;
}
/* CRT scanline overlay */
.ga-12__scan {
position: absolute;
inset: 0;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 2px,
rgba(0,0,0,.08) 2px,
rgba(0,0,0,.08) 4px
);
pointer-events: none;
z-index: 2;
}
/* Content */
.ga-12__content {
position: relative;
z-index: 3;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.ga-12__year {
font-size: .72rem;
font-weight: 900;
letter-spacing: .3em;
text-transform: uppercase;
color: var(--c2);
text-shadow: 0 0 10px var(--c2), 0 0 20px var(--c2);
animation: ga-12-flicker 4s ease-in-out infinite;
}
@keyframes ga-12-flicker {
0%, 95%, 100% { opacity: 1; }
96% { opacity: .6; }
97% { opacity: 1; }
98% { opacity: .4; }
}
.ga-12__title {
font-size: clamp(2.4rem, 7vw, 5rem);
font-weight: 900;
line-height: 1;
letter-spacing: -.02em;
text-transform: uppercase;
background: linear-gradient(180deg, var(--c1) 0%, var(--c2) 55%, var(--c3) 100%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
filter: drop-shadow(0 0 16px rgba(255,0,255,.6)) drop-shadow(0 0 32px rgba(0,255,255,.3));
}
.ga-12__sub {
font-size: .95rem;
font-weight: 600;
color: rgba(0,255,255,.7);
letter-spacing: .08em;
text-shadow: 0 0 8px rgba(0,255,255,.5);
}
.ga-12__divider {
width: 200px;
height: 1px;
background: linear-gradient(90deg, transparent, var(--c1), var(--c2), transparent);
position: relative;
}
.ga-12__divider::after {
content: '';
position: absolute;
inset: 0;
background: inherit;
filter: blur(4px);
opacity: .7;
}
/* Stat pills */
.ga-12__pills {
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
margin-top: 4px;
}
.ga-12__pill {
padding: 6px 16px;
border-radius: 4px;
font-size: .72rem;
font-weight: 800;
letter-spacing: .1em;
text-transform: uppercase;
border: 1px solid;
cursor: pointer;
transition: all .2s;
}
.ga-12__pill--m { color: var(--c1); border-color: rgba(255,0,255,.35); background: rgba(255,0,255,.07); }
.ga-12__pill--m:hover { background: rgba(255,0,255,.15); box-shadow: 0 0 12px rgba(255,0,255,.3); }
.ga-12__pill--c { color: var(--c2); border-color: rgba(0,255,255,.35); background: rgba(0,255,255,.06); }
.ga-12__pill--c:hover { background: rgba(0,255,255,.12); box-shadow: 0 0 12px rgba(0,255,255,.25); }
.ga-12__pill--p { color: var(--c3); border-color: rgba(191,0,255,.3); background: rgba(191,0,255,.06); }
.ga-12__pill--p:hover { background: rgba(191,0,255,.13); box-shadow: 0 0 12px rgba(191,0,255,.25); }
/* Speed control */
.ga-12__ctrl {
position: absolute;
bottom: 14px;
right: 14px;
z-index: 10;
display: flex;
gap: 5px;
}
.ga-12__ctrl-btn {
padding: 4px 10px;
font-size: .68rem;
font-weight: 800;
text-transform: uppercase;
letter-spacing: .07em;
border-radius: 3px;
border: 1px solid rgba(0,255,255,.2);
background: rgba(0,0,0,.4);
color: rgba(0,255,255,.4);
cursor: pointer;
transition: all .15s;
}
.ga-12__ctrl-btn.active,
.ga-12__ctrl-btn:hover {
border-color: var(--c2);
color: var(--c2);
text-shadow: 0 0 6px var(--c2);
}
@media (prefers-reduced-motion: reduce) {
.ga-12::before, .ga-12::after { animation: none; }
.ga-12__year { animation: none; }
}(function() {
const w = document.querySelector('.ga-12');
w.querySelectorAll('.ga-12__ctrl-btn').forEach(btn => {
btn.addEventListener('click', () => {
w.querySelectorAll('.ga-12__ctrl-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
w.style.setProperty('--dur', btn.dataset.dur);
});
});
})(); (function() {
const w = document.querySelector('.ga-12');
w.querySelectorAll('.ga-12__ctrl-btn').forEach(btn => {
btn.addEventListener('click', () => {
w.querySelectorAll('.ga-12__ctrl-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
w.style.setProperty('--dur', btn.dataset.dur);
});
});
})();How this works
Two ::before and ::after pseudo-elements carry four-stop radial-gradient meshes each, set with mix-blend-mode: screen so the neon colours combine additively like projected light. The pseudo-elements are oversized (inset: -20%) to prevent edge cut-off during rotation. The primary mesh rotates continuously via @keyframes ga-12-mesh { to { transform: rotate(360deg) scale(1.05) } }, while the secondary mesh uses a three-step keyframe with translate offsets between rotation steps, causing its gradient centres to drift as it spins — creating an organic wobble that breaks the mechanical symmetry of a simple rotation.
A CSS grid overlay uses background-image: linear-gradient(rgba(0,255,255,.03) 1px, transparent 1px) repeated in both axes at 40px intervals, adding the characteristic circuit-board grid. The CRT scanline effect is a repeating-linear-gradient alternating 2px opaque / 2px transparent bands at 8% opacity, applied on a dedicated .ga-12__scan layer — this is kept as a separate div rather than a pseudo-element to avoid exhausting the two pseudo-element slots.
Customize
- Change the neon palette by editing
--c1through--c4on.ga-12— all gradient stops, text colours, and glow effects reference these variables, so a full theme swap requires only four value changes. - Tighten or loosen the grid by changing
background-size: 40px 40pxon.ga-12__grid— try20pxfor a dense circuit board or80pxfor a large-tile brutalist grid. - Adjust the CRT intensity by changing the scanline opacity on
.ga-12__scanfromrepeating-linear-gradient(... rgba(0,0,0,.08) ...)— raise to.20for a heavy vintage monitor effect or drop to.03for a barely-there texture. - Add a horizontal perspective warp to the mesh by applying
transform: perspective(600px) rotateX(8deg)to the wrapper, giving the gradient the tilted-plane look of a synthwave horizon. - Enable the flickering text effect on other text elements by applying the
ga-12-flickerkeyframe with a unique delay — the 4-second cycle with brief opacity dips at 96% and 98% reads as authentic neon tube flicker.
Watch out for
mix-blend-mode: screenon the pseudo-elements requires the parent stacking context not to haveisolation: isolate— if the mesh colours stop blending and appear as flat gradients, an ancestor likely has isolation set; remove it or move the blending to an inner wrapper.- The
inset: -20%expansion creates very large GPU textures during rotation — on mobile this can cause memory pressure warnings. Reduce toinset: -10%or disable rotation and use translate-only animation on narrow viewports. - The CRT scanlines (
.ga-12__scan) use a repeating gradient with 4px pitch — on high-DPI displays with device pixel ratio > 2, the lines may render as a single blurry band rather than distinct lines. Match the pitch to the display DPR by using@media (-webkit-min-device-pixel-ratio: 2)to increase the pitch to 6px or 8px.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 79+ | 13+ | 70+ | 79+ |
mix-blend-mode: screen and radial-gradient() are universally supported. The vaporwave effect degrades gracefully: without mix-blend-mode support (very old browsers), the mesh appears as flat overlapping gradients without colour blending.