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 small terrain of block columns in an isometric view. Each block is one element: its top is lifted with translateZ and two pseudo-element walls hang from its edges.
Your edited version
rotateX(58deg) rotateZ(45deg). Each block gets --x/--y (cells), --z (top height) and --h (wall length).translateZ. ::before folds down from its bottom edge with rotateX(-90deg), ::after from its right edge with rotateY(90deg). Only the two walls that face the camera exist.one element per blockwalls: rotateX(-90deg) / rotateY(90deg) from the top edgesSass @for over a height mapbobbing + breathing shadow<div class="scene">
<div class="island">
<div class="shadow"></div>
<div class="land">
<i style="--x:0;--y:0;--z:2"></i>
<i style="--x:1;--y:0;--z:3"></i>
<i style="--x:2;--y:0;--z:2.5"></i>
<i style="--x:3;--y:0;--z:2"></i>
<i style="--x:0;--y:1;--z:2"></i>
<i style="--x:1;--y:1;--z:2.5"></i>
<i style="--x:2;--y:1;--z:2"></i>
<i style="--x:3;--y:1;--z:1.5"></i>
<i style="--x:0;--y:2;--z:1"></i>
<i style="--x:1;--y:2;--z:2"></i>
<i style="--x:2;--y:2;--z:1.5"></i>
<i style="--x:3;--y:2;--z:1"></i>
<i style="--x:0;--y:3;--z:1.5"></i>
<i style="--x:1;--y:3;--z:1"></i>
<i style="--x:2;--y:3;--z:1"></i>
<i style="--x:3;--y:3;--z:0.5" class="pond"></i>
<i class="rock" style="--x:0.2;--y:0.2;--w:3.6;--h:1.4;--z:0"></i>
<i class="rock" style="--x:0.7;--y:0.7;--w:2.6;--h:1.3;--z:-1.4"></i>
<i class="rock" style="--x:1.3;--y:1.3;--w:1.4;--h:1.1;--z:-2.7"></i>
<i class="trunk" style="--x:1.35;--y:0.35;--w:0.3;--h:0.7;--z:3.7"></i>
<i class="leaf" style="--x:1.1;--y:0.1;--w:0.8;--h:0.8;--z:4.5"></i>
<i class="trunk" style="--x:0.35;--y:2.35;--w:0.3;--h:0.5;--z:1.5"></i>
<i class="leaf" style="--x:0.15;--y:2.15;--w:0.7;--h:0.7;--z:2.2"></i>
</div>
</div>
</div>
.scene {
perspective: 800px;
}
.island {
--cell: 28px;
--unit: 14px; /* one step of height */
position: relative;
width: 200px;
height: 190px;
transform-style: preserve-3d;
}
.shadow {
position: absolute;
left: 40px;
bottom: 4px;
width: 120px;
height: 24px;
border-radius: 50%;
background: radial-gradient(closest-side, rgb(139 108 255 / 0.5), transparent);
animation: breathe 3.2s ease-in-out infinite alternate;
}
.land {
position: absolute;
left: 44px;
top: 30px;
width: 112px;
height: 112px;
transform-style: preserve-3d;
animation: bob 3.2s ease-in-out infinite alternate;
}
/* one block = top face + two walls */
.land i {
--w: 1;
--h: var(--z); /* terrain columns reach down to the ground */
--top: #2bb58f;
--grass: #1f8a66;
--side: #8a5f38;
position: absolute;
left: calc(var(--x) * var(--cell));
top: calc(var(--y) * var(--cell));
width: calc(var(--w) * var(--cell));
height: calc(var(--w) * var(--cell));
background: var(--top);
transform-style: preserve-3d;
transform: translateZ(calc(var(--z) * var(--unit)));
}
.land i::before,
.land i::after {
content: '';
position: absolute;
}
/* +y wall, folded down from the bottom edge */
.land i::before {
top: 100%;
left: 0;
width: 100%;
height: calc(var(--h) * var(--unit));
background: linear-gradient(var(--grass) 0 3px, var(--side) 3px);
transform-origin: top;
transform: rotateX(-90deg);
}
/* +x wall, folded down from the right edge, a shade darker */
.land i::after {
top: 0;
left: 100%;
width: calc(var(--h) * var(--unit));
height: 100%;
background:
linear-gradient(rgb(0 0 0 / 0.25), rgb(0 0 0 / 0.25)),
linear-gradient(90deg, var(--grass) 0 3px, var(--side) 3px);
transform-origin: left;
transform: rotateY(90deg);
}
.land .pond { --top: #6f8bff; --grass: #4a5fc4; --side: #3a4a96; }
.land .rock { --top: #5b5f78; --grass: #6b5a50; --side: #6f7294; }
.land .trunk { --top: #8a5a36; --grass: #6b4228; --side: #6b4228; }
.land .leaf { --top: #ff9cc6; --grass: #ff4d9d; --side: #ff4d9d; }
@keyframes bob {
from { transform: translateY(5px) rotateX(58deg) rotateZ(38deg); }
to { transform: translateY(-5px) rotateX(58deg) rotateZ(52deg); }
}
@keyframes breathe {
from { opacity: 1; transform: scale(1); }
to { opacity: 0.55; transform: scale(0.82); }
}
@use 'sass:list';
@use 'sass:math';
// Every block is ONE element: the element is the top face, lifted to --z, and two
// pseudo-elements hang --h units down from its +y and +x edges as walls.
// --x / --y / --w are in grid cells ($cell), --z / --h in height units ($unit).
// The 16 terrain columns get their values from the height map; rocks and trees set theirs inline.
$cell: 28px;
$unit: 14px;
$lip: 3px; // strip of grass at the top of every terrain wall
// row by row, far corner first; 0.5 is the pond
$heights: (
2, 3, 2.5, 2,
2, 2.5, 2, 1.5,
1, 2, 1.5, 1,
1.5, 1, 1, 0.5
);
.d-island {
position: relative;
width: 200px;
height: 190px;
transform-style: preserve-3d;
// a breathing glow under the island: smaller and fainter while the island is up
&__shadow {
position: absolute;
left: 40px;
bottom: 4px;
width: 120px;
height: 24px;
border-radius: 50%;
background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 50%, transparent), transparent);
animation: d-island-shadow 3.2s ease-in-out infinite alternate;
}
&__land {
position: absolute;
left: 100px - $cell * 2;
top: 86px - $cell * 2;
width: $cell * 4;
height: $cell * 4;
transform-style: preserve-3d;
animation: d-island-bob 3.2s ease-in-out infinite alternate;
i {
// grass on top, earth on the walls (terrain columns)
--top: color-mix(in srgb, var(--accent-2) 70%, #1d6b3a);
--side: color-mix(in srgb, var(--warm) 45%, #3a2414);
--grass: color-mix(in srgb, var(--accent-2) 55%, #14512b);
--w: 1;
position: absolute;
left: calc(var(--x) * #{$cell});
top: calc(var(--y) * #{$cell});
width: calc(var(--w) * #{$cell});
height: calc(var(--w) * #{$cell});
background: var(--top);
box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.12);
transform-style: preserve-3d;
transform: translateZ(calc(var(--z) * #{$unit}));
&::before,
&::after {
content: '';
position: absolute;
}
// +y wall: from the bottom edge, rotateX(-90deg) folds it down
&::before {
top: 100%;
left: 0;
width: 100%;
height: calc(var(--h) * #{$unit});
background: linear-gradient(var(--grass) 0 $lip, var(--side) $lip);
transform-origin: top;
transform: rotateX(-90deg);
}
// +x wall: from the right edge, rotateY(90deg) folds it down; a shade darker
&::after {
top: 0;
left: 100%;
width: calc(var(--h) * #{$unit});
height: 100%;
background:
linear-gradient(rgb(0 0 0 / 0.25), rgb(0 0 0 / 0.25)),
linear-gradient(90deg, var(--grass) 0 $lip, var(--side) $lip);
transform-origin: left;
transform: rotateY(90deg);
}
}
@for $n from 1 through 16 {
$h: list.nth($heights, $n);
i:nth-child(#{$n}) {
--x: #{($n - 1) % 4};
--y: #{math.floor(math.div($n - 1, 4))};
--z: #{$h};
--h: #{$h};
}
}
// the pond
i:nth-child(16) {
--top: color-mix(in srgb, var(--accent) 55%, #3fa9ff);
--grass: color-mix(in srgb, var(--accent) 45%, #1d4f99);
--side: color-mix(in srgb, var(--accent) 40%, #173a6e);
}
// stone underside, hanging below the terrain
.is-rock {
--top: #5b5f78;
--grass: color-mix(in srgb, var(--warm) 30%, #4a3a36);
--side: color-mix(in srgb, var(--muted) 55%, #3a3550);
}
.is-trunk {
--top: #8a5a36;
--grass: #6b4228;
--side: #6b4228;
}
.is-leaf {
--top: color-mix(in srgb, var(--hot) 70%, #fff);
--grass: var(--hot);
--side: var(--hot);
}
}
}
@keyframes d-island-bob {
from {
transform: translateY(5px) rotateX(58deg) rotateZ(38deg);
}
to {
transform: translateY(-5px) rotateX(58deg) rotateZ(52deg);
}
}
@keyframes d-island-shadow {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0.55;
transform: scale(0.82);
}
}