src/components/image/ImageGallery.astro
---
/**
* ImageGallery — heading + responsive grid van afbeeldingen met hover-caption.
*
* Props:
* - eyebrow?: string
* - headline?: string
* - columns?: number
* - gap?: string
* - items?: { src: string; alt?: string; caption?: string }[]
*/
interface Item {
src: string;
alt?: string;
caption?: string;
}
interface Props {
eyebrow?: string;
headline?: string;
columns?: number;
gap?: string;
items?: Item[];
}
const {
eyebrow,
headline,
columns = 3,
gap = '1rem',
items = [
{ src: '/img/ext/photo-1460925895917-afdab827c52f-w800-646ccd.jpg', alt: 'Dashboard analyse', caption: 'Performance dashboard' },
{ src: '/img/ext/photo-1551434678-e076c223a692-w800-09e674.jpg', alt: 'Team overleg', caption: 'Strategiesessie' },
{ src: '/img/ext/photo-1542744173-8e7e53415bb0-w800-cea885.jpg', alt: 'Presentatie', caption: 'Klantpresentatie' },
{ src: '/img/ext/photo-1504868584819-f8e8b4b6d7e3-w800-777316.jpg', alt: 'Data visualisatie', caption: 'Campagne inzichten' },
{ src: '/img/ext/photo-1522071820081-009f0129c71c-w800-fff847.jpg', alt: 'Teamfoto', caption: 'Het team' },
{ src: '/img/ext/photo-1611532736597-de2d4265fba3-w800-4b9f1e.jpg', alt: 'Werkplek', caption: 'De studio' },
],
} = Astro.props;
---
<section class="bl-section ig">
<div class="bl-inner ig__inner">
{(eyebrow || headline) && (
<div class="ig__header">
{eyebrow && <p class="ig__eyebrow">{eyebrow}</p>}
{headline && <h2 class="ig__headline">{headline}</h2>}
</div>
)}
<div class="ig__grid" data-columns={String(columns)} style={`--ig-columns: ${columns}; --ig-gap: ${gap};`}>
{items.map((it, i) => (
<div class="ig__item ig__item--rounded" style={`--ig-delay: ${i * 80}ms;`}>
<div class="ig__img-wrap">
<img data-allow-img src={it.src} alt={it.alt || ''} class="ig__img" loading="lazy" decoding="async" />
{it.caption && (
<div class="ig__caption">
<span class="ig__caption-text">{it.caption}</span>
</div>
)}
</div>
</div>
))}
</div>
</div>
</section>
<style>
.ig{background:var(--color-bg)}
.ig__header{margin-bottom:2.5rem}
.ig__eyebrow{font-size:.75rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--color-accent);margin:0 0 .5rem}
.ig__headline{font-size:clamp(1.75rem,4vw,2.75rem);font-weight:700;color:var(--color-primary);margin:0;line-height:1.15}
.ig__grid{display:grid;grid-template-columns:repeat(var(--ig-columns, 3),1fr);gap:var(--ig-gap, 1rem)}
.ig__item{opacity:0;transform:translateY(24px);transition:opacity .5s ease var(--ig-delay, 0ms),transform .5s ease var(--ig-delay, 0ms)}
.ig__item--visible{opacity:1;transform:translateY(0)}
.ig__img-wrap{position:relative;aspect-ratio:4 / 3;overflow:hidden}
.ig__item--rounded .ig__img-wrap{border-radius:var(--radius)}
.ig__img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .35s ease,filter .35s ease}
.ig__img-wrap:hover .ig__img{transform:scale(1.03);filter:brightness(1.05)}
.ig__caption{position:absolute;bottom:0;left:0;right:0;padding:1.5rem 1rem .75rem;background:linear-gradient(to top,rgba(0,0,0,.65) 0%,transparent 100%);opacity:0;transition:opacity .3s ease}
.ig__img-wrap:hover .ig__caption{opacity:1}
.ig__caption-text{color:#fff;font-size:.8125rem;line-height:1.4}
@media (max-width:900px){.ig__grid{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.ig__grid{grid-template-columns:1fr}}
</style>
<script>
const items = document.querySelectorAll('.ig__item');
if (items.length) {
if ('IntersectionObserver' in window) {
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add('ig__item--visible');
io.unobserve(e.target);
}
});
}, { threshold: 0.15 });
items.forEach((el) => io.observe(el));
} else {
items.forEach((el) => el.classList.add('ig__item--visible'));
}
}
</script>