JSFiddle - React, Tailwind, and code Playground

by HDL52

HTML

<ul class="threeD-button-set">
   <li><button>New Game</button></li>
   <li><button>Continue</button></li>
   <li><button>Online</button></li>
   <li><button>Settings</button></li>
   <li><button>Quit</button></li>
 </ul>

CSS

@import url("https://fonts.googleapis.com/css2?family=Audiowide&family=Ma+Shan+Zheng&family=Roboto:wght@400;700&display=swap");

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --HDcolor (dynamic-range: high);

.threeD-button-set {
  --y: ;
  --x: ;
  --distance: 1px;
  --theme: hsl(270 100% 50%);
  --theme-bg: hsl(270 100% 50% / 25%);
  --theme-bg-hover: hsl(270 100% 50% / 40%);
  --theme-text: white;
  --theme-shadow: hsl(270 100% 10% / 25%);

  --_max-rotateY: 10deg;
  --_max-rotateX: 15deg;
  --_btn-bg: var(--theme-bg);
  --_btn-bg-hover: var(--theme-bg-hover);
  --_btn-text: var(--theme-text);
  --_btn-text-shadow: var(--theme-shadow);
  --_bounce-ease: cubic-bezier(0.5, 1.75, 0.75, 1.25);

  @media (--dark) {
    --theme: hsl(180 100% 50%);
    --theme-bg: hsl(180 100% 71% / 25%);
    --theme-bg-hover: hsl(180 100% 50% / 40%);
    --theme-shadow: hsl(180 100% 10% / 25%);
  }

  @media (--HDcolor) {
    @supports (color: color(display-p3 0 0 0)) {
      --theme: color(display-p3 0.4 0 0.9);
    }
  }
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  width: 100%;
  height: 100vh;
  overflow: hidden;
  perspective: 40vw;
  display: flex;
  justify-content: center;
  align-items: center;
  background: radial-gradient(circle at center, #111, #000);
}


.threeD-button-set {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2.5vh;
  transform-style: preserve-3d;
  will-change: transform;
  animation: rotate-y 5s ease-in-out infinite;
  transform: rotateX(var(--x)) rotateY(var(--y));
}

@media (--motionOK) {
  .threeD-button-set {
    will-change: transform;
    transition: transform 0.1s ease;
  }
}

@keyframes rotate-y {
  50% {
    transform: rotateY(15deg) rotateX(-6deg);
  }
}

.threeD-button-set>li {
  display: inline-flex;
  position: relative;
  transform-style: preserve-3d;
}

.threeD-button-set button {
 ...

JavaScript

document.addEventListener('DOMContentLoaded', () => {
  const menu = document.querySelector('.threeD-button-set');
  let menuRect = menu.getBoundingClientRect();

  const { matches: motionOK } = window.matchMedia('(prefers-reduced-motion: no-preference)');

  const getAngles = (clientX, clientY) => {
    const { x, y, width, height } = menuRect;

    const dx = clientX - (x + 0.5 * width);
    const dy = clientY - (y + 0.5 * height);

    return { dx, dy };
  };

  const updateAngles = ({ clientX, clientY }) => {
    const { dx, dy } = getAngles(clientX, clientY);

    const xAngle = Math.max(Math.min(dy / 20, 15), -15);
    const yAngle = Math.max(Math.min(dx / 20, 15), -15);

    menu.style.setProperty('--x', `${xAngle}deg`);
    menu.style.setProperty('--y', `${yAngle}deg`);
  };

  if (motionOK) {
    window.addEventListener('mousemove', updateAngles);
    window.addEventListener('resize', () => {
      menuRect = menu.getBoundingClientRect();
    });
  }
});