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.bottom: 50%, the horizon's own 50%) and is only the part above it. Its circle is a clip-path and its slices a mask, both on the sun itself, so the slices stay inside the outline on every canvas.inset: 0, so it fills the canvas edge to edge whatever its shape. The sun, the cell and the perspective are multiples of one base unit, --u, tied to the canvas, so the horizon looks the same on a gallery card and on a full screen.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 {
/* one base unit, tied to the canvas; the scene itself fills the canvas edge to edge */
--u: 0.33vmin;
position: fixed;
inset: 0;
overflow: hidden;
perspective: calc(260 * var(--u));
perspective-origin: 50% 40%;
background: linear-gradient(#12062e 0%, #3b0f5c 38%, #ff4d9d 50%, #0a0618 50.5%);
}
/* the sun stands ON the horizon (bottom: 50%, the same 50% as the horizon line) and is only the
part above it: a 160-unit circle sunk 40 units, so nothing of it can reach the floor */
.sun {
position: absolute;
bottom: 50%;
left: 50%;
width: calc(160 * var(--u));
height: calc(120 * var(--u));
translate: -50% 0;
background: linear-gradient(#ffd34d, #ff4d9d 93%);
/* the circle is a clip and the slices a mask, both on the sun itself, so the slices scale
with it and stay inside the outline on every canvas; their gaps grow toward the horizon */
clip-path: circle(calc(80 * var(--u)) at 50% calc(80 * var(--u)));
mask: linear-gradient(#000 50%, transparent 50% 52%, #000 52% 63%, transparent 63% 67%,
#000 67% 76%, transparent 76% 82%, #000 82% 89%, transparent 89% 97%, #000 97%);
}
.floor {
--cell: calc(40 * var(--u));
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 calc(2 * var(--u)), transparent calc(2 * var(--u))) 0 0 / var(--cell) var(--cell),
linear-gradient(90deg, #2ee6d6 calc(2 * var(--u)), transparent calc(2 * var(--u))) 50% 0 / var(--cell) var(--cell);
animation: run 0.9s linear infinite;
}
@keyframes run {
to { transform: translateY(var(--cell)); }
}