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
Red Light Therapy Mask
£200.00
£159.99
LOW STOCK
visibility
Visibly Firmer, Smoother Skin
blur_off
Supports Collagen, Clarity & Skin Renewal
more_time
Non-Invasive, Pain-Free & Effortless
Order by ... for guaranteed FREE GIFTS of 20,98£
local_shippingFree 3-5 Day Insured Shipping
workspace_premium30-Day Satisfaction Guarantee
lockSecured Checkout
Today’s Free Beauty Ritual
Collagen Eye Mask
A cooling collagen ritual that soothes tired eyes, reduces puffiness, and restores your natural glow. A gentle, effortles upgrade to tour self-care routine
Yours Free Today With Your Order
Real women. Real Glow. Real Results.
Product information
Supports Collagen Production For Firmer Looking Skin
Visibly Improves Skin Texture and Elasticity
Helps Reduce The Appearance Of Fine Lines Over Time
Calms Redness And Low Grade Inflammation
Enhances Overall Skin Clarity And Natural Glow
Red Light Therapy Mask
Adjustable straps for a comfortable fit
Charging Cable
User Guide
Nuvoria Ritual Card
92% noticed firmer, more lifted looking skin
89% experienced smoother skin texture
87% saw a visible reduction in fine lines
85% reported clearer, more balanced skin
90% felt their skin looked healthier overall
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
The mask uses clinically researched LED wavelengths to penetrate the skin and stimulate natural repair processes.
By energizing skin cells and supporting circulation, red light therapy helps skin renew itself from within without needles, heat, or downtime.
This makes it ideal for long-term skin improvement rather than quick fixes.
Use on clean, dry skin
Wear for 10-15 minutes per session
Use 3-5 times per week
Apply skincare products after use
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 Science Behind Visible Skin Renewal
Red light therapy works by supporting the skin’s natural repair process at a cellular level. Instead of forcing quick fixes, it gently stimulates what your skin already knows how to do: renew, strengthen, and restore itself over time.
Targets Skin Renewal at a Cellular Level
Red light wavelengths penetrate beneath the skin’s surface to stimulate cellular energy. This helps support collagen production, improve elasticity, and visibly firm the skin over time.
Clinically-Researched Light Wavelengths
The mask uses carefully selected LED wavelengths commonly used in professional treatments to address fine lines, texture, and dullness without heat, needles, or irritation.
Consistent Results Without Downtime
Because the treatment is non-invasive and gentle, it can be used regularly. Consistency is what leads to smoother texture, clearer tone, and healthier-looking skin.
Results You Can Feel
92%
Noticed Firmer, More Elastic-Looking Skin
89%
Saw A Visible Reduction In Fine Lines
87%
Experienced Calmer, More Balanced Skin
91%
Reported Improved Skin Texture & Glow
90%
Would Continue Using Red Light Therapy As Part Of Their Routine
Based on 2-week user reviews
Real Results for Real Sleep Problems
See how women like you solved their biggest sleep and skin problems.
★★★★★
Laura, 41
I didn’t expect light therapy to make such a visible difference. After a few weeks, my skin looked firmer and the fine lines around my eyes were noticeably softer.
★★★★★
Emma, 38
💎 Verified Buyer
My forehead lines look smoother and my skin feels stronger overall. It’s become a must-have in my evening routine.
★★★★★
Sophie, 45
I love that it’s non-invasive but still effective. My skin looks more youthful without irritation.
★★★★★
Nina, 36
After consistent use, my skin looks tighter and more refreshed. I get compliments all the time.
★★★★★
Claire, 42
This mask helped my skin look smoother and more elastic. It feels like professional treatment at home.
★★★★★
Mila, 29
My breakouts calmed down so much after a few weeks. Red light really helped reduce inflammation.
★★★★★
Jade, 26
I still get occasional spots, but they heal faster and leave less redness.
★★★★★
Olivia, 31
My skin feels clearer and more balanced. I no longer wake up with angry breakouts.
★★★★★
Hannah, 28
It didn’t work overnight, but after consistent use my skin became much calmer.
★★★★★
Sarah, 34
This helped control my acne without drying my skin out.
★★★★★
Isabelle, 28
My skin reacts easily, but this mask actually calmed it down. Redness visibly reduced.
★★★★★
Lena, 40
I struggle with sensitive skin and rosacea. This helped soothe flare-ups gently.
★★★★★
Emily, 33
My skin looks calmer and more even-toned, especially around my cheeks.
★★★★★
Anna, 37
No irritation at all. My skin feels stronger and less reactive.
★★★★★
Fleur, 32
My skin finally has that healthy glow again. It looks brighter and more even.
★★★★★
Veronica, 30
I noticed improved texture and radiance after just a few weeks.
★★★★★
Hailey, 28
My complexion looks more balanced and refreshed every morning.
★★★★★
Riham, 30
It helped even out my skin tone without harsh products.
★★★★★
Luna, 25
My skin looks healthier and more luminous overall.
Why Women Choose Nuvoria
Features
Nuvoria
Others
Multi-Wavelength LED Technology
Clinically Researched Light Wavelengths
Targets Multiple Skin Concerns
Comfortable, Flexible Mask Design
Designed for All Skin Types
Complete Your Routine
Glow Moisturizer Creme
£54.99
£65.00
★★★★★
(65.876)
Collagen Eye Mask
£27.99
£37.99
★★★★★
(31.837)
Premium Cotton Bath Towel
£29.99
£39.99
★★★★★
(43.273)
What Experts Say
Skin professionals on the benefits of red light therapy for long-term skin health.
Dr. Megan Burst
Dermatologist
Red light therapy is widely used in dermatology to support collagen production and cellular renewal. When used consistently, it can visibly improve skin firmness, texture, and overall skin quality without irritation or downtime.
Dr. Ava Sinclair
Sleep Medicine Specialist
What makes LED therapy so effective is its ability to work gradually with the skin. Instead of aggressive treatments, red light supports natural repair processes, leading to smoother texture and healthier-looking skin over time.
Dr. Lina Carter
Regenerative Medicine
Red light therapy is non-invasive and well tolerated by sensitive skin. By improving circulation and calming low-grade inflammation, it helps restore balance while supporting visible skin renewal.
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
How often should I use the Red Light Therapy Mask?
Nuvoria
For best results, use the mask 3–5 times per week for 10–15 minutes per session. Consistency is key for visible skin improvement.
Can I use it every day?
Nuvoria
Yes. The mask is gentle and non-invasive, making it safe for regular or even daily use if desired.
When should I use it in my routine?
Nuvoria
Use the mask on clean, dry skin before applying serums or moisturizers. Continue your routine as normal afterward.
Can I relax while using it?
Nuvoria
Absolutely. The hands-free design allows you to relax, read, or unwind during each session.
When will I start seeing results?
Nuvoria
Some users notice a healthier glow within 1–2 weeks. More visible changes in texture and firmness typically appear after 4–6 weeks of consistent use.
What skin concerns does it help with?
Nuvoria
The mask supports collagen production, improves texture, helps reduce fine lines, calms redness, and promotes overall skin balance.
Are the results permanent?
Nuvoria
Results are cumulative. Continued use helps maintain improvements, while stopping use may gradually reduce visible benefits.
Is this suitable for early signs of aging?
Nuvoria
Yes. Red light therapy is commonly used to support skin firmness and elasticity, especially for early to moderate signs of aging.
Is red light therapy safe for my skin?
Nuvoria
Yes. The mask uses UV-free, clinically researched LED wavelengths that are non-invasive and safe for cosmetic use.
Can I use it if I have sensitive skin?
Nuvoria
Yes. Red light therapy is well tolerated by sensitive and reactive skin types and does not cause irritation.
Does the mask get hot or cause discomfort?
Nuvoria
No. The LEDs do not generate heat. Most users find the experience calming and comfortable.
Can I use it around my eyes?
Nuvoria
The mask is designed for full-face use. Always keep your eyes closed during treatment and follow the usage instructions.
What makes this mask different from cheaper LED masks?
Nuvoria
This mask delivers consistent wavelength output, full facial coverage, and stable power key factors often missing in low-quality devices.
How many light wavelengths does it use?
Nuvoria
The mask uses multiple clinically studied wavelengths, including red and near-infrared light, to target different skin concerns.
Is this professional-grade technology?
Nuvoria
The technology is inspired by in-clinic LED treatments, adapted for safe and effective at-home use.
Does it work with my skincare products?
NutraBoost
Yes. Use the mask first, then apply your skincare products to enhance absorption.
Glow starts at Nuvoria
Join 200.000+ in the Nuvoria Glow Movement, become one of us
Members get exclusive discounts, early access to new products and more.
Choosing a selection results in a full page refresh.