');$newDiv.html($domArticle.html());$.each($domArticle[0].attributes, function(index, attr) {if (attr.name !== 'href') {$newDiv.attr(attr.name, attr.value);}});// Find any images within the new div and apply the CSS style$newDiv.find('img').css('opacity', '1').css('display', 'block').css('margin', 'auto').css('margin-bottom', '30px');$domArticle.replaceWith($newDiv);}this.fillPriceAddToBag();// Load shortlinkthis.loadPLPProductShortLink(domArticle, imgSrc, imgSrcset);// Add product to schema.org meta datasthis.loadPLPProductSchemas(domArticle, productId, productSku, product, imgSrc);}loadPLPProductSchemas(domArticle, productId, productSku, product, imgSrc){/* Add an entry to productschemas */// Get properties necessary to fill the schema objectconst order = domArticle.parent().css("order") / 10; // Order of object into categoryconst position = this.schemas.length + 1;// Store ecapi resultlet ecapi = false;if (product.type === "machine") ecapi = product.ecapi[productSku]else ecapi = product.ecapi;let inStock = "https://schema.org/InStock";if (ecapi.inStock !== true) inStock = "https://schema.org/InStock";if (ecapi.segments){this.schemasToLoad --;if (this.schemas.length === this.schemasToLoad) this.displayProductsSchemas(); // If all products ready, display schemareturn false; // Don't add restricted products}// Create schema Itemlet schemaItem ={"@type": "ListItem","position": position, // Alternative: use "order", but then position reinitialize each range"item": { "@type": "Product", "name": ecapi.name, "image": imgSrc, "brand": "Nespresso", "mpn": ecapi.internationalId, "url": ecapi.pdpURLs.desktop, "sku": ecapi.internationalId, "offers": { "url": ecapi.pdpURLs.desktop, "priceCurrency": "CHF", "price": ecapi.price, "availability": inStock, "seller": { "@type": "Organization", "name": "Nespresso" } }}}// Add schema item to schemasthis.schemas.push(schemaItem);// Inject script with schemas?if (this.schemas.length === this.schemasToLoad) this.displayProductsSchemas();}displayProductsSchemas(){// Remove existing script if it exists$("#customPLPLDJsonScript").remove();// Create a new script elementlet scriptElement = document.createElement('script');scriptElement.type = 'application/ld+json';scriptElement.id = 'customPLPLDJsonScript';// Create the JSON-LD contentlet ldJsonContent = { "@context": "https://schema.org", "@type": "ItemList", "itemListElement": this.schemas // Assuming 'this.schemas' is a valid JSON object or array};scriptElement.textContent = JSON.stringify(ldJsonContent, false, 2); // Set the text content of the script element to the JSON-LD contentdocument.getElementById('main').appendChild(scriptElement); // Append the script element to the #main section}cleanDescription(htmlString){// Create a temporary DOM element to parse the HTML contentconst tempDiv = document.createElement('div');tempDiv.innerHTML = htmlString;// Check for the first
tag or any child if
doesn't existlet firstPara = tempDiv.querySelector('p') || tempDiv.firstChild;// Handle cases where first child is not useful (like empty or undefined)if (!firstPara) {return '';}// Extract text until the first
tag, if it existsif (firstPara.innerHTML.includes('
')) {firstPara = firstPara.innerHTML.split('
')[0];tempDiv.innerHTML = firstPara; // Update tempDiv with the content before
firstPara = tempDiv; // Update firstPara to be tempDiv for final extraction}// Extract text content and trim itlet textContent = firstPara.textContent || firstPara.innerText || '';textContent = textContent.trim();// Limit the output to 80 characters and append "..." if exceedsif (textContent.length > 80) {textContent = textContent.substring(0, 77) + '...'; // Use 77 to account for the ellipsis}// Return the final cleaned and trimmed textreturn textContent;}loadPLPProductImg(imgSrc, imgSrcset, imgAlt){const img = new Image();img.src = imgSrc;img.width = 120;img.height = 120;img.loading = 'lazy';img.srcset = imgSrcset;img.alt = imgAlt;img.onload = () => img.classList.add('loaded');return img;}loadPLPProductShortLink(domArticle, imgSrc, imgSrcset){const $domArticleParent = domArticle.parents(".byrange");if ($domArticleParent.length > 0) {const rangeId = domArticle.parents(".range").attr("id").replace("range_", "");const $linkId = $("#uPLPLinks #link_" + rangeId);// Store imgSrc and imgSrcsetthis.shortLinksImgsSrc[rangeId] = imgSrc;this.shortLinksImgsSrcSet[rangeId] = imgSrcset;if ($linkId.hasClass("empty")) {$linkId.removeClass("empty");const img = new Image();img.loading = "lazy";img.className = "product";img.src = imgSrc;img.srcset = imgSrcset;img.width = 80;img.height = 80;$linkId.append(img);img.alt = rangeId;img.onload = () => img.classList.add('loaded');}}}loadPLPProductMachine(domArticle, productId, productSku, product, colClick = false) {const ecapi = product.ecapi[productSku];// Create and setup an image elementconst imgSrc = this.srcGet(ecapi.responsiveImages.standard);const imgSrcset = this.srcsetGet(ecapi.responsiveImages.standard);const img = this.loadPLPProductImg(imgSrc, imgSrcset, ecapi.name);// Generate USPs / key features for descriptionlet usps = "", i =0;for (const feature of product.key_features){i++; if (i > 3) continue;const reg = /vl_.*_cupsizes/;const featurePng = feature.replace(reg, "vl_cupsizes");let featureTxt = enriched_features[feature][lang].replace("/", " / ");let featureImg = `/shared_res/mos/free_html/ch/machines/enriched_features/no_circle/${featurePng}.png?impolicy=small&imwidth=64`;usps += `
${featureTxt}
`;}let description = product.description[lang];if (product.bundle === true){description += " ("+this.translations.machineBundle+")";if (product.bundledescription)if (product.bundledescription[lang])description = product.bundledescription[lang];}// Generate the rest of the product HTMLconst restOfProdHtml = `${this.loadPLPProductMachineLabel(product)}${this.loadPLPProductMachineConnectivity(product)}
${product.name}
${description}
${this.fillPrice(ecapi)}`;//
${usps}
// -> FOR USPs// Clear the domArticle content and append the new elementsdomArticle.attr("href", ecapi.pdpURLs.desktop).html('').append(img).append(restOfProdHtml);this.fillPriceAddToBag();// Direct assignment for out of stock statusconst outOfStock = ecapi.inStock === false;// Check if machine base color should not be displayed because of filterslet hideColor = false;if (!this.colorinSimilarity(ecapi.colorShade.cssCode)) hideColor = true;// Load colorpointsconst promises = Object.entries(product.skus).map(([colId, colSku]) =>this.skusClass.getSKU(colSku.sku).then(data => {if (!this.colorinSimilarity(data.colorShade.cssCode)) return;domArticle.find(".colorpoints").append(this.loadPLPProductMachineColor(product, colSku.sku, colSku.sku === productSku));// Initial color hidden, look for a sku to clickif (hideColor && data.inStock){hideColor = false;return data.internationalId; // Return the sku that needs to be clicked if out of stock}if (outOfStock && data.inStock){return data.internationalId; // Return the sku that needs to be clicked if out of stock}}).catch(error => {console.log(`Failed to load: ${colId} (${colSku.sku})`);}));Promise.all(promises).then(results => {domArticle.find(".colorpoint").on("click", event => {event.preventDefault();this.updatePLPProductMachineColor(domArticle, productId, $(event.currentTarget), product, true);});const skuToClick = results.find(result => typeof result === "string");if (skuToClick) {this.preventClosePanel = true;if (colClick === false) domArticle.find(`.colorpoint[data-sku='${skuToClick}']`).click();}});// Load shortlinkif ((!ecapi.name.includes("milk")) && (product.bundle === false))this.loadPLPProductShortLink(domArticle, imgSrc, imgSrcset);// Add product to schema.org meta datasthis.loadPLPProductSchemas(domArticle, productId, productSku, product, imgSrc);}fillPrice(ecapi){/* Fill price section of an article and prepare add to bag btn */let inStock = "inStock";if (ecapi.inStock !== true) inStock = "outOfStock";let html = `
Fr. ${ecapi.price.toFixed(2)}
`;return html;}fillPriceAddToBag(){if (this.addBtns === false) {this.addBtns = true;this.fillPriceAddToBagBtn();}else{clearTimeout(this.addBtns);this.addBtns = setTimeout( () => {this.addBtns = false;this.fillPriceAddToBagBtn();}, 100);}}fillPriceAddToBagBtn(){if (typeof mosaic !== "undefined") mosaic.initializeAllFreeHTMLModules(document.getElementById('uPLP'));else setTimeout(() => {this.fillPriceAddToBagBtn();}, 50);}loadPLPProductMachineConnectivity(product){/** Returns connectivty badge for machine tiles*/if (product.connectivity === false) return ``; // No connectivity badgereturn `
`;}loadPLPProductMachineLabel(product){/** Returns energy label for machine tiles*/let labelColor = ``;switch (product.energy_label){case "A+++" : labelColor = "#009641"; break;case "A++" : labelColor = "#52ae32"; break;case "A+" : labelColor = "#c8d300"; break;case "A" : labelColor = "#ffed00"; break;case "B" : labelColor = "#fbba00"; break;case "C" : labelColor = "#ec6608"; break;case "D" : labelColor = "#e30613"; break;}return `
${product.energy_label}
`;}loadPLPProductMachineColor(product, colSku, current) {const ecapi = product.ecapi[colSku];return `
`;}updatePLPProductMachineColor(domArticle, productId, colorpoint, product){/* Change a machine's color */const colSku = colorpoint.attr("data-sku");const domParent = domArticle.parent();// Update articlethis.loadPLPProductMachine(domArticle, productId, colSku, product, true);// Update machine subscription link if availablelet machineLink = domParent.find(".subscriptionLink");const url = product.ecapi[colorpoint.attr("data-sku")].pdpURLs.desktop;machineLink.attr("href", url + "?subscription");domParent.find(".plp_badges").remove();domParent.attr("data-product-item-id", colSku);domParent.attr("data-product-code", colSku);domParent.find(".subscriptionCTA").attr("href", product.ecapi[colSku]["pdpURLs"]["desktop"]+"?subscription"); // Update sub btn link as well// Re apply badges?if (product.badgesSkus){if (product.badgesSkus.includes(colSku)){$.each(ctool.badges, (campaign, banners) => {set_customSB({campaign:campaign}, banners, false);});}}// Instead offif (typeof insteadOffDisplay === 'function') insteadOffDisplay();}getPLPArticle(productId, product, order){let html = ``, bundleClass = ``;order = order*10;html = `
`;return html;}// PLP global eventsleftRightVisibility(){// Get width of all shortkinlslet innerWidth = -5; // Securitylet aWidth = false;let aMargin = false;$("#uPLPLinks > a").each(function(){if (aWidth === false){aWidth = $(this).width() + parseInt($(this).css('marginRight'));aMargin = parseInt($(this).css('marginRight'));}innerWidth += aWidth;});innerWidth -= aMargin;// Show or hide shortlinksif (innerWidth > $("#uPLPLinks").width()){$("#uPLPLinksWrapper").addClass("showLeftRight");}else $("#uPLPLinksWrapper").removeClass("showLeftRight");}setPLPEvents(){// Prevent add-to-bag link click$(document).off("click", "#uPLP article > a .preventLink").on("click", "#uPLP article > a .preventLink", function(event){event.preventDefault();});// Machine energy label click$(document).off("click", ".machine .energylabel").on("click", ".machine .energylabel", function(event){let html = `
✕
`;$("#uPLP").prepend(html);});// Machine energy label close$(document).off("click", "#uPLP .close").on("click", "#uPLP .close", function(){$("#energypopup").remove();});// Left arrow click$(document).off("click", "#uPLPLinksWrapper .left span").on("click", "#uPLPLinksWrapper .left span", function(){let wrapper = $("#uPLPLinksWrapper");let left = parseInt(wrapper.attr("data-left")) + 1;wrapper.attr("data-left", left);});// Right arrow click$(document).off("click", "#uPLPLinksWrapper .right span").on("click", "#uPLPLinksWrapper .right span", function(){let wrapper = $("#uPLPLinksWrapper");let left = parseInt(wrapper.attr("data-left")) - 1;wrapper.attr("data-left", left);});// Top top arrow click$(document).off("click", "#uPLPToTop").on("click", "#uPLPToTop", function(){window.scrollTo({top: 0,left: 0,behavior: 'smooth'});});// Left - right arrows visibilitythis.leftRightVisibility();window.onresize = () => {this.leftRightVisibility();}// Scrolling timer functionlet resultsTopPosition = false, filtersTopPosition;scrollFunction();function scrollFunction() {let scrollTop = $(window).scrollTop();// Toggle scroll to top arrow visibility based on scroll position$("#uPLPToTop").toggleClass("visible", scrollTop > 500);// Check if page has a dynamic banner, if so, add corresponding class to uPLPToTopif ($("#CDB").length > 0) $("#uPLPToTop").addClass("withDB");else $("#uPLPToTop").removeClass("withDB");// Sticky results nb (wide)let $domResults = $("#uPLPResults"); // Cache the selectorif ($domResults.hasClass("sticky")){ // Should remove Sticky?if (scrollTop < resultsTopPosition ){$domResults.removeClass("sticky");resultsTopPosition = false;}}else{ // Should add sticky?if (!isElementFullyVisible($domResults)) if (resultsTopPosition === false){// Get and save top coordinateslet headerHeight = $(".Header__top-wrapper").height();if (typeof headerHeight === "undefined") return false;resultsTopPosition = $domResults.offset().top - headerHeight;$domResults.addClass("sticky");}}// Sticky filters (narrow)$domResults = $("#uPLPFilter"); // Cache the selectorlet filtersTopPositionNarrowLimit = filtersTopPosition;if ($domResults.hasClass("sticky")){ // Should remove Sticky?if (scrollTop < filtersTopPosition){$domResults.removeClass("sticky");}}else{ // Should add sticky?if (!isElementFullyVisible($domResults, 10)){// Get and save top coordinateslet headerHeight = $(".Header__top-wrapper").height();if (typeof headerHeight === "undefined") return false;filtersTopPosition = $domResults.offset().top - headerHeight;$domResults.addClass("sticky");}}}function isElementFullyVisible(el,) { var rect = el[0].getBoundingClientRect();var headerHeight = $(".Header__top-wrapper").height(); var isVisible = (rect.top >= headerHeight ); return isVisible; }$(window).off("scroll").on('scroll', function() {scrollFunction(); // Then execute after delay});}// MIXremoveTags(str){return str.replaceAll(/(<([^>]+)>)/gi, '');}isNumber(str){const num = parseFloat(str);return !isNaN(num) && num.toString() === str;}getProductSku(product){/** Return either the SKU of a product ,or the first SKU of a machine product, */if (product.type !== "machine") return product.sku;// Machine productreturn product.skus[Object.keys(product.skus)[0]].sku;}// Src - srcsetsrcGet(src){return src + "?impolicy=product&imwidth=120";}srcsetGet(src){return `${src}?impolicy=product&imwidth=120 1x,${src}?impolicy=product&imwidth=180 1.5x, ${src}?impolicy=product&imwidth=240 2x`;}// Color similarityhexToRgb(hex) {// Convert hex code to rgbvar r = parseInt(hex.substring(0,2), 16);var g = parseInt(hex.substring(2,4), 16);var b = parseInt(hex.substring(4,6), 16);return [r, g, b];}colorinSimilarity(color){if (this.enabledMachinesColors === false) return 1;color = color.replace("#", "");for (let i = 0; i< this.enabledMachinesColors.length; i++){if (this.colorSimilarity(color, this.enabledMachinesColors[i]) >= this.ColorSimilarityLimit) return true;}return false;}colorSimilarity(hexColor1, hexColor2) {hexColor1 = hexColor1.replace("#", "");hexColor2 = hexColor2.replace("#", "");// Convert hex to RGBvar rgb1 = this.hexToRgb(hexColor1);var rgb2 = this.hexToRgb(hexColor2);// Convert RGB to HSLvar hsl1 = this.rgbToHsl(rgb1[0], rgb1[1], rgb1[2]);var hsl2 = this.rgbToHsl(rgb2[0], rgb2[1], rgb2[2]);// Calculate the differences in hue, saturation, and lightnessvar hueDifference = Math.abs(hsl1[0] - hsl2[0]);var satDifference = Math.abs(hsl1[1] - hsl2[1]);var lightDifference = Math.abs(hsl1[2] - hsl2[2]);// Adjust for cyclicity of the hue wheelif (hueDifference > 180) hueDifference = 360 - hueDifference;hueDifference = hueDifference / 180;// Weigh the differences, giving more importance to hue, should be edited to edit color permeabilityvar hueWeight = 0.4;var satWeight = 0.3;var lightWeight = 0.3;// Calculate and normalize total differencevar totalDifference = Math.sqrt((hueDifference * hueDifference * hueWeight) + (satDifference * satDifference * satWeight) + (lightDifference * lightDifference * lightWeight));var normalizedDifference = totalDifference / (hueWeight + satWeight + lightWeight);// Calculate similarity instead of differencelet similarity = 1 - normalizedDifference// For white / grey / black filters, make filter less permissiveif (hsl2[0] + hsl2[1] === 0) similarity = similarity * similarity;return similarity}rgbToHsl(r, g, b) {r /= 255, g /= 255, b /= 255;var max = Math.max(r, g, b), min = Math.min(r, g, b);var h, s, l = (max + min) / 2;if (max == min) h = s = 0; // achromaticelse {var d = max - min;s = l > 0.5 ? d / (2 - max - min) : d / (max + min);switch (max) {case r: h = (g - b) / d + (g < b ? 6 : 0); break;case g: h = (b - r) / d + 2; break;case b: h = (r - g) / d + 4; break;}h /= 6;}return [h * 360, s, l];}// Tracking eventsaddEventGA(action, label){//console.log("add GA: "+action+" -> "+label);window.gtmDataObject = window.gtmDataObject || [];//console.log(action+" -> "+label);// GA4gtmDataObject.push({event: 'local_event', //as is, do not change!!event_raised_by:'ch',local_event_category: 'custom - '+this.tracking, //free to fill field, please use lower caselocal_event_action: action, //free to fill field, please use lower caselocal_event_label: label //free to fill field, please use lower case});}}