News
Behind The Name | Moku Pae
Behind The Name | Moku Pae
Kumu Kamuela Yim describes the inspiration behind our modern approach to our No-Tie Boat Shoe: Moku Pae.
Shop The Story
{"id":6536037138567,
"handle":"moku-pae-mens-shoes-trench-blue-off-white",
"title":"Moku Pae - Trench Blue \/ Off White",
"available":true,
"tags":["2024-gift-guide","arch:high","arch:standard","Beach\/Boat","best sellers","best selling","best:beach_boat","best:everday_wear","best:travel","boat shoe","boating sneakers","boatshoes","bundle|1|3|0|1","cat:shoes","color:blue","drop-in heel®","enhance gip","enhanced traction outsole","Everyday Wear","fit:wide","gender:men","interest:driving","labor day","landtextile","likes:beach","likes:exercising","likes:friends","likes:hiking","likes:relaxing","likes:urban-exploring","material:textile","mens","mesh","mock pae","Moke pea","mokea pae","mokepai","moki pae","moko noe","mokpae","moku","moku ae","moku pae","moku peha","moku pei","mokupae","moly pae","navy moku pai","no shoestring","prime","RGroup_10472","shoes","size:large","size:true","split_color","style:10472","swim","swim shoes","Travel","vegan","water-friendly","watershoes","wide toed","wide width","ygroup_10472"],
"type":"Men’s Breathable Boat Shoes",
"options_with_values": [{"name":"Color","position":1,"values":["Trench Blue \/ Off White"]},{"name":"Size","position":2,"values":["7","8","8.5","9","9.5","10","10.5","11","11.5","12","13","14","15"]}],
"price":15000,
"price_min":15000,
"price_max":15000,
"compare_at_price":null,"featured_image": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_001_M_MokuPae_TrbOfw_71ffbee4-40cf-48e5-b4f4-df0de1ecec11.png?v=1762443006",
"featured_image_aspect_ratio": 1.0,
"featured_image_width": 2000,
"featured_image_height": 2000,
"featured_image_id": 34216746385543,
"featured_image_alt": "Trench Blue \/ Off White",
"hover_image": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_101_M_MokuPae_TrbOfw_Web_Opt_1024x1024_572f6511-68d9-43f8-9201-96d98bf72f07.jpg?v=1743633483",
"hover_image_alt": "Trench Blue \/ Off White",
"images": [{
"id": 34216746385543,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_001_M_MokuPae_TrbOfw_71ffbee4-40cf-48e5-b4f4-df0de1ecec11.png?v=1762443006",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216746483847,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_101_M_MokuPae_TrbOfw_Web_Opt_1024x1024_572f6511-68d9-43f8-9201-96d98bf72f07.jpg?v=1743633483",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216746582151,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_201_2226f8da-f71e-45a1-b94a-f580f6805d1b.jpg?v=1743633484",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216746647687,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_103_M_MokuPae_TrbOfw_Web_Opt_1024x1024_jpeg.jpg?v=1743633484",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216746745991,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_203_a5879a13-f389-48ca-9afa-355b067e7c1d.jpg?v=1743633484",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216746811527,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_102_M_MokuPae_TrbOfw_57e319a9-bc88-429a-b8ff-4678b5419e3c.jpg?v=1743633484",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216746877063,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_103_M_MokuPae_TrbOfw_7fe3f7d1-80e7-483c-bde3-0fefae7f0e9b.jpg?v=1743633484",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216746942599,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_202_8dab76cf-91d5-45ef-9e6e-7020d1f064cc.jpg?v=1743633484",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216746975367,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_203_a4672294-6cdc-42b5-9724-b8ee8a3f2e16.jpg?v=1743633484",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
},{
"id": 34216747040903,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_DE18_005_M_MokuPae_TrbOfw.png?v=1743633484",
"alt": "Trench Blue \/ Off White",
"width": 2000,
"height": 2000
}],
"plp_image_order_positions": "",
"sku": "10472-DE18-8"
}
{ $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":6536036941959,
"handle":"moku-pae-mens-shoes-black-blue-coral",
"title":"Moku Pae - Black \/ Blue Coral",
"available":true,
"tags":["app exclusive","arch:high","arch:standard","Beach\/Boat","best:beach_boat","best:everday_wear","best:travel","boat ahoe","boat shoe","boating sneakers","boatshoes","cat:shoes","color:blue","Dark Soled","drop-in heel®","enhance gip","enhanced traction outsole","Everyday Wear","Father’s Day Gifts","fit:wide","gender:men","gifts","interest:driving","labor day","landtextile","likes:beach","likes:exercising","likes:friends","likes:hiking","likes:urban-exploring","mamu pae","material:textile","mens","mens black sneaker slip on","mesh","mike pae","miki pae","mock pae","mock up pae","Moke pea","mokea pae","mokepai","moki pae","mokpae","moku","moku pae","moku peha","mokupae","mokypae","moly pae","Mooku pase","no shoestring","online only","pae","RGroup_10472","shoes","size:large","size:true","split_color","style:10472","swim","swim shoes","Travel","vegan","water friendly shoe","watershoe","watershoes","wide toed","wide width","widewidh"],
"type":"Men’s Breathable Boat Shoes",
"options_with_values": [{"name":"Color","position":1,"values":["Black \/ Blue Coral"]},{"name":"Size","position":2,"values":["7","8","8.5","9","9.5","10","10.5","11","11.5","12","13","14","15"]}],
"price":15000,
"price_min":15000,
"price_max":15000,
"compare_at_price":null,"featured_image": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_001_M_MokuPae_BlkBlc.png?v=1767730668",
"featured_image_aspect_ratio": 1.0,
"featured_image_width": 2000,
"featured_image_height": 2000,
"featured_image_id": 38443281678471,
"featured_image_alt": "Black \/ Blue Coral",
"hover_image": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_105_M_MOKUPAE_BlackBlueCoral.png?v=1767730669",
"hover_image_alt": "Moku Pae - Black \/ Blue Coral",
"images": [{
"id": 38443281678471,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_001_M_MokuPae_BlkBlc.png?v=1767730668",
"alt": "Black \/ Blue Coral",
"width": 2000,
"height": 2000
},{
"id": 38443281744007,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_105_M_MOKUPAE_BlackBlueCoral.png?v=1767730669",
"alt": "Moku Pae - Black \/ Blue Coral",
"width": 2000,
"height": 2000
},{
"id": 38443281842311,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472-40QS-101.jpg?v=1767730669",
"alt": "Moku Pae - Black \/ Blue Coral",
"width": 2000,
"height": 2000
},{
"id": 38443281973383,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_002_M_MokuPae_BlkBlc.png?v=1767730670",
"alt": "Black \/ Blue Coral",
"width": 2000,
"height": 2000
},{
"id": 38443282137223,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_103_M_MOKUPAE_BlackBlueCoral.png?v=1767730671",
"alt": "Moku Pae - Black \/ Blue Coral",
"width": 2000,
"height": 2000
},{
"id": 38443282301063,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_003_M_MokuPae_BlkBlc.png?v=1767730671",
"alt": "Black \/ Blue Coral",
"width": 2000,
"height": 2000
},{
"id": 38443282432135,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_004_M_MokuPae_BlkBlc.png?v=1767730672",
"alt": "Black \/ Blue Coral",
"width": 2000,
"height": 2000
},{
"id": 38443282497671,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472-40QS-103.jpg?v=1767730672",
"alt": "Moku Pae - Black \/ Blue Coral",
"width": 2000,
"height": 2000
},{
"id": 38443282661511,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_40QS_005_M_MokuPae_BlkBlc.png?v=1767730672",
"alt": "Black \/ Blue Coral",
"width": 2000,
"height": 2000
}],
"plp_image_order_positions": "",
"sku": "10472-40QS-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":6536037007495,
"handle":"moku-pae-mens-shoes-island-salt-koi",
"title":"Moku Pae - Island Salt \/ Koi",
"available":true,
"tags":["arch:high","arch:standard","Beach\/Boat","best:beach_boat","best:everday_wear","best:travel","boat ahoe","boat shoe","boaters","boating sneakers","boatshoes","bundle|1|3|0|1","cat:shoes","color:grey","Dark Soled","drop-in heel®","enhance gip","enhanced traction outsole","Everyday Wear","fit:wide","gender:men","interest:driving","labor day","landtextile","likes:beach","likes:exercising","likes:friends","likes:hiking","likes:relaxing","likes:urban-exploring","material:textile","mens","mesh","mike pae","mock pae","mock up pae","Moke pea","mokea pae","moki pae","Mokia","mokpae","moku","moku ae","moku pae","moku peha","moku pei","mokupae","mokypae","moly pae","no shoestring","RGroup_10472","shoes","size:large","size:true","split_color","style:10472","swim","swim shoes","Travel","trending","vegan","water-friendly","waterboat","watershoe","watershoes","wide toed","wide width","ygroup_10472"],
"type":"Men’s Breathable Boat Shoes",
"options_with_values": [{"name":"Color","position":1,"values":["Island Salt \/ Koi"]},{"name":"Size","position":2,"values":["7","8","8.5","9","9.5","10","10.5","11","11.5","12","13","14","15"]}],
"price":15000,
"price_min":15000,
"price_max":15000,
"compare_at_price":null,"featured_image": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_001_M_MokuPae_IslsKoi_1814cda1-50ed-404b-902e-210f2ae126fa.png?v=1762443005",
"featured_image_aspect_ratio": 1.0,
"featured_image_width": 2000,
"featured_image_height": 2000,
"featured_image_id": 34414956413063,
"featured_image_alt": "Island Salt \/ Koi",
"hover_image": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_105_M_MokuPae_IslsKoi_8adf784d-70ab-4c2f-982a-41f9ca0c229d.jpg?v=1744996249",
"hover_image_alt": "Island Salt \/ Koi",
"images": [{
"id": 34414956413063,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_001_M_MokuPae_IslsKoi_1814cda1-50ed-404b-902e-210f2ae126fa.png?v=1762443005",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956445831,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_105_M_MokuPae_IslsKoi_8adf784d-70ab-4c2f-982a-41f9ca0c229d.jpg?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956478599,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_201_8796cd53-80df-45c7-94cf-f43b067b2efb.jpg?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956511367,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_002_M_MokuPae_IslsKoi.png?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956544135,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_102_M_MokuPae_IslsKoi_480695c6-7ad4-494c-a2cd-e49e47c5a226.jpg?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956576903,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_103_M_MokuPae_IslsKoi_3d8dba75-8f8f-4cb2-a33e-7ce2a0a7fb76.jpg?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956609671,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_004_M_MokuPae_IslsKoi.png?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956642439,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_202_c4c7915b-164b-4e6d-949a-2fea5aa6d475.jpg?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956675207,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_203_4087eb1e-e031-4de1-b03c-b39b2d4fa4db.jpg?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
},{
"id": 34414956707975,
"src": "\/\/olukai.ca\/cdn\/shop\/files\/10472_4EKZ_005_M_MokuPae_IslsKoi.png?v=1744996249",
"alt": "Island Salt \/ Koi",
"width": 2000,
"height": 2000
}],
"plp_image_order_positions": "",
"sku": "10472-4EKZ-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()"
>