News
Details Matter - Mea Ola
Details Matter
Showing the unmatched craftsmanship of the OluKai - Mea Ola leather sandal.
Shop The Story
{"id":5156384866439,
"handle":"mea-ola-mens-leather-beach-sandals-dark-java",
"title":"Mea Ola - Dark Java",
"available":true,
"tags":["arch:high","archsupport","beach sandals","beachbum","best sellers","best selling","best:special_occasion","best:travel","brotherly love","cat:sandals","collection:social-strategist","color:brown","color:Dark Java","dk java","eagles sandals","EXP_REC_MEN","fit:wide","fliflops","flip flops","flip fls","flip ops","flipflop men","flipflps","flipfops","football sandals","free shipping","gender:men","highest rated flip flop","interest:driving","jason kelce","jason keller","jason kelse","kelce","kielce","labor day","leather","likes:beach","likes:friends","likes:urban-exploring","material:leather","mea","mea oa","mea ola","meaola","mei ola","men","men s flipslops","mens","mens flipflops","Mens sansandals","menβs","meola sandal","mrs ola","nature-inspired","prime","RGroup_10138","size:small","size:true","SP-19-COLOR","SP19-Style","Special Occasion","split_color","style:10138","Travel","trendsetter","vacation:city-downtown","vacation:tropical","wide sizes","wide width","YGroup_10138"],
"type":"Menβs Leather Beach Sandals",
"options_with_values": [{"name":"Color","position":1,"values":["Dark Java"]},{"name":"Size","position":2,"values":["7","8","9","10","11","12","13","14","15"]}],
"price":18000,
"price_min":18000,
"price_max":18000,
"compare_at_price":null,"featured_image": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_001_M_MeaOla_DkjDkj_1_b644587c-c3c0-4924-89fe-a23326261020.png?v=1762441217",
"featured_image_aspect_ratio": 1.0,
"featured_image_width": 2000,
"featured_image_height": 2000,
"featured_image_id": 34587122630791,
"featured_image_alt": "Dark Java",
"hover_image": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_101_54a66018-7ade-497d-8722-d309eb7efa9b.jpg?v=1746706357",
"hover_image_alt": "Dark Java",
"images": [{
"id": 34587122630791,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_001_M_MeaOla_DkjDkj_1_b644587c-c3c0-4924-89fe-a23326261020.png?v=1762441217",
"alt": "Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34587122663559,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_101_54a66018-7ade-497d-8722-d309eb7efa9b.jpg?v=1746706357",
"alt": "Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34587122729095,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/MeaOla-10138-4848DarkJava_1.jpg?v=1746706357",
"alt": "Mea Ola - Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34587122827399,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_302_7c6d2ac7-0fcd-430e-b3ed-925a5fef6190.jpg?v=1746706357",
"alt": "Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34587122892935,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_102_1d7fafc8-8b34-4934-8f6b-14c6ed0a977e.jpg?v=1746706357",
"alt": "Dark Java",
"width": 1080,
"height": 1080
},{
"id": 34587122958471,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/MeaOla-10138-4848DarkJava_2.jpg?v=1746706357",
"alt": "Mea Ola - Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34587123056775,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_103_e0f1bd9e-0183-4fab-8373-82c6e55afb72.jpg?v=1746706357",
"alt": "Dark Java",
"width": 1080,
"height": 1080
},{
"id": 34587123122311,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_102_72814dd7-d375-44d0-9602-ba62b2757c79.jpg?v=1746706357",
"alt": "Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34587123220615,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_201_0618e241-6d77-4558-bb23-e6c786fb0303.jpg?v=1746706357",
"alt": "Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34587123286151,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_004_M_MeaOla_DkjDkj.png?v=1746706357",
"alt": "Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34587123384455,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_4848_005_M_Meaola_DkjDkj.png?v=1746706357",
"alt": "Dark Java",
"width": 2000,
"height": 2000
}],
"plp_image_order_positions": "",
"sku": "10138-4848-7"
}
{ $dispatch('product-item:images-changed', { product: product, source: 'swatch-container' }) })"
@pointercancel="intendedSwatchIndex = null; intendedSwatchEl = null"
x-data="{
wrapper: $el,
selectedSibling:product.siblings[0],
intendedSwatchIndex: null,
intendedSwatchEl: null,
swatchGap: 8,
scrollable: product.siblings.filter(s => !!s.available).length > 9.5,
canPrev: false,
canNext: true,
_edgeReady: false,
imageLimit: 0,
limitImages(nextProduct) {
const images = Array.isArray(nextProduct && nextProduct.images) ? nextProduct.images : [];
const limitedImages = this.imageLimit > 0 ? images.slice(0, this.imageLimit) : images;
const hoverImage = limitedImages[1] || null;
return Object.assign({}, nextProduct, {
images: limitedImages,
hover_image: hoverImage ? hoverImage.src : null,
hover_image_alt: hoverImage ? hoverImage.alt : null
});
},
selectProduct(nextProduct) {
const productRoot = $el.closest('.product-item');
const productData = productRoot && productRoot._x_dataStack && productRoot._x_dataStack[0];
if (productData && typeof productData.setProduct === 'function') {
productData.setProduct(nextProduct);
} else {
$data.product = this.limitImages(nextProduct);
}
},
getSwatchImageSrc(sibling) {
return sibling.images[0].src;
},
buildSwatchSrcset(src) {
if (!src) return '';
const widths = ['48', '96', '192'];
const separator = src.includes('?') ? '&' : '?';
return widths.map(w => `${src}${separator}width=${w} ${w}w`).join(', ');
},
isSwatchPriority(index) {
return window.innerWidth >= 1024 ? index < 3 : index < 2;
},
swatchBg(sibling) {
// swatch_bg precomputed at hydration. Nullish (not ||): null = no colors set
// (fall back to theme default), empty string = invalid combo, no fallback.
return sibling.swatch_bg ?? '#000000';
},
centerSwatch(el) {
if ($data.wrapper.scrollWidth <= $data.wrapper.clientWidth) return false
const wrapperRect = $data.wrapper.getBoundingClientRect()
const elRect = el.getBoundingClientRect()
const centerPoint = wrapperRect.x + (wrapperRect.width / 2)
const behavior = window.innerWidth < 1024 ? 'auto' : 'smooth'
$data.wrapper.scrollTo({
left: $data.wrapper.scrollLeft + (elRect.x - centerPoint + (elRect.width / 2)),
behavior
})
},
selectSwatchFromTap(siblingIndex, el) {
const idx = (this.intendedSwatchIndex !== null && this.intendedSwatchIndex !== undefined)
? this.intendedSwatchIndex
: siblingIndex
const targetEl = this.intendedSwatchEl || el
this.intendedSwatchIndex = null
this.intendedSwatchEl = null
const next = product.siblings[idx]
if (next) product = next
this.centerSwatch(targetEl)
},
scroll(direction = 'forward') {
const swatch = $data.wrapper.querySelector('.product-item__swatch')
const unit = swatch ? swatch.offsetWidth + $data.swatchGap : $data.wrapper.clientWidth * 0.3
let amount = 2 * unit
$data.wrapper.scrollTo({
left: direction == 'forward' ? $data.wrapper.scrollLeft + amount : $data.wrapper.scrollLeft - amount,
behavior:'smooth'
})
},
// Lazily wire the prev/next edge detection on first desktop hover/focus β
// the arrows are a desktop affordance, so no observer is built at load and
// none is ever built on mobile. canPrev/canNext default to the at-start state.
initSwatchEdges() {
if (this._edgeReady || window.innerWidth <= 1023) return
this._edgeReady = true
const root = this.wrapper
const swatches = root.querySelectorAll('.product-item__swatch')
if (swatches.length < 2) return
const first = swatches[0], last = swatches[swatches.length - 1]
const io = new IntersectionObserver((entries) => {
const d = window.Alpine && window.Alpine.$data ? window.Alpine.$data(root) : null
if (!d) return
entries.forEach((e) => {
if (e.target === first) d.canPrev = !e.isIntersecting
if (e.target === last) d.canNext = !e.isIntersecting
})
}, { root, threshold: 0.99 })
io.observe(first)
io.observe(last)
root._swatchEdgeIO = io
}
}"
@pointerenter="initSwatchEdges()"
@focusin="initSwatchEdges()"
>
{"id":5156384964743,
"handle":"mea-ola-mens-leather-beach-sandals-tan-dark-java",
"title":"Mea Ola - Tan \/ Dark Java",
"available":true,
"tags":["arch:high","beach sandals","beachbum","best:special_occasion","best:travel","brotherly love","cat:sandals","collection:social-strategist","color:tan","color:Tan \/ Dark Java","eagles sandals","EXP_REC_MEN","Fatherβs Day Gifts","fit:wide","fli p flflop men","fliflops","flip flops","flip ops","flipflips","flipflop men","flipflps","fliplops","football sandals","free shipping","gender:men","gifts","interest:driving","jason kelce","jason keller","jason kelse","kelce","kielce","labor day","leather","likes:beach","likes:friends","likes:urban-exploring","material:leather","mea","mea oa","mea ola","meaola","mei ola","men","men s flipslops","mens","Mens sansandals","menβs","meola sandal","mrs ola","nature-inspired","prime","RGroup_10138","size:small","size:true","SP-19-COLOR","SP19-Style","Special Occasion","split_color","style:10138","Travel","trendsetter","vacation:city-downtown","vacation:tropical","wide width","widewidh","YGroup_10138"],
"type":"Menβs Leather Beach Sandals",
"options_with_values": [{"name":"Color","position":1,"values":["Tan \/ Dark Java"]},{"name":"Size","position":2,"values":["7","8","9","10","11","12","13","14","15"]}],
"price":18000,
"price_min":18000,
"price_max":18000,
"compare_at_price":null,"featured_image": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_001_M_MeaOla_TanDkj.png?v=1762441219",
"featured_image_aspect_ratio": 1.0,
"featured_image_width": 2000,
"featured_image_height": 2000,
"featured_image_id": 34400637583495,
"featured_image_alt": "Tan \/ Dark Java",
"hover_image": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_101_MeaOla_TanDkJava_251104a4-4e86-4d4f-8852-dbca89bb45b6.jpg?v=1744750104",
"hover_image_alt": "Tan \/ Dark Java",
"images": [{
"id": 34400637583495,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_001_M_MeaOla_TanDkj.png?v=1762441219",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34400637616263,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_101_MeaOla_TanDkJava_251104a4-4e86-4d4f-8852-dbca89bb45b6.jpg?v=1744750104",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34400637649031,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_201_9cebf6b5-bde6-48d4-933a-7cca9a4ffc0d.jpg?v=1744750104",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34400637681799,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_004_M_MeaOla_TanDkj.png?v=1744750104",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34400637714567,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_102_MeaOla_TanDkJava_b6430b33-f302-45c9-a2a7-81941cbd4c2b.jpg?v=1744750104",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34400637747335,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_202_06af3be7-2cc3-4f82-a4d1-9dac14c7949b.jpg?v=1744750104",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34400637812871,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_103_MeaOla_TanDkJava_765832bc-f45c-4b8b-8a35-3950b9493d07.jpg?v=1744750104",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34400637878407,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_203_16e8c90f-775a-4489-8a0f-3b2b054001d1.jpg?v=1744750104",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
},{
"id": 34400637943943,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10138_3448_005_M_MeaOla_TanDkj.png?v=1744750104",
"alt": "Tan \/ Dark Java",
"width": 2000,
"height": 2000
}],
"plp_image_order_positions": "",
"sku": "10138-3448-7"
}
{ $dispatch('product-item:images-changed', { product: product, source: 'swatch-container' }) })"
@pointercancel="intendedSwatchIndex = null; intendedSwatchEl = null"
x-data="{
wrapper: $el,
selectedSibling:product.siblings[0],
intendedSwatchIndex: null,
intendedSwatchEl: null,
swatchGap: 8,
scrollable: product.siblings.filter(s => !!s.available).length > 9.5,
canPrev: false,
canNext: true,
_edgeReady: false,
imageLimit: 0,
limitImages(nextProduct) {
const images = Array.isArray(nextProduct && nextProduct.images) ? nextProduct.images : [];
const limitedImages = this.imageLimit > 0 ? images.slice(0, this.imageLimit) : images;
const hoverImage = limitedImages[1] || null;
return Object.assign({}, nextProduct, {
images: limitedImages,
hover_image: hoverImage ? hoverImage.src : null,
hover_image_alt: hoverImage ? hoverImage.alt : null
});
},
selectProduct(nextProduct) {
const productRoot = $el.closest('.product-item');
const productData = productRoot && productRoot._x_dataStack && productRoot._x_dataStack[0];
if (productData && typeof productData.setProduct === 'function') {
productData.setProduct(nextProduct);
} else {
$data.product = this.limitImages(nextProduct);
}
},
getSwatchImageSrc(sibling) {
return sibling.images[0].src;
},
buildSwatchSrcset(src) {
if (!src) return '';
const widths = ['48', '96', '192'];
const separator = src.includes('?') ? '&' : '?';
return widths.map(w => `${src}${separator}width=${w} ${w}w`).join(', ');
},
isSwatchPriority(index) {
return window.innerWidth >= 1024 ? index < 3 : index < 2;
},
swatchBg(sibling) {
// swatch_bg precomputed at hydration. Nullish (not ||): null = no colors set
// (fall back to theme default), empty string = invalid combo, no fallback.
return sibling.swatch_bg ?? '#000000';
},
centerSwatch(el) {
if ($data.wrapper.scrollWidth <= $data.wrapper.clientWidth) return false
const wrapperRect = $data.wrapper.getBoundingClientRect()
const elRect = el.getBoundingClientRect()
const centerPoint = wrapperRect.x + (wrapperRect.width / 2)
const behavior = window.innerWidth < 1024 ? 'auto' : 'smooth'
$data.wrapper.scrollTo({
left: $data.wrapper.scrollLeft + (elRect.x - centerPoint + (elRect.width / 2)),
behavior
})
},
selectSwatchFromTap(siblingIndex, el) {
const idx = (this.intendedSwatchIndex !== null && this.intendedSwatchIndex !== undefined)
? this.intendedSwatchIndex
: siblingIndex
const targetEl = this.intendedSwatchEl || el
this.intendedSwatchIndex = null
this.intendedSwatchEl = null
const next = product.siblings[idx]
if (next) product = next
this.centerSwatch(targetEl)
},
scroll(direction = 'forward') {
const swatch = $data.wrapper.querySelector('.product-item__swatch')
const unit = swatch ? swatch.offsetWidth + $data.swatchGap : $data.wrapper.clientWidth * 0.3
let amount = 2 * unit
$data.wrapper.scrollTo({
left: direction == 'forward' ? $data.wrapper.scrollLeft + amount : $data.wrapper.scrollLeft - amount,
behavior:'smooth'
})
},
// Lazily wire the prev/next edge detection on first desktop hover/focus β
// the arrows are a desktop affordance, so no observer is built at load and
// none is ever built on mobile. canPrev/canNext default to the at-start state.
initSwatchEdges() {
if (this._edgeReady || window.innerWidth <= 1023) return
this._edgeReady = true
const root = this.wrapper
const swatches = root.querySelectorAll('.product-item__swatch')
if (swatches.length < 2) return
const first = swatches[0], last = swatches[swatches.length - 1]
const io = new IntersectionObserver((entries) => {
const d = window.Alpine && window.Alpine.$data ? window.Alpine.$data(root) : null
if (!d) return
entries.forEach((e) => {
if (e.target === first) d.canPrev = !e.isIntersecting
if (e.target === last) d.canNext = !e.isIntersecting
})
}, { root, threshold: 0.99 })
io.observe(first)
io.observe(last)
root._swatchEdgeIO = io
}
}"
@pointerenter="initSwatchEdges()"
@focusin="initSwatchEdges()"
>
{ $dispatch('product-item:images-changed', { product: product, source: 'swatch-container' }) })"
@pointercancel="intendedSwatchIndex = null; intendedSwatchEl = null"
x-data="{
wrapper: $el,
selectedSibling:product.siblings[0],
intendedSwatchIndex: null,
intendedSwatchEl: null,
swatchGap: 8,
scrollable: product.siblings.filter(s => !!s.available).length > 9.5,
canPrev: false,
canNext: true,
_edgeReady: false,
imageLimit: 0,
limitImages(nextProduct) {
const images = Array.isArray(nextProduct && nextProduct.images) ? nextProduct.images : [];
const limitedImages = this.imageLimit > 0 ? images.slice(0, this.imageLimit) : images;
const hoverImage = limitedImages[1] || null;
return Object.assign({}, nextProduct, {
images: limitedImages,
hover_image: hoverImage ? hoverImage.src : null,
hover_image_alt: hoverImage ? hoverImage.alt : null
});
},
selectProduct(nextProduct) {
const productRoot = $el.closest('.product-item');
const productData = productRoot && productRoot._x_dataStack && productRoot._x_dataStack[0];
if (productData && typeof productData.setProduct === 'function') {
productData.setProduct(nextProduct);
} else {
$data.product = this.limitImages(nextProduct);
}
},
getSwatchImageSrc(sibling) {
return sibling.images[0].src;
},
buildSwatchSrcset(src) {
if (!src) return '';
const widths = ['48', '96', '192'];
const separator = src.includes('?') ? '&' : '?';
return widths.map(w => `${src}${separator}width=${w} ${w}w`).join(', ');
},
isSwatchPriority(index) {
return window.innerWidth >= 1024 ? index < 3 : index < 2;
},
swatchBg(sibling) {
// swatch_bg precomputed at hydration. Nullish (not ||): null = no colors set
// (fall back to theme default), empty string = invalid combo, no fallback.
return sibling.swatch_bg ?? '#000000';
},
centerSwatch(el) {
if ($data.wrapper.scrollWidth <= $data.wrapper.clientWidth) return false
const wrapperRect = $data.wrapper.getBoundingClientRect()
const elRect = el.getBoundingClientRect()
const centerPoint = wrapperRect.x + (wrapperRect.width / 2)
const behavior = window.innerWidth < 1024 ? 'auto' : 'smooth'
$data.wrapper.scrollTo({
left: $data.wrapper.scrollLeft + (elRect.x - centerPoint + (elRect.width / 2)),
behavior
})
},
selectSwatchFromTap(siblingIndex, el) {
const idx = (this.intendedSwatchIndex !== null && this.intendedSwatchIndex !== undefined)
? this.intendedSwatchIndex
: siblingIndex
const targetEl = this.intendedSwatchEl || el
this.intendedSwatchIndex = null
this.intendedSwatchEl = null
const next = product.siblings[idx]
if (next) product = next
this.centerSwatch(targetEl)
},
scroll(direction = 'forward') {
const swatch = $data.wrapper.querySelector('.product-item__swatch')
const unit = swatch ? swatch.offsetWidth + $data.swatchGap : $data.wrapper.clientWidth * 0.3
let amount = 2 * unit
$data.wrapper.scrollTo({
left: direction == 'forward' ? $data.wrapper.scrollLeft + amount : $data.wrapper.scrollLeft - amount,
behavior:'smooth'
})
},
// Lazily wire the prev/next edge detection on first desktop hover/focus β
// the arrows are a desktop affordance, so no observer is built at load and
// none is ever built on mobile. canPrev/canNext default to the at-start state.
initSwatchEdges() {
if (this._edgeReady || window.innerWidth <= 1023) return
this._edgeReady = true
const root = this.wrapper
const swatches = root.querySelectorAll('.product-item__swatch')
if (swatches.length < 2) return
const first = swatches[0], last = swatches[swatches.length - 1]
const io = new IntersectionObserver((entries) => {
const d = window.Alpine && window.Alpine.$data ? window.Alpine.$data(root) : null
if (!d) return
entries.forEach((e) => {
if (e.target === first) d.canPrev = !e.isIntersecting
if (e.target === last) d.canNext = !e.isIntersecting
})
}, { root, threshold: 0.99 })
io.observe(first)
io.observe(last)
root._swatchEdgeIO = io
}
}"
@pointerenter="initSwatchEdges()"
@focusin="initSwatchEdges()"
>
Explore Similar Stories
{ $dispatch('product-item:images-changed', { product: product, source: 'swatch-container' }) })"
@pointercancel="intendedSwatchIndex = null; intendedSwatchEl = null"
x-data="{
wrapper: $el,
selectedSibling:product.siblings[0],
intendedSwatchIndex: null,
intendedSwatchEl: null,
swatchGap: 8,
scrollable: product.siblings.filter(s => !!s.available).length > 9.5,
canPrev: false,
canNext: true,
_edgeReady: false,
imageLimit: 0,
limitImages(nextProduct) {
const images = Array.isArray(nextProduct && nextProduct.images) ? nextProduct.images : [];
const limitedImages = this.imageLimit > 0 ? images.slice(0, this.imageLimit) : images;
const hoverImage = limitedImages[1] || null;
return Object.assign({}, nextProduct, {
images: limitedImages,
hover_image: hoverImage ? hoverImage.src : null,
hover_image_alt: hoverImage ? hoverImage.alt : null
});
},
selectProduct(nextProduct) {
const productRoot = $el.closest('.product-item');
const productData = productRoot && productRoot._x_dataStack && productRoot._x_dataStack[0];
if (productData && typeof productData.setProduct === 'function') {
productData.setProduct(nextProduct);
} else {
$data.product = this.limitImages(nextProduct);
}
},
getSwatchImageSrc(sibling) {
return sibling.images[0].src;
},
buildSwatchSrcset(src) {
if (!src) return '';
const widths = ['48', '96', '192'];
const separator = src.includes('?') ? '&' : '?';
return widths.map(w => `${src}${separator}width=${w} ${w}w`).join(', ');
},
isSwatchPriority(index) {
return window.innerWidth >= 1024 ? index < 3 : index < 2;
},
swatchBg(sibling) {
// swatch_bg precomputed at hydration. Nullish (not ||): null = no colors set
// (fall back to theme default), empty string = invalid combo, no fallback.
return sibling.swatch_bg ?? '#000000';
},
centerSwatch(el) {
if ($data.wrapper.scrollWidth <= $data.wrapper.clientWidth) return false
const wrapperRect = $data.wrapper.getBoundingClientRect()
const elRect = el.getBoundingClientRect()
const centerPoint = wrapperRect.x + (wrapperRect.width / 2)
const behavior = window.innerWidth < 1024 ? 'auto' : 'smooth'
$data.wrapper.scrollTo({
left: $data.wrapper.scrollLeft + (elRect.x - centerPoint + (elRect.width / 2)),
behavior
})
},
selectSwatchFromTap(siblingIndex, el) {
const idx = (this.intendedSwatchIndex !== null && this.intendedSwatchIndex !== undefined)
? this.intendedSwatchIndex
: siblingIndex
const targetEl = this.intendedSwatchEl || el
this.intendedSwatchIndex = null
this.intendedSwatchEl = null
const next = product.siblings[idx]
if (next) product = next
this.centerSwatch(targetEl)
},
scroll(direction = 'forward') {
const swatch = $data.wrapper.querySelector('.product-item__swatch')
const unit = swatch ? swatch.offsetWidth + $data.swatchGap : $data.wrapper.clientWidth * 0.3
let amount = 2 * unit
$data.wrapper.scrollTo({
left: direction == 'forward' ? $data.wrapper.scrollLeft + amount : $data.wrapper.scrollLeft - amount,
behavior:'smooth'
})
},
// Lazily wire the prev/next edge detection on first desktop hover/focus β
// the arrows are a desktop affordance, so no observer is built at load and
// none is ever built on mobile. canPrev/canNext default to the at-start state.
initSwatchEdges() {
if (this._edgeReady || window.innerWidth <= 1023) return
this._edgeReady = true
const root = this.wrapper
const swatches = root.querySelectorAll('.product-item__swatch')
if (swatches.length < 2) return
const first = swatches[0], last = swatches[swatches.length - 1]
const io = new IntersectionObserver((entries) => {
const d = window.Alpine && window.Alpine.$data ? window.Alpine.$data(root) : null
if (!d) return
entries.forEach((e) => {
if (e.target === first) d.canPrev = !e.isIntersecting
if (e.target === last) d.canNext = !e.isIntersecting
})
}, { root, threshold: 0.99 })
io.observe(first)
io.observe(last)
root._swatchEdgeIO = io
}
}"
@pointerenter="initSwatchEdges()"
@focusin="initSwatchEdges()"
>