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
Premium Cotton Bath Towel
£39.99
£29.99
LOW STOCK
water_drop
Ultra-soft on skin, even when wet
snowing_heavy
Highly absorbent without heaviness
spa
Designed for everyday comfort & calm
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 skin. Real results.
Product information
Ultra soft cotton feel
High absorbency without heaviness
Gentle on sensitive skin
Designed for everyday comfort
Nuvoria Premium towel 70*140cm
Refined Nuvoria Packaging
Nuvoria Ritual Card
92% noticed lasting softness after washing.
91% experienced faster, more comfortable drying
89% felt it was gentler on skin
92% said it upgraded their daily routine
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 pat skin dry after shower or bath
Allow towel to fully dry between uses
Wash before first use for optimal softness
100% premium cotton
Breathable, skin-friendly weave
Machine wash warm
Tumble dry low
Designed to stay soft wash after wash
Wordlwide tracked shipping
2-3 working days delivery
100% insured shipping
30-day hassle-free returns
No questions asked
Full refund on unused items
Designed to Feel Better, Every Single Day
This towel isn’t just soft it’s thoughtfully crafted to turn an everyday moment into a calming ritual. From the cotton we choose to the way it absorbs water, every detail is designed for comfort, skin health, and longevity.
Pure Cotton Comfort
Made from carefully selected premium cotton fibers that feel naturally soft against the skin.No rough texture, no stiffness just gentle comfort, even on sensitive skin.
Absorbs Fast, Feels Light
The balanced weave absorbs moisture quickly without becoming heavy or soggy.Your skin dries faster, while the towel stays light, breathable, and comfortable to use.
Soft Today. Soft Tomorrow.
Unlike ordinary towels that lose softness after washing, this towel is designed to stay comfortable wash after wash.Built for everyday routines without sacrificing luxury.
Results You Can Feel
94%
Felt the towel stay soft after multiple washes
91%
Noticed better moisture absorption
89%
Sait it felt gentler than their previous towels
87%
Experienced faster drying without roughness
92%
Sait it elevated their daily routine
Based on 2-week user reviews
Real Results for Real Daily Comfort
See how women upgraded their everyday routine with a towel that feels better, dries faster, and stays soft wash after wash.
★★★★★
Ashley, 30
My old towels felt harsh on my skin. This one feels soft and gentle every time I use it, even after washing.
★★★★★
Katherin, 38
I have sensitive skin and most towels irritate me. This one feels smooth and comfortable from the first use.
★★★★★
Cecile, 26
No scratchy feeling at all. It feels more like hotel-quality than a regular towel.
★★★★★
Amey, 23
I noticed the difference immediately. Drying off feels calming instead of uncomfortable.
★★★★★
Brianna, 32
Finally a towel that actually feels good on the skin. I won’t go back to cheap ones.
★★★★★
Ashley, 30
It absorbs water so quickly without feeling heavy. I don’t need to rub my skin anymore.
★★★★★
Karen, 27
It absorbs water so quickly without feeling heavy. I don’t need to rub my skin anymore.
★★★★★
Kirsten, 22
One towel is enough now. My old ones needed two just to feel dry.
★★★★★
Kimmy, 33
It dries my skin fast but still feels light. That surprised me.
★★★★★
Chloe, 29
I didn’t expect a towel to make this much difference, but it really does.
★★★★★
Hailey, 31
I’ve washed it multiple times and it’s still soft. No stiffness at all.
★★★★★
Amirah, 23
Most towels feel rough after a few washes. This one stayed exactly the same.”
★★★★★
Jennifer, 28
t feels durable and well-made. You can tell the quality is higher.
★★★★★
Lisann, 30
I don’t need fabric softener anymore. That alone is a win.
★★★★★
Tia, 26
It instantly gave my bathroom a more premium feel.
★★★★★
Veronica, 30
It instantly gave my bathroom a more premium feel.
★★★★★
Hailey, 28
Using it feels like a small self-care moment after a shower.
★★★★★
Riham, 30
Simple product, but the experience feels elevated.
★★★★★
Luna, 25
t made my daily routine feel more intentional and calm.
Why Women Choose Nuvoria
Features
Nuvoria
Others
100% Premium Cotton
Soft Even After Washing
Gentle on Sensitive Skin
Fast Absorbing
Balanced Plush Weight
Complete Your Routine
Glow Moisturizer Creme
£54.99
£65.00
★★★★★
(65.876)
Collagen Eye Mask
£27.99
£37.99
★★★★★
(31.837)
Hydrating Glow Serum
£55.00
£65.00
★★★★★
(43.273)
What Experts Say
Trusted guidance from female doctors who understand skin, sleep, and calm routines.
Dr. Megan Burst
Dermatologist
“Soft, breathable cotton towels are important for maintaining healthy skin, especially for those with sensitivity or dryness.”
Dr. Ava Sinclair
Sleep Medicine Specialist
“Gentle drying reduces friction on the skin barrier. A quality towel is an underrated part of daily skincare.”
Dr. Lina Carter
Regenerative Medicine
“Choosing the right towel can significantly improve post-shower skin comfort.”
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 big is the towel?
Nuvoria
The towel is generously sized to comfortably wrap around the body, making it ideal for everyday use after showering or bathing.
Is it suitable for daily use?
Nuvoria
Yes. It’s designed for everyday use and maintains its softness and absorbency even with frequent washing.
Does it dry quickly after use?
Nuvoria
The breathable cotton fibers allow the towel to dry faster than standard towels, helping prevent damp smells.
Is it heavy or lightweight?
Nuvoria
It offers a balanced feel plush and luxurious without feeling heavy or bulky on the skin.
What material is the towel made from?
Nuvoria
The towel is made from premium-quality cotton selected for softness, durability, and high absorbency.
Is it suitable for sensitive skin?
Nuvoria
Yes. The cotton is gentle and skin-friendly, making it suitable for sensitive or easily irritated skin.
How should I wash the towel?
Nuvoria
Machine wash at 30–40°C with similar colors. Avoid fabric softener to maintain absorbency. Tumble dry on low or air dry.
Will it stay soft after washing?
Nuvoria
Yes. When cared for properly, the towel retains its softness wash after wash.
What results can I expect compared to regular towels?
Nuvoria
Users report softer skin, faster drying, and a more comfortable post-shower experience compared to standard towels.
Does it absorb water better than normal towels?
Nuvoria
Yes. The dense cotton weave is designed to absorb moisture efficiently without feeling heavy.
Will it help reduce skin irritation?
Nuvoria
Many customers notice less friction and irritation, especially compared to rough or low-quality towels.
How long does it last?
Nuvoria
The towel is made for long-term use and maintains quality over time with proper care.
Is the cotton responsibly sourced?
Nuvoria
The cotton is selected with quality and durability in mind, helping reduce the need for frequent replacements.
Does it last longer than standard towels?
Nuvoria
Yes. Its durable construction means fewer replacements over time, supporting a more mindful consumption approach.
Is it suitable for eco-conscious customers?
Nuvoria
Yes. The towel is designed for long-term use, making it a more sustainable choice than low-quality alternatives.
Is excessive packaging used?
Nuvoria
No. Packaging is kept minimal while still protecting the product during shipping.
Is this towel suitable as a gift?
Nuvoria
Absolutely. Its premium look and feel make it a popular gift choice for birthdays, housewarmings, or self-care upgrades.
Does it feel like hotel-quality?
Nuvoria
Yes. Many customers compare it to towels found in luxury hotels or spas.
What if I’m not satisfied?
Nuvoria
We offer a 30-day satisfaction guarantee. If you’re not happy, you can return it easily.
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.