1customElements.get("ntl-cross-sell")||customElements.define("ntl-cross-sell",class extends HTMLElement{constructor(){super(),this.productData=[],this.gwpData=[],this.cards=[],this.addButton=null,this.cart=null,this.isAddingToCart=!1,this.isBundle=!1,this.packId="",this._bvc=this.onVariantChange.bind(this),this._bpc=this.onPlanChange.bind(this),this._bac=this.onAddToCart.bind(this),this._bqc=()=>{this.applySmartPreselect(),this.updateTotalPrice()}}connectedCallback(){this.init()}disconnectedCallback(){this.querySelectorAll(".ntl-cs__variant-select").forEach(s=>s.removeEventListener("change",this._bvc)),this.querySelectorAll(".ntl-cs__plan-select").forEach(s=>s.removeEventListener("change",this._bpc)),this.addButton&&this.addButton.removeEventListener("click",this._bac),document.removeEventListener("ntl:quiz:completed",this._bqc)}init(){const ds=this.querySelector(".ntl-cs__product-data");if(ds)try{const raw=JSON.parse(ds.textContent);Array.isArray(raw)?(this.productData=raw,this.gwpData=[]):(this.productData=raw.products||[],this.gwpData=raw.gwps||[])}catch(e){console.error("[ntl-cs] Bad JSON:",e);return}this.isBundle=this.dataset.layout==="bundle-discount",this.packId=this.dataset.packId||"",this.cards=[...this.querySelectorAll(".ntl-cs__card")],this.addButton=this.querySelector(".ntl-cs__add-button"),this.cart=document.querySelector("cart-notification")||document.querySelector("cart-drawer"),this.querySelectorAll(".ntl-cs__variant-select").forEach(s=>s.addEventListener("change",this._bvc)),this.querySelectorAll(".ntl-cs__plan-select").forEach(s=>s.addEventListener("change",this._bpc)),this.addButton&&this.addButton.addEventListener("click",this._bac),this.translatePlanNames(),this.applySmartPreselect(),document.addEventListener("ntl:quiz:completed",this._bqc),this.updateTotalPrice(),this.trackEvent("view"),this.pushViewItemList(),this.tagCartWithAttribution()}pushViewItemList(){if(typeof window.dataLayer>"u")return;const listId=this.isBundle?"pdp_bundle":"pdp_cross_sell",listName=this.isBundle?"PDP Bundle":"PDP Cross-Sell",items=[];this.cards.forEach((card,idx)=>{const prod=this.productData.find(p=>p.id===parseInt(card.dataset.productId));if(!prod)return;const vs=card.querySelector(".ntl-cs__variant-select"),ps=card.querySelector(".ntl-cs__plan-select"),vid=vs?parseInt(vs.value):prod.variants[0]?.id,v=prod.variants.find(x=>x.id===vid);if(!v)return;const price=ps&&ps.value&&parseInt(ps.options[ps.selectedIndex]?.dataset.planPrice)||v.price;items.push({item_id:String(prod.id),item_sku:v.sku||void 0,item_variant_id:String(v.id),item_product_id:String(prod.id),item_name:prod.title||prod.handle,item_brand:prod.vendor||"Natulim",item_category:prod.type||void 0,item_variant:v.title||void 0,price:price/100,quantity:1,index:idx,item_list_id:listId,item_list_name:listName})}),items.length&&(window.dataLayer.push({ecommerce:null}),window.dataLayer.push({event:"view_item_list",ecommerce:{item_list_id:listId,item_list_name:listName,currency:window.Shopify&&Shopify.currency&&Shopify.currency.active||"EUR",items}}))}tagCartWithAttribution(){const handle=window.location.pathname.split("/products/")[1]?.split("?")[0]||"",attrs={_cs_session:this._getSessionId(),_cs_pdp:handle,_cs_exposed_at:String(Math.floor(Date.now()/1e3))};fetch((window.Shopify?.routes?.root||"/")+"cart/update.js",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({attributes:attrs}),keepalive:!0}).catch(()=>{})}translatePlanNames(){const lang=document.documentElement.lang||"es";if(lang==="es")return;const m={ca:{mes:"mes",meses:"mesos",semana:"setmana",semanas:"setmanes"},en:{mes:"month",meses:"months",semana:"week",semanas:"weeks"},fr:{mes:"mois",meses:"mois",semana:"semaine",semanas:"semaines"},it:{mes:"mese",meses:"mesi",semana:"settimana",semanas:"settimane"},pt:{mes:"m\xEAs",meses:"meses",semana:"semana",semanas:"semanas"}}[lang];m&&this.querySelectorAll(".ntl-cs__plan-select option").forEach(opt=>{if(!opt.value)return;let t=opt.textContent;for(const[es,loc]of Object.entries(m))t=t.replace(new RegExp("\\b"+es.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"\\b","gi"),loc);opt.textContent=t})}applySmartPreselect(){const freq=this._getQuizFreq(),hh=this._getBuyboxHousehold();this.cards.forEach(card=>{const sel=card.querySelector(".ntl-cs__plan-select");if(!sel)return;const productId=card.dataset.productId,handle=card.dataset.productHandle;let days=null;freq&&productId&&freq[productId]?days=freq[productId]:hh&&(days=this._estimateFreq(handle,hh)),days&&this._preselectPlan(sel,days)})}
1static FREQ_BASE={"tiras-de-detergente-para-lavadora-ecologicas-fragancia-natural":56,"tiras-de-detergente-para-lavadora-ecologicas":56,"perlas-perfumadas-natulim":56,"pastillas-de-lavavajillas":42,"friegasuelos-ecologico":42,"oxi-clean-natulim":56,"limpia-lavadoras":84,"limpia-lavavajillas":84,"jabon-de-manos-ecologico":42,"multiusos-ecologico":42};_getQuizFreq(){try{const raw=localStorage.getItem("natulim_quiz_results");if(!raw)return null;const res=JSON.parse(raw);if(!res||typeof res!="object")return null;const out={};for(const[,val]of Object.entries(res)){if(!val||!val.freq||!val.id)continue;const days=this._parseFreq(String(val.freq));days&&(out[String(val.id)]=days)}return Object.keys(out).length?out:null}catch{return null}}_parseFreq(s){const m=s.match(/^(\d+)(d|m|w)$/);if(m)return parseInt(m[1])*(m[2]==="d"?1:m[2]==="w"?7:30);const n=parseInt(s,10);return isNaN(n)?null:n}_getBuyboxHousehold(){try{return sessionStorage.getItem("ntl_buybox_household")||null}catch{return null}}_estimateFreq(handle,hh){const base=this.constructor.FREQ_BASE[handle];if(!base)return null;const sz=parseInt(hh,10)||2,scale=sz<=1?1.5:sz<=2?1:sz<=3?.8:.65;return Math.round(base*scale)}_preselectPlan(sel,targetDays){let best=null,bestDiff=1/0;for(const opt of sel.options){if(!opt.value)continue;const days=this._parsePlanDays(opt.dataset.planName||opt.textContent);days&&Math.abs(days-targetDays)<bestDiff&&(bestDiff=Math.abs(days-targetDays),best=opt)}best&&best.value!==sel.value&&(sel.value=best.value,sel.dispatchEvent(new Event("change",{bubbles:!1})))}_parsePlanDays(name){const l=name.toLowerCase(),n=l.match(/(\d+)/);if(!n)return null;const num=parseInt(n[1]);return/semana|setmana|week|semaine|settimana/.test(l)?num*7:/mes|mesos|month|mois|mesi|mês/.test(l)?num*30:/dÃa|dia|day|jour|giorno/.test(l)?num:null}_updateQuizFreq(productId,days,planId,handle){try{const res=JSON.parse(localStorage.getItem("natulim_quiz_results")||"{}");let quizKey=Object.keys(res).find(k=>res[k]&&String(res[k].id)===String(productId));if(!quizKey){if(!handle)return;quizKey=handle,res[quizKey]={id:String(productId),qty:1}}res[quizKey].freq=days+"d",planId&&(res[quizKey].sellingPlan=String(planId)),localStorage.setItem("natulim_quiz_results",JSON.stringify(res))}catch{}}
1onVariantChange(e){const sel=e.target,card=sel.closest(".ntl-cs__card"),prod=this.productData.find(p=>p.id===parseInt(card.dataset.productId));if(!prod)return;const variant=prod.variants.find(v=>v.id===parseInt(sel.value));if(!variant)return;const img=card.querySelector(".ntl-cs__image img");img&&(variant.image_1x||variant.image)&&(img.src=variant.image_1x||variant.image,variant.image_1x&&(img.srcset=variant.image_1x+" 1x, "+(variant.image_2x||variant.image_1x)+" 2x"));const plan=card.querySelector(".ntl-cs__plan-select");if(plan)for(const opt of plan.options)if(opt.value){const alloc=variant.selling_plan_allocations.find(a=>a.selling_plan_id===parseInt(opt.value));alloc&&(opt.dataset.planPrice=alloc.price)}else opt.dataset.planPrice=variant.price;this.updateCardPrice(card,variant,plan),this.updateTotalPrice(),this.trackEvent("variant_change")}onPlanChange(e){const sel=e.target,card=sel.closest(".ntl-cs__card"),vs=card.querySelector(".ntl-cs__variant-select"),prod=this.productData.find(p=>p.id===parseInt(card.dataset.productId));if(!prod)return;const vid=vs?parseInt(vs.value):prod.variants[0].id,variant=prod.variants.find(v=>v.id===vid);variant&&(this.updateCardPrice(card,variant,sel),this.updateTotalPrice(),this.trackEvent("plan_change"))}updateCardPrice(card,variant,planSel){const priceEl=card.querySelector(".ntl-cs__price"),compEl=card.querySelector(".ntl-cs__compare-price"),badge=card.querySelector(".ntl-cs__savings-badge");if(priceEl)if(planSel&&planSel.value){const subPrice=parseInt(planSel.options[planSel.selectedIndex].dataset.planPrice)||variant.price;if(priceEl.textContent=this.fmt(subPrice),compEl&&(compEl.textContent=this.fmt(variant.price),compEl.removeAttribute("hidden")),badge){const sav=variant.price-subPrice;sav>0?(badge.textContent=(badge.dataset.saveLabel||"Ahorras")+" "+this.fmt(sav),badge.removeAttribute("hidden")):badge.setAttribute("hidden","")}}else priceEl.textContent=this.fmt(variant.price),compEl&&compEl.setAttribute("hidden",""),badge&&badge.setAttribute("hidden","")}updateTotalPrice(){let total=0,otp=0;if(this.cards.forEach(card=>{const prod=this.productData.find(p=>p.id===parseInt(card.dataset.productId));if(!prod)return;const vs=card.querySelector(".ntl-cs__variant-select"),ps=card.querySelector(".ntl-cs__plan-select"),vid=vs?parseInt(vs.value):prod.variants[0].id,v=prod.variants.find(x=>x.id===vid);v&&(otp+=v.price,total+=ps&&ps.value&&parseInt(ps.options[ps.selectedIndex].dataset.planPrice)||v.price)}),this.isBundle){const packTotal=this.querySelector(".ntl-cs__pack-total"),packComp=this.querySelector(".ntl-cs__pack-compare"),packSavings=this.querySelector(".ntl-cs__pack-savings"),footerPrice=this.querySelector(".ntl-cs__pack-footer .ntl-cs__add-price");if(packTotal&&(packTotal.textContent=this.fmt(total)),packComp&&(otp>total?(packComp.textContent=this.fmt(otp),packComp.removeAttribute("hidden")):packComp.setAttribute("hidden","")),packSavings){const sav=otp-total;sav>0?(packSavings.textContent=(packSavings.dataset.saveLabel||"Ahorras")+" "+this.fmt(sav),packSavings.removeAttribute("hidden")):packSavings.setAttribute("hidden","")}footerPrice&&(footerPrice.textContent=this.fmt(total))}const label=this.querySelector(".ntl-cs__wrapper > .ntl-cs__add-button .ntl-cs__add-price"),comp=this.querySelector(".ntl-cs__wrapper > .ntl-cs__add-button .ntl-cs__add-compare-price");label&&(label.textContent=this.fmt(total),comp&&(otp>total?(comp.textContent=this.fmt(otp),comp.removeAttribute("hidden")):comp.setAttribute("hidden","")))}fmt(cents){return typeof Shopify<"u"&&Shopify.formatMoney?Shopify.formatMoney(cents):(cents/100).toFixed(2).replace(".",",")+"\xA0\u20AC"}async onAddToCart(e){if(e.preventDefault(),this.isAddingToCart)return;this.isAddingToCart=!0,this.addButton.setAttribute("disabled",""),this.addButton.classList.add("loading");const items=[],source=this.isBundle?"ntl-bundles":"ntl-crosssell";if(this.cards.forEach(card=>{const prod=this.productData.find(p=>p.id===parseInt(card.dataset.productId));if(!prod)return;const vs=card.querySelector(".ntl-cs__variant-select"),ps=card.querySelector(".ntl-cs__plan-select"),vid=vs?parseInt(vs.value):prod.variants[0].id,variant=prod.variants.find(v=>v.id===vid);if(!variant||variant.available===!1)return;const props={_source:source};this.isBundle&&this.packId&&(props._bundle=this.packId);const item={id:vid,quantity:1,properties:props};ps&&ps.value&&(item.selling_plan=parseInt(ps.value)),items.push(item)}),this.isBundle&&this.gwpData.length&&this.gwpData.forEach(gwp=>{const props={_source:"ntl-bundles-gwp"};this.packId&&(props._bundle=this.packId),items.push({id:gwp.variant_id,quantity:1,properties:props})}),!items.length){this._resetBtn();return}try{const data=await(await fetch((window.Shopify?.routes?.root||"/")+"cart/add.js",{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({items})})).json();if(data.status){console.error("[ntl-cs] ATC error:",data.description),typeof publish=="function"&&publish(PUB_SUB_EVENTS.cartError,{source:"ntl-cross-sell",errors:data.errors||data.description}),this.addButton.classList.add("error"),setTimeout(()=>this.addButton.classList.remove("error"),2e3);return}typeof publish=="function"?publish(PUB_SUB_EVENTS.cartUpdate,{source:"ntl-cross-sell",cartData:data,productVariantId:items[0].id}):this.cart&&this.cart.renderContents(data),this.trackEvent("add_to_cart",items),this.cards.forEach(card=>{const ps=card.querySelector(".ntl-cs__plan-select");if(!ps||!ps.value)return;const selectedOpt=ps.options[ps.selectedIndex],days=this._parsePlanDays(selectedOpt.dataset.planName||selectedOpt.textContent);
1days&&this._updateQuizFreq(card.dataset.productId,days,ps.value,card.dataset.productHandle)})}catch(err){console.error("[ntl-cs] Fetch error:",err),this.addButton.classList.add("error"),setTimeout(()=>this.addButton.classList.remove("error"),2e3)}finally{this._resetBtn()}}_resetBtn(){this.isAddingToCart=!1,this.addButton.removeAttribute("disabled"),this.addButton.classList.remove("loading")}static DASHBOARD_URL="https://crosssell-dashboard-v2.vercel.app/api/events";trackEvent(action,items){const products=this.cards.map(c=>({id:c.dataset.productId,handle:c.dataset.productHandle,selling_plan:c.querySelector(".ntl-cs__plan-select")?.value||"otp"})),sellingPlans=this.cards.map(c=>{const ps=c.querySelector(".ntl-cs__plan-select");return ps&&ps.value?ps.options[ps.selectedIndex].dataset.planName||ps.value:"otp"});if(typeof window.dataLayer<"u"){const prefix=this.isBundle?"ntl_bundle":"ntl_crosssell",gtmPayload={event:prefix+"_interaction",[prefix+"_action"]:action,[prefix+"_pdp"]:window.location.pathname,[prefix+"_products"]:products,[prefix+"_element_id"]:this.id};this.isBundle&&this.packId&&(gtmPayload[prefix+"_pack_id"]=this.packId),window.dataLayer.push(gtmPayload)}const isMobile=window.innerWidth<750,handle=window.location.pathname.split("/products/")[1]?.split("?")[0]||"";let totalValue=0;this.cards.forEach(card=>{const prod=this.productData.find(p=>p.id===parseInt(card.dataset.productId));if(!prod)return;const vs=card.querySelector(".ntl-cs__variant-select"),ps=card.querySelector(".ntl-cs__plan-select"),vid=vs?parseInt(vs.value):prod.variants[0]?.id,v=prod.variants.find(x=>x.id===vid);v&&(totalValue+=ps&&ps.value&&parseInt(ps.options[ps.selectedIndex]?.dataset.planPrice)||v.price)});const body={action,section_id:this.id,pdp_url:window.location.href,pdp_handle:handle,device:isMobile?"mobile":"desktop",products,selling_plans:sellingPlans,total_value:totalValue,session_id:this._getSessionId(),locale:document.documentElement.lang||"es"};fetch(this.constructor.DASHBOARD_URL,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body),keepalive:!0}).catch(()=>{})}_getSessionId(){try{let sid=sessionStorage.getItem("ntl_cs_sid");return sid||(sid=Math.random().toString(36).substring(2,12),sessionStorage.setItem("ntl_cs_sid",sid)),sid}catch{return"unknown"}}}); 2//# sourceMappingURL=/cdn/shop/t/280/assets/ntl-cross-sell.js.map?v=169051605319271373121784803533
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.