<main></main>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  perspective: 800px;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: linear-gradient(#3a4149, #111722);
}

main {
  position: relative;
  transform-style: preserve-3d;
  width: 50vmin;
  height: 75vmin;
  transition: all 500ms ease;
}

img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
}

article {
  position: absolute;
  inset: 0;
  -webkit-box-reflect: below 50px
    linear-gradient(transparent, rgba(255, 255, 255, 0.15));

  button {
    border: 0;
    outline: 0;
    cursor: pointer;
    width: 100%;
    height: 100%;
    overflow: hidden;
    border-radius: 16px;
    transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1),
      box-shadow 200ms ease;

    &:focus-visible {
      box-shadow: 0 0 0 3px cyan;
    }
    // fallback
    &:focus {
      box-shadow: 0 0 0 3px cyan;
    }
    &:focus:not(:focus-visible) {
      box-shadow: none;
    }

    &:hover,
    &:focus-visible {
      transform: scale(1.03) translateY(-4%);
    }
  }
}
View Compiled
/**
 * Initially the elements are positioned in front of the camera.
 * We define here the transformations to position them in the scene.
 * When clicking on an item, we apply the inversed transformation but on the main container to "zoom" on the item
 */
const articles = new Map([
  ["6392322", { tx: "-90%", tz: "-70vmin", ry: "60deg" }],
  ["1761279", { tz: "-110vmin" }],
  ["1679772", { tx: "90%", tz: "-70vmin", ry: "-60deg" }]
]);

window.addEventListener("load", () => {
  const main = document.querySelector("main");

  for (const [id, { tx, tz, ry }] of articles.entries()) {
    main.appendChild(makeArticleElement(id, tx, tz, ry));
  }

  document.addEventListener("click", ({ target }) => {
    const targetId = target.closest("article")?.id;
    let [itx, itz, iry] = [0, 0, 0]; // inversed transformation to apply to the main element

    if (targetId && main.dataset.focus !== targetId) {
      // zoom in
      const { tx, tz, ry } = articles.get(targetId) || {};
      [itx, itz, iry] = [tx, tz, ry].map(inverseTransformation);
      main.setAttribute("data-focus", targetId);
    } else {
      // zoom out
      main.removeAttribute("data-focus");
    }
    main.style.transform = `rotateY(${iry}) translate3d(${itx}, 0, ${itz})`;
  });
});

// e.g. turn "90%" into "-90%" or "-10vmin" into "10vmin"
function inverseTransformation(transform) {
  if (!transform) return 0;
  const [_, value, unit] = transform.match(/(-?\d+)(.*)/);
  return `${-Number(value)}${unit}`;
}

function makeArticleElement(id, tx = 0, tz = 0, ry = 0) {
  const img = document.createElement("img");
  img.src = `https://images.pexels.com/photos/${id}/pexels-photo-${id}.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500`;

  const button = document.createElement("button");
  button.appendChild(img);

  const element = document.createElement("article");
  element.id = id;
  element.style.transform = `translate3d(${tx}, 0, ${tz}) rotateY(${ry})`;
  element.appendChild(button);
  return element;
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.