<div id="card-wrapper">
<div class="card"></div>
</div>
<style>
#card-wrapper {
perspective: 800px;
}
.card {
--background: linear-gradient(
192deg,
rgba(255, 255, 255, 0.4) 0%,
rgba(235, 238, 241, 0.8) 100%
);
overflow: hidden;
display: flex;
width: 268px;
height: 368px;
padding: 16px;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
border-radius: 16px;
background: var(--background);
background-blend-mode: overlay, normal;
box-shadow:
0px 1px 0px 0px rgba(10, 37, 64, 0.12),
-1px 0px 0px 0px #fff inset,
0px 60px 60px -40px rgba(50, 72, 93, 0.25),
0px 1px 0px 0px #fff inset;
transition: all 0.2s ease;
}
body {
font-family: sans-serif;
color: #353a44;
background: linear-gradient(to right, #12c2e9, #c471ed, #f64f59);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
</style>
<script>
const clamp = (min, num, max) => {
return Math.min(Math.max(num, min), max)
}
const mapRange = (x, inMin, inMax, outMin, outMax) => {
return ((x - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin
}
// makes the card 3D angle follow cursor when hovered
document.addEventListener("DOMContentLoaded", () => {
const body = document.body
const card = document.querySelector(".card")
document.body.addEventListener("mousemove", (event) => {
const rect = card.getBoundingClientRect()
const offsetX = event.clientX - rect.left
const offsetY = event.clientY - rect.top
const centerX = rect.width / 2
const centerY = rect.height / 2
const rotateX = clamp(-20, ((offsetY - centerY) / centerY) * 8, 20)
const rotateY = clamp(-20, -((offsetX - centerX) / centerX) * 12, 20)
const xMin = rect.left
const xMax = rect.left + rect.width
const yMin = rect.top
const yMax = rect.top + rect.height
const borderX = mapRange(event.clientX, xMin, xMax, -1, 1)
const borderY = mapRange(event.clientY, yMin, yMax, -1, 2)
card.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
card.style.boxShadow = `0px 1px 0px 0px rgba(10, 37, 64, 0.12), ${borderX}px 0px 0px 0px #fff, 0px ${borderY}px 0px 0px #fff, 0px 60px 60px -40px rgba(50, 72, 93, 0.25), 0px 1px 0px 0px #fff inset`
})
document.body.addEventListener("mouseleave", () => {
card.style.transform = `none`
card.style.boxShadow = `0px 1px 0px 0px rgba(10, 37, 64, 0.12),
-1px 0px 0px 0px #fff inset,
0px 60px 60px -40px rgba(50, 72, 93, 0.25),
0px 1px 0px 0px #fff inset`
})
})
// makes the glare move around inside the card
document.addEventListener("DOMContentLoaded", () => {
const container = document.querySelector(".card")
document.body.addEventListener("mousemove", (event) => {
const { left, top, width, height } = container.getBoundingClientRect()
const x = event.clientX - left
const y = event.clientY - top
const gradientX = clamp(0, (x / width) * 100, 100)
const gradientY = clamp(0, (y / height) * 100, 100)
container.style.background = `radial-gradient(circle at ${gradientX}% ${gradientY}%, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.0) 75%), var(--background)`
})
container.addEventListener("mouseleave", () => {
container.style.background = `rgba(255, 255, 255, 0.2)`
})
})
</script>