<div class="orbital-wrapper">
<div class="orbit-center">
<img src="https://placehold.co/120x120/1e293b/ffffff?text=User" alt="Avatar" class="center-avatar">
</div>
<div class="orbit-track" id="track-inner">
<!-- Decoupled wrapper for translation, inner card for scaling -->
<div class="orbit-node node-inner"><div class="node-card">HTML</div></div>
<div class="orbit-node node-inner"><div class="node-card">CSS</div></div>
<div class="orbit-node node-inner"><div class="node-card">JS</div></div>
</div>
<div class="orbit-track" id="track-outer">
<div class="orbit-node node-outer"><div class="node-card">React</div></div>
<div class="orbit-node node-outer"><div class="node-card">Node</div></div>
<div class="orbit-node node-outer"><div class="node-card">SQL</div></div>
<div class="orbit-node node-outer"><div class="node-card">Python</div></div>
</div>
</div>*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
margin: 0;
background: #f8fafc;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.orbital-wrapper {
position: relative;
width: 600px;
height: 600px;
display: flex;
justify-content: center;
align-items: center;
}
.orbit-center {
position: relative;
z-index: 10;
}
.center-avatar {
width: 120px;
height: 120px;
border-radius: 50%;
border: 4px solid #ffffff;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
object-fit: cover;
}
.orbit-track {
position: absolute;
top: 50%;
left: 50%;
border: 1px dashed #cbd5e1;
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
#track-inner {
width: 300px;
height: 300px;
}
#track-outer {
width: 480px;
height: 480px;
}
.orbit-node {
position: absolute;
top: 50%;
left: 50%;
/* Shift element center to exact mathematical point */
margin-top: -22px;
margin-left: -40px;
width: 80px;
height: 44px;
pointer-events: auto;
z-index: 5;
}
/*
The hover scale is applied to the child card.
This perfectly prevents the transform conflict bug!
*/
.node-card {
width: 100%;
height: 100%;
background: #ffffff;
border-radius: 22px;
font-weight: 700;
color: #0f172a;
box-shadow: 0 6px 12px rgba(0,0,0,0.08);
border: 2px solid #e2e8f0;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s, border-color 0.3s, color 0.3s;
}
.orbit-node:hover {
z-index: 100;
}
.orbit-node:hover .node-card {
transform: scale(1.2);
box-shadow: 0 15px 25px rgba(59, 130, 246, 0.3);
border-color: #3b82f6;
color: #3b82f6;
}const innerNodes = document.querySelectorAll(".node-inner");
const outerNodes = document.querySelectorAll(".node-outer");
let angleInner = 0;
let angleOuter = 0;
/* Radius must match half the CSS width of the tracks */
const radiusInner = 150;
const radiusOuter = 240;
function animateOrbits() {
/* Slow, elegant rotation speeds */
angleInner += 0.003;
angleOuter -= 0.002;
/* Position Inner Nodes securely */
const innerTotal = innerNodes.length;
for (let i = 0; i < innerTotal; i++) {
const node = innerNodes.item(i);
const slice = (Math.PI * 2) / innerTotal;
const currentAngle = angleInner + (slice * i);
const x = Math.cos(currentAngle) * radiusInner;
const y = Math.sin(currentAngle) * radiusInner;
node.style.transform = "translate(" + x + "px, " + y + "px)";
}
/* Position Outer Nodes securely */
const outerTotal = outerNodes.length;
for (let i = 0; i < outerTotal; i++) {
const node = outerNodes.item(i);
const slice = (Math.PI * 2) / outerTotal;
const currentAngle = angleOuter + (slice * i);
const x = Math.cos(currentAngle) * radiusOuter;
const y = Math.sin(currentAngle) * radiusOuter;
node.style.transform = "translate(" + x + "px, " + y + "px)";
}
window.requestAnimationFrame(animateOrbits);
}
animateOrbits();Nothing to show yetType HTML, CSS or JavaScript and your page appears here.
Click any element to find its code Esc
Check this code before you run it
It came from a shared link. It runs with fewer permissions, so it cannot open pop-ups, submit forms or start downloads.
0.00s / 0.00s
Nothing has animated yet. Hover, click or type in the preview and each CSS animation, transition or
element.animate() call shows up here as it plays.