div.vhs
  div.vhs__control
    div.vhs__case
      div.vhs__front
      div.vhs__back
      div.vhs__left
      div.vhs__right
      div.vhs__top
      div.vhs__bottom
View Compiled
/* Shout out to https://vhscollector.com/ for the box art! */

:root {
  --cw: 122;
  --ch: 202; 
  --cd: 27;
  --brl: 4;
  --brs: 1;
  --m: min(0.3svw, 0.3svh);
}

body {
  background-color: oklch(98% 0.05 200deg);
  display: grid;
  margin: 0;
  min-block-size: 100dvh;
  place-content: center;
}

.vhs {
  perspective: 1000px;
}

.vhs__control {
  cursor: all-scroll;
  transform-origin: center center calc(((var(--cd) * var(--m)) / 2) * -1);
  transform-style: preserve-3d;
}

.vhs__case {
  position: relative;
  transform-style: preserve-3d;
}

.vhs__case > * {
  transform-style: preserve-3d;
}

.vhs__front,
.vhs__back,
.vhs__left,
.vhs__right,
.vhs__top,
.vhs__bottom {
  background-color: oklch(95% 0.01 200deg);
  box-shadow: inset 0 0 calc(10 * var(--m)) 0 oklch(50% 0.01 200deg / 30%);
  position: absolute;
}

.vhs__case,
.vhs__front,
.vhs__back {
  block-size: calc(var(--ch) * var(--m));
  inline-size: calc(var(--cw) * var(--m));
}

.vhs__front,
.vhs__back {
  inset: 0;
}

.vhs__front::after,
.vhs__back::after,
.vhs__left::after {
  background-image: url("https://www.transparenttextures.com/patterns/solid.png");
  content: '';
  inset: 2.5% 0;
  opacity: 0.7;
  position: absolute;
}

.vhs__front::after {
  inset-inline-end: 2.5%;
}

.vhs__back::after {
  inset-inline-start: 2.5%;
}

.vhs__front::before,
.vhs__back::before {
  background-color: oklch(100% 0 0deg / 20%);
  content: '';
  position: absolute;
  inline-size: 2px;
  inset-block: 2.5%;
}

.vhs__front::before {
  inset-inline: auto 6%;
}

.vhs__back::before {
  inset-inline: 6% auto;
}

.vhs__front {
  background-image: url(https://vhscollector.com/sites/default/files/vhsimages/20475_Stand%2520by%2520Me%25201.jpg);
  background-size: 96% 96%;
  background-position: left center;
  background-repeat: no-repeat;
  border-radius: calc(var(--brs) * var(--m)) calc(var(--brl) * var(--m)) calc(var(--brl) * var(--m)) calc(var(--brs) * var(--m));
}


.vhs__back {
  background-image: url(https://vhscollector.com/sites/default/files/vhsimages/20475_Stand%2520by%2520Me%25202.jpg);
  background-size: 96% 96%;
  background-position: right center;
  background-repeat: no-repeat;
  border-radius: calc(var(--brl) * var(--m)) calc(var(--brs) * var(--m)) calc(var(--brs) * var(--m)) calc(var(--brl) * var(--m));
  rotate: 0 1 0 180deg;
  translate: 0 0 calc((var(--cd) * var(--m)) * -1);
}

.vhs__left,
.vhs__right {
  block-size: calc(var(--ch) * var(--m));
  inline-size: calc(var(--cd) * var(--m));
}

.vhs__left {
  background-image: url(https://vhscollector.com/sites/default/files/vhsimages/20475_Stand%2520by%2520Me%25203.jpg);
  background-size: 100% 96%;
  background-position: center center;
  background-repeat: no-repeat;
  border-radius: calc(var(--brs) * var(--m));
  inset-inline: auto 100%;
  rotate: 0 1 0 -90deg;
  transform-origin: right center;
}

.vhs__right {
  block-size: calc(((var(--ch) * var(--m)) / 100) * 97);
  inset: 50% auto auto 98.5%;
  overflow: clip;
  rotate: 0 1 0 90deg;
  transform-origin: left center;
  translate: 0 -50%;
}

.vhs__right::before {
  background-color: oklch(10% 0 0deg / 40%);
  content: '';
  filter: blur(calc(1 * var(--m)));
  inset: 0 6%;
  position: absolute;
}

.vhs__top,
.vhs__bottom {
  block-size: calc(var(--cd) * var(--m));
  inline-size: calc(((var(--cw) * var(--m)) / 100) * 98.5);
}

.vhs__top::before,
.vhs__bottom::before {
  background-color: oklch(10% 0 0deg / 40%);
  content: '';
  filter: blur(calc(1 * var(--m)));
  position: absolute;
}

.vhs__top {
  inset-block: auto 98.5%;
  rotate: 1 0 0 90deg;
  transform-origin: center bottom;
}

.vhs__top::before {
  inset: 6% 0 6% 3%;
}

.vhs__bottom {
  inset-block: 98.5% auto;
  rotate: 1 0 0 -90deg;
  transform-origin: center top;
}

.vhs__bottom::before {
  inset: 6% 0 6% 3%;
}
const vhs = document.querySelector('.vhs__control');

let isDragging = false;
let previousX = 0;
let previousY = 0;
let rotateX = -30;
let rotateY = 0;
let velocityX = 0;
let velocityY = 0;
let autoRotate = true;

function animate() {
  if (autoRotate) {
    rotateY += 0.1;
  } else {
    rotateX -= velocityY;
    rotateY += velocityX;

    // Apply friction.
    velocityX *= 0.95;
    velocityY *= 0.95;

    // Stop completely if velocity is low.
    if (Math.abs(velocityX) < 0.001 && Math.abs(velocityY) < 0.001) {
      velocityX = 0;
      velocityY = 0;
    }
  }

  vhs.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
  requestAnimationFrame(animate);
}

// Mouse events.
vhs.addEventListener('mousedown', (e) => {
  isDragging = true;
  autoRotate = false;
  previousX = e.clientX;
  previousY = e.clientY;
});

window.addEventListener('mousemove', (e) => {
  if (!isDragging) return;

  const deltaX = e.clientX - previousX;
  const deltaY = e.clientY - previousY;

  velocityX = deltaX * 0.1;
  velocityY = deltaY * 0.1;

  rotateY += velocityX;
  rotateX -= velocityY;

  previousX = e.clientX;
  previousY = e.clientY;
});

window.addEventListener('mouseup', () => {
  isDragging = false;
  autoRotate = true;
});

// Touch events.
vhs.addEventListener('touchstart', (e) => {
  isDragging = true;
  autoRotate = false;
  previousX = e.touches[0].clientX;
  previousY = e.touches[0].clientY;
});

vhs.addEventListener('touchmove', (e) => {
  const deltaX = e.touches[0].clientX - previousX;
  const deltaY = e.touches[0].clientY - previousY;

  velocityX = deltaX * 0.1;
  velocityY = deltaY * 0.1;

  rotateY += velocityX;
  rotateX -= velocityY;

  previousX = e.touches[0].clientX;
  previousY = e.touches[0].clientY;
});

vhs.addEventListener('touchend', () => {
  isDragging = false;
  autoRotate = true;
});

// Start animation loop.
requestAnimationFrame(animate);
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.