News
How do we See the World? - Sam Potter
Join Sam Potter & Pelika Andrade to Learn About Kilo, a Hawaiian Practice of Observation!
Shop The Story
{"id":5156404396167,
"handle":"ohana-mens-beach-sandals-dark-java-ray",
"title":"‘Ohana - Dark Java \/ Ray",
"available":true,
"tags":["'oahni","2024-gift-guide","arch:high","beach sandals","Beach\/Boat","beachbum","best sellers","best selling","best:beach_boat","best:everday_wear","best:post_workout","cat:sandals","collection:tide-tracker","collection:wfh","color:brown","color:Dark Java \/ Ray","dk java","enhanced traction outsole","Everyday Wear","EXP_REC_MEN","extra-colors","Father’s Day Gifts","fit:wide","flip flops","flipfops","gender:men","gifts","hana","labor day","likes:beach","material:synthetic","men","mens","mens flipflops","men’s","o hana","obama","Oh","oha","ohan","ohana men s sandals","ohanaa","ohanaflip flops","ohanna","ohano","ohanu","ohona","Post-Workout","prime","RGroup_10110","size:true","SP-19-COLOR","SP19-Style","split_color","style:10110","synthetic","tempstandard","vacation:tropical","vegan","water reistant","water-friendly","wide width","widewidh","YGroup_10110","‘ohana"],
"type":"Men’s Water-Friendly Beach Sandals",
"options_with_values": [{"name":"Color","position":1,"values":["Dark Java \/ Ray"]},{"name":"Size","position":2,"values":["7","8","9","10","11","12","13","14","15","16","17","18"]}],
"price":10000,
"price_min":10000,
"price_max":10000,
"compare_at_price":null,"featured_image": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_001_M_Ohana_DarkJava_f6321f63-1b0b-446f-a607-5f8e3d241990.png?v=1762441465",
"featured_image_aspect_ratio": 1.0,
"featured_image_width": 2000,
"featured_image_height": 2000,
"featured_image_id": 34400638435463,
"featured_image_alt": "Dark Java \/ Ray",
"hover_image": "\/\/olukai.ca\/cdn\/shop\/files\/Ohana-DarkJava-galleryimage-01.jpg?v=1746734340",
"hover_image_alt": "Dark Java \/ Ray",
"images": [{
"id": 34400638435463,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_001_M_Ohana_DarkJava_f6321f63-1b0b-446f-a607-5f8e3d241990.png?v=1762441465",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
},{
"id": 34587117027463,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/Ohana-DarkJava-galleryimage-01.jpg?v=1746734340",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
},{
"id": 34587117060231,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_201.jpg?v=1746734340",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
},{
"id": 34587117092999,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_101_M_OHANA_DarkJavaRay.jpg?v=1746734340",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
},{
"id": 34587117125767,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_103_bdd18907-7a8d-42b4-bc9b-bf1bea50e003.jpg?v=1746734340",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
},{
"id": 34400638664839,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_102_M_OHANA_DarkJavaRay_1ca47e3c-3fa6-4062-9e87-8ec31f3772c9.jpg?v=1746734340",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
},{
"id": 34400638697607,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_101_6dd8d973-9fda-457b-a6b8-aaa9a390a1f3.jpg?v=1746734340",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
},{
"id": 34400638730375,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_203_3eaf126b-c4dc-4d8f-ac5b-2f56b10bd4de.jpg?v=1746734340",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
},{
"id": 34400638763143,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10110_4827_005_M_Ohana_DarkJava.png?v=1746734340",
"alt": "Dark Java \/ Ray",
"width": 2000,
"height": 2000
}],
"plp_image_order_positions": "",
"sku": "10110-4827-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":5156386898055,
"handle":"mio-li-mens-athletic-shoes-hunter-lava-rock",
"title":"Mio Lī - Hunter \/ Lava Rock",
"available":true,
"tags":["arch:standard","as seen on tv","athletic shoes","best:everday_wear","best:gym_trainers","best:travel","cat:shoes","collection:chief-home-officer","collection:day-tripper","collection:wfh","color:green","color:Hunter \/ Lava Rock","commerical","crossfit","drop-in heel®","enhance gip","enhanced traction outsole","Everyday Wear","EXP_REC_MEN","Father’s Day Gifts","fit:narrow","fit:standard","foldable heal","free shipping","gender:men","gifts","greenshoes","Gym\/Trainers","labor day","land","material:textile","mens","mens runnners","mi oli","Mia li","mia lli","milo li","milo’s","mio le","Mio Li","mio lii","mio lio","mio loi","mio lu","miola","miolio","miolo","miso li","mo li","molí","moo li","on tv","RGroup_10440","shoes","size:true","sneakers","split_color","style:10440","tennis shoes","tennis shors","tennis shos","textile","Travel","tv","tv ad","Walking","weight lifting","YGroup_10440"],
"type":"Men’s Everyday Athletic Shoes",
"options_with_values": [{"name":"Color","position":1,"values":["Hunter \/ Lava Rock"]},{"name":"Size","position":2,"values":["7","8","8.5","9","9.5","10","10.5","11","11.5","12","13","14","15"]}],
"price":18000,
"price_min":18000,
"price_max":18000,
"compare_at_price":null,"featured_image": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_001_M_MioLi_HunLvar.png?v=1762441238",
"featured_image_aspect_ratio": 1.0,
"featured_image_width": 2000,
"featured_image_height": 2000,
"featured_image_id": 34414952349831,
"featured_image_alt": "Hunter \/ Lava Rock",
"hover_image": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_201_6be5372c-929c-48f0-8f25-b8b4b8297826.jpg?v=1744996244",
"hover_image_alt": "Hunter \/ Lava Rock",
"images": [{
"id": 34414952349831,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_001_M_MioLi_HunLvar.png?v=1762441238",
"alt": "Hunter \/ Lava Rock",
"width": 2000,
"height": 2000
},{
"id": 34414952415367,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_201_6be5372c-929c-48f0-8f25-b8b4b8297826.jpg?v=1744996244",
"alt": "Hunter \/ Lava Rock",
"width": 2000,
"height": 2000
},{
"id": 34414952513671,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_101_M_MioLi_HunLvar_Web_Opt_1024x1024_1fdcdc8c-435f-44eb-a815-d3afce4fe687.jpg?v=1744996244",
"alt": "Hunter \/ Lava Rock",
"width": 2000,
"height": 2000
},{
"id": 34414952611975,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_002_M_MioLi_HunLvar.png?v=1744996244",
"alt": "Hunter \/ Lava Rock",
"width": 2000,
"height": 2000
},{
"id": 34414952710279,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_201_e47e921f-ee05-4dfa-915d-66f9e25c2fa0.webp?v=1744996244",
"alt": "Hunter \/ Lava Rock",
"width": 1608,
"height": 1608
},{
"id": 34414952808583,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_103_M_MioLi_HunLvar_Web_Opt_1024x1024_ec1124b3-b431-474a-9f1e-d787139fe6d7.jpg?v=1744996244",
"alt": "Hunter \/ Lava Rock",
"width": 2000,
"height": 2000
},{
"id": 34414952906887,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_003_M_MioLi_HunLvar.png?v=1744996244",
"alt": "Hunter \/ Lava Rock",
"width": 2000,
"height": 2000
},{
"id": 34414953005191,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_004_M_MioLi_HunLvar_94e9be10-e3a3-48d2-a600-50c01073a27c.png?v=1744996244",
"alt": "Hunter \/ Lava Rock",
"width": 750,
"height": 750
},{
"id": 34414953103495,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10440_HGLR_005_M_MioLi_HunLvar.png?v=1744996244",
"alt": "Hunter \/ Lava Rock",
"width": 2000,
"height": 2000
}],
"plp_image_order_positions": "",
"sku": "10440-HGLR-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":5156388176007,
"handle":"moloa-mens-leather-slippers-dark-wood",
"title":"Moloā Slipper - Dark Wood",
"available":true,
"tags":["arch:high","arch:standard","best sellers","best selling","best:cold_climate","best:indoor","cat:slippers","CM2025","Cold Climate","collection:cart-upsell","collection:comfort-seeker","collection:HGG-Slider-M","collection:wfh","color:brown","color:Dark Wood","drop-in heel®","fit:wide","free shipping","gender:men","high arch slippers","high arch slippers men","homebody","Indoor","interest:driving","leather","likes:relaxing","maloa","material:leather","men","mens","menslippers","Menslppers","mensslippers","mensslper","men’s","milia slipper","mo loa","moala","moloa","moloa liper","moloa moccasin","moloa slipper","moloamoa","prime","RGroup_10252","s ippers","shearling lined","sheepskin","sherling","size:true","slippers","split_color","style:10252","vacation:road-trip","YGroup_10252"],
"type":"Men’s Premium Leather Slippers",
"options_with_values": [{"name":"Color","position":1,"values":["Dark Wood"]},{"name":"Size","position":2,"values":["7","8","9","10","11","12","13","14","15","16"]}],
"price":18000,
"price_min":18000,
"price_max":18000,
"compare_at_price":null,"featured_image": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363_001_M_MOLOA_SLIPPER_DKWDKW_5aa0f439-bcf4-426c-b9a1-ce1e4ca3b6c0.png?v=1759861403",
"featured_image_aspect_ratio": 1.0,
"featured_image_width": 2000,
"featured_image_height": 2000,
"featured_image_id": 36465007886471,
"featured_image_alt": "Moloā Slipper - Dark Wood",
"hover_image": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363_102_39ed68b2-19da-4cfb-91c3-4f8f6949049c.jpg?v=1761169470",
"hover_image_alt": "Moloā Slipper - Dark Wood",
"images": [{
"id": 36465007886471,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363_001_M_MOLOA_SLIPPER_DKWDKW_5aa0f439-bcf4-426c-b9a1-ce1e4ca3b6c0.png?v=1759861403",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2000
},{
"id": 36625231708295,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363_102_39ed68b2-19da-4cfb-91c3-4f8f6949049c.jpg?v=1761169470",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2000
},{
"id": 46794106372231,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363-DARKWOOD_2a5781e5-2a25-46b9-98cb-25d7ece95575.jpg?v=1789592097",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2000
},{
"id": 46794071572615,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/11.webp?v=1789591352",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2000
},{
"id": 36465008083079,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363_201_1178956f-f316-4b0a-95c5-868c7c982318.jpg?v=1761169323",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2002
},{
"id": 36465009328263,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363_103_M_MoloaSlipper_DkwDkw_bc41d48a-5c86-4e2d-a077-13d881529ad7.jpg?v=1761169470",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2000
},{
"id": 36625226072199,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363_102_M_MoloaSlipper_DkwDkw.png?v=1761169470",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2000
},{
"id": 46794106536071,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10252-6363-004.png?v=1789592121",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2000
},{
"id": 36465009655943,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10252_6363_005_M_MoloaSlipper_DkwDkw.png?v=1761169470",
"alt": "Moloā Slipper - Dark Wood",
"width": 2000,
"height": 2000
}],
"plp_image_order_positions": "",
"sku": "10252-6363-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()"
>