add mobile swipe for product card images and fix dev asset caching
Product cards now use CSS scroll-snap on touch devices (mobile) for swiping between images, with dot indicators and a JS hook for active state. Desktop keeps the existing hover crossfade via @media (hover: hover). Dots use size differentiation (WCAG 2.2 AA compliant) with outline rings for contrast on any background. Also fixes: no-image placeholder (SVG icon instead of broken img), unnecessary wrapper div for single-image cards, and dev static asset caching (was immutable for all envs, now only prod). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -329,24 +329,87 @@
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Product Hover Image */
|
||||
/* Product Card Images — mobile: swipe, desktop: hover crossfade */
|
||||
.product-image-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-image-hover {
|
||||
/* Mobile default: horizontal scroll-snap for swiping between images */
|
||||
.product-image-scroll {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
height: 100%;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.product-image-scroll > img,
|
||||
.product-image-scroll > picture {
|
||||
flex: 0 0 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
/* Dot indicators for swipeable images (mobile only) */
|
||||
.product-image-dots {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
bottom: 0.5rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.product-card:hover .product-image-hover {
|
||||
opacity: 1;
|
||||
.product-image-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.42);
|
||||
border: none;
|
||||
padding: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.product-card:hover .product-image-primary:has(+ .product-image-hover) {
|
||||
opacity: 0;
|
||||
.product-image-dot-active {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
|
||||
/* Desktop: hover crossfade instead of scroll */
|
||||
@media (hover: hover) {
|
||||
.product-image-scroll {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.product-image-dots {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.product-image-hover {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.product-card:hover .product-image-hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.product-card:hover .product-image-primary:has(+ .product-image-hover) {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Social Links */
|
||||
|
||||
@@ -284,10 +284,24 @@ const Lightbox = {
|
||||
}
|
||||
}
|
||||
|
||||
const ProductImageScroll = {
|
||||
mounted() {
|
||||
const dots = this.el.parentElement.querySelector('.product-image-dots')
|
||||
if (!dots) return
|
||||
const spans = dots.querySelectorAll('.product-image-dot')
|
||||
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)
|
||||
})
|
||||
}, {passive: true})
|
||||
}
|
||||
}
|
||||
|
||||
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||
const liveSocket = new LiveSocket("/live", Socket, {
|
||||
params: {_csrf_token: csrfToken},
|
||||
hooks: {...colocatedHooks, ColorSync, Lightbox, CartPersist, CartDrawer},
|
||||
hooks: {...colocatedHooks, ColorSync, Lightbox, CartPersist, CartDrawer, ProductImageScroll},
|
||||
})
|
||||
|
||||
// Show progress bar on live navigation and form submits
|
||||
|
||||
Reference in New Issue
Block a user