
This is a responsive gallery that transforms a series of image cards into an interactive, accordion-style layout where images smoothly expand to full width on hover.
Features:
- Smooth flex-basis transitions using cubic-bezier timing functions
- Responsive design that adapts to different container widths
- Opacity-based visual hierarchy for focused attention
- Event delegation for optimized JavaScript performance
- Staggered margin positioning for visual depth
- Mobile-friendly touch interaction support
Use Cases
- Portfolio Galleries: A clean way for developers and designers to show project thumbnails. Hovering gives a larger, more detailed preview.
- Product Feature Tours: An e-commerce site can display multiple product shots or highlight key features. A user can quickly scan them and hover to expand the one they’re interested in.
- Team “About Us” Pages: You can use it to display headshots of team members. Hovering over a photo could expand the card to show their name, title, and social links.
How to use it:
1. Create a main container element with individual card elements for each image. Each card requires an inner wrapper and picture element to maintain proper aspect ratios and positioning:
<div class="container">
<div class="card">
<div class="card__inner">
<picture>
img src="your-image-url.jpg" alt="Description">
</picture>
</div>
</div>
<!-- Repeat card structure for additional images -->
</div>2. Apply the necessary CSS styles to the container & cards. The container uses flexbox properties to distribute space equally among cards while enabling dynamic resizing during hover interactions:
.container {
display: flex;
flex: 1;
max-width: 1440px;
padding: 0 3rem;
margin: 0 auto;
overflow: auto;
align-items: flex-start;
justify-content: center;
}
.card {
flex: 1 1 1%;
position: relative;
opacity: 0.2;
transition: flex 600ms cubic-bezier(0.25, 1, 0.5, 1), opacity 250ms ease;
}
.card:hover {
flex-basis: 30%;
}
.card__inner {
margin: 0.25rem;
background: #fff;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.card picture {
width: 100%;
height: 0;
padding-bottom: 600px;
overflow: hidden;
position: relative;
}
.card picture img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.card.is-active, .card:hover {
opacity: 1;
}
.card:nth-child(2), .card:nth-child(5), .card:nth-child(9) {
margin-top: 0;
}
.card:nth-child(2), .card:nth-child(4), .card:nth-child(6), .card:nth-child(8), .card:nth-child(10) {
margin-top: 2.5%;
}
.card:nth-child(3), .card:nth-child(7) {
margin-top: 5%;
}3. The necessary JavaScript. When the mouse enters a card, the script identifies it and adds an .is-active class. At the same time, it removes that class from all other cards. The CSS rule .card { opacity: 0.2; } combined with .card.is-active, .card:hover { opacity: 1; } creates the “spotlight” where the active card is bright and the others are dimmed. When the mouse leaves, the script adds .is-active back to all cards.
const cards = document.querySelectorAll(".card");
// Add "is-active" class to all cards initially
cards.forEach(card => card.classList.add("is-active"));
// Use event delegation for better performance
document.addEventListener("mouseenter", event => {
const card = event.target.closest(".card");
if (card) {
cards.forEach(c => c.classList.remove("is-active"));
card.classList.add("is-active");
}
}, true);
document.addEventListener("mouseleave", event => {
const card = event.target.closest(".card");
if (card) {
cards.forEach(c => c.classList.add("is-active"));
}
}, true);






