Part of the Lost Fantasy kit
Lantern night
HTML
<div class="tv-ambient-particles">
<div class="tv-ambient-particles-field" aria-hidden="true">
<span class="tv-ambient-particles-bit" style="--x: 3.1%; --y: 24.3%; --size: 3px; --dur: 7.3s; --delay: -3.9s; --drift: 28px; --hue: 48; --rot: 259deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 8.0%; --y: 19.8%; --size: 5px; --dur: 7.3s; --delay: -0.7s; --drift: 11px; --hue: 300; --rot: 63deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 17.1%; --y: 56.9%; --size: 6px; --dur: 10.8s; --delay: -6.2s; --drift: 11px; --hue: 52; --rot: 23deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 20.5%; --y: 23.1%; --size: 5px; --dur: 7.6s; --delay: -0.9s; --drift: 27px; --hue: 330; --rot: 92deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 23.7%; --y: 52.8%; --size: 4px; --dur: 8.5s; --delay: -4.7s; --drift: -28px; --hue: 48; --rot: 316deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 29.6%; --y: 60.3%; --size: 5px; --dur: 10.1s; --delay: -4.7s; --drift: 21px; --hue: 140; --rot: 127deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 37.9%; --y: 61.5%; --size: 4px; --dur: 7.3s; --delay: -2.2s; --drift: 20px; --hue: 330; --rot: 229deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 40.8%; --y: 80.7%; --size: 3px; --dur: 9.0s; --delay: -1.5s; --drift: 14px; --hue: 160; --rot: 215deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 45.0%; --y: 59.4%; --size: 6px; --dur: 9.3s; --delay: -8.1s; --drift: 20px; --hue: 330; --rot: 179deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 53.2%; --y: 53.4%; --size: 5px; --dur: 7.3s; --delay: -0.7s; --drift: 25px; --hue: 330; --rot: 340deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 55.9%; --y: 63.7%; --size: 4px; --dur: 9.6s; --delay: -9.5s; --drift: 19px; --hue: 330; --rot: 197deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 65.5%; --y: 37.6%; --size: 5px; --dur: 8.4s; --delay: -5.1s; --drift: 11px; --hue: 52; --rot: 147deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 67.1%; --y: 30.8%; --size: 5px; --dur: 10.7s; --delay: -5.3s; --drift: -24px; --hue: 160; --rot: 281deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 73.2%; --y: 23.3%; --size: 5px; --dur: 10.5s; --delay: -2.9s; --drift: 21px; --hue: 330; --rot: 194deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 82.1%; --y: 24.3%; --size: 4px; --dur: 7.6s; --delay: -5.0s; --drift: -25px; --hue: 300; --rot: 93deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 84.0%; --y: 14.3%; --size: 5px; --dur: 9.1s; --delay: -5.5s; --drift: 14px; --hue: 330; --rot: 263deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 92.8%; --y: 58.5%; --size: 3px; --dur: 8.8s; --delay: -7.7s; --drift: 22px; --hue: 160; --rot: 201deg"></span>
<span class="tv-ambient-particles-bit" style="--x: 94.0%; --y: 57.1%; --size: 3px; --dur: 7.8s; --delay: -7.7s; --drift: 15px; --hue: 48; --rot: 174deg"></span>
</div>
<p class="tv-ambient-particles-label">Lantern night</p>
</div>
CSS
/* A layer of drifting particles behind content. Each bit sets its own inline
--x, --y, --size, --dur, --delay, --drift, --hue and --rot; variants change
what the bits are. Longhand animation properties are used on purpose:
the shorthand cannot take a var() for its duration or delay. */
.tv-ambient-particles {
--tv-ambient-particles-travel: 15rem;
position: relative;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 15rem;
overflow: hidden;
border-radius: 6px;
background: radial-gradient(120% 90% at 50% 100%, #2a2140 0%, #14111f 55%, #0b0a12 100%);
font-family: ui-sans-serif, system-ui, sans-serif;
}
/* Fades every edge so nothing looks cut off at the box. */
.tv-ambient-particles-field {
position: absolute;
inset: 0;
pointer-events: none;
-webkit-mask-image:
linear-gradient(to right, transparent 0%, #000000 8%, #000000 92%, transparent 100%),
linear-gradient(to bottom, transparent 0%, #000000 10%, #000000 90%, transparent 100%);
-webkit-mask-composite: source-in;
mask-image:
linear-gradient(to right, transparent 0%, #000000 8%, #000000 92%, transparent 100%),
linear-gradient(to bottom, transparent 0%, #000000 10%, #000000 90%, transparent 100%);
mask-composite: intersect;
}
.tv-ambient-particles-label {
position: relative;
margin: 0;
font-size: 1.125rem;
font-weight: 600;
letter-spacing: 0.04em;
color: #f5f3ff;
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
}
/* Motes (default): soft violet dots rising and fading. */
.tv-ambient-particles-bit {
position: absolute;
bottom: -10px;
left: var(--x, 50%);
width: var(--size, 4px);
height: var(--size, 4px);
border-radius: 50%;
background: radial-gradient(circle, rgba(196, 181, 253, 0.9) 0%, transparent 70%);
filter: blur(0.5px);
opacity: 0;
animation-name: tv-ambient-particles-rise;
animation-duration: var(--dur, 8s);
animation-delay: var(--delay, 0s);
animation-timing-function: linear;
animation-iteration-count: infinite;
}
/* Fireflies: glowing points hovering in place, each in its own hue. */
.tv-ambient-particles--fireflies .tv-ambient-particles-bit {
top: var(--y, 50%);
bottom: auto;
background: hsla(var(--hue, 52), 95%, 85%, 1);
filter: none;
box-shadow:
0 0 8px 3px hsla(var(--hue, 52), 95%, 75%, 0.85),
0 0 22px 8px hsla(var(--hue, 52), 95%, 62%, 0.45),
0 0 40px 14px hsla(var(--hue, 52), 95%, 52%, 0.2);
mix-blend-mode: screen;
animation-name: tv-ambient-particles-hover;
animation-timing-function: ease-in-out;
}
/* Petals: blossoms falling with a sway and a slow spin. */
.tv-ambient-particles--petals .tv-ambient-particles-bit {
top: -20px;
bottom: auto;
width: calc(var(--size, 4px) * 2);
height: calc(var(--size, 4px) * 2.8);
border-radius: 50% 0 50% 0;
background: radial-gradient(circle at 30% 30%, rgba(255, 240, 245, 0.95) 0%, rgba(244, 180, 200, 0.9) 55%, rgba(220, 140, 170, 0.7) 100%);
filter: drop-shadow(0 0 2px rgba(255, 200, 220, 0.35));
animation-name: tv-ambient-particles-fall;
animation-duration: calc(var(--dur, 8s) * 1.5);
}
/* Embers: bright sparks rising fast off an unseen fire glowing at the bottom. */
.tv-ambient-particles--embers .tv-ambient-particles-field::after {
content: "";
position: absolute;
right: 12%;
bottom: -18%;
left: 12%;
height: 45%;
background: radial-gradient(ellipse at 50% 100%, rgba(255, 140, 50, 0.32), transparent 70%);
filter: blur(10px);
}
.tv-ambient-particles--embers .tv-ambient-particles-bit {
bottom: 4px;
background: radial-gradient(circle, rgba(255, 190, 90, 1) 0%, rgba(255, 140, 50, 0.9) 45%, transparent 75%);
box-shadow: 0 0 6px 1px rgba(255, 140, 50, 0.9);
mix-blend-mode: screen;
animation-duration: calc(var(--dur, 8s) * 0.55);
animation-timing-function: ease-out;
}
/* Rain: thin slanted streaks. */
.tv-ambient-particles--rain .tv-ambient-particles-bit {
top: -40px;
bottom: auto;
width: 1px;
height: calc(var(--size, 4px) * 4);
border-radius: 0;
background: linear-gradient(to bottom, transparent 0%, rgba(180, 200, 230, 0.85) 60%, rgba(180, 200, 230, 0.85) 100%);
filter: blur(0.3px);
animation-name: tv-ambient-particles-rain;
animation-duration: calc(var(--dur, 8s) * 0.12);
}
@keyframes tv-ambient-particles-rise {
0% { transform: translate(0, 0) scale(0.6); opacity: 0; }
10% { opacity: 0.9; }
50% { transform: translate(calc(var(--drift, 10px) * 0.5), calc(var(--tv-ambient-particles-travel, 15rem) * -0.5)) scale(1); }
85% { opacity: 0.9; }
100% { transform: translate(var(--drift, 10px), calc(var(--tv-ambient-particles-travel, 15rem) * -1)) scale(0.4); opacity: 0; }
}
@keyframes tv-ambient-particles-hover {
0% { transform: translate(0, 0) scale(0.7); opacity: 0; }
10% { transform: translate(-6px, -6px) scale(1); opacity: 1; }
30% { transform: translate(6px, -10px) scale(1.1); opacity: 0.9; }
55% { transform: translate(8px, 4px) scale(1); opacity: 1; }
80% { transform: translate(-4px, 6px) scale(0.95); opacity: 0.8; }
100% { transform: translate(0, 0) scale(0.7); opacity: 0; }
}
@keyframes tv-ambient-particles-fall {
0% { transform: translate(0, 0) rotate(var(--rot, 0deg)); opacity: 0; }
6% { opacity: 0.95; }
25% { transform: translate(var(--drift, 10px), calc(var(--tv-ambient-particles-travel, 15rem) * 0.27)) rotate(calc(var(--rot, 0deg) + 90deg)); }
50% { transform: translate(calc(var(--drift, 10px) * -0.85), calc(var(--tv-ambient-particles-travel, 15rem) * 0.55)) rotate(calc(var(--rot, 0deg) + 180deg)); }
75% { transform: translate(calc(var(--drift, 10px) * 0.9), calc(var(--tv-ambient-particles-travel, 15rem) * 0.83)) rotate(calc(var(--rot, 0deg) + 270deg)); opacity: 0.85; }
100% { transform: translate(calc(var(--drift, 10px) * -0.4), calc(var(--tv-ambient-particles-travel, 15rem) * 1.1)) rotate(calc(var(--rot, 0deg) + 360deg)); opacity: 0; }
}
@keyframes tv-ambient-particles-rain {
0% { transform: translate(0, 0) rotate(10deg); opacity: 0; }
8% { opacity: 0.9; }
90% { opacity: 0.85; }
100% { transform: translate(-3rem, calc(var(--tv-ambient-particles-travel, 15rem) * 1.25)) rotate(10deg); opacity: 0; }
}
/* Still: hold every particle at a visible resting point instead of hiding it. */
@media (prefers-reduced-motion: reduce) {
.tv-ambient-particles-bit {
animation: none;
opacity: 0.7;
bottom: auto;
top: var(--y, 50%);
}
}
Usage & credits
- Use when
- Hero backgrounds, seasonal and festival pages, quiet atmosphere behind a headline.
- Avoid when
- Behind long text, dense UI, or alongside other motion competing for attention.
- Requires
- Tailwind v4. No additional markup requirements.
- From
- Lost Fantasy