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 striped tower of leaning strips on a rock in a night sea. Two long translucent planes, crossed, make each light beam; they sweep round with rotateY while a lit wedge on the water turns with them.
Your edited version
rotateY(side) translateZ(bottom radius) rotateX(lean), with the lean atan((bottom − top) / height) and a trapezoid clip-path. Rock, striped tower, glass room and roof are the same recipe with other numbers.rotateY, and the far beam passes behind the tower.rotate(-a) turns the same way as rotateY(a) above it.--u, tied to the canvas, and the frustum numbers are worked out in it: the rock is 80 units across and the tower stands 84 high on it, so the lighthouse is the same share of a gallery card, the editor and a recording canvas.frustums of leaning stripsbeam: two crossed clip-path planes, rotateYsea: flat disc, wave lines slid by one periodrotateZ(-a) on the floor = rotateY(a) above it<div class="scene">
<div class="lighthouse">
<div class="layer"><div class="model">
<div class="sea"><i></i><i></i><u></u></div>
</div></div>
<div class="layer"><div class="model">
<div class="rock">
<i style="--a:0deg;--d:0.06"></i>
<i style="--a:45deg;--d:0.22"></i>
<i style="--a:90deg;--d:0.40"></i>
<i style="--a:135deg;--d:0.50"></i>
<i style="--a:180deg;--d:0.46"></i>
<i style="--a:225deg;--d:0.30"></i>
<i style="--a:270deg;--d:0.12"></i>
<i style="--a:315deg;--d:0.02"></i>
<b></b>
</div>
</div></div>
<div class="layer"><div class="model">
<div class="tower">
<i style="--a:0deg;--d:0.06"></i>
<i style="--a:36deg;--d:0.18"></i>
<i style="--a:72deg;--d:0.33"></i>
<i style="--a:108deg;--d:0.45"></i>
<i style="--a:144deg;--d:0.50"></i>
<i style="--a:180deg;--d:0.46"></i>
<i style="--a:216deg;--d:0.34"></i>
<i style="--a:252deg;--d:0.19"></i>
<i style="--a:288deg;--d:0.07"></i>
<i style="--a:324deg;--d:0.02"></i>
</div>
<div class="gallery"></div>
<div class="rail"></div>
<div class="room">
<i style="--a:0deg"></i>
<i style="--a:60deg"></i>
<i style="--a:120deg"></i>
<i style="--a:180deg"></i>
<i style="--a:240deg"></i>
<i style="--a:300deg"></i>
<b></b>
</div>
<div class="roof">
<i style="--a:0deg;--d:0.06"></i>
<i style="--a:60deg;--d:0.28"></i>
<i style="--a:120deg;--d:0.48"></i>
<i style="--a:180deg;--d:0.46"></i>
<i style="--a:240deg;--d:0.24"></i>
<i style="--a:300deg;--d:0.04"></i>
<b></b>
</div>
<div class="beam"><i></i><i></i><i></i><i></i></div>
</div></div>
</div>
</div>
.scene {
/* one base unit: every length below is a multiple of it, so the lighthouse is the same share
of a gallery card, the editor, a full screen and a recording canvas */
--u: 0.33vmin;
display: grid;
place-items: center;
}
/* the root is flat (three layers painted in order), so it holds the perspective itself */
.lighthouse {
position: relative;
width: calc(220 * var(--u));
height: calc(200 * var(--u));
perspective: calc(800 * var(--u));
}
.lighthouse * {
box-sizing: border-box;
}
/* each layer carries the same camera */
.layer {
position: absolute;
inset: 0;
transform-style: preserve-3d;
transform: rotateX(-22deg) rotateY(-24deg);
}
/* the model's origin: the middle of the sea */
.model,
.rock,
.tower,
.room,
.roof {
position: absolute;
transform-style: preserve-3d;
}
.model {
left: 50%;
top: calc(150 * var(--u));
}
/* the sea: a flat disc; it may clip, nothing in it is 3D */
.sea {
position: absolute;
left: calc(-110 * var(--u));
top: calc(-110 * var(--u));
width: calc(220 * var(--u));
height: calc(220 * var(--u));
overflow: hidden;
border: calc(1.5 * var(--u)) solid rgb(46 230 214 / 0.4);
border-radius: 50%;
background:
radial-gradient(circle, transparent 0 calc(44 * var(--u)), rgb(255 255 255 / 0.5) calc(45 * var(--u)) calc(47 * var(--u)), transparent calc(50 * var(--u)) calc(54 * var(--u)), rgb(255 255 255 / 0.16) calc(55 * var(--u)) calc(56 * var(--u)), transparent calc(58 * var(--u))),
radial-gradient(circle, #3f3b94, #1e1c4a 72%);
transform: rotateX(90deg);
}
/* wave lines broken into dashes by a mask; they slide by exactly one period */
.sea i {
position: absolute;
inset: calc(-20 * var(--u)) 0 0;
background: repeating-linear-gradient(transparent 0 calc(16 * var(--u)), rgb(46 230 214 / 0.55) calc(16 * var(--u)) calc(17.5 * var(--u)), transparent calc(17.5 * var(--u)) calc(20 * var(--u)));
mask: repeating-linear-gradient(90deg, #000 0 calc(22 * var(--u)), transparent calc(22 * var(--u)) calc(36 * var(--u)));
animation: waves 2.6s linear infinite;
}
.sea i + i {
inset: calc(-28 * var(--u)) 0 0;
background: repeating-linear-gradient(transparent 0 calc(9 * var(--u)), rgb(160 140 255 / 0.6) calc(9 * var(--u)) calc(10 * var(--u)), transparent calc(10 * var(--u)) calc(28 * var(--u)));
mask: repeating-linear-gradient(90deg, transparent 0 calc(14 * var(--u)), #000 calc(14 * var(--u)) calc(30 * var(--u)), transparent calc(30 * var(--u)) calc(46 * var(--u)));
animation: waves-2 4.2s linear infinite;
}
/* light on the water under the beams, turning with them */
.sea u {
position: absolute;
inset: 0;
background: conic-gradient(from 70deg, transparent, rgb(255 181 71 / 0.55) 20deg, transparent 40deg 180deg, rgb(255 181 71 / 0.55) 200deg, transparent 220deg);
mask: radial-gradient(closest-side, transparent 14%, #000 34%, transparent 98%);
animation: pool 8s linear infinite;
}
/* rock: 8 sides, radius 40 → 31 over 18 units */
.rock i {
position: absolute;
left: calc(-16.87 * var(--u));
top: calc(-20.12 * var(--u));
width: calc(33.74 * var(--u));
height: calc(20.12 * var(--u));
clip-path: polygon(11.94% 0, 88.06% 0, 100% 100%, 0 100%);
transform-origin: 50% 100%;
/* turn to its side, step out to the bottom radius, lean in by atan((bottom - top) / height) */
transform: rotateY(var(--a)) translateZ(calc(40 * var(--u))) rotateX(26.57deg);
backface-visibility: hidden;
background:
linear-gradient(rgb(6 8 26 / var(--d)), rgb(6 8 26 / var(--d))),
radial-gradient(circle at 30% 30%, rgb(255 255 255 / 0.12) 0 calc(3 * var(--u)), transparent calc(3.5 * var(--u))) 0 0 / calc(13 * var(--u)) calc(11 * var(--u)),
linear-gradient(#636885, #393c4f 80%, #114949);
}
/* the rock's flat top, an octagon */
.rock b {
position: absolute;
left: calc(-31 * var(--u));
top: calc(-49 * var(--u));
width: calc(62 * var(--u));
height: calc(62 * var(--u));
clip-path: polygon(29.3% 0, 70.7% 0, 100% 29.3%, 100% 70.7%, 70.7% 100%, 29.3% 100%, 0 70.7%, 0 29.3%);
background: radial-gradient(circle at 40% 40%, #82869d, #636885 70%);
transform: rotateX(90deg);
}
/* tower: 10 sides, radius 16 → 11 over 84 units, standing on the rock (y = -18) */
.tower i {
position: absolute;
left: calc(-5.5 * var(--u));
top: calc(-102.15 * var(--u));
width: calc(11 * var(--u));
height: calc(84.15 * var(--u));
clip-path: polygon(17.5% 0, 82.5% 0, 100% 100%, 0 100%);
transform-origin: 50% 100%;
/* turn to its side, step out to the bottom radius, lean in by atan((bottom - top) / height) */
transform: rotateY(var(--a)) translateZ(calc(16 * var(--u))) rotateX(3.41deg);
backface-visibility: hidden;
background:
linear-gradient(rgb(6 8 26 / var(--d)), rgb(6 8 26 / var(--d))),
repeating-linear-gradient(#e04285 0 calc(14 * var(--u)), #f3efe7 calc(14 * var(--u)) calc(28 * var(--u)));
}
/* the front side: a door and a lit window */
.tower i:first-child {
background:
linear-gradient(#2a1c28, #2a1c28) 50% 100% / calc(6 * var(--u)) calc(11 * var(--u)) no-repeat,
linear-gradient(#ffb547, #ffb547) 50% 45% / calc(4 * var(--u)) calc(6 * var(--u)) no-repeat,
repeating-linear-gradient(#e04285 0 calc(14 * var(--u)), #f3efe7 calc(14 * var(--u)) calc(28 * var(--u)));
}
.gallery,
.rail {
position: absolute;
left: calc(-18 * var(--u));
top: calc(-120 * var(--u));
width: calc(36 * var(--u));
height: calc(36 * var(--u));
border-radius: 50%;
background: radial-gradient(closest-side, #3a3e58 70%, #23263a);
transform: rotateX(90deg);
}
.rail {
top: calc(-126 * var(--u));
border: calc(1.5 * var(--u)) solid #a3a8c8;
background: none;
}
/* lamp room: 6 glass panes (open: the far panes show through) */
.room i {
position: absolute;
left: calc(-4.92 * var(--u));
top: calc(-116 * var(--u));
width: calc(9.84 * var(--u));
height: calc(14 * var(--u));
clip-path: polygon(3.05% 0, 96.95% 0, 100% 100%, 0 100%);
transform-origin: 50% 100%;
/* turn to its side, step out to the bottom radius, lean in by atan((bottom - top) / height) */
transform: rotateY(var(--a)) translateZ(calc(8 * var(--u))) rotateX(0deg);
background:
linear-gradient(90deg, #2b2e44 0 calc(1 * var(--u)), transparent calc(1 * var(--u)) calc(100% - calc(1 * var(--u))), #2b2e44 0),
rgb(255 181 71 / 0.4);
}
/* a flat halo at the lamp, level with the beams */
.room b {
position: absolute;
left: calc(-16 * var(--u));
top: calc(-16 * var(--u));
width: calc(32 * var(--u));
height: calc(32 * var(--u));
border-radius: 50%;
background: radial-gradient(closest-side, #fff 0 18%, #ffd08a 30%, rgb(255 181 71 / 0.45) 55%, transparent);
transform: translateY(calc(-109 * var(--u))) rotateX(90deg);
animation: flash 4s ease-in-out infinite;
}
/* roof: a cone of 6 triangles */
.roof i {
position: absolute;
left: calc(-6.65 * var(--u));
top: calc(-132.28 * var(--u));
width: calc(13.3 * var(--u));
height: calc(16.28 * var(--u));
clip-path: polygon(50% 0, 100% 100%, 0 100%);
transform-origin: 50% 100%;
/* turn to its side, step out to the bottom radius, lean in by atan((bottom - top) / height) */
transform: rotateY(var(--a)) translateZ(calc(11 * var(--u))) rotateX(42.51deg);
backface-visibility: hidden;
background:
linear-gradient(rgb(6 8 26 / var(--d)), rgb(6 8 26 / var(--d))),
linear-gradient(#5f53a3, #1d2033);
}
.roof b {
position: absolute;
left: calc(-3 * var(--u));
top: calc(-132 * var(--u));
width: calc(6 * var(--u));
height: calc(6 * var(--u));
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #fff, #8a8fb0);
}
/* two beams, each two tapering planes crossed at ±14deg around the beam's axis */
.beam {
position: absolute;
top: calc(-109 * var(--u));
transform-style: preserve-3d;
animation: sweep 8s linear infinite;
}
.beam i {
position: absolute;
left: calc(4 * var(--u));
top: calc(-17 * var(--u));
width: calc(104 * var(--u));
height: calc(34 * var(--u));
clip-path: polygon(0 42%, 100% 0, 100% 100%, 0 58%);
background: linear-gradient(90deg, #ffe2b0, rgb(255 181 71 / 0.45) 30%, transparent 97%);
transform-origin: calc(-4 * var(--u)) 50%; /* the lamp */
transform: rotateX(76deg);
}
.beam i:nth-child(2) { transform: rotateX(104deg); }
.beam i:nth-child(3) { transform: rotateY(180deg) rotateX(76deg); }
.beam i:nth-child(4) { transform: rotateY(180deg) rotateX(104deg); }
@keyframes sweep {
to { transform: rotateY(360deg); }
}
/* on the floor, rotate(-a) turns the same way as rotateY(a) above it */
@keyframes pool {
to { transform: rotate(-360deg); }
}
@keyframes waves {
to { transform: translateY(calc(20 * var(--u))); }
}
@keyframes waves-2 {
to { transform: translateY(calc(28 * var(--u))); }
}
/* half a sweep: a beam faces you at about 114deg and 294deg of the turn */
@keyframes flash {
0%, 45%, 82%, 100% { transform: translateY(calc(-109 * var(--u))) rotateX(90deg) scale(1); opacity: 0.7; }
63.3% { transform: translateY(calc(-109 * var(--u))) rotateX(90deg) scale(2.2); opacity: 1; }
}