JSFiddle - React, Tailwind, and code Playground

by Scott Kaye

HTML

<div class="grid-wrapper">
    <div class="grid">
        <div class="tile">
            <span class="label">TR1ASD<strong>322</strong></span>
            <div class="connector right"></div>
            <div class="connector down"></div>
        </div>

        <div class="tile">
            <span class="label">TR1DBD<strong>322</strong></span>
        </div>

        <div class="tile">
            <span class="label">TR1ASQ<strong>318</strong></span>
        </div>
    </div>
</div>

<div class="controls">
    <div>Pitch: <input type="range" id="pitch" min="10" max="70" value="50"></div>
    <div>Yaw: <input type="range" id="yaw" min="-65" max="-25" value="-35"></div>
</div>

CSS

:root {
    --square-height: 30px;
	--ease: cubic-bezier(.41,.47,.45,.97);
}

body {
    transform-style: preserve-3d;
    display: flex;
    height: 100vh;
    overflow: hidden;
    justify-content: center;
    align-items: center;
    background: radial-gradient(#727C87, #262F3B);
    font-family: Segoe UI, sans-serif;
}

.controls {
    position: absolute;
    top: 0;
    right: 0;
    background: rgba(0, 0, 0, 0.25);
    color: #ddd;
    padding: 10px;
    border-radius: 2px;
    box-shadow: 1px 1px 0 rgba(255, 255, 255, 0.05), inset 5px 5px 10px rgba(0, 0, 0, 0.1);
    margin: 10px;
}

.controls>div {
    display: grid;
    grid-template-columns: 1fr 2fr;
    align-items: center;
    font-weight: 500;
}

.grid-wrapper {
	animation: grid-wrapper-in 1500ms var(--ease) 1 forwards;
}

@keyframes grid-wrapper-in {
	from { opacity: 0; }
	to { opacity: 1; }
}

.grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-gap: 100px;
    transition: transform 1500ms var(--ease);
    transform-style: preserve-3d;
}

.grid.dragging {
    transition: none;
}

.tile {
    backface-visibility: hidden;
    width: 200px;
    height: 200px;
    background: radial-gradient(#ccc, #fff);
    transform-style: preserve-3d;
    position: relative;
    display: flex;
    justify-content: center;
    align-items: center;
    /* Bloom */
    box-shadow: 0 0 50px 5px rgba(255, 255, 255, 0.25), inset 0 0 10px rgba(50, 127, 255, 0.5);
}

.tile::before,
.tile::after {
    content: "";
    position: absolute;
    background: #8B929C;
    bottom: 0;
    left: 0;
    right: 0;
    height: var(--square-height);
    transform: translateY(50%) rotateX(-90deg) translateY(50%);
    /* AO */
    filter: drop-shadow(0 0 30px rgba(0, 0, 0, 0.2));
}

.tile::after {
    background: #7d7f82;
    top: 0;
    left: 0;
    height: auto;
    width: var(--square-height);
    transform: rotateY(90deg) translateX(50%) translateZ(calc(var(--square-height) / -2));
}

.tile .label {
    user-select:...

JavaScript

P(async () => {
    const pitchControl = document.querySelector("#pitch");
    const yawControl = document.querySelector("#yaw");

    let camera = {
        pitch: pitchControl.value,
        yaw: yawControl.value
    };

    document.addEventListener("input", function(e) {
        switch (e.target) {
            case pitchControl:
                camera.pitch = e.target.value;
                break;
            case yawControl:
                camera.yaw = e.target.value;
                break;
        }

        updateCamera();
    }, false);

    function updateCamera() {
        document.querySelectorAll(".grid").forEach(grid => {
            let transform = `rotateX(${camera.pitch}deg) rotateZ(${camera.yaw}deg)`;
            grid.style.transform = transform;
        });
    }

    updateCamera();

    setTimeout(() => {
        document.querySelectorAll(".grid").forEach(grid => {
            grid.classList.add("dragging");
        });
    }, 1500);
})();