replace PDP image gallery with scroll-snap carousel

Mobile: swipeable carousel with dot indicators, no lightbox trigger.
Desktop: carousel with thumbnail grid, prev/next arrows, click to
open existing lightbox. Keeps all lightbox appearance and behaviour.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-10 15:33:41 +00:00
parent 1a69736734
commit 8445e9e8b1
4 changed files with 335 additions and 43 deletions

View File

@@ -435,6 +435,112 @@
transition: transform 0.2s ease;
}
/* PDP Gallery — mobile: swipe + dots, desktop: carousel + thumbs */
.pdp-gallery-carousel,
.pdp-gallery-single {
aspect-ratio: 1 / 1;
background-color: #e5e7eb;
overflow: hidden;
}
.pdp-gallery-single {
position: relative;
}
.pdp-gallery-carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
}
}
.pdp-carousel-img {
flex: 0 0 100%;
width: 100%;
height: 100%;
object-fit: cover;
scroll-snap-align: start;
}
/* Desktop-only: lightbox click target + nav arrows (hidden on mobile) */
.pdp-lightbox-click,
.pdp-nav {
display: none;
}
.pdp-gallery-thumbs {
display: none;
}
@media (hover: hover) {
.pdp-lightbox-click {
display: block;
position: absolute;
inset: 0;
z-index: 1;
cursor: zoom-in;
}
.pdp-nav {
display: flex;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 9999px;
background: rgba(255, 255, 255, 0.9);
color: #374151;
border: none;
cursor: pointer;
opacity: 0;
transition: opacity 0.15s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
& svg {
width: 1.25rem;
height: 1.25rem;
}
}
.pdp-nav-prev {
left: 0.75rem;
}
.pdp-nav-next {
right: 0.75rem;
}
/* Show arrows on gallery hover */
.pdp-gallery:hover .pdp-nav {
opacity: 1;
}
.pdp-nav:hover {
background: rgba(255, 255, 255, 1);
}
.pdp-gallery-single {
cursor: zoom-in;
}
.pdp-gallery-thumbs {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin-top: 1rem;
}
}
/* Lightbox */
.lightbox {
position: fixed;
@@ -572,7 +678,3 @@
font-family: var(--t-font-body);
}
/* PDP Main Image zoom cursor */
.pdp-main-image-container {
cursor: zoom-in;
}

View File

@@ -286,15 +286,41 @@ const Lightbox = {
const ProductImageScroll = {
mounted() {
const dots = this.el.parentElement.querySelector('.product-image-dots')
if (!dots) return
const spans = dots.querySelectorAll('.product-image-dot')
const container = this.el.parentElement
const dots = container.querySelector('.product-image-dots')
const spans = dots ? dots.querySelectorAll('.product-image-dot') : []
const lightbox = container.parentElement.querySelector('dialog')
const thumbs = container.parentElement.querySelector('.pdp-gallery-thumbs')
const thumbButtons = thumbs ? thumbs.querySelectorAll('.pdp-thumbnail') : []
const imageCount = this.el.children.length
this.el.addEventListener('scroll', () => {
const index = Math.round(this.el.scrollLeft / this.el.offsetWidth)
spans.forEach((dot, i) => {
dot.classList.toggle('product-image-dot-active', i === index)
})
thumbButtons.forEach((btn, i) => {
btn.classList.toggle('pdp-thumbnail-active', i === index)
})
if (lightbox) lightbox.dataset.currentIndex = index.toString()
}, {passive: true})
this.el.addEventListener('pdp:scroll-to', (e) => {
const index = e.detail.index
this.el.scrollTo({left: index * this.el.offsetWidth, behavior: 'smooth'})
})
this.el.addEventListener('pdp:scroll-prev', () => {
const current = Math.round(this.el.scrollLeft / this.el.offsetWidth)
const target = (current - 1 + imageCount) % imageCount
this.el.scrollTo({left: target * this.el.offsetWidth, behavior: 'smooth'})
})
this.el.addEventListener('pdp:scroll-next', () => {
const current = Math.round(this.el.scrollLeft / this.el.offsetWidth)
const target = (current + 1) % imageCount
this.el.scrollTo({left: target * this.el.offsetWidth, behavior: 'smooth'})
})
}
}