smarthomes.dev.magento24.sozowebdesign.com
Open in
urlscan Pro
35.189.74.243
Public Scan
Submitted URL: http://smarthomes.dev.magento24.sozowebdesign.com/
Effective URL: https://smarthomes.dev.magento24.sozowebdesign.com/
Submission Tags: @phish_report
Submission: On November 12 via api from FI — Scanned from GB
Effective URL: https://smarthomes.dev.magento24.sozowebdesign.com/
Submission Tags: @phish_report
Submission: On November 12 via api from FI — Scanned from GB
Form analysis
37 forms found in the DOMPOST
<form class="form form-login" method="post" @submit.prevent="submitForm();" id="login-form">
<div class="fieldset login">
<div class="field email required">
<label class="label" for="form-login-username" form="login-form">
<span>Email Address</span>
</label>
<div class="control">
<input name="username" id="form-login-username" x-ref="customer-email" @change="errors = 0" type="email" required="" class="form-input input-text">
</div>
</div>
<div class="field password required">
<label for="form-login-password" class="label" form="login-form">
<span>Password</span>
</label>
<div class="control">
<input name="password" id="form-login-password" type="password" class="form-input input-text" required="" x-ref="customer-password" @change="errors = 0">
</div>
</div>
<input name="context" type="hidden" value="checkout">
<div class="actions-toolbar flex justify-between pt-6 pb-2 items-center">
<button type="submit" class="inline-flex btn btn-primary disabled:opacity-75"> Log In </button>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/customer/account/forgotpassword/">
Forgot your password? </a>
</div>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/933/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/933/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="933">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £45</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-one-sl" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1" x-data="showHoverImage_673358d963209">
<figure @mouseover="showHover()" @mouseleave="showMain()" class="transition duration-300 absolute inset-0 opacity-100" :class="{
'opacity-0': !mainImage,
'opacity-100': mainImage,
}">
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-933.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/o/n/one-angle-dropped-2_1.jpg" loading="lazy" width="460" height="460" alt="Sonos One SL" title="Sonos One SL">
</figure>
<figure @mouseover="showHover()" @mouseleave="showMain()" class="transition duration-300 absolute inset-0 opacity-0" :class="{
'opacity-0': !hoverImage,
'opacity-100': hoverImage,
}">
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-933.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-one-sl-living-bookshelf-2-square.jpg" loading="lazy" width="460" height="460" alt="Sonos One SL" title="Sonos One SL">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(933)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<script>
function showHoverImage_673358d963209() {
return {
mainImage: true,
hoverImage: false,
showHover() {
this.mainImage = false;
this.hoverImage = true;
},
showMain() {
this.mainImage = true;
this.hoverImage = false;
}
}
}
</script>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '933')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-one-sl">
Sonos One SL </a>
</div>
<span class="text-secondary order-2">The speaker for stereo pairing and home theatre surrounds</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_933(),
...amXnotifSubscribeComponent_933(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-933"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-933">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="933">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_933() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '933',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_933() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "933",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "933",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"931":"1","932":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_933(),
...amXnotifSubscribeComponentList_933()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_933" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_933() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '933',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_933() {
const configurableOptionsComponent = initConfigurableOptions('933', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["931"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["932"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"931": {
"baseOldPrice": {
"amount": 149.16666566667
},
"oldPrice": {
"amount": 179
},
"basePrice": {
"amount": 111.66666566667
},
"finalPrice": {
"amount": 134
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"932": {
"baseOldPrice": {
"amount": 149.16666566667
},
"oldPrice": {
"amount": 179
},
"basePrice": {
"amount": 111.66666566667
},
"finalPrice": {
"amount": 134
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 149.16666566667
},
"oldPrice": {
"amount": 179
},
"basePrice": {
"amount": 111.66666566667
},
"finalPrice": {
"amount": 134
}
},
"productId": "933",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"931": {
"93": "4"
},
"932": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-933", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_933()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-933", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-933-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-933-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="ONESL;ONESLUK1;ONESLUK1BLK" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.89 Stars - 36 Reviews" aria-label="4.89 Stars - 36 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(36 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d95df3e() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d95df3e()" @update-prices-933.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="933" data-price-box="product-id-933"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-933" data-price-amount="134" data-price-type="finalPrice" class="price-wrapper "><span class="price">£134<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-933" data-price-amount="179" data-price-type="oldPrice" class="price-wrapper "><span class="price">£179.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_933()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_933()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_933 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_933 || window.initConfigurableDropdownOptions_933);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £45 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3449/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3449/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3449">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-move-2" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1" x-data="showHoverImage_673358d978dcd">
<figure @mouseover="showHover()" @mouseleave="showMain()" class="transition duration-300 absolute inset-0 opacity-100" :class="{
'opacity-0': !mainImage,
'opacity-100': mainImage,
}">
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3449.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-move-2-hero-black_listing.jpg" loading="lazy" width="460" height="460" alt="Sonos Move 2" title="Sonos Move 2">
</figure>
<figure @mouseover="showHover()" @mouseleave="showMain()" class="transition duration-300 absolute inset-0 opacity-0" :class="{
'opacity-0': !hoverImage,
'opacity-100': hoverImage,
}">
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3449.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/0/1/01e3623849b27f6aa146ba93f75bcf7f653cb16d-2500x1667.jpg" loading="lazy" width="460" height="460" alt="Sonos Move 2" title="Sonos Move 2">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3449)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<script>
function showHoverImage_673358d978dcd() {
return {
mainImage: true,
hoverImage: false,
showHover() {
this.mainImage = false;
this.hoverImage = true;
},
showMain() {
this.mainImage = true;
this.hoverImage = false;
}
}
}
</script>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '3449')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-move-2">
Sonos Move 2 </a>
</div>
<span class="text-secondary order-2">Portable Wi-Fi & Bluetooth Smart Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3449(),
...amXnotifSubscribeComponent_3449(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3449"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3449">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3449">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3449() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3449',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3449() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3449",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3449",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3447":"1","3448":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3449(),
...amXnotifSubscribeComponentList_3449()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3449" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3449() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3449',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3449() {
const configurableOptionsComponent = initConfigurableOptions('3449', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["3447"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["3448"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3447": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3448": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
}
},
"productId": "3449",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3447": {
"93": "4"
},
"3448": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3449", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3449()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-3449", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3449-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3449-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SM2;MOVE2UK1;MOVE2UK1BLK" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.8 Stars - 10 Reviews" aria-label="4.8 Stars - 10 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--75"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(10 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9775e2() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9775e2()" @update-prices-3449.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3449" data-price-box="product-id-3449"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3449" data-price-amount="449" data-price-type="finalPrice" class="price-wrapper "><span class="price">£449<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3449()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3449()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3449 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3449 || window.initConfigurableDropdownOptions_3449);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/651/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/651/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="651">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £50</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-beam" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-651.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-beam-gen2-black-small.png" loading="lazy" width="460" height="460" alt="Sonos Beam (Gen 2)" title="Sonos Beam (Gen 2)">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(651)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '651')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-beam">
Sonos Beam (Gen 2) </a>
</div>
<span class="text-secondary order-2">Compact Smart Soundbar with Dolby Atmos</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_651(),
...amXnotifSubscribeComponent_651(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-651"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-651">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="651">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_651() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '651',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_651() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "651",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "651",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"650":"1","649":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_651(),
...amXnotifSubscribeComponentList_651()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_651" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_651() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '651',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_651() {
const configurableOptionsComponent = initConfigurableOptions('651', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["650"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["649"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"650": {
"baseOldPrice": {
"amount": 415.83333233333
},
"oldPrice": {
"amount": 499
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"649": {
"baseOldPrice": {
"amount": 415.83333233333
},
"oldPrice": {
"amount": 499
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 415.83333233333
},
"oldPrice": {
"amount": 499
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
}
},
"productId": "651",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"650": {
"93": "4"
},
"649": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-651", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_651()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-651", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-651-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-651-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SHS299;BEAM2UK1;BEAM2UK1BLK" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.8 Stars - 172 Reviews" aria-label="4.8 Stars - 172 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--75"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(172 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9822c9() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9822c9()" @update-prices-651.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="651" data-price-box="product-id-651"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-651" data-price-amount="449" data-price-type="finalPrice" class="price-wrapper "><span class="price">£449<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-651" data-price-amount="499" data-price-type="oldPrice" class="price-wrapper "><span class="price">£499.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_651()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_651()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_651 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_651 || window.initConfigurableDropdownOptions_651);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £50 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2429/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2429/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2429">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/kef-lsxii-active-bookshelf-speaker" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2429.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/k/e/kef-lsx-ii-black-hero-small.jpg" loading="lazy" width="460" height="460" alt="KEF LSX II Active Bookshelf Speaker (Pair)" title="KEF LSX II Active Bookshelf Speaker (Pair)">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/5-year-guarantee.png" alt="Free 5 Year KEF Warranty*" class="h-12" height="48" title="Free 5 Year KEF Warranty*" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2429)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '2429')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/kef-lsxii-active-bookshelf-speaker">
KEF LSX II Active Bookshelf Speaker (Pair) </a>
</div>
<span class="text-secondary order-2">Wireless Hi-Fi Speakers</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2429(),
...amXnotifSubscribeComponent_2429(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2429"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2429">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2429">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2429() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2429',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2429() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2429",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2429",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2424":"0","2425":"1","2426":"1","2427":"1","2428":"1","3404":"0"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2429(),
...amXnotifSubscribeComponentList_2429()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2429" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2429() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2429',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2429() {
const configurableOptionsComponent = initConfigurableOptions('2429', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2426"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2425"],
"class": ""
}, {
"id": "43",
"label": "Blue",
"products": ["2424"],
"class": "out-of-stock"
}, {
"id": "44",
"label": "Olive",
"products": ["3404"],
"class": "out-of-stock"
}, {
"id": "64",
"label": "Red",
"products": ["2428"],
"class": ""
}, {
"id": "223",
"label": "Soundwave",
"products": ["2427"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2424": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2425": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2426": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2427": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2428": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3404": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
}
},
"productId": "2429",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2424": {
"93": "43"
},
"2425": {
"93": "5"
},
"2426": {
"93": "4"
},
"2427": {
"93": "223"
},
"2428": {
"93": "64"
},
"3404": {
"93": "44"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"43": {
"type": "1",
"value": "#2049b0",
"label": "Blue"
},
"44": {
"type": "1",
"value": "#377039",
"label": "Olive"
},
"64": {
"type": "1",
"value": "#f20c0c",
"label": "Red"
},
"223": {
"type": "1",
"value": "#f5f5dc",
"label": "Soundwave"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2429", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2429()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-2429", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2429-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2429-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="KLSXII;10309126;10309125;10309127;10309129;10309128;1000828449" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Star - 1 Review" aria-label="5 Star - 1 Review"
data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(1 Review)</span></div>
</div>
<script>
function initPriceBox__673358d98d8c7() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d98d8c7()" @update-prices-2429.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2429" data-price-box="product-id-2429"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2429" data-price-amount="1199" data-price-type="finalPrice" class="price-wrapper "><span class="price">£1,199<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2429()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2429()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2429 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2429 || window.initConfigurableDropdownOptions_2429);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2897/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2897/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2897">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-era-300" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2897.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-era-300-black-thumbnail.jpg" loading="lazy" width="460" height="460" alt="Sonos ERA 300" title="Sonos ERA 300">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2897)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '2897')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-era-300">
Sonos ERA 300 </a>
</div>
<span class="text-secondary order-2">Dolby Atmos Smart Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2897(),
...amXnotifSubscribeComponent_2897(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2897"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2897">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2897">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2897() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2897',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2897() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2897",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2897",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2895":"1","2896":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2897(),
...amXnotifSubscribeComponentList_2897()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2897" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2897() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2897',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2897() {
const configurableOptionsComponent = initConfigurableOptions('2897', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2895"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2896"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2895": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2896": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
}
},
"productId": "2897",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2895": {
"93": "4"
},
"2896": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2897", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2897()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-2897", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2897-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2897-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SE300;E30G1UK1;E30G1UK1BLK" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 9 Reviews" aria-label="5 Stars - 9 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(9 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d99df73() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d99df73()" @update-prices-2897.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2897" data-price-box="product-id-2897"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2897" data-price-amount="449" data-price-type="finalPrice" class="price-wrapper "><span class="price">£449<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2897()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2897()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2897 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2897 || window.initConfigurableDropdownOptions_2897);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2406/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2406/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2406">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/marshall-willen" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2406.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/m/a/marshall-willen-black-hero-small_1.jpg" loading="lazy" width="460" height="460" alt="Marshall Willen" title="Marshall Willen">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2406)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '2406')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/marshall-willen">
Marshall Willen </a>
</div>
<span class="text-secondary order-2">Compact, Portable Bluetooth Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2406(),
...amXnotifSubscribeComponent_2406(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2406"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2406">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2406">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2406() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2406',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2406() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2406",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2406",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2404":"1","2405":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2406(),
...amXnotifSubscribeComponentList_2406()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2406" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2406() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2406',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2406() {
const configurableOptionsComponent = initConfigurableOptions('2406', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "218",
"label": "Cream",
"products": ["2404"],
"class": ""
}, {
"id": "219",
"label": "Black & Brass",
"products": ["2405"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2404": {
"baseOldPrice": {
"amount": 0
},
"oldPrice": {
"amount": 0
},
"basePrice": {
"amount": 0
},
"finalPrice": {
"amount": 0
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2405": {
"baseOldPrice": {
"amount": 0
},
"oldPrice": {
"amount": 0
},
"basePrice": {
"amount": 0
},
"finalPrice": {
"amount": 0
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 82.499999
},
"oldPrice": {
"amount": 99
},
"basePrice": {
"amount": 82.499999
},
"finalPrice": {
"amount": 99
}
},
"productId": "2406",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2404": {
"93": "218"
},
"2405": {
"93": "219"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"218": {
"type": "1",
"value": "#fffdd0",
"label": "Cream"
},
"219": {
"type": "1",
"value": "#000000",
"label": "Black & Brass"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2406", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2406()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-2406", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2406-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2406-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="MAWILL;285930;285929" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 2 Reviews" aria-label="5 Stars - 2 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(2 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9a8e82() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9a8e82()" @update-prices-2406.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2406" data-price-box="product-id-2406"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2406" data-price-amount="99" data-price-type="finalPrice" class="price-wrapper "><span class="price">£99<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2406()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2406()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2406 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2406 || window.initConfigurableDropdownOptions_2406);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2592/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2592/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2592">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £40</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-sub-mini" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2592.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-sub-mini-hero-thumbnail-black.png" loading="lazy" width="460" height="460" alt="Sonos SUB Mini" title="Sonos SUB Mini">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2592)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '2592')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-sub-mini">
Sonos SUB Mini </a>
</div>
<span class="text-secondary order-2">Compact Wireless Subwoofer</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2592(),
...amXnotifSubscribeComponent_2592(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2592"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2592">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2592">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2592() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2592',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2592() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2592",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2592",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2590":"1","2591":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2592(),
...amXnotifSubscribeComponentList_2592()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2592" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2592() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2592',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2592() {
const configurableOptionsComponent = initConfigurableOptions('2592', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2591"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2590"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2590": {
"baseOldPrice": {
"amount": 357.499999
},
"oldPrice": {
"amount": 429
},
"basePrice": {
"amount": 324.16666566667
},
"finalPrice": {
"amount": 389
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2591": {
"baseOldPrice": {
"amount": 357.499999
},
"oldPrice": {
"amount": 429
},
"basePrice": {
"amount": 324.16666566667
},
"finalPrice": {
"amount": 389
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 357.499999
},
"oldPrice": {
"amount": 429
},
"basePrice": {
"amount": 324.16666566667
},
"finalPrice": {
"amount": 389
}
},
"productId": "2592",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2590": {
"93": "5"
},
"2591": {
"93": "4"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2592", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2592()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-2592", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2592-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2592-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SSUBM;SUBM1UK1BLK;SUBM1UK1" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.85 Stars - 59 Reviews" aria-label="4.85 Stars - 59 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--75"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(59 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9b38be() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9b38be()" @update-prices-2592.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2592" data-price-box="product-id-2592"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2592" data-price-amount="389" data-price-type="finalPrice" class="price-wrapper "><span class="price">£389<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-2592" data-price-amount="429" data-price-type="oldPrice" class="price-wrapper "><span class="price">£429.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2592()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2592()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2592 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2592 || window.initConfigurableDropdownOptions_2592);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £40 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3078/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3078/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3078">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save £201</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sony-xr65a95lu-65-qd-oled" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3078.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sony-a95l-thumbnail.png" loading="lazy" width="460" height="460" alt="Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV" title="Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/5-year-guarantee-lrg.png" alt="Free 5 Year Warranty Included" class="h-12" height="48" title="Free 5 Year Warranty Included" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3078)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '3078')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sony-xr65a95lu-65-qd-oled">
Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV </a>
</div>
<span class="text-secondary order-2">Sony XR65A95LU</span>
<div class="order-12 ">
<script>
'use strict';
function amNotificationProductViewComponent_3078() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3078",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3078",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('[]');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3078(),
...amXnotifSubscribeComponentList_3078()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3078" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3078() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '',
productIdentifier: '3078',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="XR65A95LU" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 2 Reviews" aria-label="5 Stars - 2 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(2 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9c9452() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9c9452()" @update-prices-3078.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3078" data-price-box="product-id-3078">
<span class="special-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">now</span>
<span id="product-price-3078" data-price-amount="3498" data-price-type="finalPrice" class="price-wrapper "><span class="price">£3,498<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-3078" data-price-amount="3699" data-price-type="oldPrice" class="price-wrapper "><span class="price">£3,699.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3078()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3078()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3078 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3078 || window.initConfigurableDropdownOptions_3078);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save £201 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3433/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3433/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3433">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/devialet-gemini-ii" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3433.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/d/e/devialet-gemini-ii-matte-black-thumbnail_3.png" loading="lazy" width="460" height="460" alt="Devialet Gemini II" title="Devialet Gemini II">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3433)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '3433')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/devialet-gemini-ii">
Devialet Gemini II </a>
</div>
<span class="text-secondary order-2">True Wireless Earbuds With Adaptive Noise Cancellation</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3433(),
...amXnotifSubscribeComponent_3433(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3433"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3433">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3433">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3433() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3433',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3433() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3433",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3433",
preOrderLabels: JSON.parse('{"3431":"Available soon"}') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3431":"1","3432":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3433(),
...amXnotifSubscribeComponentList_3433()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3433" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3433() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3433',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3433() {
const configurableOptionsComponent = initConfigurableOptions('3433', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["3431"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["3432"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3431": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3432": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
}
},
"productId": "3433",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3431": {
"93": "4"
},
"3432": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3433", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3433()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-3433", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3433-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3433-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="DVGII;1000828452;1000828453" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 3 Reviews" aria-label="5 Stars - 3 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(3 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9cebb1() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9cebb1()" @update-prices-3433.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3433" data-price-box="product-id-3433"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3433" data-price-amount="349" data-price-type="finalPrice" class="price-wrapper "><span class="price">£349<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3433()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3433()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3433 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3433 || window.initConfigurableDropdownOptions_3433);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3359/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3359/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3359">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/soundcore-motion-x600" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3359.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/a/n/anker-soundcore-portable-hi-res-wireless-speaker-thumbnail-space-gray_1.png" loading="lazy" width="460" height="460" alt="Soundcore Motion X600" title="Soundcore Motion X600">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3359)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '3359')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/soundcore-motion-x600">
Soundcore Motion X600 </a>
</div>
<span class="text-secondary order-2">Portable Hi-Res Wireless Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3359(),
...amXnotifSubscribeComponent_3359(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3359"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3359">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3359">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3359() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3359',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3359() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3359",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3359",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3358":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3359(),
...amXnotifSubscribeComponentList_3359()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3359" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3359() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3359',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3359() {
const configurableOptionsComponent = initConfigurableOptions('3359', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "5",
"label": "Black",
"products": ["3358"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3358": {
"baseOldPrice": {
"amount": 165.83333233333
},
"oldPrice": {
"amount": 199
},
"basePrice": {
"amount": 165.83333233333
},
"finalPrice": {
"amount": 199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 165.83333233333
},
"oldPrice": {
"amount": 199
},
"basePrice": {
"amount": 165.83333233333
},
"finalPrice": {
"amount": 199
}
},
"productId": "3359",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3358": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3359", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3359()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-3359", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3359-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3359-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SMX600;A3130011" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Star - 1 Review" aria-label="5 Star - 1 Review" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(1 Review)</span></div>
</div>
<script>
function initPriceBox__673358d9d99b0() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9d99b0()" @update-prices-3359.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3359" data-price-box="product-id-3359"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3359" data-price-amount="199" data-price-type="finalPrice" class="price-wrapper "><span class="price">£199<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3359()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3359()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3359 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3359 || window.initConfigurableDropdownOptions_3359);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3349/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3349/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3349">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r1s-smart-radio-mid-grey" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3349.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/r/u/ruark-r1s-smart-radio-hero-thumbnail.jpg" loading="lazy" width="460" height="460" alt="Ruark R1S Smart Radio" title="Ruark R1S Smart Radio">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3349)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '3349')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r1s-smart-radio-mid-grey">
Ruark R1S Smart Radio </a>
</div>
<span class="text-secondary order-2">DAB, DAB+, FM Tuner + WiFi, Bluetooth (Mid Grey)</span>
<div class="order-12 ">
<script>
'use strict';
function amNotificationProductViewComponent_3349() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3349",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3349",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('[]');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3349(),
...amXnotifSubscribeComponentList_3349()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3349" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3349() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '',
productIdentifier: '3349',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="R1DX-MG" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true"></div>
</div>
<script>
function initPriceBox__673358d9e618c() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9e618c()" @update-prices-3349.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3349" data-price-box="product-id-3349">
<span class="price-container price-final_price tax weee rewards_earn">
<span id="product-price-3349" data-price-amount="299" data-price-type="finalPrice" class="price-wrapper "><span class="price">£299<span class="align-text-top text-xs">.00</span></span></span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3349()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3349()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3349 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3349 || window.initConfigurableDropdownOptions_3349);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3336/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3336/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3336">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r410-integrated-music-system" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3336.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/r/u/ruark-r410-integrated-music-system-walnut_1.png" loading="lazy" width="460" height="460" alt="Ruark R410 Integrated music system" title="Ruark R410 Integrated music system">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3336)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '3336')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r410-integrated-music-system">
Ruark R410 Integrated music system </a>
</div>
<span class="text-secondary order-2">All-in-one Wireless Music System </span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3336(),
...amXnotifSubscribeComponent_3336(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3336"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3336">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3336">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3336() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3336',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3336() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3336",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3336",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3334":"1","3335":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3336(),
...amXnotifSubscribeComponentList_3336()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3336" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3336() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3336',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3336() {
const configurableOptionsComponent = initConfigurableOptions('3336', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "66",
"label": "Walnut",
"products": ["3335"],
"class": ""
}, {
"id": "266",
"label": "Soft Grey",
"products": ["3334"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3334": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3335": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
}
},
"productId": "3336",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3334": {
"93": "266"
},
"3335": {
"93": "66"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"66": {
"type": "1",
"value": "#6e5a0c",
"label": "Walnut"
},
"266": {
"type": "1",
"value": "#858585",
"label": "Soft Grey"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3336", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3336()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-3336", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3336-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3336-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="RR410;R410-SG;R410-FW" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Star - 1 Review" aria-label="5 Star - 1 Review" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(1 Review)</span></div>
</div>
<script>
function initPriceBox__673358d9ea046() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9ea046()" @update-prices-3336.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3336" data-price-box="product-id-3336"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3336" data-price-amount="1299" data-price-type="finalPrice" class="price-wrapper "><span class="price">£1,299<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3336()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3336()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3336 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3336 || window.initConfigurableDropdownOptions_3336);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2202/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2202/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2202">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £99</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/audio-pro-a15" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2202.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/a/u/audio-pro-a15-main-small.jpg" loading="lazy" width="460" height="460" alt="Audio Pro A15" title="Audio Pro A15">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2202)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '2202')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/audio-pro-a15">
Audio Pro A15 </a>
</div>
<span class="text-secondary order-2">The portable, multi-room speaker with Bluetooth & WiFi functionality</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2202(),
...amXnotifSubscribeComponent_2202(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2202"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2202">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2202">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2202() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2202',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2202() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2202",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2202",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2200":"1","2201":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2202(),
...amXnotifSubscribeComponentList_2202()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2202" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2202() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2202',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2202() {
const configurableOptionsComponent = initConfigurableOptions('2202', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "194",
"label": "Dark Grey",
"products": ["2200"],
"class": ""
}, {
"id": "207",
"label": "Light Grey",
"products": ["2201"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2200": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2201": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
}
},
"productId": "2202",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2200": {
"93": "194"
},
"2201": {
"93": "207"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"194": {
"type": "1",
"value": "#3d3c3d",
"label": "Dark Grey"
},
"207": {
"type": "1",
"value": "#ccc8cc",
"label": "Light Grey"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2202", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2202()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-2202", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2202-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2202-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="AP15;00213786;00213787" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true"></div>
</div>
<script>
function initPriceBox__673358da00960() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358da00960()" @update-prices-2202.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2202" data-price-box="product-id-2202"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2202" data-price-amount="250" data-price-type="finalPrice" class="price-wrapper "><span class="price">£250<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-2202" data-price-amount="349" data-price-type="oldPrice" class="price-wrapper "><span class="price">£349.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2202()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2202()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2202 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2202 || window.initConfigurableDropdownOptions_2202);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £99 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3433/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3433/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3433">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/devialet-gemini-ii" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3433.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/d/e/devialet-gemini-ii-matte-black-thumbnail_3.png" loading="lazy" width="460" height="460" alt="Devialet Gemini II" title="Devialet Gemini II">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3433)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/devialet-gemini-ii">
Devialet Gemini II </a>
</div>
<span class="text-secondary order-2">True Wireless Earbuds With Adaptive Noise Cancellation</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3433(),
...amXnotifSubscribeComponent_3433(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3433"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3433">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3433">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3433() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3433',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3433() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3433",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3433",
preOrderLabels: JSON.parse('{"3431":"Available soon"}') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3431":"1","3432":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3433(),
...amXnotifSubscribeComponentList_3433()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3433" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3433() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3433',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3433() {
const configurableOptionsComponent = initConfigurableOptions('3433', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["3431"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["3432"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3431": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3432": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
}
},
"productId": "3433",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3431": {
"93": "4"
},
"3432": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3433", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3433()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3433-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3433-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="DVGII;1000828452;1000828453" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 3 Reviews" aria-label="5 Stars - 3 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(3 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9cebb1() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9cebb1()" @update-prices-3433.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3433" data-price-box="product-id-3433"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3433" data-price-amount="349" data-price-type="finalPrice" class="price-wrapper "><span class="price">£349<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3433()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3433()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3433 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3433 || window.initConfigurableDropdownOptions_3433);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3359/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3359/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3359">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/soundcore-motion-x600" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3359.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/a/n/anker-soundcore-portable-hi-res-wireless-speaker-thumbnail-space-gray_1.png" loading="lazy" width="460" height="460" alt="Soundcore Motion X600" title="Soundcore Motion X600">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3359)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/soundcore-motion-x600">
Soundcore Motion X600 </a>
</div>
<span class="text-secondary order-2">Portable Hi-Res Wireless Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3359(),
...amXnotifSubscribeComponent_3359(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3359"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3359">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3359">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3359() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3359',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3359() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3359",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3359",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3358":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3359(),
...amXnotifSubscribeComponentList_3359()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3359" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3359() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3359',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3359() {
const configurableOptionsComponent = initConfigurableOptions('3359', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "5",
"label": "Black",
"products": ["3358"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3358": {
"baseOldPrice": {
"amount": 165.83333233333
},
"oldPrice": {
"amount": 199
},
"basePrice": {
"amount": 165.83333233333
},
"finalPrice": {
"amount": 199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 165.83333233333
},
"oldPrice": {
"amount": 199
},
"basePrice": {
"amount": 165.83333233333
},
"finalPrice": {
"amount": 199
}
},
"productId": "3359",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3358": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3359", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3359()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3359-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3359-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SMX600;A3130011" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Star - 1 Review" aria-label="5 Star - 1 Review" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(1 Review)</span></div>
</div>
<script>
function initPriceBox__673358d9d99b0() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9d99b0()" @update-prices-3359.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3359" data-price-box="product-id-3359"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3359" data-price-amount="199" data-price-type="finalPrice" class="price-wrapper "><span class="price">£199<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3359()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3359()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3359 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3359 || window.initConfigurableDropdownOptions_3359);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3349/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3349/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3349">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r1s-smart-radio-mid-grey" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3349.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/r/u/ruark-r1s-smart-radio-hero-thumbnail.jpg" loading="lazy" width="460" height="460" alt="Ruark R1S Smart Radio" title="Ruark R1S Smart Radio">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3349)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r1s-smart-radio-mid-grey">
Ruark R1S Smart Radio </a>
</div>
<span class="text-secondary order-2">DAB, DAB+, FM Tuner + WiFi, Bluetooth (Mid Grey)</span>
<div class="order-12 ">
<script>
'use strict';
function amNotificationProductViewComponent_3349() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3349",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3349",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('[]');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3349(),
...amXnotifSubscribeComponentList_3349()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3349" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3349() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '',
productIdentifier: '3349',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="R1DX-MG" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true"></div>
</div>
<script>
function initPriceBox__673358d9e618c() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9e618c()" @update-prices-3349.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3349" data-price-box="product-id-3349">
<span class="price-container price-final_price tax weee rewards_earn">
<span id="product-price-3349" data-price-amount="299" data-price-type="finalPrice" class="price-wrapper "><span class="price">£299<span class="align-text-top text-xs">.00</span></span></span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3349()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3349()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3349 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3349 || window.initConfigurableDropdownOptions_3349);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3336/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3336/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3336">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r410-integrated-music-system" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3336.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/r/u/ruark-r410-integrated-music-system-walnut_1.png" loading="lazy" width="460" height="460" alt="Ruark R410 Integrated music system" title="Ruark R410 Integrated music system">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3336)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r410-integrated-music-system">
Ruark R410 Integrated music system </a>
</div>
<span class="text-secondary order-2">All-in-one Wireless Music System </span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3336(),
...amXnotifSubscribeComponent_3336(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3336"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3336">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3336">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3336() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3336',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3336() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3336",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3336",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3334":"1","3335":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3336(),
...amXnotifSubscribeComponentList_3336()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3336" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3336() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3336',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3336() {
const configurableOptionsComponent = initConfigurableOptions('3336', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "66",
"label": "Walnut",
"products": ["3335"],
"class": ""
}, {
"id": "266",
"label": "Soft Grey",
"products": ["3334"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3334": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3335": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
}
},
"productId": "3336",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3334": {
"93": "266"
},
"3335": {
"93": "66"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"66": {
"type": "1",
"value": "#6e5a0c",
"label": "Walnut"
},
"266": {
"type": "1",
"value": "#858585",
"label": "Soft Grey"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3336", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3336()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3336-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3336-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="RR410;R410-SG;R410-FW" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Star - 1 Review" aria-label="5 Star - 1 Review" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(1 Review)</span></div>
</div>
<script>
function initPriceBox__673358d9ea046() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9ea046()" @update-prices-3336.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3336" data-price-box="product-id-3336"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3336" data-price-amount="1299" data-price-type="finalPrice" class="price-wrapper "><span class="price">£1,299<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3336()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3336()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3336 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3336 || window.initConfigurableDropdownOptions_3336);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3078/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3078/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3078">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save £201</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sony-xr65a95lu-65-qd-oled" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3078.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sony-a95l-thumbnail.png" loading="lazy" width="460" height="460" alt="Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV" title="Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/5-year-guarantee-lrg.png" alt="Free 5 Year Warranty Included" class="h-12" height="48" title="Free 5 Year Warranty Included" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3078)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sony-xr65a95lu-65-qd-oled">
Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV </a>
</div>
<span class="text-secondary order-2">Sony XR65A95LU</span>
<div class="order-12 ">
<script>
'use strict';
function amNotificationProductViewComponent_3078() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3078",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3078",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('[]');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3078(),
...amXnotifSubscribeComponentList_3078()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3078" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3078() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '',
productIdentifier: '3078',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="XR65A95LU" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 2 Reviews" aria-label="5 Stars - 2 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(2 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9c9452() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9c9452()" @update-prices-3078.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3078" data-price-box="product-id-3078">
<span class="special-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">now</span>
<span id="product-price-3078" data-price-amount="3498" data-price-type="finalPrice" class="price-wrapper "><span class="price">£3,498<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-3078" data-price-amount="3699" data-price-type="oldPrice" class="price-wrapper "><span class="price">£3,699.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3078()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3078()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3078 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3078 || window.initConfigurableDropdownOptions_3078);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save £201 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2202/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2202/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2202">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £99</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/audio-pro-a15" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2202.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/a/u/audio-pro-a15-main-small.jpg" loading="lazy" width="460" height="460" alt="Audio Pro A15" title="Audio Pro A15">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2202)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/audio-pro-a15">
Audio Pro A15 </a>
</div>
<span class="text-secondary order-2">The portable, multi-room speaker with Bluetooth & WiFi functionality</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2202(),
...amXnotifSubscribeComponent_2202(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2202"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2202">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2202">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2202() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2202',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2202() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2202",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2202",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2200":"1","2201":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2202(),
...amXnotifSubscribeComponentList_2202()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2202" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2202() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2202',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2202() {
const configurableOptionsComponent = initConfigurableOptions('2202', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "194",
"label": "Dark Grey",
"products": ["2200"],
"class": ""
}, {
"id": "207",
"label": "Light Grey",
"products": ["2201"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2200": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2201": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
}
},
"productId": "2202",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2200": {
"93": "194"
},
"2201": {
"93": "207"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"194": {
"type": "1",
"value": "#3d3c3d",
"label": "Dark Grey"
},
"207": {
"type": "1",
"value": "#ccc8cc",
"label": "Light Grey"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2202", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2202()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2202-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2202-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="AP15;00213786;00213787" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true"></div>
</div>
<script>
function initPriceBox__673358da00960() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358da00960()" @update-prices-2202.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2202" data-price-box="product-id-2202"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2202" data-price-amount="250" data-price-type="finalPrice" class="price-wrapper "><span class="price">£250<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-2202" data-price-amount="349" data-price-type="oldPrice" class="price-wrapper "><span class="price">£349.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2202()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2202()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2202 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2202 || window.initConfigurableDropdownOptions_2202);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £99 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3449/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3449/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3449">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-move-2" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1" x-data="showHoverImage_673358d978dcd">
<figure @mouseover="showHover()" @mouseleave="showMain()" class="transition duration-300 absolute inset-0 opacity-100" :class="{
'opacity-0': !mainImage,
'opacity-100': mainImage,
}">
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3449.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-move-2-hero-black_listing.jpg" loading="lazy" width="460" height="460" alt="Sonos Move 2" title="Sonos Move 2">
</figure>
<figure @mouseover="showHover()" @mouseleave="showMain()" class="transition duration-300 absolute inset-0 opacity-0" :class="{
'opacity-0': !hoverImage,
'opacity-100': hoverImage,
}">
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3449.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/0/1/01e3623849b27f6aa146ba93f75bcf7f653cb16d-2500x1667.jpg" loading="lazy" width="460" height="460" alt="Sonos Move 2" title="Sonos Move 2">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3449)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<script>
function showHoverImage_673358d978dcd() {
return {
mainImage: true,
hoverImage: false,
showHover() {
this.mainImage = false;
this.hoverImage = true;
},
showMain() {
this.mainImage = true;
this.hoverImage = false;
}
}
}
</script>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-move-2">
Sonos Move 2 </a>
</div>
<span class="text-secondary order-2">Portable Wi-Fi & Bluetooth Smart Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3449(),
...amXnotifSubscribeComponent_3449(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3449"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3449">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3449">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3449() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3449',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3449() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3449",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3449",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3447":"1","3448":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3449(),
...amXnotifSubscribeComponentList_3449()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3449" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3449() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3449',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3449() {
const configurableOptionsComponent = initConfigurableOptions('3449', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["3447"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["3448"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3447": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3448": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
}
},
"productId": "3449",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3447": {
"93": "4"
},
"3448": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3449", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3449()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3449-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3449-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SM2;MOVE2UK1;MOVE2UK1BLK" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.8 Stars - 10 Reviews" aria-label="4.8 Stars - 10 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--75"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(10 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9775e2() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9775e2()" @update-prices-3449.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3449" data-price-box="product-id-3449"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3449" data-price-amount="449" data-price-type="finalPrice" class="price-wrapper "><span class="price">£449<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3449()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3449()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3449 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3449 || window.initConfigurableDropdownOptions_3449);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2897/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2897/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2897">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-era-300" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2897.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-era-300-black-thumbnail.jpg" loading="lazy" width="460" height="460" alt="Sonos ERA 300" title="Sonos ERA 300">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2897)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-era-300">
Sonos ERA 300 </a>
</div>
<span class="text-secondary order-2">Dolby Atmos Smart Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2897(),
...amXnotifSubscribeComponent_2897(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2897"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2897">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2897">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2897() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2897',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2897() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2897",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2897",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2895":"1","2896":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2897(),
...amXnotifSubscribeComponentList_2897()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2897" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2897() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2897',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2897() {
const configurableOptionsComponent = initConfigurableOptions('2897', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2895"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2896"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2895": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2896": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 374.16666566667
},
"oldPrice": {
"amount": 449
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
}
},
"productId": "2897",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2895": {
"93": "4"
},
"2896": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2897", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2897()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2897-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2897-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SE300;E30G1UK1;E30G1UK1BLK" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 9 Reviews" aria-label="5 Stars - 9 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(9 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d99df73() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d99df73()" @update-prices-2897.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2897" data-price-box="product-id-2897"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2897" data-price-amount="449" data-price-type="finalPrice" class="price-wrapper "><span class="price">£449<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2897()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2897()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2897 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2897 || window.initConfigurableDropdownOptions_2897);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2592/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2592/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2592">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £40</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-sub-mini" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2592.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-sub-mini-hero-thumbnail-black.png" loading="lazy" width="460" height="460" alt="Sonos SUB Mini" title="Sonos SUB Mini">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2592)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-sub-mini">
Sonos SUB Mini </a>
</div>
<span class="text-secondary order-2">Compact Wireless Subwoofer</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2592(),
...amXnotifSubscribeComponent_2592(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2592"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2592">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2592">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2592() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2592',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2592() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2592",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2592",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2590":"1","2591":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2592(),
...amXnotifSubscribeComponentList_2592()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2592" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2592() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2592',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2592() {
const configurableOptionsComponent = initConfigurableOptions('2592', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2591"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2590"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2590": {
"baseOldPrice": {
"amount": 357.499999
},
"oldPrice": {
"amount": 429
},
"basePrice": {
"amount": 324.16666566667
},
"finalPrice": {
"amount": 389
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2591": {
"baseOldPrice": {
"amount": 357.499999
},
"oldPrice": {
"amount": 429
},
"basePrice": {
"amount": 324.16666566667
},
"finalPrice": {
"amount": 389
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 357.499999
},
"oldPrice": {
"amount": 429
},
"basePrice": {
"amount": 324.16666566667
},
"finalPrice": {
"amount": 389
}
},
"productId": "2592",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2590": {
"93": "5"
},
"2591": {
"93": "4"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2592", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2592()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2592-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2592-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SSUBM;SUBM1UK1BLK;SUBM1UK1" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.85 Stars - 59 Reviews" aria-label="4.85 Stars - 59 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--75"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(59 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9b38be() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9b38be()" @update-prices-2592.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2592" data-price-box="product-id-2592"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2592" data-price-amount="389" data-price-type="finalPrice" class="price-wrapper "><span class="price">£389<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-2592" data-price-amount="429" data-price-type="oldPrice" class="price-wrapper "><span class="price">£429.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2592()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2592()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2592 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2592 || window.initConfigurableDropdownOptions_2592);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £40 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2429/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2429/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2429">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/kef-lsxii-active-bookshelf-speaker" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2429.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/k/e/kef-lsx-ii-black-hero-small.jpg" loading="lazy" width="460" height="460" alt="KEF LSX II Active Bookshelf Speaker (Pair)" title="KEF LSX II Active Bookshelf Speaker (Pair)">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/5-year-guarantee.png" alt="Free 5 Year KEF Warranty*" class="h-12" height="48" title="Free 5 Year KEF Warranty*" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2429)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/kef-lsxii-active-bookshelf-speaker">
KEF LSX II Active Bookshelf Speaker (Pair) </a>
</div>
<span class="text-secondary order-2">Wireless Hi-Fi Speakers</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2429(),
...amXnotifSubscribeComponent_2429(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2429"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2429">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2429">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2429() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2429',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2429() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2429",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2429",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2424":"0","2425":"1","2426":"1","2427":"1","2428":"1","3404":"0"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2429(),
...amXnotifSubscribeComponentList_2429()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2429" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2429() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2429',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2429() {
const configurableOptionsComponent = initConfigurableOptions('2429', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2426"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2425"],
"class": ""
}, {
"id": "43",
"label": "Blue",
"products": ["2424"],
"class": "out-of-stock"
}, {
"id": "44",
"label": "Olive",
"products": ["3404"],
"class": "out-of-stock"
}, {
"id": "64",
"label": "Red",
"products": ["2428"],
"class": ""
}, {
"id": "223",
"label": "Soundwave",
"products": ["2427"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2424": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2425": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2426": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2427": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2428": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3404": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 999.16666566667
},
"oldPrice": {
"amount": 1199
},
"basePrice": {
"amount": 999.16666566667
},
"finalPrice": {
"amount": 1199
}
},
"productId": "2429",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2424": {
"93": "43"
},
"2425": {
"93": "5"
},
"2426": {
"93": "4"
},
"2427": {
"93": "223"
},
"2428": {
"93": "64"
},
"3404": {
"93": "44"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"43": {
"type": "1",
"value": "#2049b0",
"label": "Blue"
},
"44": {
"type": "1",
"value": "#377039",
"label": "Olive"
},
"64": {
"type": "1",
"value": "#f20c0c",
"label": "Red"
},
"223": {
"type": "1",
"value": "#f5f5dc",
"label": "Soundwave"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2429", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2429()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2429-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2429-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="KLSXII;10309126;10309125;10309127;10309129;10309128;1000828449" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Star - 1 Review" aria-label="5 Star - 1 Review"
data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(1 Review)</span></div>
</div>
<script>
function initPriceBox__673358d98d8c7() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d98d8c7()" @update-prices-2429.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2429" data-price-box="product-id-2429"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2429" data-price-amount="1199" data-price-type="finalPrice" class="price-wrapper "><span class="price">£1,199<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2429()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2429()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2429 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2429 || window.initConfigurableDropdownOptions_2429);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2406/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2406/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2406">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/marshall-willen" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2406.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/m/a/marshall-willen-black-hero-small_1.jpg" loading="lazy" width="460" height="460" alt="Marshall Willen" title="Marshall Willen">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2406)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/marshall-willen">
Marshall Willen </a>
</div>
<span class="text-secondary order-2">Compact, Portable Bluetooth Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2406(),
...amXnotifSubscribeComponent_2406(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2406"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2406">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2406">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2406() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2406',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2406() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2406",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2406",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2404":"1","2405":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2406(),
...amXnotifSubscribeComponentList_2406()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2406" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2406() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2406',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2406() {
const configurableOptionsComponent = initConfigurableOptions('2406', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "218",
"label": "Cream",
"products": ["2404"],
"class": ""
}, {
"id": "219",
"label": "Black & Brass",
"products": ["2405"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2404": {
"baseOldPrice": {
"amount": 0
},
"oldPrice": {
"amount": 0
},
"basePrice": {
"amount": 0
},
"finalPrice": {
"amount": 0
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2405": {
"baseOldPrice": {
"amount": 0
},
"oldPrice": {
"amount": 0
},
"basePrice": {
"amount": 0
},
"finalPrice": {
"amount": 0
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 82.499999
},
"oldPrice": {
"amount": 99
},
"basePrice": {
"amount": 82.499999
},
"finalPrice": {
"amount": 99
}
},
"productId": "2406",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2404": {
"93": "218"
},
"2405": {
"93": "219"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"218": {
"type": "1",
"value": "#fffdd0",
"label": "Cream"
},
"219": {
"type": "1",
"value": "#000000",
"label": "Black & Brass"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2406", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2406()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2406-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2406-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="MAWILL;285930;285929" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 2 Reviews" aria-label="5 Stars - 2 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(2 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9a8e82() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9a8e82()" @update-prices-2406.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2406" data-price-box="product-id-2406"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2406" data-price-amount="99" data-price-type="finalPrice" class="price-wrapper "><span class="price">£99<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2406()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2406()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2406 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2406 || window.initConfigurableDropdownOptions_2406);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/933/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/933/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="933">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £45</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-one-sl" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1" x-data="showHoverImage_673358d963209">
<figure @mouseover="showHover()" @mouseleave="showMain()" class="transition duration-300 absolute inset-0 opacity-100" :class="{
'opacity-0': !mainImage,
'opacity-100': mainImage,
}">
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-933.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/o/n/one-angle-dropped-2_1.jpg" loading="lazy" width="460" height="460" alt="Sonos One SL" title="Sonos One SL">
</figure>
<figure @mouseover="showHover()" @mouseleave="showMain()" class="transition duration-300 absolute inset-0 opacity-0" :class="{
'opacity-0': !hoverImage,
'opacity-100': hoverImage,
}">
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-933.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-one-sl-living-bookshelf-2-square.jpg" loading="lazy" width="460" height="460" alt="Sonos One SL" title="Sonos One SL">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(933)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<script>
function showHoverImage_673358d963209() {
return {
mainImage: true,
hoverImage: false,
showHover() {
this.mainImage = false;
this.hoverImage = true;
},
showMain() {
this.mainImage = true;
this.hoverImage = false;
}
}
}
</script>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-one-sl">
Sonos One SL </a>
</div>
<span class="text-secondary order-2">The speaker for stereo pairing and home theatre surrounds</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_933(),
...amXnotifSubscribeComponent_933(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-933"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-933">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="933">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_933() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '933',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_933() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "933",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "933",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"931":"1","932":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_933(),
...amXnotifSubscribeComponentList_933()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_933" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_933() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '933',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_933() {
const configurableOptionsComponent = initConfigurableOptions('933', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["931"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["932"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"931": {
"baseOldPrice": {
"amount": 149.16666566667
},
"oldPrice": {
"amount": 179
},
"basePrice": {
"amount": 111.66666566667
},
"finalPrice": {
"amount": 134
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"932": {
"baseOldPrice": {
"amount": 149.16666566667
},
"oldPrice": {
"amount": 179
},
"basePrice": {
"amount": 111.66666566667
},
"finalPrice": {
"amount": 134
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 149.16666566667
},
"oldPrice": {
"amount": 179
},
"basePrice": {
"amount": 111.66666566667
},
"finalPrice": {
"amount": 134
}
},
"productId": "933",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"931": {
"93": "4"
},
"932": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-933", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_933()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-933-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-933-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="ONESL;ONESLUK1;ONESLUK1BLK" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.89 Stars - 36 Reviews" aria-label="4.89 Stars - 36 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(36 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d95df3e() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d95df3e()" @update-prices-933.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="933" data-price-box="product-id-933"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-933" data-price-amount="134" data-price-type="finalPrice" class="price-wrapper "><span class="price">£134<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-933" data-price-amount="179" data-price-type="oldPrice" class="price-wrapper "><span class="price">£179.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_933()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_933()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_933 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_933 || window.initConfigurableDropdownOptions_933);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £45 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/651/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/651/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="651">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £50</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-beam" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-651.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-beam-gen2-black-small.png" loading="lazy" width="460" height="460" alt="Sonos Beam (Gen 2)" title="Sonos Beam (Gen 2)">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(651)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-beam">
Sonos Beam (Gen 2) </a>
</div>
<span class="text-secondary order-2">Compact Smart Soundbar with Dolby Atmos</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_651(),
...amXnotifSubscribeComponent_651(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-651"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-651">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="651">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_651() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '651',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_651() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "651",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "651",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"650":"1","649":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_651(),
...amXnotifSubscribeComponentList_651()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_651" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_651() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '651',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_651() {
const configurableOptionsComponent = initConfigurableOptions('651', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["650"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["649"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"650": {
"baseOldPrice": {
"amount": 415.83333233333
},
"oldPrice": {
"amount": 499
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"649": {
"baseOldPrice": {
"amount": 415.83333233333
},
"oldPrice": {
"amount": 499
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 415.83333233333
},
"oldPrice": {
"amount": 499
},
"basePrice": {
"amount": 374.16666566667
},
"finalPrice": {
"amount": 449
}
},
"productId": "651",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"650": {
"93": "4"
},
"649": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-651", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_651()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-651-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-651-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SHS299;BEAM2UK1;BEAM2UK1BLK" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.8 Stars - 172 Reviews" aria-label="4.8 Stars - 172 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--75"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(172 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9822c9() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9822c9()" @update-prices-651.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="651" data-price-box="product-id-651"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-651" data-price-amount="449" data-price-type="finalPrice" class="price-wrapper "><span class="price">£449<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-651" data-price-amount="499" data-price-type="oldPrice" class="price-wrapper "><span class="price">£499.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_651()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_651()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_651 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_651 || window.initConfigurableDropdownOptions_651);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £50 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3078/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3078/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3078">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save £201</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sony-xr65a95lu-65-qd-oled" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3078.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sony-a95l-thumbnail.png" loading="lazy" width="460" height="460" alt="Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV" title="Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/5-year-guarantee-lrg.png" alt="Free 5 Year Warranty Included" class="h-12" height="48" title="Free 5 Year Warranty Included" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3078)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sony-xr65a95lu-65-qd-oled">
Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV </a>
</div>
<span class="text-secondary order-2">Sony XR65A95LU</span>
<div class="order-12 ">
<script>
'use strict';
function amNotificationProductViewComponent_3078() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3078",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3078",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('[]');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3078(),
...amXnotifSubscribeComponentList_3078()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3078" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3078() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '',
productIdentifier: '3078',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="XR65A95LU" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 2 Reviews" aria-label="5 Stars - 2 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(2 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9c9452() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9c9452()" @update-prices-3078.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3078" data-price-box="product-id-3078">
<span class="special-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">now</span>
<span id="product-price-3078" data-price-amount="3498" data-price-type="finalPrice" class="price-wrapper "><span class="price">£3,498<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-3078" data-price-amount="3699" data-price-type="oldPrice" class="price-wrapper "><span class="price">£3,699.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3078()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3078()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3078 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3078 || window.initConfigurableDropdownOptions_3078);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save £201 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3433/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3433/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3433">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/devialet-gemini-ii" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3433.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/d/e/devialet-gemini-ii-matte-black-thumbnail_3.png" loading="lazy" width="460" height="460" alt="Devialet Gemini II" title="Devialet Gemini II">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3433)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/devialet-gemini-ii">
Devialet Gemini II </a>
</div>
<span class="text-secondary order-2">True Wireless Earbuds With Adaptive Noise Cancellation</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3433(),
...amXnotifSubscribeComponent_3433(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3433"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3433">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3433">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3433() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3433',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3433() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3433",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3433",
preOrderLabels: JSON.parse('{"3431":"Available soon"}') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3431":"1","3432":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3433(),
...amXnotifSubscribeComponentList_3433()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3433" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3433() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3433',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3433() {
const configurableOptionsComponent = initConfigurableOptions('3433', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["3431"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["3432"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3431": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3432": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 290.83333233333
},
"finalPrice": {
"amount": 349
}
},
"productId": "3433",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3431": {
"93": "4"
},
"3432": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3433", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3433()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3433-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3433-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="DVGII;1000828452;1000828453" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 3 Reviews" aria-label="5 Stars - 3 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(3 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358d9cebb1() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9cebb1()" @update-prices-3433.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3433" data-price-box="product-id-3433"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3433" data-price-amount="349" data-price-type="finalPrice" class="price-wrapper "><span class="price">£349<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3433()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3433()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3433 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3433 || window.initConfigurableDropdownOptions_3433);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3359/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3359/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3359">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/soundcore-motion-x600" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3359.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/a/n/anker-soundcore-portable-hi-res-wireless-speaker-thumbnail-space-gray_1.png" loading="lazy" width="460" height="460" alt="Soundcore Motion X600" title="Soundcore Motion X600">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3359)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/soundcore-motion-x600">
Soundcore Motion X600 </a>
</div>
<span class="text-secondary order-2">Portable Hi-Res Wireless Speaker</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3359(),
...amXnotifSubscribeComponent_3359(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3359"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3359">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3359">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3359() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3359',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3359() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3359",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3359",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3358":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3359(),
...amXnotifSubscribeComponentList_3359()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3359" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3359() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3359',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3359() {
const configurableOptionsComponent = initConfigurableOptions('3359', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "5",
"label": "Black",
"products": ["3358"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3358": {
"baseOldPrice": {
"amount": 165.83333233333
},
"oldPrice": {
"amount": 199
},
"basePrice": {
"amount": 165.83333233333
},
"finalPrice": {
"amount": 199
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 165.83333233333
},
"oldPrice": {
"amount": 199
},
"basePrice": {
"amount": 165.83333233333
},
"finalPrice": {
"amount": 199
}
},
"productId": "3359",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3358": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3359", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3359()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3359-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3359-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SMX600;A3130011" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Star - 1 Review" aria-label="5 Star - 1 Review" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(1 Review)</span></div>
</div>
<script>
function initPriceBox__673358d9d99b0() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9d99b0()" @update-prices-3359.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3359" data-price-box="product-id-3359"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3359" data-price-amount="199" data-price-type="finalPrice" class="price-wrapper "><span class="price">£199<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3359()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3359()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3359 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3359 || window.initConfigurableDropdownOptions_3359);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3349/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3349/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3349">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r1s-smart-radio-mid-grey" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3349.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/r/u/ruark-r1s-smart-radio-hero-thumbnail.jpg" loading="lazy" width="460" height="460" alt="Ruark R1S Smart Radio" title="Ruark R1S Smart Radio">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3349)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r1s-smart-radio-mid-grey">
Ruark R1S Smart Radio </a>
</div>
<span class="text-secondary order-2">DAB, DAB+, FM Tuner + WiFi, Bluetooth (Mid Grey)</span>
<div class="order-12 ">
<script>
'use strict';
function amNotificationProductViewComponent_3349() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3349",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3349",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('[]');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3349(),
...amXnotifSubscribeComponentList_3349()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3349" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3349() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '',
productIdentifier: '3349',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="R1DX-MG" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true"></div>
</div>
<script>
function initPriceBox__673358d9e618c() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9e618c()" @update-prices-3349.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3349" data-price-box="product-id-3349">
<span class="price-container price-final_price tax weee rewards_earn">
<span id="product-price-3349" data-price-amount="299" data-price-type="finalPrice" class="price-wrapper "><span class="price">£299<span class="align-text-top text-xs">.00</span></span></span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3349()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3349()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3349 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3349 || window.initConfigurableDropdownOptions_3349);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3336/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/3336/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="3336">
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r410-integrated-music-system" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-3336.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/r/u/ruark-r410-integrated-music-system-walnut_1.png" loading="lazy" width="460" height="460" alt="Ruark R410 Integrated music system" title="Ruark R410 Integrated music system">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(3336)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/ruark-r410-integrated-music-system">
Ruark R410 Integrated music system </a>
</div>
<span class="text-secondary order-2">All-in-one Wireless Music System </span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_3336(),
...amXnotifSubscribeComponent_3336(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-3336"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-3336">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="3336">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_3336() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3336',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_3336() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "3336",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "3336",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"3334":"1","3335":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_3336(),
...amXnotifSubscribeComponentList_3336()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_3336" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_3336() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '3336',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_3336() {
const configurableOptionsComponent = initConfigurableOptions('3336', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "66",
"label": "Walnut",
"products": ["3335"],
"class": ""
}, {
"id": "266",
"label": "Soft Grey",
"products": ["3334"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"3334": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"3335": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 1082.499999
},
"oldPrice": {
"amount": 1299
},
"basePrice": {
"amount": 1082.499999
},
"finalPrice": {
"amount": 1299
}
},
"productId": "3336",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"3334": {
"93": "266"
},
"3335": {
"93": "66"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"66": {
"type": "1",
"value": "#6e5a0c",
"label": "Walnut"
},
"266": {
"type": "1",
"value": "#858585",
"label": "Soft Grey"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-3336", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_3336()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-3336-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-3336-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="RR410;R410-SG;R410-FW" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Star - 1 Review" aria-label="5 Star - 1 Review" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(1 Review)</span></div>
</div>
<script>
function initPriceBox__673358d9ea046() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358d9ea046()" @update-prices-3336.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="3336" data-price-box="product-id-3336"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-3336" data-price-amount="1299" data-price-type="finalPrice" class="price-wrapper "><span class="price">£1,299<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_3336()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_3336()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_3336 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_3336 || window.initConfigurableDropdownOptions_3336);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2202/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2202/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2202">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £99</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/audio-pro-a15" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2202.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/a/u/audio-pro-a15-main-small.jpg" loading="lazy" width="460" height="460" alt="Audio Pro A15" title="Audio Pro A15">
</figure>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2202)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline "> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/audio-pro-a15">
Audio Pro A15 </a>
</div>
<span class="text-secondary order-2">The portable, multi-room speaker with Bluetooth & WiFi functionality</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2202(),
...amXnotifSubscribeComponent_2202(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2202"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2202">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2202">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2202() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2202',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2202() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2202",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2202",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2200":"1","2201":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2202(),
...amXnotifSubscribeComponentList_2202()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2202" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2202() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2202',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2202() {
const configurableOptionsComponent = initConfigurableOptions('2202', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "194",
"label": "Dark Grey",
"products": ["2200"],
"class": ""
}, {
"id": "207",
"label": "Light Grey",
"products": ["2201"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2200": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2201": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 290.83333233333
},
"oldPrice": {
"amount": 349
},
"basePrice": {
"amount": 208.33333233333
},
"finalPrice": {
"amount": 250
}
},
"productId": "2202",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2200": {
"93": "194"
},
"2201": {
"93": "207"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"194": {
"type": "1",
"value": "#3d3c3d",
"label": "Dark Grey"
},
"207": {
"type": "1",
"value": "#ccc8cc",
"label": "Light Grey"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2202", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2202()" x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();" @private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()"
class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2202-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2202-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="AP15;00213786;00213787" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true"></div>
</div>
<script>
function initPriceBox__673358da00960() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358da00960()" @update-prices-2202.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2202" data-price-box="product-id-2202"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2202" data-price-amount="250" data-price-type="finalPrice" class="price-wrapper "><span class="price">£250<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-2202" data-price-amount="349" data-price-type="oldPrice" class="price-wrapper "><span class="price">£349.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2202()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2202()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2202 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2202 || window.initConfigurableDropdownOptions_2202);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £99 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2906/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2906/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2906">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £49</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/2-x-sonos-era-300" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2906.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-era-300-pair-thumbnail-black.jpg" loading="lazy" width="460" height="460" alt="2 x Sonos ERA 300" title="2 x Sonos ERA 300">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2906)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '2906')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/2-x-sonos-era-300">
2 x Sonos ERA 300 </a>
</div>
<span class="text-secondary order-2">Dolby Atmos stereo pair or as dedicated upwards-firing rears for a Sonos Arc or Sonos Beam (Gen 2) soundbar.</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2906(),
...amXnotifSubscribeComponent_2906(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2906"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2906">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2906">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2906() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2906',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2906() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2906",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2906",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2904":"1","2905":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2906(),
...amXnotifSubscribeComponentList_2906()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2906" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2906() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2906',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2906() {
const configurableOptionsComponent = initConfigurableOptions('2906', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2904"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2905"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2904": {
"baseOldPrice": {
"amount": 748.33333233333
},
"oldPrice": {
"amount": 898
},
"basePrice": {
"amount": 707.499999
},
"finalPrice": {
"amount": 849
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2905": {
"baseOldPrice": {
"amount": 748.33333233333
},
"oldPrice": {
"amount": 898
},
"basePrice": {
"amount": 707.499999
},
"finalPrice": {
"amount": 849
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 748.33333233333
},
"oldPrice": {
"amount": 898
},
"basePrice": {
"amount": 707.499999
},
"finalPrice": {
"amount": 849
}
},
"productId": "2906",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2904": {
"93": "4"
},
"2905": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2906", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2906()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-2906", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2906-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2906-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SER300B;1000819722;1000819723" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.82 Stars - 28 Reviews" aria-label="4.82 Stars - 28 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--75"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(28 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358da3182c() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358da3182c()" @update-prices-2906.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2906" data-price-box="product-id-2906"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2906" data-price-amount="849" data-price-type="finalPrice" class="price-wrapper "><span class="price">£849<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-2906" data-price-amount="898" data-price-type="oldPrice" class="price-wrapper "><span class="price">£898.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2906()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2906()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2906 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2906 || window.initConfigurableDropdownOptions_2906);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £49 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2903/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2903/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2903">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £50</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/2-x-sonos-era-100" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2903.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-era-100-hero-black-pair.jpg" loading="lazy" width="460" height="460" alt="2 x Sonos ERA 100" title="2 x Sonos ERA 100">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2903)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '2903')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/2-x-sonos-era-100">
2 x Sonos ERA 100 </a>
</div>
<span class="text-secondary order-2">Immersive smart speaker bundle</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2903(),
...amXnotifSubscribeComponent_2903(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2903"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2903">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2903">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2903() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2903',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2903() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2903",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2903",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2901":"1","2902":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2903(),
...amXnotifSubscribeComponentList_2903()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2903" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2903() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2903',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2903() {
const configurableOptionsComponent = initConfigurableOptions('2903', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2902"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2901"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2901": {
"baseOldPrice": {
"amount": 414.999999
},
"oldPrice": {
"amount": 498
},
"basePrice": {
"amount": 373.33333233333
},
"finalPrice": {
"amount": 448
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2902": {
"baseOldPrice": {
"amount": 414.999999
},
"oldPrice": {
"amount": 498
},
"basePrice": {
"amount": 373.33333233333
},
"finalPrice": {
"amount": 448
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 414.999999
},
"oldPrice": {
"amount": 498
},
"basePrice": {
"amount": 373.33333233333
},
"finalPrice": {
"amount": 448
}
},
"productId": "2903",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2901": {
"93": "5"
},
"2902": {
"93": "4"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2903", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2903()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-2903", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2903-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2903-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SE100B;1000819721;1000819720" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="4.97 Stars - 33 Reviews" aria-label="4.97 Stars - 33 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(33 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358da3c6fa() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358da3c6fa()" @update-prices-2903.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2903" data-price-box="product-id-2903"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2903" data-price-amount="448" data-price-type="finalPrice" class="price-wrapper "><span class="price">£448<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-2903" data-price-amount="498" data-price-type="oldPrice" class="price-wrapper "><span class="price">£498.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2903()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2903()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2903 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2903 || window.initConfigurableDropdownOptions_2903);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £50 - Lowest price promise </span>
</div>
</form>
POST https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2927/
<form method="post" action="https://smarthomes.dev.magento24.sozowebdesign.com/checkout/cart/add/uenc/%25uenc%25/product/2927/" class="item product product-item product_addtocart_form flex flex-col w-full relative max-w-[340px]
h-full flex-grow-1 flex-shrink-0 mx-auto group " novalidate="" x-data="" x-on:submit.prevent="$dispatch('amcart-add-to-cart-action', $el)">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <input type="hidden" name="product" value="2927">
<div class="absolute top-2 left-2 z-20 flex flex-wrap gap-1">
<div class="product-tag rounded-full px-2 whitespace-nowrap bg-promo text-white ">
<p class="text-xs uppercase font-semibold">
<strong>Save up to £126</strong>
</p>
</div>
</div>
<a href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-beam-2-x-sonos-era-100" class="product photo product-item-photo block relative mx-auto mb-4 w-full aspect-square overflow-hidden" tabindex="-1">
<figure>
<img class="object-contain product-image-photo product-image-photo" x-data="" @update-gallery-2927.window="$root.src = $event.detail" src="https://smarthomes.dev.magento24.sozowebdesign.com/media/catalog/product/cache/c0cb753c7525f3cd6afe989e69afe7c7/s/o/sonos-beam-gen2-era-100-home-cinema-bundle-black-thumbnail.jpg" loading="lazy" width="460" height="460" alt="Sonos Beam + 2 x Sonos ERA 100" title="Sonos Beam + 2 x Sonos ERA 100">
</figure>
<div class="absolute left-3 bottom-3 right-3 flex flex-row">
<figure class="block h-12 mr-4">
<img src="https://smarthomes.dev.magento24.sozowebdesign.com/media/sozo_productincludedextras/6-year-guarantee-lrg.png" alt="6 Year Guarantee included FREE!" class="h-12" height="48" title="6 Year Guarantee included FREE!" loading="lazy">
</figure>
</div>
<p class="amcart-card-info inset-0 w-full h-full overflow-hidden hidden justify-center items-center absolute ease-in duration-300 backdrop-blur-sm bg-white/30" x-data="initAmcartQtyProduct()" x-init="initQtyOnProduct(2927)" @private-content-loaded.window="updateQtyData($event.detail)" x-show="qty" style="display: none;">
<template x-if="qty">
<span class="relative">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-14 w-14" width="24" height="24" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path d="m21.79,9.38c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38s-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.92,2.84.02,0,.04,0,.07,0h9.97s.04,0,.07,0c1.44,0,2.67-1.18,2.93-2.76l2-8c.08-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Zm6.01,12.28c-.11.67-.56,1.15-1.01,1.16H6.97s0,0-.02,0c-.45,0-.86-.5-.99-1.24l-1.69-6.76h15.44l-1.71,6.84Z"></path><title>basket</title></svg>
<b class="amcart-counter bg-primary -right-2 -top-2 absolute text-white px-3 py-1.5 rounded-full inline-block" x-text="qty"></b>
</span>
</template>
</p></a>
<div class="product-info flex flex-col grow">
<div class="flex flex-wrap justify-center items-center mt-auto order-last relative
md:order-first md:h-0 md:mt-0">
<button class="w-full btn btn-primary bg-primary border-primary justify-center text-sm
transition duration-300
hover:bg-primary-darker hover:border-primary-darker
md:opacity-0 md:absolute md:-top-20 md:left-2 md:right-2 md:w-[calc(100%-16px)] md:translate-y-4
group-hover:md:translate-y-1 group-hover:opacity-100
" aria-label="Add to Basket">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="h-5 w-5 border-current inline" width="20" height="20" role="img">
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path style="fill:none;" d="m0,0h24v24H0V0Z"></path>
<path
d="m19,17c.55,0,1,.45,1,1v1h1c.55,0,1,.45,1,1s-.45,1-1,1h-1v1c0,.55-.45,1-1,1s-1-.45-1-1v-1h-1c-.55,0-1-.45-1-1s.45-1,1-1h1v-1c0-.55.45-1,1-1h0Zm2.79-7.62c-.19-.24-.48-.38-.79-.38h-3.53l-4.7-5.64c-.38-.46-1.16-.46-1.54,0l-4.7,5.64h-3.53c-.31,0-.6.14-.79.38-.19.24-.26.56-.18.86l1.98,7.92c.27,1.66,1.5,2.84,2.93,2.84.02,0,.04,0,.05,0h7c.55,0,1-.45,1-1s-.45-1-1-1h-7.03c-.45,0-.87-.5-1-1.24l-1.69-6.76h15.44l-.96,3.85c-.13.54.19,1.08.73,1.21.53.13,1.08-.19,1.21-.73l1.27-5.09c.07-.3,0-.62-.18-.86Zm-9.79-3.82l2.86,3.44h-5.73l2.86-3.44Z">
</path>
<title>basket-add</title>
</svg>
<span class="ml-2 inline " x-data="{...initAmPreorderBtn(' Add to Basket ', '2927')}" x-bind="eventListeners" x-spread="eventListeners" x-text="cartLabel"> Add to Basket </span>
</button>
</div>
<div class="items-center justify-center text-primary font-bold text-lg order-1">
<a class="product-item-link" href="https://smarthomes.dev.magento24.sozowebdesign.com/sonos-beam-2-x-sonos-era-100">
Sonos Beam + 2 x Sonos ERA 100 </a>
</div>
<span class="text-secondary order-2">Compact Dolby Atmos Soundbar & Rear Surrounds bundle</span>
<div class="order-12 ">
<div x-data="{
...amNotificationProductViewComponent_2927(),
...amXnotifSubscribeComponent_2927(),
...hyva.modal()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail)" x-show="!isAvailable || ''" style="display: none;">
<div class="amxnotif-container">
<div>
<div class="w-full text-sm bg-container flex gap-1 px-3 py-2.5 items-center md:mt-3">
<span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5" width="20" height="20" role="img">
<rect style="fill:none;" width="24" height="24"></rect>
<path
d="m12,17c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71v-4.02c0-.28-.1-.52-.29-.7-.19-.18-.43-.27-.71-.27s-.52.1-.71.29c-.19.19-.29.43-.29.71v4.02c0,.28.1.52.29.7.19.18.43.27.71.27Zm0-8c.28,0,.52-.1.71-.29.19-.19.29-.43.29-.71s-.1-.52-.29-.71c-.19-.19-.43-.29-.71-.29s-.52.1-.71.29c-.19.19-.29.43-.29.71s.1.52.29.71c.19.19.43.29.71.29Zm0,13c-1.38,0-2.68-.26-3.9-.79-1.22-.52-2.28-1.24-3.18-2.14s-1.61-1.96-2.14-3.18c-.53-1.22-.79-2.52-.79-3.9s.26-2.68.79-3.9c.52-1.22,1.24-2.28,2.14-3.18s1.96-1.61,3.18-2.14c1.22-.52,2.52-.79,3.9-.79s2.68.26,3.9.79c1.22.53,2.28,1.24,3.18,2.14s1.61,1.96,2.14,3.18c.53,1.22.79,2.52.79,3.9s-.26,2.68-.79,3.9c-.52,1.22-1.24,2.28-2.14,3.18s-1.96,1.61-3.18,2.14c-1.22.53-2.52.79-3.9.79Zm0-10h0Zm0,8c2.22,0,4.1-.78,5.66-2.34,1.56-1.56,2.34-3.45,2.34-5.66s-.78-4.1-2.34-5.66c-1.56-1.56-3.45-2.34-5.66-2.34s-4.1.78-5.66,2.34-2.34,3.45-2.34,5.66.78,4.1,2.34,5.66c1.56,1.56,3.45,2.34,5.66,2.34Z">
</path>
<title>info</title>
</svg>
</span>
<span>Currently out of stock.</span>
<a x-on:click.prevent="show" class="underline cursor-pointer">
<span>Notify me.</span>
</a>
</div>
<div x-spread="overlay()" x-bind="overlay()" class="fixed inset-0 flex items-center justify-center text-left bg-black bg-opacity-50 z-30" style="display: none;">
<div x-ref="dialog" role="dialog" aria-labelledby="the-label" class="relative inline-block max-h-screen overflow-auto shadow-xl bg-container p-10 rounded">
<input name="form_key" type="hidden" value="8JUFfHaKEkV4cJOB"> <label class="cursor-pointer h4 mb-3" :for="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" for="amxnotif-guest-email-2927"> Notify me when back in stock </label>
<div class="items-start">
<div class="input-fields fieldset pb-2">
<input name="guest_email" @input.debounce="validateForm()" class="w-full form-input" x-ref="guest_email" :id="'amxnotif-guest-email-'
+ (!isConfigurable ? productIdentifier : productIndex)" type="email" placeholder="Insert your email" required="" x-model="emailInput" id="amxnotif-guest-email-2927">
<input type="hidden" name="product_id" :value="!isConfigurable ? productIdentifier : productIndex" value="2927">
<input type="hidden" name="type" value="email">
<input type="hidden" name="uenc" value="aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v">
</div>
<button type="submit" class="btn mb-2 w-full" :disabled="buttonDisabled"> Subscribe </button>
<p class="text-xs text-secondary text-center"> By completing this form you agree to our <a class="underline" href="/privacy">Privacy Policy</a> </p>
</div>
<button @click="hide" type="button" class="absolute top-3 right-3">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="24" height="24" role="img">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 18 6M6 6l12 12"></path>
<title>x</title>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script>
'use strict';
function amXnotifSubscribeComponent_2927() {
return {
emailInput: '',
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isGDPREnabled: '0',
isGdprChecked: false,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2927',
isValid: true,
isUsePopup: '1',
buttonDisabled: false,
validateEmailsRegex: /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i,
validateForm() {
const emailField = this.$refs['guest_email'];
const emails = emailField.value.split(/[\s\n\,]+/g);
for (let i = 0; i < emails.length; i++) {
if (!this.validateEmailsRegex.test(emails[i].trim())) {
this.isValid = false;
emailField.setCustomValidity("Please\u0020enter\u0020a\u0020valid\u0020email\u0020address\u0020\u0028Ex\u003A\u0020johndoe\u0040domain.com\u0029");
return false;
}
}
this.isValid = true;
emailField.setCustomValidity("");
return true;
},
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
email: String(this.emailInput),
gdpr_agreement: Boolean(this.isGDPREnabled)
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
this.buttonDisabled = false;
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
if (this.isUsePopup) {
this.hide();
}
}).finally(() => {
this.emailInput = '';
this.isGdprChecked = false;
this.buttonDisabled = false;
})
},
}
}
</script>
<script>
'use strict';
function amNotificationProductViewComponent_2927() {
return {
isAvailable: "1",
isAvailableConfigurableSimple: true,
isOutofstockConfigurable: "1",
title: "Availability",
options: {},
productIndex: "2927",
hideAddToCartBtn: "",
notificationLabel: "Notify me.",
productId: "2927",
preOrderLabels: JSON.parse('[]') || {},
/**
* Overridden method for Product Alert View Model to output url according to product id in js
*
* @param productIndex
* @param type
* @returns {*}
*/
getSubscriptionUrl(productIndex, type) {
const urlParts = ['xnotif/email/',
type, '/product_id/',
productIndex,
(parseInt(productIndex) !== parseInt(this.productId)) ? '/parent_id/' + this.productId : '', '/uenc/', 'aHR0cHM6Ly9zbWFydGhvbWVzLmRldi5tYWdlbnRvMjQuc296b3dlYmRlc2lnbi5jb20v'
];
return BASE_URL + urlParts.join('');
},
getSimpleProduct(data, el) {
let children = JSON.parse('{"2925":"1","2926":"1"}');
/**
* hide addtocart button if simple for configurable product is out of stock
*/
window.addEventListener('configurable-select-changed', (event) => {
// PLB
// SO: Moved this code to a separate function to be called on simple product selection
let productId = event.detail.productId;
this.toggleProductCardAddToCart(productId);
// EO: Moved this code to a separate function to be called on simple product selection
});
window.addEventListener('configurable-selection-changed', (event) => {
// PDP
// SO: Moved this code to a separate function to be called on simple product selection
this.toggleAddToCartButton();
// EO: Moved this code to a separate function to be called on simple product selection
});
const swatchesComponent = (document.querySelector('[x-data="initConfigurableSwatchOptions_' + data.productId + '()"]') || document.querySelector('[x-data="initAmastyXnotifCatalogSwatchesMixin_' + data.productId + '()"]') || document
.querySelector('[x-data="initAmastyCustomStockStatusOptions_' + data.productId + '()"]'));
const swatchesCount = swatchesComponent?.querySelector('div')?.children?.length || 0;
for (const [key, value] of Object.entries(children[data.productIndex] || {})) {
const previousIndex = this.productIndex;
this.productIndex = data.productIndex;
if (Object.keys(data.selectedValues).length >= swatchesCount) {
this.isAvailable = Number(value);
this.isAvailableConfigurableSimple = Number(value);
// SO: Trigger event to hide/show add to cart button on simple product selection
let productId = event.detail.productId;
this.toggleAddToCartButton();
this.toggleProductCardAddToCart(productId);
// EO: Trigger event to hide/show add to cart button on simple product selection
}
if (this.hideAddToCartBtn) {
this.hideAddToCartBtn.style.display = !this.isAvailable ? 'none' : 'flex';
}
// plp duplicating indexes case
if (el && (previousIndex === this.productIndex) && !this.isAvailable) {
el.closest('.product-info').querySelector('.btn.btn-primary:not(.subscribe-button)').style.display = 'none';
}
}
},
getAvailable() {
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
const preorderLabel = this.getPreOrderLabel(this.productIndex);
const availableLabel = (preorderLabel) ? preorderLabel : 'In stock';
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
return this.options = {
additionalClass: this.isAvailable ? 'text-positive' : '',
categoryNotificationLabel: this.isAvailable ? '' : this.notificationLabel,
available: this.isAvailable,
label: this.isAvailable ? availableLabel : 'Out of stock',
preorder: preorderLabel ? true : false,
};
},
// SO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
getPreOrderLabel(index) {
return this.preOrderLabels[index] || null;
},
// EO: SOZO Additional viewModel to pass through preorder notes as temp fix for amasty module conflict
// SO: SOZO add function to show/hide add to cart button on configurable product
toggleAddToCartButton() {
// PDP
const addToCartButton = document.getElementById('product-addtocart-button')?.closest('div');
if (addToCartButton) {
addToCartButton.style.display = this.isAvailableConfigurableSimple ? 'flex' : 'none';
}
},
// EO: SOZO add function to show/hide add to cart button on configurable product
// SO: SOZO add function to show/hide add to cart button on simple product selection in listing block
toggleProductCardAddToCart(productId) {
// PLB
let componentElement = document.querySelector('[x-data="amNotificationProductViewComponent_' + productId + '()"]') || document.querySelector('#stock_notification_' + productId);
if (!componentElement) {
return;
}
let parentElement = componentElement.closest('.product-info');
if (parentElement) {
let button = parentElement.querySelector('.btn.btn-primary:not(.subscribe-button)');
this.hideAddToCartBtn = button;
}
}
// EO: SOZO add function to show/hide add to cart button on simple product selection in listing block
}
}
</script>
<div x-data="{
...amNotificationProductViewComponent_2927(),
...amXnotifSubscribeComponentList_2927()
}" x-on:configurable-select-changed.window="getSimpleProduct($event.detail, $el)" x-show="getAvailable()" id="stock_notification_2927" class="text-right relative">
<p class="flex items-center justify-center align-middle available gap-x-2 stock" :title="title" title="Availability">
<span class="w-3 h-3 rounded-full flex-shrink-0 text-positive" :class="options.additionalClass"></span>
<span x-text="options.label">In stock</span>
</p>
</div>
<script>
'use strict';
function amXnotifSubscribeComponentList_2927() {
return {
mutationStockSubscription: `mutation AmxnotifStockSubscribe($input: AmxnotifSubscribeInput) {
AmxnotifStockSubscribe(input: $input) {
response_message
}
}`,
isInStock: '1',
isConfigurable: '1',
productIdentifier: '2927',
isValid: true,
buttonDisabled: false,
getMessages(type, message) {
return typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(
[{
type: type,
text: message
}]);
},
fetchSubscribe(productId) {
let variables = {
input: {
product_uid: btoa(productId),
parent_uid: this.isConfigurable ? btoa(this.productIdentifier) : '',
}
};
this.buttonDisabled = true;
fetch(BASE_URL + 'graphql', {
method: 'POST',
headers: {
'Store': 'default',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: this.mutationStockSubscription,
variables
}),
mode: "cors",
credentials: "include"
}).then((response) => response.json()).then((result) => {
if (result?.data?.AmxnotifStockSubscribe) {
this.getMessages("success", result.data.AmxnotifStockSubscribe.response_message);
} else {
let message = result?.errors ? result.errors[0].message : "Something went wrong.";
this.getMessages("error", message)
}
if (this.isConfigurable) {
setTimeout(() => {
window.dispatchEvent(new Event('showAmLoader'));
window.location.reload()
}, 2000)
}
}).finally(() => {
this.buttonDisabled = false;
})
}
}
}
</script>
</div>
<div class="order-11 flex justify-between items-center ">
<script>
function initConfigurableSwatchOptions_2927() {
const configurableOptionsComponent = initConfigurableOptions('2927', {
"attributes": {
"93": {
"id": "93",
"code": "color",
"label": "Colour",
"options": [{
"id": "4",
"label": "White",
"products": ["2925"],
"class": ""
}, {
"id": "5",
"label": "Black",
"products": ["2926"],
"class": ""
}],
"position": "0"
}
},
"template": "\u00a3<%- data.price %>",
"currencyFormat": "\u00a3%s",
"optionPrices": {
"2925": {
"baseOldPrice": {
"amount": 830.83333233333
},
"oldPrice": {
"amount": 997
},
"basePrice": {
"amount": 725.83333233333
},
"finalPrice": {
"amount": 871
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
},
"2926": {
"baseOldPrice": {
"amount": 830.83333233333
},
"oldPrice": {
"amount": 997
},
"basePrice": {
"amount": 725.83333233333
},
"finalPrice": {
"amount": 871
},
"tierPrices": [],
"msrpPrice": {
"amount": 0
}
}
},
"priceFormat": {
"pattern": "\u00a3%s",
"precision": 2,
"requiredPrecision": 2,
"decimalSymbol": ".",
"groupSymbol": ",",
"groupLength": 3,
"integerRequired": false
},
"prices": {
"baseOldPrice": {
"amount": 830.83333233333
},
"oldPrice": {
"amount": 997
},
"basePrice": {
"amount": 725.83333233333
},
"finalPrice": {
"amount": 871
}
},
"productId": "2927",
"chooseText": "Choose an Option...",
"images": [],
"index": {
"2925": {
"93": "4"
},
"2926": {
"93": "5"
}
},
"salable": [],
"canDisplayShowOutOfStockStatus": false
});
const swatchOptionsComponent = initSwatchOptions({
"93": {
"4": {
"type": "1",
"value": "#ffffff",
"label": "White"
},
"5": {
"type": "1",
"value": "#000000",
"label": "Black"
},
"additional_data": "{\"swatch_input_type\":\"visual\",\"update_product_preview_image\":\"1\",\"use_product_image_for_swatch\":\"0\"}"
}
});
return Object.assign(configurableOptionsComponent, swatchOptionsComponent, {
mediaCallback: "https\u003A\u002F\u002Fsmarthomes.dev.magento24.sozowebdesign.com\u002Fswatches\u002Fajax\u002Fmedia\u002F",
changeOption(optionId, value, skipUpdateGallery) {
this.selectedValues[optionId] = value;
this.findSimpleIndex();
this.findAllowedAttributeOptions();
this.updatePrices();
!skipUpdateGallery && this.updateGallery();
window.dispatchEvent(new CustomEvent('listing-configurable-selection-changed', {
detail: {
productId: this.productId,
optionId,
value,
productIndex: this.productIndex,
selectedValues: this.selectedValues,
candidates: this.findProductIdsForPartialSelection(this.selectedValues),
}
}));
},
updateGallery() {
if (!this.productIndex) {
return;
}
fetch(`${this.mediaCallback}?product_id=${this.productIndex}&isAjax=true`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
}).then(response => {
return response.json()
}).then(data => {
if (data.errors) {
// non critical failure only console logged
console.warn(data.errors);
} else {
const image = data && data.medium;
image && window.dispatchEvent(new CustomEvent("update-gallery-2927", {
detail: image
}));
}
}).catch(error => {
console.warn(error)
});
},
preselectQuerystringItems() {
// pre-select option like ?size=167
const urlQueryParams = new URLSearchParams(window.location.search.replace('?', ''));
Object.values(this.optionConfig.attributes).map(attribute => {
// Don't update images on load, since PLPs already set the main image to the selected options
const skipUpdateGallery = true;
urlQueryParams.get(attribute.code) && this.changeOption(attribute.id, urlQueryParams.get(attribute.code), skipUpdateGallery);
});
},
mouseDown: false,
startX: 0,
maxScroll: 0,
scrollLeft: null,
slider: null,
scrollEvents: {
['@mousedown'](e) {
this.slider = e.target.closest('.snap');
if (!this.slider) {
return;
}
this.maxScroll = this.slider.scrollWidth - this.slider.offsetWidth;
this.startX = e.pageX - this.slider.offsetLeft;
this.scrollLeft = this.slider.scrollLeft;
this.mouseDown = true;
},
['@mouseout.self']() {
this.mouseDown = false;
},
['@mouseup']() {
this.mouseDown = false;
},
['@mousemove'](e) {
e.preventDefault();
if (!this.mouseDown) {
return;
}
const x = e.pageX - this.slider.offsetLeft;
const scroll = x - this.startX;
const scrollLeft = this.scrollLeft - scroll;
if (scrollLeft > this.maxScroll) {
this.slider.scrollLeft = this.maxScroll;
return
}
this.slider.scrollLeft = this.scrollLeft - scroll;
},
['@onselectstart']() {
return false;
}
},
resizeEvent() {
Array.from(this.$root.querySelectorAll('.snap')).forEach(slider => {
slider.scrollLeft = 0;
})
}
});
}
</script>
<div x-data="initConfigurableSwatchOptions_2927()"
x-init="findAllowedAttributeOptions(); initShowSwatchesIntersect();;$watch('selectedValues', (selectedValues) => {$dispatch("am-configurable-selection-changed-2927", {selectedValues})});"
@private-content-loaded.window="onGetCartData($event.detail.data)" @resize.window="resizeEvent()" class="relative mb-4 md:mb-0">
<div>
<div class="swatch-attribute
color">
<div class="w-full overflow-x-hidden swatch-attribute-options">
<template x-if="showSwatches">
<div class="flex flex-wrap w-full overflow-auto items-center" role="radiogroup" x-bind="scrollEvents">
<label class="sr-only" for="attributecolor">
<span> Colour </span>
</label>
<template x-for="(item, index) in optionConfig.attributes[93].options" :key="item.id">
<div class="w-5 h-6">
<div>
<template x-if="optionIsEnabled(93, item.id) && optionIsActive(93, item.id)">
<label :for="'attribute-option-2927-'+item.id" class="swatch-option relative border-1 border-secondary shadow-sm cursor-pointer select-none bg-container-lighter rounded-full product-option-value-label" :class="{
'out-of-stock': getStockClass(93, item.id) === 'out-of-stock',
'ring ring-offset-1 ring-1 ring-secondary selected': (selectedValues[93] === item.id),
'': (selectedValues[93] !== item.id),
'w-4 h-4' : !isTextSwatch(93, item.id),
'ring ring-offset-1 ring-1 ring-primary-lighter' : focusedLabel === item.id
}" :style="getSwatchBackgroundStyle('93',item.id)">
<input :id="'attribute-option-2927-'+item.id" :value="item.id" name="super_attribute[93]" type="radio" class="inline-block absolute p-0 border-0 focus:border-0 focus:ring-0 product-option-value-input" style="z-index:-1"
x-on:focus="focusLabel(item.id)" x-on:blur="blurLabel()" x-on:change="changeOption(93, $event.target.value)" x-model="selectedValues[93]" :required="getAllowedAttributeOptions(93).filter(
attributeOption => selectedValues[attributeOption]
).length === 0">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
</label>
</template>
<template x-if="optionIsEnabled(93, item.id) && !optionIsActive(93, item.id)">
<div class="relative border-2 shadow-sm opacity-50 cursor-pointer select-none border-container-darker swatch-option bg-container-lighter" :class="{
'w-6 h-6' : !isTextSwatch(93, item.id),
}" :style="getSwatchBackgroundStyle('93',item.id)">
<div x-html="getSwatchText(93, item.id)" class="whitespace-nowrap" :class="{ 'sr-only' : !isTextSwatch(93, item.id) }"></div>
<svg class="absolute inset-0 w-full h-full text-gray-500 bg-white/25">
<line x1="0" y1="100%" x2="100%" y2="0" class="stroke-current stroke-1"></line>
</svg>
</div>
</template>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<!-- Reviews.io Rating Snippet -->
<div class="ruk_rating_snippet" data-sku="SBER100;1000819734;1000819735" rating-snippet-batch="0" style="color: rgb(253, 176, 41);" data-retrieved="true" title="5 Stars - 3 Reviews" aria-label="5 Stars - 3 Reviews" data-done="true">
<i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><i aria-hidden="true" class="ruk-icon-percentage-star--100"></i><span
class="ruk-rating-snippet-count" style="color: rgb(51, 51, 51);">(3 Reviews)</span></div>
</div>
<script>
function initPriceBox__673358da479f2() {
return {
// SO: Added to update price styling
init() {
this.updateStyle();
},
// EO: Added to update price styling
updatePrice(priceData) {
const regularPriceLabel = this.$root.querySelector('.normal-price .price-label');
const regularPriceElement = this.$root.querySelector('.normal-price [data-price-type=finalPrice].price-wrapper .price');
const basePriceElement = this.$root.querySelector('.normal-price [data-price-type=basePrice].price-wrapper .price');
if (regularPriceLabel) {
if (priceData.finalPrice.amount < priceData.oldPrice.amount) {
regularPriceLabel.classList.add('hidden');
} else {
regularPriceLabel.classList.remove('hidden');
}
}
if (regularPriceElement) {
regularPriceElement.innerText = hyva.formatPrice(priceData.finalPrice.amount);
}
basePriceElement && (basePriceElement.innerText = hyva.formatPrice(priceData.basePrice.amount));
// SO: Added to update price styling
this.updateStyle();
// EO: Added to update price styling
},
// SO: Added to update price styling
updateStyle() {
const finalPriceElement = this.$root.querySelector('[data-price-type=finalPrice].price-wrapper .price'),
finalPriceText = (finalPriceElement) ? finalPriceElement.innerText : '';
const priceComponents = finalPriceText.split(".");
const finalPriceStyling = priceComponents[0] + '<span class="align-text-top text-xs">.' + priceComponents[1] + '</span>';
if (finalPriceElement) {
finalPriceElement.innerHTML = finalPriceStyling;
}
}
// EO: Added to update price styling
}
}
</script>
<div class="pt-3 order-3 relative" x-data="initPriceBox__673358da479f2()" @update-prices-2927.window="updatePrice($event.detail);">
<div class="price-box price-final_price" data-role="priceBox" data-product-id="2927" data-price-box="product-id-2927"><span class="normal-price">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">from</span>
<span id="product-price-2927" data-price-amount="871" data-price-type="finalPrice" class="price-wrapper "><span class="price">£871<span class="align-text-top text-xs">.00</span></span></span>
</span>
</span>
<span class="old-price sly-old-price no-display">
<span class="price-container price-final_price tax weee rewards_earn">
<span class="price-label">was</span>
<span id="old-price-2927" data-price-amount="997" data-price-type="oldPrice" class="price-wrapper "><span class="price">£997.00</span></span>
</span>
</span>
</div>
<script>
// mixin for swatch renderer
(_super => {
const selectors = {
products: '.column.main .products',
parent: '.catalog-category-view'
}
const pageWrapper = document.querySelector(selectors.parent);
if (!pageWrapper) {
return;
}
// SO: SOZO fix for category landing pages
const categoryLanding = document.querySelector('.catalog-category-landing');
if (categoryLanding) {
return;
}
// EO: SOZO fix for category landing pages
const listingWrapper = pageWrapper.querySelector(selectors.products);
const getWidgetElement = widget => listingWrapper.querySelector(`[x-data="${widget}_2927()"]`)
// for swatches and dropdown options
const widgetElement = getWidgetElement('initConfigurableSwatchOptions') || getWidgetElement('initConfigurableDropdownOptions');
if (!widgetElement) {
return;
}
widgetElement.setAttribute('x-data', 'initAmastyXnotifCatalogSwatchesMixin_2927()');
if (typeof(Alpine) !== 'undefined') {
Alpine.initializeComponent(widgetElement);
}
window.initAmastyXnotifCatalogSwatchesMixin_2927 = () => {
const swatchInit = _super();
const changeOption = swatchInit.changeOption.bind(swatchInit);
//changeOptions events
swatchInit.changeOption = (optionId, value, skipUpdateGallery) => {
changeOption(optionId, value, skipUpdateGallery);
window.dispatchEvent(new CustomEvent('configurable-select-changed', {
detail: {
productId: swatchInit.productId,
productIndex: swatchInit.productIndex,
selectedValues: swatchInit.selectedValues,
}
}));
}
return swatchInit;
}
})(window.initConfigurableSwatchOptions_2927 || window.initConfigurableDropdownOptions_2927);
</script>
</div>
<span class="text-negative text-sm mb-4 order-4 font-medium"> Save up to £126 - Lowest price promise </span>
</div>
</form>
<form aria-live="polite" class="needsclick klaviyo-form klaviyo-form-version-cid_1 go3725832945 kl-private-reset-css-Xuajs1" data-testid="klaviyo-form-VgE7G2" novalidate=""
style="display: flex; flex-direction: row; box-sizing: border-box; width: 450px; min-width: 200px; max-width: 1000px; border-radius: 4px; border-style: none; border-width: 0px; border-color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); background-repeat: no-repeat; background-position-y: 50%; padding: 20px; flex: 1 1 0%;">
<div class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; flex-direction: column; width: 100%; margin: 0px; padding: 0px; min-height: 450px; justify-content: center;">
<div data-testid="form-row" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; flex-direction: row; align-items: stretch; position: relative;">
<div component="[object Object]" data-testid="form-component" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; justify-content: flex-start; padding: 10px 6px 0px; position: relative; flex: 1 0 0px;">
<div class="kl-private-reset-css-Xuajs1 go3176171171" id="rich-text-016JB91TTR00000000001EBTM6" style="width: 100%;">
<p style="font-size: 14px; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-weight: 400; text-align: center;"><span
style="color: #000000; font-size: 14px; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-weight: 400;"><span style="font-size: 16px;">Sign up & join our community! </span><br></span></p>
</div>
</div>
</div>
<div data-testid="form-row" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; flex-direction: row; align-items: stretch; position: relative;">
<div component="[object Object]" data-testid="form-component" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; justify-content: flex-start; padding: 10px 3px 0px; position: relative; flex: 1 0 0px;">
<div class="kl-private-reset-css-Xuajs1 go3176171171" id="rich-text-016JB91TTR00000000001EBTM7" style="width: 100%;">
<p style="text-align: center; font-size: 14px; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-weight: 400;"><span style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-weight: bold;"><span
class="ql-font-poppins" style="font-size: 60px; color: #373f47;">£10 OFF</span><span style="font-size: 24px;"> </span></span></p>
<p style="text-align: center; font-size: 14px; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-weight: 400;"><span class="ql-font-poppins"
style="font-size: 26px; color: #373f47; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-weight: bold;">Your first order over £100</span></p>
</div>
</div>
</div>
<div data-testid="form-row" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; flex-direction: row; align-items: stretch; position: relative;">
<div component="[object Object]" data-testid="form-component" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; justify-content: flex-start; padding: 0px 6px 10px; position: relative; flex: 1 0 0px;">
<div class="kl-private-reset-css-Xuajs1 go3176171171" id="rich-text-016JB91TTR00000000001EBTM8" style="width: 100%;">
<p style="text-align: center; font-size: 14px; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-weight: 400;"><span
style="font-size: 16px; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-weight: 400;">Signup to get your code!<br></span></p>
</div>
</div>
</div>
<div data-testid="form-row" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; flex-direction: row; align-items: stretch; position: relative;">
<div component="[object Object]" data-testid="form-component" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; justify-content: flex-start; padding: 10px 6px; position: relative; flex: 1 0 0px;">
<div class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; flex-grow: 1; flex-direction: column; align-self: flex-end;"><input id="email_016JB91TTR00000000001EBTM9" class="needsclick go2544359273 kl-private-reset-css-Xuajs1"
type="email" autocomplete="email" name="email" tabindex="0" placeholder="Enter your email address" aria-label="Enter your email address" aria-required="true" aria-invalid="false" options="[object Object]"
style="box-sizing: border-box; border-radius: 0px; padding: 0px 0px 0px 16px; height: 50px; text-align: left; color: rgb(0, 0, 0); font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; font-size: 16px; font-weight: 400; letter-spacing: 0px; background-color: rgb(255, 255, 255); border: 1px solid rgb(148, 149, 150);">
<div class="needsclick kl-private-reset-css-Xuajs1" style="width: 100%; position: relative;"></div>
</div>
</div>
</div>
<div data-testid="form-row" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; flex-direction: row; align-items: stretch; position: relative;">
<div component="[object Object]" data-testid="form-component" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; justify-content: flex-start; padding: 5px 6px 10px; position: relative; flex: 1 0 0px;"><button
class="needsclick go2354709111 kl-private-reset-css-Xuajs1" type="button" tabindex="0"
style="background: rgb(161, 203, 39); border-radius: 0px; border-style: none; border-color: rgb(0, 0, 0); border-width: 2px; color: rgb(255, 255, 255); font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; font-size: 16px; font-weight: 700; letter-spacing: 0px; line-height: 1; white-space: normal; padding-top: 0px; padding-bottom: 0px; text-align: center; word-break: break-word; align-self: flex-end; cursor: pointer; pointer-events: auto; height: 51px; width: 100%;">GET
£10 OFF NOW!</button></div>
</div>
<div data-testid="form-row" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; flex-direction: row; align-items: stretch; position: relative;">
<div component="[object Object]" data-testid="form-component" class="needsclick kl-private-reset-css-Xuajs1" style="display: flex; justify-content: flex-start; padding: 10px 4px; position: relative; flex: 0 1 auto; margin: 0px auto;"><button
class="needsclick go300628013 kl-private-reset-css-Xuajs1" type="button" tabindex="0"
style="background: rgba(48, 59, 67, 0); border-radius: 0px; border-style: none; border-color: rgb(0, 0, 0); border-width: 0px; color: rgb(22, 27, 32); font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; font-size: 16px; font-weight: 400; letter-spacing: 0px; line-height: 1; white-space: normal; padding: 11px 10px; text-align: center; word-break: break-word; align-self: flex-end; cursor: pointer; pointer-events: auto; height: auto;">No,
thanks</button></div>
</div>
</div><input type="submit" tabindex="-1" value="Submit" style="display: none;">
</form>
Text Content
Sign up to our newsletter for 10% off your first order Authorised Dealer * About Us * Support * Show Room * Blog Menu x menu TV new chevron-right TV chevron-left Televisions View All TVs Sony TVs LG TV's Samsung TVs TV Offers Screen Size 32'' - 45'' TVs 46'' - 54'' TVs 55" - 64" TVs 65'' - 74'' TVs 75'' or Larger TVs Features OLED TVs QLED TVs QD-OLED Artwork TVs Mini LED 8K TVs Projectors View All Projectors View all Sonos Shop chevron-right Sonos Shop chevron-left Sonos Shop Sonos Speakers Sonos Portable Speakers Sonos Bundles Sonos Era Sonos Home Cinema Sonos Soundbars Sonos Subwoofers Sonos Surround Sound Bundles Sonos Architectural Sonos ceiling Speakers Sonos In-Wall Speakers Sonos Outdoor Speakers Sonos AMP & Bundles View all Wireless Speakers chevron-right Wireless Speakers chevron-left Shop Wireless Speakers View All Wireless Speakers View All TV Speakers View All Audio Components View Portable Speakers Party Speakers Smart Radios Shop Brand Sonos KEF Wireless Marshall Sony Bang & Olufsen Bluesound Devialet Bowers & Wilkins Audioengine JBL Audio Pro View all Home Cinema chevron-right Home Cinema chevron-left Speakers & Soundbars Soundbars Subwoofers Atmos Speakers Floorstanding Speakers Standmount Speakers Centre Speakers Home Cinema Separates AV Receivers DVD & Blu-Ray Players Home Cinema Bundles Home Cinema Bundles Speaker Bundles Sonos Home Cinema Bundles View all Ceiling & Outdoor Speakers chevron-right Ceiling & Outdoor Speakers chevron-left Speaker Type Ceiling Speakers In-Wall Speakers Wireless All-In-One - Powered Ceiling Speaker & Amplifier Bundles Speaker Cable & Fire Hoods Sonos Amp & Speaker Packages Outdoor Audio Wall-Mounted Speakers Rock Speakers Waterproof Ceiling Speakers Sonos Outdoor Speakers All Outdoor Speakers Outdoor Speaker Bundles Shop Room Kitchen Bedroom Bathroom Lounge Garden Shop Brand Monitor Audio Bowers & Wilkins KEF Blucube Sonos Sonance Lithe Audio View all HiFi chevron-right HiFi chevron-left Speaker Type Bookshelf Speakers Floor Standing Speakers Subwoofers Active Speakers Computer Speakers Amplifiers / Streamers 2-Channel Amplifiers AV Receivers Smart / Streaming Amplifiers Wireless Streamers Separates CD Players HiFi Bundle Deals Amplifier & Speaker Bundles Turntable & Speaker Bundles HiFi Accessories Speaker Stands Speaker Cable Turntable Pre-Amps Speaker Switches View all Headphones Televisions* Gaming chevron-right Gaming chevron-left PS5 PS5 Best Sellers Console & Bundles Playstation 5 Games PS5 Pre-Orders PS5 Accessories PS4 PS4 Best Sellers PS4 Console & Bundles Games PS4 Pre-Orders PS4 Accessories Nintendo Switch Switch Best Sellers Nintendo Switch Console & Bundles Games Nintendo Switch Pre-Orders Nintendo Switch Accessories Xbox Series X/S Series X/S Best Sellers Xbox Series X/S Console & Bundles Games Xbox Series X/S Pre-Orders Xbox One Xbox One Best Sellers Games Xbox One Pre-Orders View all Accessories chevron-right Accessories chevron-left All Accessories Sonos Accessories Speaker Switches Turntable Accessories TV Brackets Cables & Charging Speaker Cable HDMI Cables Hi-Fi Interconnects Power Cables & Charging View all save Clearance brands Brands chevron-right Brands chevron-left A-E LG F - L soundcore Audioquest JBL Zuma Onkyo View all discount Deals * account Account * contact Contact us * About Us * Support * Show Room * Blog What are you looking for? What are you looking for? scale Compare 0 item items contact Contact us account Log In Create an Account Basket basket 0 TV new * Televisions * View All TVs * Sony TVs * LG TV's * Samsung TVs * TV Offers * Screen Size * 32'' - 45'' TVs * 46'' - 54'' TVs * 55" - 64" TVs * 65'' - 74'' TVs * 75'' or Larger TVs * Features * OLED TVs * QLED TVs * QD-OLED * Artwork TVs * Mini LED * 8K TVs * Projectors * View All Projectors SCREEN TECHNOLOGY EXPLAINED Read the guide Sonos Shop * Sonos Shop * Sonos Speakers * Sonos Portable Speakers * Sonos Bundles * Sonos Era * Sonos Home Cinema * Sonos Soundbars * Sonos Subwoofers * Sonos Surround Sound Bundles * Sonos Architectural * Sonos ceiling Speakers * Sonos In-Wall Speakers * Sonos Outdoor Speakers * Sonos AMP & Bundles Wireless Speakers * Shop Wireless Speakers * View All Wireless Speakers * View All TV Speakers * View All Audio Components * View Portable Speakers * Party Speakers * Smart Radios * Shop Brand * Sonos * KEF Wireless * Marshall * Sony * Bang & Olufsen * Bluesound * Devialet * Bowers & Wilkins * Audioengine * JBL * Audio Pro Home Cinema * Speakers & Soundbars * Soundbars * Subwoofers * Atmos Speakers * Floorstanding Speakers * Standmount Speakers * Centre Speakers * Home Cinema Separates * AV Receivers * DVD & Blu-Ray Players * Home Cinema Bundles * Home Cinema Bundles * Speaker Bundles * Sonos Home Cinema Bundles Ceiling & Outdoor Speakers * Speaker Type * Ceiling Speakers * In-Wall Speakers * Wireless All-In-One - Powered * Ceiling Speaker & Amplifier Bundles * Speaker Cable & Fire Hoods * Sonos Amp & Speaker Packages * Outdoor Audio * Wall-Mounted Speakers * Rock Speakers * Waterproof Ceiling Speakers * Sonos Outdoor Speakers * All Outdoor Speakers * Outdoor Speaker Bundles * Shop Room * Kitchen * Bedroom * Bathroom * Lounge * Garden * Shop Brand * Monitor Audio * Bowers & Wilkins * KEF * Blucube * Sonos * Sonance * Lithe Audio HiFi * Speaker Type * Bookshelf Speakers * Floor Standing Speakers * Subwoofers * Active Speakers * Computer Speakers * Amplifiers / Streamers * 2-Channel Amplifiers * AV Receivers * Smart / Streaming Amplifiers * Wireless Streamers * Separates * CD Players * HiFi Bundle Deals * Amplifier & Speaker Bundles * Turntable & Speaker Bundles * HiFi Accessories * Speaker Stands * Speaker Cable * Turntable Pre-Amps * Speaker Switches Headphones Televisions* Gaming * PS5 * PS5 Best Sellers * Console & Bundles * Playstation 5 Games * PS5 Pre-Orders * PS5 Accessories * PS4 * PS4 Best Sellers * PS4 Console & Bundles * Games * PS4 Pre-Orders * PS4 Accessories * Nintendo Switch * Switch Best Sellers * Nintendo Switch Console & Bundles * Games * Nintendo Switch Pre-Orders * Nintendo Switch Accessories * Xbox Series X/S * Series X/S Best Sellers * Xbox Series X/S Console & Bundles * Games * Xbox Series X/S Pre-Orders * Xbox One * Xbox One Best Sellers * Games * Xbox One Pre-Orders Accessories * All Accessories * Sonos Accessories * Speaker Switches * Turntable Accessories * TV Brackets * Cables & Charging * Speaker Cable * HDMI Cables * Hi-Fi Interconnects * Power Cables & Charging Clearance Brands * A-E * LG * F - L * soundcore * Audioquest * JBL * Zuma * Onkyo Deals close Basket 10 of 0 products in cart displayed Your basket is empty. Waiting to be filled with great entertainment. View basket Shop now Your basket is empty. Waiting to be filled with great entertainment. View basket Shop now Qty: Edit Remove small-info small-info Savings Subtotal Total * apple * paypal-alt * mastercard * amex-alt * visa * amazon-alt * deko secure Checkout Securely loader Loading... x Checkout using your account Email Address Password Log In Forgot your password? Checkout as a new customer Creating an account has many benefits: * See order and shipping status * Track order history * Check out faster Create an Account Skip to Content large-delivery Order in the next: For delivery tomorrow :: Free next day delivery* large-delivery Free next day delivery* large-price-check Lowest price guaranteed We'll match or beat it. large-warranty Extended Warranty We've got you covered We're rated excellent Read our reviews on Reviews.io large-delivery Order in the next: For delivery tomorrow :: Free next day delivery* large-delivery Free next day delivery* large-delivery Free next day delivery* large-price-check Lowest price guaranteed We'll match or beat it. large-warranty Extended Warranty We've got you covered We're rated excellent Read our reviews on Reviews.io large-delivery Order in the next: For delivery tomorrow :: Free next day delivery* large-delivery Free next day delivery* large-price-check Lowest price guaranteed We'll match or beat it. large-warranty Extended Warranty We've got you covered We're rated excellent Read our reviews on Reviews.io x WELCOME. GREAT ENTERTAINMENT STARTS HERE... Here to help! Chat with us Shop Differently The SHS Promise Sonos Shop TVs Soundbars Portable audio Gaming Hi-Fi Turntables Accessories chevron-left chevron-right NEW & TRENDING... OUTDOOR AUDIO Soundtrack your summer with our outdoor audio solutions, built to brave the elements. Perfect for the British weather! Shop now TELEVISIONS Bring the cinema home! Upgrade your entertainment with our range of Samsung, Sony, Panasonic & LG TVs. Shop now New NEW IN Explore the latest from your go-to brands or discover new favourites to enhance your setup. Shop now BEST SELLERS POPULAR PRODUCTS RIGHT NOW... * Best sellers * Staff favourites * New in Save up to £45 basket basket-add Add to Basket Sonos One SL The speaker for stereo pairing and home theatre surrounds info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (36 Reviews) from £134.00 was £179.00 Save up to £45 - Lowest price promise basket basket-add Add to Basket Sonos Move 2 Portable Wi-Fi & Bluetooth Smart Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (10 Reviews) from £449.00 Lowest price promise Save up to £50 basket basket-add Add to Basket Sonos Beam (Gen 2) Compact Smart Soundbar with Dolby Atmos info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (172 Reviews) from £449.00 was £499.00 Save up to £50 - Lowest price promise basket basket-add Add to Basket KEF LSX II Active Bookshelf Speaker (Pair) Wireless Hi-Fi Speakers info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (1 Review) from £1,199.00 Lowest price promise basket basket-add Add to Basket Sonos ERA 300 Dolby Atmos Smart Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (9 Reviews) from £449.00 Lowest price promise basket basket-add Add to Basket Marshall Willen Compact, Portable Bluetooth Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (2 Reviews) from £99.00 Lowest price promise Save up to £40 basket basket-add Add to Basket Sonos SUB Mini Compact Wireless Subwoofer info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (59 Reviews) from £389.00 was £429.00 Save up to £40 - Lowest price promise chevron-left chevron-right Save £201 basket basket-add Add to Basket Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV Sony XR65A95LU In stock (2 Reviews) now £3,498.00 was £3,699.00 Save £201 - Lowest price promise basket basket-add Add to Basket Devialet Gemini II True Wireless Earbuds With Adaptive Noise Cancellation info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (3 Reviews) from £349.00 Lowest price promise basket basket-add Add to Basket Soundcore Motion X600 Portable Hi-Res Wireless Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (1 Review) from £199.00 Lowest price promise basket basket-add Add to Basket Ruark R1S Smart Radio DAB, DAB+, FM Tuner + WiFi, Bluetooth (Mid Grey) In stock £299.00 Lowest price promise basket basket-add Add to Basket Ruark R410 Integrated music system All-in-one Wireless Music System info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (1 Review) from £1,299.00 Lowest price promise Save up to £99 basket basket-add Add to Basket Audio Pro A15 The portable, multi-room speaker with Bluetooth & WiFi functionality info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour from £250.00 was £349.00 Save up to £99 - Lowest price promise chevron-left chevron-right basket basket-add Add to Basket Devialet Gemini II True Wireless Earbuds With Adaptive Noise Cancellation info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (3 Reviews) from £349.00 Lowest price promise basket basket-add Add to Basket Soundcore Motion X600 Portable Hi-Res Wireless Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (1 Review) from £199.00 Lowest price promise basket basket-add Add to Basket Ruark R1S Smart Radio DAB, DAB+, FM Tuner + WiFi, Bluetooth (Mid Grey) In stock £299.00 Lowest price promise basket basket-add Add to Basket Ruark R410 Integrated music system All-in-one Wireless Music System info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (1 Review) from £1,299.00 Lowest price promise Save £201 basket basket-add Add to Basket Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV Sony XR65A95LU In stock (2 Reviews) now £3,498.00 was £3,699.00 Save £201 - Lowest price promise Save up to £99 basket basket-add Add to Basket Audio Pro A15 The portable, multi-room speaker with Bluetooth & WiFi functionality info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour from £250.00 was £349.00 Save up to £99 - Lowest price promise chevron-left chevron-right large-price-check Lowest prices guaranteed ~ Price match in 90 mins or less! Seen it cheaper elsewhere? We'll match or beat it! Learn more Ts & Cs apply. SONOS DEALS SAVE ON SONOS FAVOURITES... * Hot deals * Bundle offers basket basket-add Add to Basket Sonos Move 2 Portable Wi-Fi & Bluetooth Smart Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (10 Reviews) from £449.00 Lowest price promise basket basket-add Add to Basket Sonos ERA 300 Dolby Atmos Smart Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (9 Reviews) from £449.00 Lowest price promise Save up to £40 basket basket-add Add to Basket Sonos SUB Mini Compact Wireless Subwoofer info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (59 Reviews) from £389.00 was £429.00 Save up to £40 - Lowest price promise basket basket-add Add to Basket KEF LSX II Active Bookshelf Speaker (Pair) Wireless Hi-Fi Speakers info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (1 Review) from £1,199.00 Lowest price promise basket basket-add Add to Basket Marshall Willen Compact, Portable Bluetooth Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (2 Reviews) from £99.00 Lowest price promise Save up to £45 basket basket-add Add to Basket Sonos One SL The speaker for stereo pairing and home theatre surrounds info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (36 Reviews) from £134.00 was £179.00 Save up to £45 - Lowest price promise Save up to £50 basket basket-add Add to Basket Sonos Beam (Gen 2) Compact Smart Soundbar with Dolby Atmos info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (172 Reviews) from £449.00 was £499.00 Save up to £50 - Lowest price promise chevron-left chevron-right Save £201 basket basket-add Add to Basket Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV Sony XR65A95LU In stock (2 Reviews) now £3,498.00 was £3,699.00 Save £201 - Lowest price promise basket basket-add Add to Basket Devialet Gemini II True Wireless Earbuds With Adaptive Noise Cancellation info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (3 Reviews) from £349.00 Lowest price promise basket basket-add Add to Basket Soundcore Motion X600 Portable Hi-Res Wireless Speaker info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (1 Review) from £199.00 Lowest price promise basket basket-add Add to Basket Ruark R1S Smart Radio DAB, DAB+, FM Tuner + WiFi, Bluetooth (Mid Grey) In stock £299.00 Lowest price promise basket basket-add Add to Basket Ruark R410 Integrated music system All-in-one Wireless Music System info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (1 Review) from £1,299.00 Lowest price promise Save up to £99 basket basket-add Add to Basket Audio Pro A15 The portable, multi-room speaker with Bluetooth & WiFi functionality info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour from £250.00 was £349.00 Save up to £99 - Lowest price promise chevron-left chevron-right GET INSPIRED Sonos Shop Shop now Luxury Listening Shop now Home Cinema Shop now SHOP DIFFERENTLY. NOT JUST A SALE, A JOURNEY OF GREAT ENTERTAINMENT. Learn more 01. What should I buy Expert advice & resources * Honest knowledgeable support * Helpful articles & guides * Free speaker design service * 115k strong YouTube channel 02. Why buy from SHS Our promise to you * Lowest prices guaranteed * Free next day delivery* * 5* star service * Extended warranties Our favourite part! 03. I’ve bought, what next… Great entertainment starts here * Enjoy your purchase * Join our 150k strong community * Exclusive loyalty pricing * 1st class customer support large-expert-team Our team of Tech Guides are here to help! We're here every step of the way. Call us on 0800 677 1100 or Chat with us online. Chat now FEATURED PRODUCTS YOU MIGHT LIKE THESE... Save up to £49 basket basket-add Add to Basket 2 x Sonos ERA 300 Dolby Atmos stereo pair or as dedicated upwards-firing rears for a Sonos Arc or Sonos Beam (Gen 2) soundbar. info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (28 Reviews) from £849.00 was £898.00 Save up to £49 - Lowest price promise Save up to £50 basket basket-add Add to Basket 2 x Sonos ERA 100 Immersive smart speaker bundle info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (33 Reviews) from £448.00 was £498.00 Save up to £50 - Lowest price promise Save up to £126 basket basket-add Add to Basket Sonos Beam + 2 x Sonos ERA 100 Compact Dolby Atmos Soundbar & Rear Surrounds bundle info Currently out of stock. Notify me. Notify me when back in stock Subscribe By completing this form you agree to our Privacy Policy x In stock Colour (3 Reviews) from £871.00 was £997.00 Save up to £126 - Lowest price promise Order the all-new 2024 Samsung, Sony & LG TVs now! Grab a great deal on the latest TVs with lowest prices guaranteed & FREE soundbars on selected models. Shop now EVERYTHING YOU NEED TO CREATE YOUR IDEAL SETUP Years of experience and knowledge Meet our Tech Guides Hundreds of in-depth product reviews & comparisons Join our 100K+ YouTube community Free speaker design service Learn more Expert reviews without the complicated tech jargon Visit our Knowledge Hub chevron-left chevron-right I'M LOOKING FOR... Wireless speakers Shop wireless speakers Turntables Shop turntables Headphones & earbuds Shop heaphones Ceiling speakers Shop ceiling speakers New NEW BRAND SPOTLIGHT: SHOKZ Upgrade your TV, music, and gaming experiences with this impressively compact and easy-to-use new soundbar from Sonos. Browse the range INSPIRATION & ADVICE * FAQS & Tutorials Top 10 Best Black Friday TV Deals 2023: Sony, LG & Samsung On the hunt for the best Black Friday TV deals? * Sonos Releases The Best Sonos Speakers to Buy In 2023 * FAQS & Tutorials Sonos Early Black Friday Sale: Save up to 20% on Sonos Home Cinema * play FAQS & Tutorials #PRESSPLAY ON GREAT ENTERTAINMENT WITH SMART HOME SOUNDS * FAQS & Tutorials ARCAM unveil new Radia Series in brand refresh CUSTOMER REVIEWS DON'T JUST TAKE OUR WORD FOR IT! "I totally recommend Smart Home Sounds, their advice and support was first class, adding to the quality and functionality of the Sonos system." Mike Tindall MBE (Smart Home Sounds customer) Read more reviews Excellent 4.89 average 5,175 reviews Andrew Verified Customer Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV Sony XR65A95LU Absolutely brilliant. Smart home and this tv is lovely. Morden, United Kingdom, 11 minutes ago Anonymous Verified Customer Sonos Sub (Gen 3) Great bass and good looking sub. Smart Home Sounds offered a great discount and warranty too. 14 hours ago Alan Stow Verified Customer Sonos Arc Ultra Fantastic upgrade to the Arc the sound and voice is much clearer Manchester, United Kingdom, 1 day ago Michael Reid Verified Customer Excellent delivery ⭐️ Excellent Communication ⭐️ Excellent price ⭐️ Excellent product ⭐️ (Sonos Era300) I am Definitely going to use smart home sounds again , Thanks Reading, United Kingdom, 2 days ago Robert Verified Customer Sonos Arc Ultra An upgrade from the original Arc. Was it worth it. Yes! Dunfermline, United Kingdom, 3 days ago Louis Hopwood Verified Customer Hama Anti static carbon fibre record cleaner Anti-static carbon fibre record brush with aluminium handle Great brush that's easy to use, compact and easy to clean. Pudsey, United Kingdom, 3 days ago Jurijus lukas Verified Customer Sennheiser Momentum True Wireless 4 The product is pretty much exactly as the reviews on YT indicated. They sure are a step up from my previous true wireless. However the mic during calls is still really poor. I do love the fact that they have the rubber bits on the sides which allows the pods to lock in your ear. The sound quality is amazing, I can now run, cycle amd skate with them without fearing that they will fall out when I do backflips and all that. The noise cancellation is also better too. Anti wind function is great. Overall the product is wonderful but as always nothing is perfect and implements are required. Can't leave without giving smart home a shout out. First time user and very happy with how quick everything got processed as well as the fast and free delivery all top notch. 6 days ago Louis Hopwood Verified Customer Rega Planar 1 inc Pre-Amp A great package for those looking to start out in the world of vinyl with decent quality and performance. The P1 is probably the best of the best in the high-end of the entry level for a turntable. Inverurie, United Kingdom, 1 week ago neil verow Verified Customer Sonos ERA 300 I normally use Sonos to manage my CDs and vinyl - (translation: desperately try to find stuff in a still-growing 50-year long collection) all digitised and stored on a NAS to playback through various old-school separates throughout the house. My first ERA 300 was a bit of an unnecessary indulgence - well isn't it all, really - but its wifi performance was so impressive I indulged still further and now have a stereo pair. Part of its working life will be spent hanging like noisy fat bats in the roof of my campervan, fed by a Jackery Power Station. Bluetooth in the van was a tad unreliable so I resigned myself to plugging a cable between the SD card player and one of the ERAs, and seeing which one of the channels I got, (albeit FLAC). "Imagine my surprise" to find the tethered one somehow shares the signal with the abandoned sibling, and I'm still getting stereo! Clever buggers. Looking forward now to some suitable weather for lashings of off-grid fun! And great service from Smart Home Sounds matched by unbeatable prices. Thanks again. Walsall, United Kingdom, 1 week ago Peter Marsden Verified Customer Sonos SUB Mini Does exactly what it should, perfect for my living room size and easily diabled or reduced bass for nights. London, United Kingdom, 1 week ago roger Verified Customer Sonos SUB Mini I have two sonos era 100s set up as a stereo pair just as a music source in my lounge. I was delighted with the sound but was wondering what improvement adding and sonos mini sub would make. I took the chance and purchased one from smart home sounds. Wow the quality of the bass response was phenomenal not too overpowering but just balanced enough to drastically add the extra sound stage. I have always been a HiFi nerd having owned the top system in the past and will say I cannot really tell the difference. No not true I think the current set up surpasses my earlier system. Would definitely recommend adding the mini sub to your eras . The help and quick response from Smart home sounds was superb and I would thoroughly recommend them. Also as a side they offered the best price for the sonos I could find on the internet. Isleworth, United Kingdom, 1 week ago Alan Stow Verified Customer Always a good service from a great company 👍 1 week ago Ian young Verified Customer Sonos ERA 300 Fantastic speaker on its own. But as a stereo pair for music or as surrounds in a movie room. Wow, just wow. 2 weeks ago Tina Champ Verified Customer Sonos ERA 100 One of the best things I have purchased in a long while Romsey, United Kingdom, 2 weeks ago Mark Verified Customer Sonos ERA 100 Smart homes always deliver promptly with the best price available. I have purchased multiple things from them and shall continue to do so.. Basildon, United Kingdom, 2 weeks ago LEO Verified Customer 16 Gauge 2 Core speaker cable (50 Metre Roll) Industry-Standard Speaker Cable Good Audio Cable. But nothing of extraordinary. There are many cable manufacturers similar to this one. In addition, it is very expensive. Nearly £2.00 per meter. So, reseach market thoughrouly before making buying choice. St Albans, United Kingdom, 2 weeks ago tony raynor Verified Customer Sonos ERA 100 + Pro-Ject T1 SB + Sonos ERA Line-In Adapter Bundle Great product sounds amazing Lancaster, United Kingdom, 2 weeks ago John Verified Customer Arcam Radia A25 Integrated Amplifier High-Resolution Stereo Amplifier Superb bit of kit….the sound quality is top notch. Worth every penny. Chelmsford, United Kingdom, 2 weeks ago Alan Mountaine Verified Customer Samsung HW-Q990D Wireless Soundbar 11.1.4ch Dolby Atmos & DTS:X Soundbar with Subwoofer & Rear Speakers Very good soundbar, the instructions need a bit of work, but the Samsung HWQ990D soundbar is brilliant and worth the money Stockton-on-Tees, United Kingdom, 2 weeks ago Andrew Knight Verified Customer An absolutely delightful company to purchase from and deal with. The communications from the outset were superb regarding my order confirmation right through to the courier collection and delivery time. Smart Home pricing is excellent too, as many similar outlets were charging a lot more. I had a technical query and Tom and Jamie worked through the problem swiftly and came to a solution within a day, so overall, an excellent company to deal with. I highly recommend Smart Home products and the back up service is second to none. Thank you. Blackburn, United Kingdom, 2 weeks ago * * * * * * * * * * * * * * * * * A team of experts at every step of your journey. Call us on 0800 677 1100 or Chat with us online CONTINUE THE JOURNEY ON INSTAGRAM #SMARTHOMESOUNDS large-delivery Free next day delivery Receive free delivery on orders over £99. Enjoy great entertainment sooner. large-price-check Lowest price guaranteed Found it cheaper? Like-for-like, we’ll match or beat it! Get in touch today. large-returns Hassle free returns Return your order hassle free within 30 days for a full refund. 100% satisfaction. large-contact Speak with an expert Call 0800 677 1100 or chat with us online for help creating your ideal setup. Great entertainment starts here. Join the community! Sign up & get £10 off your first order. PLUS get exclusive deals & the latest news straight to your inbox. Subscribe We’d love to send you exclusive offers and the latest info from Smart Home Sounds by email. We’ll always treat your details with the utmost care and won’t share them with any third party. You can, of course, opt out of these communications at any time! See our Privacy Policy for more info. * facebook * twitter * youtube * instagram Explore chevron-down * Sonos Shop * How Sonos Works * Why Sonos? * About Us * Reviews * Contact * Home Company chevron-down * Privacy Policy * Cookie Policy * Terms and Conditions * Terms of Use * Sitemap Support chevron-down * Delivery & Returns * My Basket * My Account * Unsubscribe * Help Center Contact chevron-down info@smarthomes.sozowebdesign.com 0800 677 1100 (9am - 5pm) location Unit 12 Triangle park, Gloucester, Gloucestershire, GL1 1AJ Proud to support © Smart Home Sounds ltd. All rights reserved. Website By SOZO * apple * paypal-alt * mastercard * amex-alt * visa * amazon-alt * deko close The Smart Home Sounds 6 Year Guarantee Get true peace of mind with our FREE extended 6 year warranty on every Sonos product bought from us TRUE PEACE OF MIND WITH OUR 6 YEAR SONOS WARRANTY We endeavour to provide the best service possible & go the extra mile. We are so impressed with the build quality of all Sonos products, we offer an automatic 6-year extended warranty on all Sonos products at no extra cost, giving you complete peace of mind. If your product becomes faulty in the first 6 years from purchase, we will repair or replace your unit with a brand new item. If the product is unrepairable and the model purchased is no longer available, you will be issued with a store credit for the full value paid of the failed product. HOW DO I ACTIVATE MY 6-YEAR SONOS WARRANTY? The good news is you don't need to follow any additional steps to activate your extended 6-year warranty. No paperwork is required as this process is done digitally. * Your warranty starts from the date your speaker is first activated * We will be able to obtain all your details from the serial number of your registered product. * All batteries will be covered by the standard manufactures warranty Please read the full terms & conditions on our 6-year warranty. ABOUT SMART HOME SOUNDS At Smart Home Sounds, we are leading Home Audio Visual Specialists and one of the UK’s largest Sonos dealers. Our knowledgeable team provide expert service & advice on a wide range of audiovisual products and are here to help you find the perfect setup for your home. Subscribe We’d love to send you exclusive offers and the latest info from Smart Home Sounds by email. We’ll always treat your details with the utmost care and won’t share them with any third party. You can, of course, opt out of these communications at any time! See our Privacy Policy for more info. close 30 Day Hassle-free returns We promise you'll love your new purchase. If you don't, we'll refund you with no questions asked! OUR 30 DAY HASSLE-FREE RETURNS POLICY At Smart Home Sounds, we strive to offer the best service possible for our customers and only sell and recommend products we have tried, tested and love ourselves. However, the best way to know if a product is right for you is to test it yourself. For a risk-free purchase, we’ve given you 30 days to test out your new items and if you’re not 100% satisfied, we’re happy to refund your order with no questions asked. Return costs for non-faulty items are the responsibility of the purchaser as our standard shipping is free. All we ask is that all items are sent back as received, in their original condition and with the original packaging, manuals & components. HOW DOES THE RETURNS POLICY WORK? 1. Place an order with Smart Home Sounds on any qualifying items (look for the 30 days hassle-free returns icon on the product page) 2. Your 30 Days start when your order arrives. Test and enjoy your new purchase from the comfort of your home over the next 30 days. Not applicable on in-ear headphones. 3. If, for any reason, it’s not quite what you expected and you’re not 100% satisfied, simply get in touch with our team via email, phone or live chat and we will start the return process for you. Please read our full Terms & Conditions here. ABOUT SMART HOME SOUNDS At Smart Home Sounds, we are leading Home Audio Visual Specialists and one of the UK's largest Sonos dealers. Our knowledgeable team provide expert service & advice on a wide range of audiovisual products and are here to help you find the perfect setup for your home. Subscribe We’d love to send you exclusive offers and the latest info from Smart Home Sounds by email. We’ll always treat your details with the utmost care and won’t share them with any third party. You can, of course, opt out of these communications at any time! See our Privacy Policy for more info. close Rega Lifetime Warranty Rega products carry a lifetime warranty against manufacture defects. TRUE PEACE OF MIND WITH THE REGA LIFETIME WARRANTY Every product Rega make is designed and assembled in the UK to the highest possible standard. This warranty covers genuine confirmed manufacture defects. This warranty does not cover wear and tear. EXCLUSIONS Any unauthorised modifications or failure to follow the Rega recommended guidelines may invalidate the warranty. Please see your user manual for detailed warranty information for your particular product. Your statutory rights are not affected. See more. ABOUT SMART HOME SOUNDS At Smart Home Sounds, we are leading Home Audio Visual Specialists. Our knowledgeable team provide expert service & advice on a wide range of audiovisual products and are here to help you find the perfect setup for your home. Subscribe We’d love to send you exclusive offers and the latest info from Smart Home Sounds by email. We’ll always treat your details with the utmost care and won’t share them with any third party. You can, of course, opt out of these communications at any time! See our Privacy Policy for more info. close SHS Loyalty Points Start collecting your points today to spend on future purchases from SHS. WHAT ARE SHS LOYALTY POINTS? We love rewarding our loyal customers - that's why you'll earn SHS Loyalty Points for every pound you spend on gaming purchase from us which can be spent on future purchases! HOW DO I EARN SHS LOYALTY POINTS? * You will earn 10 Loyalty Points for every £1 spent. * An average £50 game will earn you £1.25 to spend on future purchases. * Points will be earned on any gaming purchase made at Smart Home Sounds. * You must create a Smart Home Sounds account to collect your points. Points earned will then show in your SHS account. * Points are added to your account 30 days after purchase. HOW DO I SPEND MY SHS LOYALTY POINTS? * 400 SHS Loyalty Points equate to £1.00 to spend on future purchases. * You can spend your SHS Loyalty Points on any product on our website from future games to headphones, speakers, TVs and more. * Click 'Spend Points' at checkout to apply your points - points cannot be used alongside any other coupons or discount codes. * If you return your order, any points spent on that order will be reimbursed. Any points earned on a returned item will be rescinded. Please allow up to 7 working days for your points to adjust in your account. Full Ts & Cs apply - read more here. Subscribe We’d love to send you exclusive offers and the latest info from Smart Home Sounds by email. We’ll always treat your details with the utmost care and won’t share them with any third party. You can, of course, opt out of these communications at any time! See our Privacy Policy for more info. close Extended Christmas Returns Shop with confidence this festive period. If they don't love it, return it! Our Extended Christmas Returns We've extended our normal returns for the festive season. Any gifts bought between 27th October 2023 and 22nd January 2024 can be returned up until 31st January 2024. How does the returns policy work? * If a gift is unwanted, customers must contact Smart Home Sounds by the 31st January 2023 via phone, email or live chat. Our team will assist you in the returns process. * Items must be fully unopened and sealed. Return postage will be at the cost of the customer. * If the unwanted item is opened, this will revert to our regular 30 days returns policy. * Faulty items will follow our standard returns procedure. ABOUT SMART HOME SOUNDS At Smart Home Sounds, we are leading Home Audio Visual Specialists and one of the UK's largest Sonos dealers. Our knowledgeable team provide expert service & advice on a wide range of audiovisual products and are here to help you find the perfect setup for your home. Subscribe We’d love to send you exclusive offers and the latest info from Smart Home Sounds by email. We’ll always treat your details with the utmost care and won’t share them with any third party. You can, of course, opt out of these communications at any time! See our Privacy Policy for more info. close FREE Extended 5 Year Sony Warranty True peace of mind on your new TV purchase ENJOY YOUR ENTERTAINMENT WORRY FREE Sony's extended 5 year warranty gives you peace of mind that you're covered for longer. HOW DO I ACTIVATE MY SONY 5 YEAR WARRANTY? Simply submit your extra warranty request from the moment of purchase up to 90 days after purchase. You will need your TV serial number to make your claim. Make your extended warranty claim here. Ts & Cs apply. Valid on selected models purchased since the 1st of April 2023. ABOUT SMART HOME SOUNDS At Smart Home Sounds, we are leading Home Audio Visual Specialists and one of the UK’s largest Sonos dealers. Our knowledgeable team provide expert service & advice on a wide range of audiovisual products and are here to help you find the perfect setup for your home. Subscribe We’d love to send you exclusive offers and the latest info from Smart Home Sounds by email. We’ll always treat your details with the utmost care and won’t share them with any third party. You can, of course, opt out of these communications at any time! See our Privacy Policy for more info. cross Close in 's cross Proceed to Checkout loader Loading... Chat loader Loading... Andrew Verified Customer Sony A95L 65” Bravia XR QD-OLED 4K Ultra HD TV Sony XR65A95LU Absolutely brilliant. Smart home and this tv is lovely. Was this review helpful? Yes No Morden, United Kingdom, 11 minutes ago Anonymous Verified Customer Sonos Sub (Gen 3) Great bass and good looking sub. Smart Home Sounds offered a great discount and warranty too. Was this review helpful? Yes No 14 hours ago Alan Stow Verified Customer Sonos Arc Ultra Fantastic upgrade to the Arc the sound and voice is much clearer 1 person found this review helpful. Was this review helpful? Yes No Manchester, United Kingdom, 1 day ago Michael Reid Verified Customer Excellent delivery ⭐️ Excellent Communication ⭐️ Excellent price ⭐️ Excellent product ⭐️ (Sonos Era300) I am Definitely going to use smart home sounds again , Thanks 1 person found this review helpful. Was this review helpful? Yes No Reading, United Kingdom, 2 days ago Robert Verified Customer Sonos Arc Ultra An upgrade from the original Arc. Was it worth it. Yes! 1 person found this review helpful. Was this review helpful? Yes No Dunfermline, United Kingdom, 3 days ago Louis Hopwood Verified Customer Hama Anti static carbon fibre record cleaner Anti-static carbon fibre record brush with aluminium handle Great brush that's easy to use, compact and easy to clean. Was this review helpful? Yes No Pudsey, United Kingdom, 3 days ago Jurijus lukas Verified Customer Sennheiser Momentum True Wireless 4 The product is pretty much exactly as the reviews on YT indicated. They sure are a step up from my previous true wireless. However the mic during calls is still really poor. I do love the fact that they have the rubber bits on the sides which allows the pods to lock in your ear. The sound quality is amazing, I can now run, cycle amd skate with them without fearing that they will fall out when I do backflips and all that. The noise cancellation is also better too. Anti wind function is great. Overall the product is wonderful but as always nothing is perfect and implements are required. Can't leave without giving smart home a shout out. First time user and very happy with how quick everything got processed as well as the fast and free delivery all top notch. Was this review helpful? Yes No 6 days ago Louis Hopwood Verified Customer Rega Planar 1 inc Pre-Amp A great package for those looking to start out in the world of vinyl with decent quality and performance. The P1 is probably the best of the best in the high-end of the entry level for a turntable. Was this review helpful? Yes No Inverurie, United Kingdom, 1 week ago neil verow Verified Customer Sonos ERA 300 I normally use Sonos to manage my CDs and vinyl - (translation: desperately try to find stuff in a still-growing 50-year long collection) all digitised and stored on a NAS to playback through various old-school separates throughout the house. My first ERA 300 was a bit of an unnecessary indulgence - well isn't it all, really - but its wifi performance was so impressive I indulged still further and now have a stereo pair. Part of its working life will be spent hanging like noisy fat bats in the roof of my campervan, fed by a Jackery Power Station. Bluetooth in the van was a tad unreliable so I resigned myself to plugging a cable between the SD card player and one of the ERAs, and seeing which one of the channels I got, (albeit FLAC). "Imagine my surprise" to find the tethered one somehow shares the signal with the abandoned sibling, and I'm still getting stereo! Clever buggers. Looking forward now to some suitable weather for lashings of off-grid fun! And great service from Smart Home Sounds matched by unbeatable prices. Thanks again. Was this review helpful? Yes No Walsall, United Kingdom, 1 week ago Peter Marsden Verified Customer Sonos SUB Mini Does exactly what it should, perfect for my living room size and easily diabled or reduced bass for nights. Was this review helpful? Yes No London, United Kingdom, 1 week ago roger Verified Customer Sonos SUB Mini I have two sonos era 100s set up as a stereo pair just as a music source in my lounge. I was delighted with the sound but was wondering what improvement adding and sonos mini sub would make. I took the chance and purchased one from smart home sounds. Wow the quality of the bass response was phenomenal not too overpowering but just balanced enough to drastically add the extra sound stage. I have always been a HiFi nerd having owned the top system in the past and will say I cannot really tell the difference. No not true I think the current set up surpasses my earlier system. Would definitely recommend adding the mini sub to your eras . The help and quick response from Smart home sounds was superb and I would thoroughly recommend them. Also as a side they offered the best price for the sonos I could find on the internet. Was this review helpful? Yes No Isleworth, United Kingdom, 1 week ago Alan Stow Verified Customer Always a good service from a great company 👍 1 person found this review helpful. Was this review helpful? Yes No 1 week ago Ian young Verified Customer Sonos ERA 300 Fantastic speaker on its own. But as a stereo pair for music or as surrounds in a movie room. Wow, just wow. 2 people found this review helpful. Was this review helpful? Yes No 2 weeks ago Tina Champ Verified Customer Sonos ERA 100 One of the best things I have purchased in a long while 1 person found this review helpful. Was this review helpful? Yes No Romsey, United Kingdom, 2 weeks ago Mark Verified Customer Sonos ERA 100 Smart homes always deliver promptly with the best price available. I have purchased multiple things from them and shall continue to do so.. 1 person found this review helpful. Was this review helpful? Yes No Basildon, United Kingdom, 2 weeks ago LEO Verified Customer 16 Gauge 2 Core speaker cable (50 Metre Roll) Industry-Standard Speaker Cable Good Audio Cable. But nothing of extraordinary. There are many cable manufacturers similar to this one. In addition, it is very expensive. Nearly £2.00 per meter. So, reseach market thoughrouly before making buying choice. 2 people found this review helpful. Was this review helpful? Yes No St Albans, United Kingdom, 2 weeks ago tony raynor Verified Customer Sonos ERA 100 + Pro-Ject T1 SB + Sonos ERA Line-In Adapter Bundle Great product sounds amazing 1 person found this review helpful. Was this review helpful? Yes No Lancaster, United Kingdom, 2 weeks ago John Verified Customer Arcam Radia A25 Integrated Amplifier High-Resolution Stereo Amplifier Superb bit of kit….the sound quality is top notch. Worth every penny. 1 person found this review helpful. Was this review helpful? Yes No Chelmsford, United Kingdom, 2 weeks ago Alan Mountaine Verified Customer Samsung HW-Q990D Wireless Soundbar 11.1.4ch Dolby Atmos & DTS:X Soundbar with Subwoofer & Rear Speakers Very good soundbar, the instructions need a bit of work, but the Samsung HWQ990D soundbar is brilliant and worth the money 1 person found this review helpful. Was this review helpful? Yes No Stockton-on-Tees, United Kingdom, 2 weeks ago Andrew Knight Verified Customer An absolutely delightful company to purchase from and deal with. The communications from the outset were superb regarding my order confirmation right through to the courier collection and delivery time. Smart Home pricing is excellent too, as many similar outlets were charging a lot more. I had a technical query and Tom and Jamie worked through the problem swiftly and came to a solution within a day, so overall, an excellent company to deal with. I highly recommend Smart Home products and the back up service is second to none. Thank you. 2 people found this review helpful. Was this review helpful? Yes No Blackburn, United Kingdom, 2 weeks ago × Close dialog Sign up & join our community! £10 OFF Your first order over £100 Signup to get your code! GET £10 OFF NOW! No, thanks