@use 'sprucecss/scss/spruce' as *;
.gallery-block {
padding-block: spacer-clamp('xl', 'xxl');
&__inner {
display: flex;
flex-direction: column;
gap: spacer('l');
}
&__grid {
@include clear-list;
columns: 3 16rem;
gap: spacer('m');
li {
break-inside: avoid;
margin: 0 0 spacer('m') !important;
}
}
&__item {
border-radius: radius('lg');
display: block;
overflow: hidden;
position: relative;
img {
inline-size: 100%;
@include transition($property: 'scale');
}
&:hover img {
@include motion-safe {
scale: 1.04;
}
}
&:focus-visible {
outline: 2px solid color('focus-ring');
outline-offset: 3px;
}
}
&__caption {
background-image: linear-gradient(to top, hsl(0deg 0% 0% / 65%), transparent);
color: hsl(0deg 0% 100%);
font-weight: 700;
inset-block-end: 0;
inset-inline: 0;
padding: spacer('l') spacer('s') spacer('s');
position: absolute;
}
&__lightbox {
background: transparent;
block-size: 100dvb;
border: 0;
box-shadow: none;
inline-size: 100vi;
max-block-size: none;
max-inline-size: none;
padding: spacer('s');
&[open] {
align-items: center;
display: grid;
gap: spacer('xs') spacer-clamp('xs', 'm');
grid-template-areas:
'. . close'
'prev figure next'
'. counter .';
grid-template-columns: auto minmax(0, 1fr) auto;
grid-template-rows: auto minmax(0, 1fr) auto;
}
&::backdrop {
background-color: hsl(0deg 0% 0% / 85%);
}
}
&__close {
grid-area: close;
}
&__nav {
&--prev {
grid-area: prev;
}
&--next {
grid-area: next;
}
}
&__figure {
align-self: stretch;
display: flex;
flex-direction: column;
grid-area: figure;
justify-content: center;
margin: 0;
min-block-size: 0;
img {
border-radius: radius('md');
margin-inline: auto;
max-block-size: 100%;
min-block-size: 0;
object-fit: contain;
}
figcaption {
color: hsl(0deg 0% 100%);
margin-block-start: spacer('xs');
text-align: center;
}
}
&__counter {
color: hsl(0deg 0% 100%);
font-size: config('font-size-sm', $typography);
grid-area: counter;
text-align: center;
}
}
[dir='rtl'] .gallery-block__nav svg {
scale: -1 1;
}
<section class="gallery-block" aria-labelledby="gallery-title" data-gallery>
<div class="container">
<div class="gallery-block__inner">
<header class="block-heading">
<p class="block-heading__eyebrow">Gallery</p>
<h2 class="block-heading__title" id="gallery-title">Behind the scenes</h2>
<p class="block-heading__text">A look at our studio, our workshops and our work.</p>
</header>
<ul class="gallery-block__grid" role="list">
<li>
<a class="gallery-block__item" href="https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=1600&q=70" data-gallery-item data-caption="Studio">
<img src="https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=700&q=70" alt="A bright studio with plants." loading="lazy">
<span class="gallery-block__caption">Studio</span>
</a>
</li>
<li>
<a class="gallery-block__item" href="https://images.unsplash.com/photo-1522071820081-009f0129c71c?auto=format&fit=crop&w=1600&q=70" data-gallery-item data-caption="Team">
<img src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?auto=format&fit=crop&w=700&q=70" alt="A team meeting around a table." loading="lazy">
<span class="gallery-block__caption">Team</span>
</a>
</li>
<li>
<a class="gallery-block__item" href="https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=1600&q=70" data-gallery-item data-caption="Code">
<img src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?auto=format&fit=crop&w=700&q=70" alt="A laptop with code." loading="lazy">
<span class="gallery-block__caption">Code</span>
</a>
</li>
<li>
<a class="gallery-block__item" href="https://images.unsplash.com/photo-1561070791-2526d30994b5?auto=format&fit=crop&w=1600&q=70" data-gallery-item data-caption="Color">
<img src="https://images.unsplash.com/photo-1561070791-2526d30994b5?auto=format&fit=crop&w=700&q=70" alt="Color swatches." loading="lazy">
<span class="gallery-block__caption">Color</span>
</a>
</li>
<li>
<a class="gallery-block__item" href="https://images.unsplash.com/photo-1531403009284-440f080d1e12?auto=format&fit=crop&w=1600&q=70" data-gallery-item data-caption="Workshop">
<img src="https://images.unsplash.com/photo-1531403009284-440f080d1e12?auto=format&fit=crop&w=700&q=70" alt="A workshop in progress." loading="lazy">
<span class="gallery-block__caption">Workshop</span>
</a>
</li>
<li>
<a class="gallery-block__item" href="https://images.unsplash.com/photo-1558655146-d09347e92766?auto=format&fit=crop&w=1600&q=70" data-gallery-item data-caption="Interface">
<img src="https://images.unsplash.com/photo-1558655146-d09347e92766?auto=format&fit=crop&w=700&q=70" alt="Interface sketches on a screen." loading="lazy">
<span class="gallery-block__caption">Interface</span>
</a>
</li>
</ul>
</div>
</div>
<dialog class="gallery-block__lightbox" aria-label="Image viewer" data-gallery-dialog>
<button class="btn btn--secondary btn--icon btn--sm gallery-block__close" type="button" aria-label="Close" data-gallery-close><svg class="btn__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
<button class="btn btn--secondary btn--icon gallery-block__nav gallery-block__nav--prev" type="button" aria-label="Previous image" data-gallery-prev><svg class="btn__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m15 18-6-6 6-6"/></svg></button>
<figure class="gallery-block__figure">
<img src="" alt="" data-gallery-image>
<figcaption data-gallery-caption hidden></figcaption>
</figure>
<button class="btn btn--secondary btn--icon gallery-block__nav gallery-block__nav--next" type="button" aria-label="Next image" data-gallery-next><svg class="btn__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m9 18 6-6-6-6"/></svg></button>
<span class="gallery-block__counter" aria-live="polite" data-gallery-counter></span>
</dialog>
</section>
(() => {
document.querySelectorAll('[data-gallery]').forEach((gallery) => {
const links = [...gallery.querySelectorAll('[data-gallery-item]')];
const dialog = gallery.querySelector('[data-gallery-dialog]');
const image = dialog.querySelector('[data-gallery-image]');
const caption = dialog.querySelector('[data-gallery-caption]');
const counter = dialog.querySelector('[data-gallery-counter]');
let current = 0;
const show = (index) => {
current = (index + links.length) % links.length;
const link = links[current];
const thumbnail = link.querySelector('img');
image.src = link.href;
image.alt = thumbnail.alt;
caption.textContent = link.dataset.caption || '';
caption.hidden = !link.dataset.caption;
counter.textContent = `${current + 1} of ${links.length}`;
};
links.forEach((link, index) => {
link.addEventListener('click', (event) => {
event.preventDefault();
show(index);
dialog.showModal();
});
});
dialog.querySelector('[data-gallery-prev]').addEventListener('click', () => show(current - 1));
dialog.querySelector('[data-gallery-next]').addEventListener('click', () => show(current + 1));
dialog.querySelector('[data-gallery-close]').addEventListener('click', () => dialog.close());
dialog.addEventListener('click', (event) => {
if (event.target === dialog) dialog.close();
});
dialog.addEventListener('keydown', (event) => {
const isRtl = getComputedStyle(gallery).direction === 'rtl';
if (event.key === 'ArrowRight') show(current + (isRtl ? -1 : 1));
if (event.key === 'ArrowLeft') show(current + (isRtl ? 1 : -1));
});
});
})();