Backgrounds
Silk Ribbons
Blurred ellipses of saturated light drifting and crossing on screen blend, so overlaps brighten like folds catching the light.
Original by Plinth · MIT
Report a problem or suggest an improvementInstall
npx shadcn@latest add https://plinthui.com/r/silk-ribbons.json// No hooks or handlers, so this stays a server component — it is pure CSS.
const css = `
.silk {
position: relative;
min-height: 300px;
height: 100%;
overflow: hidden;
background: radial-gradient(130% 100% at 50% 0%, #170e33 0%, #0b0718 55%, #06040f 100%);
isolation: isolate;
}
/* Each ribbon is ONE heavily blurred ellipse, not a striped rectangle. A
repeating gradient inside a skewed box seems like the obvious way to make
bands, and it is wrong: blurred, the stripes turn to mush, and unblurred
they read as a mosaic of hard blocks. A single wide ellipse has no internal
edges to betray it, so blur only ever softens its silhouette — which is
exactly what fabric catching light looks like. */
.silk__band {
position: absolute;
left: -25%;
width: 150%;
height: 30%;
border-radius: 50%;
filter: blur(34px);
/* screen so overlaps ADD — where two ribbons cross is the brightest part of
the image, and that crossing is the whole effect. */
mix-blend-mode: screen;
will-change: transform;
}
.silk__band--a {
top: 4%;
background: linear-gradient(90deg, transparent, #ff3d81 28%, #ff9f45 58%, transparent);
transform: rotate(-11deg);
animation: silk-a 21s ease-in-out infinite alternate;
}
.silk__band--b {
top: 28%;
background: linear-gradient(90deg, transparent, #7c3aed 30%, #5b8cff 62%, transparent);
transform: rotate(7deg);
animation: silk-b 27s ease-in-out infinite alternate;
}
.silk__band--c {
top: 52%;
background: linear-gradient(90deg, transparent, #22d3ee 26%, #4affd0 60%, transparent);
transform: rotate(-6deg);
animation: silk-c 33s ease-in-out infinite alternate;
}
.silk__band--d {
top: 70%;
height: 24%;
background: linear-gradient(90deg, transparent, #b45bff 34%, #ff5ab4 64%, transparent);
transform: rotate(9deg);
animation: silk-d 39s ease-in-out infinite alternate;
}
/* Translate AND rotate together. Sliding alone reads as a sheet on rails;
rotating a degree or two as it travels is what makes it look like it is
turning in space. Durations share no common factor, so the four never line
up on a beat. */
@keyframes silk-a {
from { transform: translate3d(-8%, 0, 0) rotate(-11deg); }
to { transform: translate3d(10%, 5%, 0) rotate(-4deg); }
}
@keyframes silk-b {
from { transform: translate3d(9%, 0, 0) rotate(7deg); }
to { transform: translate3d(-10%, -4%, 0) rotate(1deg); }
}
@keyframes silk-c {
from { transform: translate3d(-6%, 2%, 0) rotate(-6deg); }
to { transform: translate3d(11%, -5%, 0) rotate(2deg); }
}
@keyframes silk-d {
from { transform: translate3d(7%, 0, 0) rotate(9deg); }
to { transform: translate3d(-9%, 4%, 0) rotate(3deg); }
}
/* One grade over everything, so four independently drifting ribbons sit in the
same light instead of reading as four separate objects. */
.silk__grade {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(255, 190, 130, 0.1), transparent 45%, rgba(40, 60, 200, 0.16));
mix-blend-mode: screen;
pointer-events: none;
}
.silk__vignette {
position: absolute;
inset: 0;
background: radial-gradient(125% 95% at 50% 50%, transparent 52%, rgba(4, 2, 10, 0.8) 100%);
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
.silk__band { animation: none; }
}
`;
export function SilkRibbons() {
return (
<>
<style>{css}</style>
<div className="silk">
<div className="silk__band silk__band--a" />
<div className="silk__band silk__band--b" />
<div className="silk__band silk__band--c" />
<div className="silk__band silk__band--d" />
<div className="silk__grade" />
<div className="silk__vignette" />
</div>
</>
);
}