.centered
	.card.border-left-behind
		.shadow(style="--url: url('https://i.ibb.co/PM4ghD4/full.png')")
		.image.background(style="--url: url('https://i.ibb.co/JpJVJxq/Background.png')")
		.image.cutout(style="--url: url('https://i.ibb.co/Dw3q3tZ/cutout.png')")
		.content
			h2 Hover me!
			p Lorem, ipsum dolor sit amet consectetur adipisicing elit.
	.card.border-right-behind.border-bottom-behind
		.shadow(style="--url: url('https://i.ibb.co/DC0MbxS/m-full.png')")
		.image.background(style="--url: url('https://i.ibb.co/ZdGBm4K/m-background.png')")
		.image.cutout(style="--url: url('https://i.ibb.co/RC70XmC/m-cutout.png')")
		.content
			h2 Hover me!
			p Lorem, ipsum dolor sit amet consectetur adipisicing elit.
	.card.border-left-behind
		.shadow(style="--url: url('https://i.ibb.co/gSBp82C/b-full.png')")
		.image.background(style="--url: url('https://i.ibb.co/MDBcyMW/b-background.png')")
		.image.cutout(style="--url: url('https://i.ibb.co/bQNgD6y/b-cutout.png')")
		.content
			h2 Hover me!
			p Lorem, ipsum dolor sit amet consectetur adipisicing elit.
View Compiled
@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@300;700&display=swap");

html,
body {
	width: 100%;
	height: 100%;
	// background: #393e41;
	font-family: "Montserrat", sans-serif;
}

.centered {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 3rem;
	height: 100%;
}

.card {
	position: relative;
	height: 28rem;
	width: 20rem;
	aspect-ratio: 5 / 7;
	color: #ffffff;
	perspective: 50rem;
	
	.shadow {
		position: absolute;
		inset: 0;
		background:
			var(--url);
		background-size: cover;
		background-position: center;
		opacity: 0.8;
		filter: blur(2rem) saturate(0.9);
		box-shadow: 0 -1.5rem 2rem -0.5rem rgba(0, 0, 0, 0.7);
		transform: rotateX(var(--rotateX)) rotateY(var(--rotateY))
				translate3d(0, 2rem, -2rem);
	}

	.image {
		position: absolute;
		inset: 0;
		background: linear-gradient(to top, rgba(0, 0, 0, 0.5), transparent 40%),
			var(--url);
		background-size: cover;
		background-position: center;
		mask-image: var(--url);
		mask-size: cover;
		mask-position: center;

		&.background {
			transform: rotateX(var(--rotateX)) rotateY(var(--rotateY))
				translate3d(0, 0, 0rem);
		}

		&.cutout {
			transform: rotateX(var(--rotateX)) rotateY(var(--rotateY))
				translate3d(0, 0, 4rem) scale(0.92);
			z-index: 3;
		}
	}

	.content {
		position: absolute;
		display: flex;
		flex-direction: column;
		justify-content: flex-end;
		inset: 0;
		padding: 3.5rem;

		transform: rotateX(var(--rotateX)) rotateY(var(--rotateY))
			translate3d(0, 0, 6rem);
		z-index: 4;
	}

	&::after,
	&::before {
		content: "";
		position: absolute;
		inset: 1.5rem;
		border: #e2c044 0.5rem solid;
		transform: rotateX(var(--rotateX)) rotateY(var(--rotateY))
			translate3d(0, 0, 2rem);
	}

	&::before {
		z-index: 4;
	}
	
	&.border-left-behind::before {
		border-left: transparent;
	}
	
	
	&.border-right-behind::before {
		border-right: transparent;
	}
	
	&.border-bottom-behind::before {
		border-bottom: transparent;
	}
}

h2 {
	font-size: 1.25rem;
	font-weight: 700;
	margin-bottom: 0.5rem;
	text-shadow: 0 0 2rem rgba(0, 0, 0, 0.5);
}

p {
	font-weight: 300;
	text-shadow: 0 0 2rem rgba(0, 0, 0, 0.5);
}

// TODO: Maybe add: transform-style: preserve-3d?
View Compiled
const angle = 20;
const rotateCard = window;

const lerp = (start, end, amount) => {
	return (1 - amount) * start + amount * end;
};

const remap = (value, oldMax, newMax) => {
	const newValue = ((value + oldMax) * (newMax * 2)) / (oldMax * 2) - newMax;
	return Math.min(Math.max(newValue, -newMax), newMax);
};

window.addEventListener("DOMContentLoaded", (event) => {
	const cards = document.querySelectorAll(".card");
	cards.forEach((e) => {		
		e.addEventListener("mousemove", (event) => {
			const rect = e.getBoundingClientRect();
			const centerX = (rect.left + rect.right) / 2;
			const centerY = (rect.top + rect.bottom) / 2;
			const posX = event.pageX - centerX;
			const posY = event.pageY - centerY;
			const x = remap(posX, rect.width / 2, angle);
			const y = remap(posY, rect.height / 2, angle);
			e.dataset.rotateX = x;
			e.dataset.rotateY = -y;
		});
		
		e.addEventListener("mouseout", (event) => {
			e.dataset.rotateX = 0;
			e.dataset.rotateY = 0;
		});
	});
	
	const update = () => {
		cards.forEach((e) => {
			let currentX = parseFloat(e.style.getPropertyValue('--rotateY').slice(0, -1));
			let currentY = parseFloat(e.style.getPropertyValue('--rotateX').slice(0, -1));
			if (isNaN(currentX)) currentX = 0;
			if (isNaN(currentY)) currentY = 0;
			const x = lerp(currentX, e.dataset.rotateX, 0.05);
			const y = lerp(currentY, e.dataset.rotateY, 0.05);
			e.style.setProperty("--rotateY", x + "deg");
			e.style.setProperty("--rotateX", y + "deg");
		})
	}
	setInterval (update,1000/60)
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.