if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
if (!customElements.get('media-gallery')) {
customElements.define(
'media-gallery',
class MediaGallery extends HTMLElement {
constructor() {
super();
this.elements = {
liveRegion: this.querySelector('[id^="GalleryStatus"]'),
viewer: this.querySelector('[id^="GalleryViewer"]'),
thumbnails: this.querySelector('[id^="GalleryThumbnails"]'),
};
this.mql = window.matchMedia('(min-width: 750px)');
if (!this.elements.thumbnails) return;
this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));
this.elements.thumbnails.querySelectorAll('[data-target]').forEach((mediaToSwitch) => {
mediaToSwitch
.querySelector('button')
.addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));
});
if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();
}
connectedCallback() {
// Listen for variant changes to update gallery
this.variantChangeHandler = this.handleVariantChange.bind(this);
document.addEventListener('variant:change', this.variantChangeHandler);
}
disconnectedCallback() {
// Clean up event listener
if (this.variantChangeHandler) {
document.removeEventListener('variant:change', this.variantChangeHandler);
}
}
handleVariantChange(event) {
const variant = event.detail?.variant;
if (!variant || !variant.featured_media) return;
const featuredMediaId = variant.featured_media.id;
if (!featuredMediaId) return;
// Get section ID from gallery viewer
const galleryViewer = this.elements.viewer;
if (!galleryViewer) return;
const sectionIdMatch = galleryViewer.id.match(/GalleryViewer-(.+)/);
if (!sectionIdMatch) return;
const sectionId = sectionIdMatch[1];
const mediaId = `${sectionId}-${featuredMediaId}`;
// Find the media element
const mediaElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (!mediaElement) {
console.log('Variant media not found in gallery:', mediaId);
return;
}
// Use setActiveMedia with prepend: true to move variant image to first position
this.setActiveMedia(mediaId, true);
// Additional scroll to ensure image is visible after DOM update
requestAnimationFrame(() => {
setTimeout(() => {
const activeElement = galleryViewer.querySelector(`[data-media-id="${mediaId}"]`);
if (activeElement) {
// Scroll gallery container
const sliderList = galleryViewer.querySelector('.slider, .product__media-list, ul');
if (sliderList && sliderList.scrollTo) {
sliderList.scrollTo({
left: activeElement.offsetLeft,
behavior: 'smooth'
});
}
// Scroll page to show the gallery with image in view
const elementRect = activeElement.getBoundingClientRect();
const offset = 120;
const scrollPosition = elementRect.top + window.scrollY - (window.innerHeight / 2) + (elementRect.height / 2);
window.scrollTo({
top: Math.max(0, scrollPosition - offset),
behavior: 'smooth'
});
}
}, 300);
});
}
onSlideChanged(event) {
const thumbnail = this.elements.thumbnails.querySelector(
`[data-target="${event.detail.currentElement.dataset.mediaId}"]`
);
this.setActiveThumbnail(thumbnail);
}
setActiveMedia(mediaId, prepend) {
const activeMedia =
this.elements.viewer.querySelector(`[data-media-id="${mediaId}"]`) ||
this.elements.viewer.querySelector('[data-media-id]');
if (!activeMedia) {
return;
}
this.elements.viewer.querySelectorAll('[data-media-id]').forEach((element) => {
element.classList.remove('is-active');
});
activeMedia?.classList?.add('is-active');
if (prepend) {
activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);
if (this.elements.thumbnails) {
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
activeThumbnail.parentElement.firstChild !== activeThumbnail && activeThumbnail.parentElement.prepend(activeThumbnail);
}
if (this.elements.viewer.slider) this.elements.viewer.resetPages();
}
this.preventStickyHeader();
window.setTimeout(() => {
if (!this.mql.matches || this.elements.thumbnails) {
activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });
}
const activeMediaRect = activeMedia.getBoundingClientRect();
// Don't scroll if the image is already in view
if (activeMediaRect.top > -0.5) return;
const top = activeMediaRect.top + window.scrollY;
window.scrollTo({ top: top, behavior: 'smooth' });
});
this.playActiveMedia(activeMedia);
if (!this.elements.thumbnails) return;
const activeThumbnail = this.elements.thumbnails.querySelector(`[data-target="${mediaId}"]`);
this.setActiveThumbnail(activeThumbnail);
this.announceLiveRegion(activeMedia, activeThumbnail.dataset.mediaPosition);
}
setActiveThumbnail(thumbnail) {
if (!this.elements.thumbnails || !thumbnail) return;
this.elements.thumbnails
.querySelectorAll('button')
.forEach((element) => element.removeAttribute('aria-current'));
thumbnail.querySelector('button').setAttribute('aria-current', true);
if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;
this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });
}
announceLiveRegion(activeItem, position) {
const image = activeItem.querySelector('.product__modal-opener--image img');
if (!image) return;
image.onload = () => {
this.elements.liveRegion.setAttribute('aria-hidden', false);
this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('[index]', position);
setTimeout(() => {
this.elements.liveRegion.setAttribute('aria-hidden', true);
}, 2000);
};
image.src = image.src;
}
playActiveMedia(activeItem) {
window.pauseAllMedia();
const deferredMedia = activeItem.querySelector('.deferred-media');
if (deferredMedia) deferredMedia.loadContent(false);
}
preventStickyHeader() {
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if (!this.stickyHeader) return;
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
removeListSemantic() {
if (!this.elements.viewer.slider) return;
this.elements.viewer.slider.setAttribute('role', 'presentation');
this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));
}
}
);
}
100,000+ Happy Customers
Mulberry Sleepmask
Β£56.99
Β£34.99
LOW STOCK
bedtime
Deeper sleep, every night
water_drop
Softer, calmer skin by morning
spa
From stress to deep rest
Order by ... for guaranteed FREE GIFTS of 20,98Β£
Color|Soft Rose
local_shippingFree 3-5 Day Insured Shipping
workspace_premium30-Day Satisfaction Guarantee
lockSecured Checkout
Todayβs Free Beauty Ritual
Collagen Eye Mask
A cooling overnight ritual that supports tired under-eyes after broken sleep. Helps reduce morning puffiness and supports a rested, refreshed look by morning.
Yours Free Today With Your Order
Real women. Real sleep. Real results.
Product information
Encourages deeper, uninterrupted sleep
Reduces friction to support calmer, smoother skin
Helps prevent morning puffiness & redness
Creates a soft, light-free space for full relaxation
Turns your night into a healing, calming ritual
92% noticed calmer, more balanced skin
88% experienced deeper, more uninterrupted sleep
90% saw reduced morning puffiness
85% felt more rested within the first 14 days
9 out of 10 women would recommend it to a friend
Based on customer feedback & post-purchase surveys.
Featured in luxury self-care collections
Loved by over 100,000 women worldwide
Recognised for comfort & skin-friendly design
Chosen by wellness & beauty experts
Winner of European Beauty Award 2025
Gently place the mask over your eyes before sleep
Adjust the strap for a comfortable, pressure-free fit
Relax and let your skin & mind restore overnight
100% Mulberry Silk (Grade 6A)
Naturally hypoallergenic & breathable
Hand wash cold, lay flat to dry
Designed for long lasting softness
Wordlwide tracked shipping
2-3 working days delivery
100% insured shipping
30-day hassle-free returns
No questions asked
Full refund on unused items
The Secret to Deeper Sleep and Glowy Skin
The easiest upgrade to your nightly routine. Feel the difference from night one.
Evening Unwind
Slip into instant calm as the silk contours soften the transition from day to night, helping you unwind effortlessly.
Deep Sleep
The contoured design blocks every trace of light whether you sleep on your back or side allowing your brain to shift naturally into deeper, restorative sleep.
Morning Glow
Wake up with calmer, smoother-looking skin and reduced puffiness. No creases, no irritation just a rested, natural glow.
Results You Can Feel
95%
Fell asleep faster
90%
Deeper, more restful sleep
95%
Smoother-looking skin in the morning
84%
Reported less morning puffiness
93%
Said it stayed perfectly in place. All night long
Based on 2-week user reviews
Real Results for Real Sleep Problems
See how women like you solved their biggest sleep and skin problems.
β β β β β
Ashley, 30
I used to wake up multiple times a night. Since using this mask, my sleep feels deeper and more continuous.
β β β β β
Katherin, 38
π Verified Buyer
I didnβt expect such a difference, but my nights feel calmer. Even on stressful days.
β β β β β
Cecile, 26
This is the first sleep product that actually improved my sleep quality.
β β β β β
Amey, 23
I fall asleep faster and donβt wake up as easily anymore. It feels like my body actually rests now.
β β β β β
Brianna, 32
My sleep tracker even shows longer deep sleep. I wake up less exhausted.
β β β β β
Ashley, 30
I wake up from the smallest amount of light. This blocks everything.
β β β β β
Karen, 27
Even morning light doesnβt disturb me anymore. Huge difference.
β β β β β
Kirsten, 22
Iβm a light sleeper and this really helps me stay asleep.
β β β β β
Kimmy, 33
The fit seals perfectly around my eyes. No light leaks.
β β β β β
Chloe, 29
Finally something that works even in a bright room.
β β β β β
Hailey, 31
My under-eyes look less puffy in the morning. My skin feels calm.
β β β β β
Amirah, 23
No more pressure marks or irritation when I wake up.
β β β β β
Jennifer, 28
My skin feels softer compared to other masks Iβve used.
β β β β β
Lisann, 30
I wake up looking more refreshed, not creased.
β β β β β
Tia, 26
I move a lot in my sleep, but this stays in place all night.
β β β β β
Veronica, 30
I feel more relaxed wearing it, almost like a sleep cue.
β β β β β
Hailey, 28
It helps me mentally switch off at night.
β β β β β
Riham, 30
My nights feel less restless and more consistent.
β β β β β
Luna, 25
Itβs part of my nightly routine now. I miss it when I donβt use it.
Why Women Choose Nuvoria
Features
Nuvoria
Others
100% Mulberry Silk
Fully Light Blocking
No Creases & Pressure lines
Stays in Place All Night
Cooling Breathable Fabric
Complete Your Routine
Glow Moisturizer Creme
Β£54.99
Β£65.00
β β β β β
(65.876)
Night Repair Creme
Β£35.99
Β£65.00
β β β β β
(31.837)
Premium Cotton Bath Towel
Β£29.99
Β£39.99
β β β β β
(43.273)
What Experts Say
Trusted guidance from female doctors who understand skin, sleep, and calm routines.
Dr. Megan Burst
Dermatologist
"Mulberry silk is exceptionally gentle on the skin. It reduces nightly friction, prevents sleep creases, and supports a healthier moisture barrier while you rest."
Dr. Ava Sinclair
Sleep Medicine Specialist
"Even minimal light can disrupt melatonin. A contoured silk mask that fully blocks light helps your brain shift into deeper, more restorative sleep much faster."
Dr. Lina Carter
Regenerative Medicine
"Nighttime rituals play a major role in recovery. A comfortable silk mask reduces overstimulation, helping the nervous system settle and allowing the body to fully recharge."
You relax weβll take care of the rest.
Because your self-care routine should come with total confidence.
favorite
Love It or Return It
30-day, no-questions-asked guarantee.
support_agent
Personal Customer Care
Weβre here whenever you need us.
local_shipping
Fast & Tracked Shipping
Dispatched within 3-5 Days
Product Questions & Answers
Does it stay in place overnight?
Nuvoria
Yes the adjustable comfort band keeps the mask secure without feeling tight.
Can I wear it every night?
Nuvoria
Absolutely. Itβs designed for long-term, nightly use.
Does it work for side sleepers?
Nuvoria
Yes. The contoured design prevents squishing, shifting, or pressure on the eyes.
How do I use the silk mask?
Nuvoria
Place it over your eyes before bed and adjust the strap to your perfect fit no pressure, no slipping.