<div class="direction-grid">
<div class="dir-card" id="card-1">
<img src="https://placehold.co/400x400/1e293b/ffffff?text=Project+Alpha" alt="Project" class="card-image">
<div class="dir-overlay">
<h3>Project Alpha</h3>
<p>Seamlessly integrated robust APIs.</p>
</div>
</div>
<div class="dir-card" id="card-2">
<img src="https://placehold.co/400x400/334155/ffffff?text=Project+Beta" alt="Project" class="card-image">
<div class="dir-overlay">
<h3>Project Beta</h3>
<p>Award winning spatial UI design.</p>
</div>
</div>
</div>
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
margin: 0;
background: #f8fafc;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.direction-grid {
display: flex;
gap: 30px;
flex-wrap: wrap;
justify-content: center;
}
.dir-card {
position: relative;
width: 300px;
height: 300px;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
cursor: pointer;
}
.card-image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.dir-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(59, 130, 246, 0.9);
backdrop-filter: blur(8px);
color: #ffffff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 20px;
/* Start hidden securely */
transform: translate(-100%, 0);
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.dir-overlay h3 {
margin: 0 0 10px 0;
font-size: 1.75rem;
letter-spacing: 1px;
}
.dir-overlay p {
margin: 0;
font-size: 1.1rem;
opacity: 0.9;
}
const cards = document.querySelectorAll(".dir-card");
function getDirection(e, item) {
const rect = item.getBoundingClientRect();
const w = rect.width;
const h = rect.height;
/* Calculate the exact mouse position relative to the center of the element safely */
const x = (e.clientX - rect.left - (w / 2)) * (w > h ? (h / w) : 1);
const y = (e.clientY - rect.top - (h / 2)) * (h > w ? (w / h) : 1);
/* Math.atan2 returns the angle, we map it cleanly to 4 quadrants (0: Top, 1: Right, 2: Bottom, 3: Left) */
const direction = Math.round((Math.atan2(y, x) * (180 / Math.PI) + 180) / 90 + 3) % 4;
return direction;
}
cards.forEach(function(card) {
const overlay = card.querySelector(".dir-overlay");
card.addEventListener("mouseenter", function(e) {
const dir = getDirection(e, card);
/* Pre-position the overlay instantly without transition */
overlay.style.transition = "none";
if (dir === 0) overlay.style.transform = "translate(0, -100%)";
else if (dir === 1) overlay.style.transform = "translate(100%, 0)";
else if (dir === 2) overlay.style.transform = "translate(0, 100%)";
else overlay.style.transform = "translate(-100%, 0)";
/* Force a reflow safely so the CSS engine registers the new position */
void overlay.offsetWidth;
/* Restore transition and slide into the center */
overlay.style.transition = "transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)";
overlay.style.transform = "translate(0, 0)";
});
card.addEventListener("mouseleave", function(e) {
const dir = getDirection(e, card);
/* Slide out smoothly toward the exit direction */
if (dir === 0) overlay.style.transform = "translate(0, -100%)";
else if (dir === 1) overlay.style.transform = "translate(100%, 0)";
else if (dir === 2) overlay.style.transform = "translate(0, 100%)";
else overlay.style.transform = "translate(-100%, 0)";
});
});