Solar system
Pure CSSOrbits are rings on a tilted plane, each spinning at its own speed. Every planet undoes both rotations, so it always faces the camera.
A gradient-drawn grid laid flat with rotateX, scrolling toward you forever.
Your edited version
linear-gradients drawing the grid lines.transform-origin: top) and lay it down with rotateX(80deg). A short perspective on the parent exaggerates the depth.transform, so the loop is seamless. (Animating background-position looks the same but repaints every frame; transform does not.)mask gradient fades the lines out toward the horizon.perspective on parentrotateX floor planeline layer slid with transform (not background-position)<div class="retro">
<div class="sun"></div>
<div class="floor"></div>
</div>
.retro {
position: fixed;
inset: 0;
overflow: hidden;
perspective: 260px;
perspective-origin: 50% 40%;
background: linear-gradient(#12062e 0%, #3b0f5c 38%, #ff4d9d 50%, #0a0618 50.5%);
}
.sun {
position: absolute;
top: 14%;
left: 50%;
width: 160px;
height: 160px;
translate: -50% 0;
border-radius: 50%;
background: linear-gradient(#ffd34d, #ff4d9d 70%);
mask: linear-gradient(#000 55%, transparent 55% 60%, #000 60% 70%,
transparent 70% 77%, #000 77% 85%, transparent 85%);
box-shadow: 0 0 60px #ff4d9d;
}
.floor {
--cell: 40px;
position: absolute;
top: 50%;
left: -100%;
width: 300%;
height: 300%;
transform-origin: top center;
transform: rotateX(80deg);
overflow: hidden;
mask: linear-gradient(transparent, #000 12%);
}
/* the lines slide on a child: transform animates on the compositor,
background-position would repaint the whole layer every frame */
.floor::before {
content: '';
position: absolute;
inset: calc(var(--cell) * -1) 0 0;
background:
linear-gradient(#2ee6d6 2px, transparent 2px) 0 0 / var(--cell) var(--cell),
linear-gradient(90deg, #2ee6d6 2px, transparent 2px) 50% 0 / var(--cell) var(--cell);
animation: run 0.9s linear infinite;
}
@keyframes run {
to { transform: translateY(var(--cell)); }
}
$cell: 36px;
.d-grid {
position: absolute;
inset: 0;
overflow: hidden;
perspective: 260px;
perspective-origin: 50% 40%;
background: linear-gradient(#12062e 0%, #3b0f5c 38%, #ff4d9d 50%, #0a0618 50.5%);
&__sun {
position: absolute;
top: 14%;
left: 50%;
width: 110px;
height: 110px;
translate: -50% 0;
border-radius: 50%;
background: linear-gradient(#ffd34d, #ff4d9d 70%);
// the classic striped lower half
mask: linear-gradient(#000 55%, transparent 55% 60%, #000 60% 70%, transparent 70% 77%, #000 77% 85%, transparent 85%);
box-shadow: 0 0 60px #ff4d9d;
}
&__floor {
position: absolute;
top: 50%;
left: -100%;
width: 300%;
height: 300%;
transform-origin: top center;
transform: rotateX(80deg);
overflow: hidden;
mask: linear-gradient(transparent, #000 12%);
// The lines live on a child that slides by exactly one cell: a transform animation runs on
// the compositor, whereas animating background-position repaints the layer every frame.
&::before {
content: '';
position: absolute;
inset: -$cell 0 0;
background:
linear-gradient(#2ee6d6 2px, transparent 2px) 0 0 / #{$cell} #{$cell},
linear-gradient(90deg, #2ee6d6 2px, transparent 2px) 50% 0 / #{$cell} #{$cell};
animation: d-grid-run 0.9s linear infinite;
}
}
}
// Shift by exactly one cell so the loop is seamless.
@keyframes d-grid-run {
to {
transform: translateY($cell);
}
}