1<!doctype html> 2<html lang="en" dir="ltr"> 3<head > 4 <meta charset="utf-8"/>
4<script type="text/javascript">(window.NREUM||(NREUM={})).init={privacy:{cookies_enabled:true},ajax:{deny_list:["bam.nr-data.net"]},distributed_tracing:{enabled:true}};(window.NREUM||(NREUM={})).loader_config={agentID:"1120013411",accountID:"3291613",trustKey:"1322840",xpid:"VwQOUFBSCxABVVBaBgIDUVIA",licenseKey:"NRJS-8c25a1a7b460fa8b963",applicationID:"1118134761",browserID:"1120013411"};;/*! For license information please see nr-loader-spa-1.322.0.min.js.LICENSE.txt */ 5(()=>{var e={8122(e,t,r){"use strict";r.d(t,{a:()=>i});var n=r(944);function i(e,t){try{if(!e||"object"!=typeof e)return(0,n.R)(3);if(!t||"object"!=typeof t)return(0,n.R)(4);const r=Object.create(Object.getPrototypeOf(t),Object.getOwnPropertyDescriptors(t)),s=0===Object.keys(r).length?e:r;for(let a in s)if(void 0!==e[a])try{if(null===e[a]){r[a]=null;continue}Array.isArray(e[a])&&Array.isArray(t[a])?r[a]=Array.from(new Set([...e[a],...t[a]])):e[a]instanceof Map||e[a]instanceof Set||e[a]instanceof Date||e[a]instanceof RegExp?r[a]=e[a]:"object"==typeof e[a]&&null!==t[a]&&"object"==typeof t[a]?r[a]=i(e[a],t[a]):r[a]=e[a]}catch(e){r[a]||(0,n.R)(1,e)}return r}catch(e){(0,n.R)(2,e)}}},2555(e,t,r){"use strict";r.d(t,{f:()=>a});var n=r(384),i=r(8122);const s={beacon:n.NT.beacon,errorBeacon:n.NT.errorBeacon,licenseKey:void 0,applicationID:void 0,sa:void 0,queueTime:void 0,applicationTime:void 0,ttGuid:void 0,user:void 0,account:void 0,product:void 0,extra:void 0,jsAttributes:{},userAttributes:void 0,atts:void 0,transactionName:void 0,tNamePlain:void 0};function a(e){try{return!!e.licenseKey&&!!e.errorBeacon&&!!e.applicationID}catch(e){return!1}}r.d(t,["D",0,e=>(0,i.a)(e,s)])},7699(e,t,r){"use strict";var n=r(860);const i={[n.K7.logging]:!0,[n.K7.genericEvents]:!0,[n.K7.jserrors]:!0,[n.K7.ajax]:!0};r.d(t,["It",0,1e6,"KC",0,i,"qh",0,"SESSION_ERROR"])},9324(e,t,r){"use strict";r.d(t,["A",0,"@newrelic/rrweb","x",0,"1.322.0"])},981(e,t,r){"use strict";r.d(t,["R",0,{AJAX:"AjaxRequest",PA:"PageAction",UA:"UserAction",BP:"BrowserPerformance",WS:"WebSocket",SPV:"SecurityPolicyViolation",JSE:"JavaScriptError",LOG:"Log",PVE:"PageView",PVT:"PageViewTiming",SR:"SessionReplay",ST:"SessionTrace",BI:"BrowserInteraction"}])},9752(e,t,r){"use strict";const n="newrelic-iframe-",i=n+"timing-update",s=n+"api",a=n+"api-response",o=n+"vitals-update",c=n+"ajax";r.d(t,["$8",0,o,"Gi",0,a,"KU",0,c,"Pl",0,n,"cS",0,s,"cr",0,i])},6154(e,t,r){"use strict";var n=r(1863);const i="undefined"!=typeof window&&!!window.document,s="undefined"!=typeof WorkerGlobalScope&&("undefined"!=typeof self&&self instanceof WorkerGlobalScope&&self.navigator instanceof WorkerNavigator||"undefined"!=typeof globalThis&&globalThis instanceof WorkerGlobalScope&&globalThis.navigator instanceof WorkerNavigator),a=i?window:"undefined"!=typeof WorkerGlobalScope&&("undefined"!=typeof self&&self instanceof WorkerGlobalScope&&self||"undefined"!=typeof globalThis&&globalThis instanceof WorkerGlobalScope&&globalThis),o=Boolean("hidden"===a?.document?.visibilityState),c=""+a?.location,d=/iPad|iPhone|iPod/.test(a.navigator?.userAgent),u=d&&"undefined"==typeof SharedWorker,l=(()=>{const e=a.navigator?.userAgent?.match(/Firefox[/\s](\d+\.\d+)/);return Array.isArray(e)&&e.length>=2?+e[1]:0})(),f=Date.now()-(0,n.t)();r.d(t,["OF",0,d,"RI",0,i,"WN",0,f,"bv",0,s,"gm",0,a,"lR",0,l,"m",0,c,"mw",0,o,"sb",0,u,"zk",0,()=>{const e=a?.performance?.getEntriesByType?.("navigation")?.[0];if(e&&e.responseStart>0&&e.responseStart<a.performance.now())return e}])},7295(e,t,r){"use strict";r.d(t,{Xv:()=>a,gX:()=>i,iW:()=>s});var n=[];function i(e){if(!e||s(e))return!1;if(0===n.length)return!0;if("*"===n[0].hostname)return!1;for(var t=0;t<n.length;t++){var r=n[t];if(r.hostname.test(e.hostname)&&r.pathname.test(e.pathname))return!1}return!0}function s(e){return void 0===e.hostname}function a(e){if(n=[],e&&e.length)for(var t=0;t<e.length;t++){let r=e[t];if(!r)continue;if("*"===r)return void(n=[{hostname:"*"}]);0===r.indexOf("http://")?r=r.substring(7):0===r.indexOf("https://")&&(r=r.substring(8));const i=r.indexOf("/");let s,a;i>0?(s=r.substring(0,i),a=r.substring(i)):(s=r,a="*");let[c]=s.split(":");n.push({hostname:o(c),pathname:o(a,!0)})}}function o(e,t=!1){const r=e.replace(/[.+?^${}()|[\]\\]/g,e=>"\\"+e).replace(/\*/g,".*?");return new RegExp((t?"^":"")+r+"$")}},3241(e,t,r){"use strict";r.d(t,{W:()=>i});var n=r(6154);function i(e={}){try{n.gm.dispatchEvent(new CustomEvent("newrelic",{detail:e}))}catch(e){}}},1687(e,t,r){"use strict";r.d(t,{Ak:()=>o,Ze:()=>d,x3:()=>c});var n=r(3241),i=r(3606),s=r(860),a=r(2646);function o(e,t){if(!e)return;const r={staged:!1,priority:s.P3[t]||0};
5e.runtime.drainRegistry.get(t)||e.runtime.drainRegistry.set(t,r)}function c(e,t){if(!e)return;const r=e.runtime.drainRegistry;r&&(r.get(t)&&r.delete(t),l(e,t,!1),r.size&&u(e))}function d(e,t="feature",r=!1){if(e){if(!e.runtime.drainRegistry.get(t)||r)return l(e,t);e.runtime.drainRegistry.get(t).staged=!0,u(e)}}function u(e){if(!e)return;const t=Array.from(e.runtime.drainRegistry);t.every(([e,t])=>t.staged)&&(t.sort((e,t)=>e[1].priority-t[1].priority),t.forEach(([t])=>{e.runtime.drainRegistry.delete(t),l(e,t)}))}function l(e,t,r=!0){if(!e)return;const s=e.ee,o=i.i.handlers;if(s&&!s.aborted&&s.backlog&&o){if((0,n.W)({type:"lifecycle",name:"drain",feature:t}),r){const e=s.backlog[t],r=o[t];if(r){for(let t=0;e&&t<e.length;++t)f(e[t],r);Object.entries(r).forEach(([e,t])=>{Object.values(t||{}).forEach(t=>{t[0]?.on&&t[0].context()instanceof a.y&&!t[0].listeners(e).includes(t[1])&&t[0].on(e,t[1])})})}}s.isolatedBacklog||delete o[t],s.backlog[t]=null,s.emit("drain-"+t,[])}}function f(e,t){var r=e[1];Object.values(t[r]||{}).forEach(t=>{var r=e[0];if(t[0]===r){var n=t[1],i=e[3],s=e[2];n.apply(i,s)}})}},7836(e,t,r){"use strict";var n=r(384),i=r(8990),s=r(2646),a=r(5607);const o="nr@context:".concat(a.W),c=function e(t,r){var n={},a={},u={},l=!1;try{l=16===r.length&&d.initializedAgents?.[r]?.runtime.isolatedBacklog}catch(e){}var f={on:p,addEventListener:p,removeEventListener:function(e,t){var r=n[e];if(!r)return;for(var i=0;i<r.length;i++)r[i]===t&&r.splice(i,1)},emit:function(e,r,n,i,s){!1!==s&&(s=!0);if(c.aborted&&!i)return;t&&s&&t.emit(e,r,n);var o=h(n);g(e).forEach(e=>{e.apply(o,r)});var d=v()[a[e]];d&&d.push([f,e,r,o]);return o},get:m,listeners:g,context:h,buffer:function(e,t){const r=v();if(t=t||"feature",f.aborted)return;Object.entries(e||{}).forEach(([e,n])=>{a[n]=t,t in r||(r[t]=[])})},abort:function(){f._aborted=!0,Object.keys(f.backlog).forEach(e=>{delete f.backlog[e]})},isBuffering:function(e){return!!v()[a[e]]},debugId:r,backlog:l?{}:t&&"object"==typeof t.backlog?t.backlog:{},isolatedBacklog:l};return Object.defineProperty(f,"aborted",{get:()=>{let e=f._aborted||!1;return e||(t&&(e=t.aborted),e)}}),f;function h(e){return e&&e instanceof s.y?e:e?(0,i.I)(e,o,()=>new s.y(o)):new s.y(o)}function p(e,t){n[e]=g(e).concat(t)}function g(e){return n[e]||[]}function m(t){return u[t]=u[t]||e(f,t)}function v(){return f.backlog}}(void 0,"globalEE"),d=(0,n.Zm)();d.ee||(d.ee=c),r.d(t,["P",0,o,"ee",0,c])},2646(e,t,r){"use strict";r.d(t,{y:()=>n});class n{constructor(e){this.contextId=e}}},9908(e,t,r){"use strict";r.d(t,{d:()=>n,p:()=>i});var n=r(7836).ee.get("handle");function i(e,t,r,i,s){s?(s.buffer([e],i),s.emit(e,t,r)):(n.buffer([e],i),n.emit(e,t,r))}},3606(e,t,r){"use strict";r.d(t,{i:()=>s});var n=r(9908);s.on=a;var i=s.handlers={};function s(e,t,r,s){a(s||n.d,i,e,t,r)}function a(e,t,r,i,s){s||(s="feature"),e||(e=n.d);var a=t[s]=t[s]||{};(a[r]=a[r]||[]).push([e,i])}},3878(e,t,r){"use strict";function n(e,t){return{capture:e,passive:!1,signal:t}}function i(e,t,r=!1,i){window.addEventListener(e,t,n(r,i))}function s(e,t,r=!1,i){document.addEventListener(e,t,n(r,i))}r.d(t,{DD:()=>s,jT:()=>n,sp:()=>i})},5607(e,t,r){"use strict";const n=(0,r(9566).bz)();r.d(t,["W",0,n])},9566(e,t,r){"use strict";r.d(t,{LA:()=>a,ZF:()=>o,bz:()=>s,el:()=>c});var n=r(6154);function i(e,t){return e?15&e[t]:16*Math.random()|0}function s(){const e=n.gm?.crypto||n.gm?.msCrypto;let t,r=0;return e&&e.getRandomValues&&(t=e.getRandomValues(new Uint8Array(30))),"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".split("").map(e=>"x"===e?i(t,r++).toString(16):"y"===e?(3&i()|8).toString(16):e).join("")}function a(e){const t=n.gm?.crypto||n.gm?.msCrypto;let r,s=0;t&&t.getRandomValues&&(r=t.getRandomValues(new Uint8Array(e)));const a=[];for(var o=0;o<e;o++)a.push(i(r,s++).toString(16));return a.join("")}function o(){return a(16)}function c(){return a(32)}},2614(e,t,r){"use strict";r.d(t,["BB",0,18e5,"Wt",0,"NRBA_SESSION::","g",0,{OFF:0,FULL:1,ERROR:2},"iL",0,{SAME_TAB:"same-tab",CROSS_TAB:"cross-tab"},"tS",0,{STARTED:"session-started",PAUSE:"session-pause",RESET:"session-reset",RESUME:"session-resume",UPDATE:"session-update"},"wk",0,144e5])},733(e,t,r){"use strict";function n(e,t){return function(e){let t=2166136261;for(let r=0;r<e.length;r++)t^=e.charCodeAt(r),t=Math.imul(t,16777619);return(t>>>0).toString(16).padStart(8,"0")}("".concat(String(e),":").concat(String(t)))}r.d(t,{Y:()=>n})},1863(e,t,r){"use strict";function n(){return Math.floor(performance.now())}r.d(t,{t:()=>n})},9119(e,t,r){"use strict";r.d(t,{L:()=>s});var n=/([^?#]*)[^#]*(#[^?]*|$).*/,i=/([^?#]*)().*/;function s(e,t){return e?e.replace(t?n:i,"$1$2"):e}},7485(e,t,r){"use strict";r.d(t,{D:()=>i});var n=r(6154);function i(e){if(0===(e||"").indexOf("data:"))return{protocol:"data"};try{const t=new URL(e,location.href),r={port:t.port,hostname:t.hostname,pathname:t.pathname,search:t.search,protocol:t.protocol.slice(0,t.protocol.indexOf(":")),sameOrigin:t.protocol===n.gm?.location?.protocol&&t.host===n.gm?.location?.host};return r.port&&""!==r.port||("http:"===t.protocol&&(r.port="80"),"https:"===t.protocol&&(r.port="443")),r.pathname&&""!==r.pathname?r.pathname.startsWith("/")||(r.pathname="/".concat(r.pathname)):r.pathname="/",r}catch(e){return{}}}},7866(e,t,r){"use strict";r.d(t,["Nc",0,/^\s*at Function code \(Function code:\d+:\d+\)\s*/i,"cn",0,/^\s*(?:([^@]*)(?:\(.*?\))?@)?((?:file|http|https|chrome|safari-extension).*?):(\d+)(?::(\d+))?\s*$/i,"fL",0,/^\s*at .+ \(eval at \S+ \((?:(?:file|http|https):[^)]+)?\)(?:, [^:]*:\d+:\d+)?\)$/i,"h3",0,/function (.+?)\s*\(/,"hB",0,/^\s*at (?:((?:\[object object\])?(?:[^(]*\([^)]*\))*[^()]*(?: \[as \S+\])?) )?\(?((?:file|http|https|chrome-extension):.*?)?:(\d+)(?::(\d+))?\)?\s*$/i])},944(e,t,r){"use strict";r.d(t,{R:()=>i});var n=r(3241);function i(e,t){"function"==typeof console.debug&&(console.debug("New Relic Warning: https://github.com/newrelic/newrelic-browser-agent/blob/main/docs/warning-codes.md#".concat(e),t),(0,n.W)({drained:null,type:"data",name:"warn",feature:"warn",data:{code:e,secondary:t}}))}},8990(e,t,r){"use strict";r.d(t,{I:()=>i});var n=Object.prototype.hasOwnProperty;function i(e,t,r){if(n.call(e,t))return e[t];var i=r();if(Object.defineProperty&&Object.keys)try{return Object.defineProperty(e,t,{value:i,writable:!0,enumerable:!1}),i}catch(e){}return e[t]=i,i}},6389(e,t,r){"use strict";function n(e,t=500,r={}){const n=r?.leading||!1;let i;return(...r)=>{n&&void 0===i&&(e.apply(this,r),i=setTimeout(()=>{i=clearTimeout(i)},t)),n||(clearTimeout(i),i=setTimeout(()=>{e.apply(this,r)},t))}}function i(e){let t=!1;return(...r)=>{t||(t=!0,e.apply(this,r))}}r.d(t,{J:()=>i,s:()=>n})},1910(e,t,r){"use strict";r.d(t,{i:()=>s});var n=r(944);const i=new Map;function s(...e){return e.every(e=>{if(i.has(e))return i.get(e);const t="function"==typeof e?e.toString():"",r=t.includes("[native code]"),s=t.includes("nrWrapper");return r||s||(0,n.R)(64,e?.name||t),i.set(e,r),r})}},3304(e,t,r){"use strict";r.d(t,{A:()=>i});var n=r(7836);function i(e){try{return JSON.stringify(e,(()=>{const e=new WeakSet;return(t,r)=>{if("object"==typeof r&&null!==r){if(e.has(r))return;e.add(r)}return r}})())??""}catch(e){try{n.ee.emit("internal-error",[e])}catch(e){}return""}}},9232(e,t,r){"use strict";r.d(t,["f",0,{MFE:"MFE",BA:"BA"}])},7560(e,t,r){"use strict";r.d(t,{N:()=>a,j:()=>c});var n=r(9119),i=r(944);const s=(0,r(6389).J)(e=>(0,i.R)(80,e));function a(e){if(!Array.isArray(e?.assets)||!e.assets.length)return;
5const t=e.assets.map(o).filter(Boolean);return t.length?{assets:t,scripts:t.filter(e=>e.isScript)}:void 0}function o(e){if(!e||"object"!=typeof e)return;const{matcher:t,type:r}=e;let i="string"==typeof t&&t.endsWith(".js");return void 0!==r&&(!function(e){return"script"===e||"asset"===e}(r)?s(r):i=function(e){return"script"===e}(r)),a=t,"[object RegExp]"===Object.prototype.toString.call(a)?{pattern:t,test:e=>(t.lastIndex=0,t.test((0,n.L)(e))),isScript:i}:"string"==typeof t&&t.length>0?{pattern:t,test:e=>(0,n.L)(e).includes(t),isScript:i}:void s(t);var a}function c(e,t,{scriptsOnly:r=!1}={}){if(!e||!t)return!1;return(r?e.scripts:e.assets).some(e=>e.test(t))}},5718(e,t,r){"use strict";r.d(t,{xo:()=>T,AZ:()=>v,cI:()=>g,Qr:()=>A,QL:()=>y});var n=r(6154),i=r(1863),s=r(9119),a=r(7866);class o{dom=new c;performance=new c;constructor(e){this.url=e,this.claimedBy=new Set}get script(){const e=Math.max(this.dom.start,this.performance.end);return{start:e,end:Math.max(this.dom.end,this.performance.end,e)}}}class c{start=0;end=0;value=void 0}function d(e=()=>{},t){return{get:()=>void 0!==t?t:e(),set:e=>{t=e}}}let u;try{u=v(y())[0]}catch(e){u=v(e)[0]}const l=new Map;let f=[];const h=new WeakMap;function p(e){let t=h.get(e);return t||(t={weighedAssetUrls:new Set,recordManifestScriptWindow:(t,r)=>{t&&(e.scriptStart=e.scriptStart>0?Math.min(e.scriptStart,t):t),r&&(e.scriptEnd=e.scriptEnd>0?Math.max(e.scriptEnd,r):r)}},h.set(e,t)),t}function g(e){return l.get(e)}function m(e){const t=g(e);
5if(t)return t;const r=new o(e);if(l.set(e,r),l.size>1e3){const e=l.keys().next().value;l.delete(e)}return r}if(n.gm.MutationObserver&&n.gm.document){new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{if("SCRIPT"===e.nodeName&&e.src){const t=m((0,s.L)(e.src));t.dom.start=(0,i.t)(),t.dom.value=e;const r=()=>{t.dom.end=(0,i.t)()};["load","error"].forEach(t=>e.addEventListener(t,r,{once:!0}))}})})}).observe(n.gm.document,{childList:!0,subtree:!0})}if(n.gm.PerformanceObserver?.supportedEntryTypes.includes("resource")){new PerformanceObserver(e=>{e.getEntries().forEach(e=>{if((e=>"script"===e.initiatorType||["link","fetch"].includes(e.initiatorType)&&(0,s.L)(e.name).endsWith(".js"))(e)){const t=m((0,s.L)(e.name));t.performance.start=Math.floor(e.startTime),t.performance.end=Math.floor(e.responseEnd),t.performance.value=e}if(!f.length)return;const t=[];f.forEach(({test:r,addedAt:n},s)=>{(r(e)||(0,i.t)()-n>1e4)&&t.push(s)}),f=f.filter((e,r)=>!t.includes(r))})}).observe({type:"resource",buffered:!0})}function v(e){if(!e||"string"!=typeof e)return[];const t=new Set,r=e.split("\n");for(const e of r){const r=e.match(a.cn)||e.match(a.hB)||e.match(a.fL);if(r&&r[2])t.add((0,s.L)(r[2]));else{const r=e.match(/\(([^)]+\.js):\d+:\d+\)/)||e.match(/^\s+at\s+([^\s(]+\.js):\d+:\d+/);r&&r[1]&&t.add((0,s.L)(r[1]))}}return[...t]}function y(){let e;try{const t=Error.stackTraceLimit;Error.stackTraceLimit=50,e=(new Error).stack,Error.stackTraceLimit=t}catch(t){e=(new Error).stack}return e}function b(e,t){return(0,s.L)(e.name)===t}function E(e,t){e.fetchStart=Math.floor(t.startTime),e.fetchEnd=Math.floor(t.responseEnd),e.asset=t.name,e.type=t.initiatorType,w(e,t)}function w(e,t){const r=(0,s.L)(t.name),{weighedAssetUrls:n}=p(e);n.has(r)||(n.add(r),e.totalWeight=(e.totalWeight||0)+(t.transferSize||0),"blocking"===t.renderBlockingStatus?e.renderBlocking=!0:"non-blocking"===t.renderBlockingStatus&&!0!==e.renderBlocking&&(e.renderBlocking=!1))}function R(e,t,r,n,i){if(w(e,t),"scripts"!==i&&"all"!==i)return;if("all"===i||r.isScript){const r=Math.floor(t.startTime),n=Math.floor(t.responseEnd);e.fetchStart=e.fetchStart>0?Math.min(e.fetchStart,r):r,e.fetchEnd=e.fetchEnd>0?Math.max(e.fetchEnd,n):n}if(r.isScript){const r=g((0,s.L)(t.name));if(r){const t=()=>{const{start:t,end:n}=r.script;p(e).recordManifestScriptWindow(t,n)};t(),!r.dom.end&&r.dom.value&&["load","error"].forEach(e=>r.dom.value.addEventListener(e,t,{once:!0}))}n.resolved||(e.asset=t.name,e.type=t.initiatorType,n.resolved=!0)}}function T(e,t){const r=t?.manifest;if(!r||!r.assets.length)return;const s={resolved:!1},a=new Set(r.assets);(n.gm.performance?.getEntriesByType("resource")||[]).forEach(r=>{const n=[...a].find(e=>e.test(r.name));n&&(R(e,r,n,s,t.timingMethod),a.delete(n))}),a.size&&function(e,t,r,s){n.gm.PerformanceObserver?.supportedEntryTypes?.includes("resource")&&f.push({addedAt:(0,i.t)(),test:n=>{const i=[...t].find(e=>e.test(n.name));return i&&(R(e,n,i,r,s),t.delete(i)),0===t.size}})}(e,a,s,t.timingMethod)}function A(e){const t=e?.id,r={registeredAt:(0,i.t)(),reportedAt:void 0,fetchStart:0,fetchEnd:0,scriptStart:0,scriptEnd:0,asset:void 0,type:"unknown",totalWeight:0,renderBlocking:void 0},a=y();if(!a)return r;const o=n.gm.performance?.getEntriesByType("navigation")?.[0]?.name||"";try{const e=v(a),c=(e.length>1?e.filter(e=>u!==e):e)[0];if(!c)return r;if(o.includes(c))return r.asset=(0,s.L)(o),r.type="inline",r;r.correlation=g(c);const l=r.correlation?.performance.value||n.gm.performance?.getEntriesByType("resource")?.find(e=>b(e,c));if(l)E(r,l);else{(function(e){if(!e||!n.gm.document)return!1;try{const t=n.gm.document.querySelectorAll('link[rel="preload"][as="script"]');for(const r of t)if((0,s.L)(r.href)===e)return!0}catch(e){}return!1})(c)&&(r.asset=c,r.type="preload"),function(e,t){n.gm.PerformanceObserver?.supportedEntryTypes?.includes("resource")&&f.push({addedAt:(0,i.t)(),test:r=>!!b(r,t)&&(E(e,r),!0)})}(r,c)}const h=r.correlation,m=!!t&&!!h?.claimedBy.has(t);h&&t&&h.claimedBy.add(t);const y=()=>{const e=h?.script.start;if(!m||!e)return!1;return r.registeredAt-e>1e4};let w=0,R=0;p(r).recordManifestScriptWindow=(e,t)=>{e&&(w=w>0?Math.min(w,e):e),t&&(R=R>0?Math.max(R,t):t)},Object.defineProperty(r,"scriptStart",d(()=>{const e=y()?r.registeredAt:h?.script.start??r.fetchEnd;return w>0?Math.min(e,w):e})),Object.defineProperty(r,"scriptEnd",d(()=>{const e=y()?r.registeredAt:h?.script.end??r.registeredAt;return R>0?Math.max(e,R):e}))}catch(e){}return r}},5732(e,t,r){"use strict";r.d(t,{$5:()=>m,B5:()=>g,K6:()=>u,M$:()=>
5o,Ms:()=>d,NG:()=>c,Ux:()=>h,YA:()=>p,yx:()=>l});var n=r(5718),i=r(7560),s=r(9119),a=r(9232);function o(e){return e?.type===a.f.MFE}function c(e,t){if(!y(e,t))return;const r=t.runtime.registeredEntities;return r?.find(t=>t.metadata.target.iframeInterfaceId===e)}function d(e,t){if(!y(e,t))return[];const r=t.runtime.registeredEntities;return r?.filter(t=>String(t.metadata.target.id)===String(e)).map(e=>e.metadata.target)||[]}function u(e,t){if(!y(e,t))return[];const r=t.runtime.registeredEntities,n=(0,s.L)(e),a=r?.filter(t=>{const r=t.metadata.target?.manifest;return r?(0,i.j)(r,e,{scriptsOnly:!1}):!!t.metadata.timings?.asset&&(0,s.L)(t.metadata.timings.asset)===n});return f(a).map(e=>e.metadata.target)}function l(e,t){if(!y(e,t))return[];const r=t.runtime.registeredEntities,n=r?.filter(t=>{const r=t.metadata.target?.manifest;return r?(0,i.j)(r,e,{scriptsOnly:!0}):!!t.metadata.timings?.asset?.endsWith(e)});return f(n).map(e=>e.metadata.target)}function f(e){if(!e?.length)return e||[];const t=new Map,r=[];for(const n of e){const e=n.metadata?.timings?.asset;if(!e){r.push(n);continue}const i="".concat(e,"::").concat(n.metadata?.target?.id),s=t.get(i);if(s){if(s.metadata.target?.blocked&&!n.metadata.target?.blocked){const e=r.indexOf(s);-1!==e&&(r[e]=n),t.set(i,n)}}else t.set(i,n),r.push(n)}return r}function h(e,t){return v(t)?o(e)?e.attributes:t.agentRef.runtime.v2Target.attributes:{}}function p(e,t){return g(e,t)?{"child.id":e.id,"child.type":e.type,...h(void 0,t)}:{}}function g(e,t){return o(e)&&!!v(t)&&t.agentRef.init.api.register.duplicate_data_to_container}function m(e){if(!e?.runtime)return[];if(!y(!0,e)||!e.runtime.registeredEntities?.length)return[e.runtime.v2Target];const t=[];try{var r=(0,n.AZ)((0,n.QL)());let i=r.length-1;for(;r[i];)t.push(...l(r[i--],e))}catch(e){}const i=function(e){const t=new Set,r=[];for(const n of e){const e=n?.instance;t.has(e)||(t.add(e),r.push(n))}return r}(t);return i.length||i.push(e.runtime.v2Target),i}function v(e){return 2===e?.harvestEndpointVersion}function y(e,t){return!!e&&!!t?.init.api.register.enabled}},5289(e,t,r){"use strict";r.d(t,{GG:()=>a,Qr:()=>c,sB:()=>o});var n=r(3878),i=r(6389);function s(){return"undefined"==typeof document||"complete"===document.readyState}function a(e,t){if(s())return e();const r=(0,i.J)(e),a=setInterval(()=>{s()&&(clearInterval(a),r())},500);(0,n.sp)("load",r,t)}function o(e){if(s())return e();(0,n.DD)("DOMContentLoaded",e)}function c(e){if(s())return e();(0,n.sp)("popstate",e)}},384(e,t,r){"use strict";r.d(t,{Zm:()=>c,bQ:()=>u,dV:()=>d,pV:()=>l});var n=r(6154),i=r(1863),s=r(944),a=r(1910);const o={beacon:"bam.nr-data.net",errorBeacon:"bam.nr-data.net"};function c(){return n.gm.NREUM||(n.gm.NREUM={}),void 0===n.gm.newrelic&&(n.gm.newrelic=n.gm.NREUM),n.gm.NREUM}function d(){let e=c();return e.o||(e.o={ST:n.gm.setTimeout,SI:n.gm.setImmediate||n.gm.setInterval,CT:n.gm.clearTimeout,XHR:n.gm.XMLHttpRequest,REQ:n.gm.Request,EV:n.gm.Event,PR:n.gm.Promise,MO:n.gm.MutationObserver,FETCH:n.gm.fetch,WS:n.gm.WebSocket},(0,a.i)(...Object.values(e.o))),e}function u(e,t){let r=c();r.initializedAgents??={},t.initializedAt={ms:(0,i.t)(),date:new Date},r.initializedAgents[e]=t,2===Object.keys(r.initializedAgents).length&&(0,s.R)(69)}function l(){return function(){let e=c();const t=e.info||{};e.info={beacon:o.beacon,errorBeacon:o.errorBeacon,...t}}(),function(){let e=c();const t=e.init||{};e.init={...t}}(),d(),function(){let e=c();const t=e.loader_config||{};e.loader_config={...t}}(),c()}r.d(t,["NT",0,o])},2843(e,t,r){"use strict";r.d(t,{G:()=>s,u:()=>i});var n=r(3878);function i(e,t=!1,r,i){(0,n.DD)("visibilitychange",function(){if(t)return void("hidden"===document.visibilityState&&e());e(document.visibilityState)},r,i)}function s(e,t,r){(0,n.sp)("pagehide",e,t,r)}},8139(e,t,r){"use strict";r.d(t,{u:()=>f});var n=r(7836),i=r(3434),s=r(8990),a=r(6154);const o={},c=a.gm.XMLHttpRequest,d="addEventListener",u="removeEventListener",l="nr@wrapped:".concat(n.P);function f(e,t){var r=function(e){return(e||n.ee).get("events")}(e);if(o[r.debugId]++)return r;o[r.debugId]=1;var f=(0,i.YM)(r,!0,t);function p(e){f.inPlace(e,[d,u],"-",g)}function g(e,t){return e[1]}return"getPrototypeOf"in Object&&(a.RI&&h(document,p),c&&h(c.prototype,p),h(a.gm,p)),r.on(d+"-start",function(e,t){var r=e[1];if(null!==r&&("function"==typeof r||"object"==typeof r)&&"newrelic"!==e[0]){var n=(0,s.I)(r,l,function(){var e={object:function(){if("function"!=typeof r.handleEvent)return;return r.handleEvent.apply(r,arguments)},function:r}[typeof r];return e?f(e,"fn-",null,e.name||"anonymous"):r});this.wrapped=e[1]=n}}),r.on(u+"-start",function(e){e[1]=this.wrapped||e[1]}),r}function h(e,t,...r){let n=e;for(;"object"==typeof n&&!Object.prototype.hasOwnProperty.call(n,d);)n=Object.getPrototypeOf(n);n&&t(n,...r)}},3434(e,t,r){"use strict";r.d(t,{YM:()=>d});var n=r(7836),i=r(5607),s=r(5732);const a="nr@original:".concat(i.W);var o=Object.prototype.hasOwnProperty,c=!1;function d(e,t,r){return e||(e=n.ee),i.inPlace=function(e,t,r,n,s,a){r||(r="");const o="-"===r.charAt(0);for(let c=0;c<t.length;c++){const d=t[c],u=e[d];l(u)||(e[d]=i(u,o?d+r:r,n,d,s,a))}},i.flag=a,i;function i(t,n,i,c,f,h){return l(t)?t:(n||(n=""),nrWrapper[a]=t,function(e,t,r){if(Object.defineProperty&&Object.keys)try{return Object.keys(e).forEach(function(r){Object.defineProperty(t,r,{get:function(){return e[r]},set:function(t){return e[r]=t,t}})}),t}catch(e){u([e],r)}for(var n in e)o.call(e,n)&&(t[n]=e[n])}
5(t,nrWrapper,e),nrWrapper);function nrWrapper(){var a,o,l,p;let g,m;try{o=this,a=[...arguments],m=h?(0,s.$5)(r):[r?.runtime?.v2Target],l="function"==typeof i?i(a,o):i||{}}catch(t){u([t,"",[a,o,c],l],e)}d(n+"start",[a,o,c,m],l,f);const v=performance.now();let y;try{return p=t.apply(o,a),y=performance.now(),p}catch(e){throw y=performance.now(),d(n+"err",[a,o,e,m],l,f),g=e,g}finally{const e=y-v,t={start:v,end:y,duration:e,isLongTask:e>=50,methodName:c,thrownError:g};t.isLongTask&&d("long-task",[t,o,m],l,f),d(n+"end",[a,o,p,m],l,f)}}}function d(r,n,i,s){if(!c||t){var a=c;c=!0;try{e.emit(r,n,i,t,s)}catch(t){u([t,r,n,i],e)}c=a}}}function u(e,t){t||(t=n.ee);try{t.emit("internal-error",e)}catch(e){}}function l(e){return!(e&&"function"==typeof e&&e.apply&&!e[a])}r.d(t,["Jt",0,a])},9300(e,t,r){"use strict";const n=r(860).K7.ajax;r.d(t,["TZ",0,n,"f5",0,"ajaxRequest.id","mo",0,{NONE:"none",FAILURES:"failures",ALL:"all"}])},3333(e,t,r){"use strict";var n=r(981);const i=r(860).K7.genericEvents,s=[n.R.PA,n.R.UA,n.R.BP];r.d(t,["$v",0,{RESOURCES:"experimental.resources",REGISTER:"register"},"TZ",0,i,"Zp",0,["auxclick","click","copy","keydown","paste","scrollend"],"kd",0,s,"qN",0,["focus","blur"]])},6774(e,t,r){"use strict";const n=r(860).K7.jserrors;r.d(t,["T",0,n])},993(e,t,r){"use strict";const n=r(860).K7.logging;r.d(t,["A$",0,{OFF:0,ERROR:1,WARN:2,INFO:3,DEBUG:4,TRACE:5},"TZ",0,n,"p_",0,{ERROR:"ERROR",WARN:"WARN",INFO:"INFO",DEBUG:"DEBUG",TRACE:"TRACE"}])},3785(e,t,r){"use strict";r.d(t,{R:()=>c,b:()=>d});var n=r(9908),i=r(1863),s=r(860),a=r(3969),o=r(993);function c(e,t,r={},c=o.p_.INFO,d=!0,u,l=(0,i.t)()){(0,n.p)(a.xV,["API/logging/".concat(c.toLowerCase(),"/called")],void 0,s.K7.metrics,e),(0,n.p)("log",[l,t,r,c,d,u],void 0,s.K7.logging,e)}function d(e){return"string"==typeof e&&Object.values(o.p_).some(t=>t===e.toUpperCase().trim())}},3969(e,t,r){"use strict";const n=r(860).K7.metrics;r.d(t,["TZ",0,n,"XG",0,"storeEventMetrics","xV",0,"storeSupportabilityMetrics"])},6630(e,t,r){"use strict";const n=r(860).K7.pageViewEvent;r.d(t,["T",0,n])},782(e,t,r){"use strict";const n=r(860).K7.pageViewTiming;r.d(t,["T",0,n])},6344(e,t,r){"use strict";var n=r(2614);const i=r(860).K7.sessionReplay,s={[n.g.ERROR]:15e3,[n.g.FULL]:3e5,[n.g.OFF]:0}
5;r.d(t,["Qb",0,{API:"api",RESUME:"resume",SWITCH_TO_FULL:"switchToFull",INITIALIZE:"initialize",PRELOAD:"preload"},"TZ",0,i,"Vh",0,"errorDuringReplay","_s",0,{DomContentLoaded:0,Load:1,FullSnapshot:2,IncrementalSnapshot:3,Meta:4,Custom:5},"bc",0,{RESET:{message:"Session was reset",sm:"Reset"},IMPORT:{message:"Recorder failed to import",sm:"Import"},TOO_MANY:{message:"429: Too Many Requests",sm:"Too-Many"},TOO_BIG:{message:"Payload was too large",sm:"Too-Big"},CROSS_TAB:{message:"Session Entity was set to OFF on another tab",sm:"Cross-Tab"},ENTITLEMENTS:{message:"Session Replay is not allowed and will not be started",sm:"Entitlement"}},"yP",0,s])},5270(e,t,r){"use strict";r.d(t,{Aw:()=>a,SR:()=>s,rF:()=>o});var n=r(384),i=r(7767);function s(e){return!!(0,n.dV)().o.MO&&(0,i.V)(e)&&!0===e?.session_trace.enabled}function a(e){return!0===e?.session_replay.preload&&s(e)}function o(e,t){try{if("string"==typeof t?.type&&"password"===t.type.toLowerCase())return"*".repeat(e?.length||0);if(void 0!==t?.dataset?.nrUnmask||t?.classList?.contains("nr-unmask"))return e}catch(e){}return"string"==typeof e?e.replace(/[\S]/g,"*"):"*".repeat(e?.length||0)}},3738(e,t,r){"use strict";const n=r(860).K7.sessionTrace,i="-start",s="-end",a="fn"+i,o="fn"+s;r.d(t,["He",0,"bstResource","Kp",0,s,"Lc",0,o,"Rz",0,"pushState","TZ",0,n,"bD",0,"resource","bc",0,{CROSS_TAB:{message:"Session Entity was set to OFF on another tab"},ENTITLEMENTS:{message:"Session Trace is not allowed and will not be started"},RESET:{message:"Session was reset"},SESSION_CHANGED:{message:"Session identifier changed unexpectedly"}},"d3",0,i,"uP",0,a])},3962(e,t,r){"use strict";const n=r(860).K7.softNav;r.d(t,["O2",0,{INITIAL_PAGE_LOAD:"",ROUTE_CHANGE:1,UNSPECIFIED:2},"OV",0,"popstate","Qu",0,{INTERACTION:1,AJAX:2,CUSTOM_END:3,CUSTOM_TRACER:4},"TZ",0,n,"ih",0,{IP:"in progress",PF:"pending finish",FIN:"finished",CAN:"cancelled"},"pP",0,"initialPageLoad","tC",0,["click","keydown","submit"]])},4234(e,t,r){"use strict";r.d(t,{W:()=>i});var n=r(1687);class i{constructor(e,t){this.agentRef=e,this.ee=e?.ee,this.featureName=t,this.blocked=!1}deregisterDrain(){(0,n.x3)(this.agentRef,this.featureName)}}},7767(e,t,r){"use strict";var n=r(6154);r.d(t,["V",0,e=>n.RI&&!0===e?.privacy.cookies_enabled])},8362(e,t,r){"use strict";r.d(t,{d:()=>s});var n=r(9566),i=r(1741);class s extends i.W{agentIdentifier=(0,n.LA)(16)}},1741(e,t,r){"use strict";r.d(t,{W:()=>s});var n=r(944),i=r(4261);class s{#e(e,...t){if(this[e]!==s.prototype[e])return this[e](...t);(0,n.R)(35,e)}addPageAction(e,t){return this.#e(i.hG,e,t)}register(e){return this.#e(i.eY,e)}recordCustomEvent(e,t){return this.#e(i.fF,e,t)}setPageViewName(e,t){return this.#e(i.Fw,e,t)}setCustomAttribute(e,t,r){return this.#e(i.cD,e,t,r)}noticeError(e,t){return this.#e(i.o5,e,t)}setUserId(e,t=!1){return this.#e(i.Dl,e,t)}setApplicationVersion(e){return this.#e(i.nb,e)}setErrorHandler(e){return this.#e(i.bt,e)}addRelease(e,t){return this.#e(i.k6,e,t)}log(e,t){return this.#e("log",e,t)}start(){return this.#e("start")}finished(e){return this.#e(i.BL,e)}recordReplay(){return this.#e(i.CH)}pauseReplay(){return this.#e(i.Tb)}addToTrace(e){return this.#e(i.U2,e)}setCurrentRouteName(e){return this.#e(i.PA,e)}interaction(e){return this.#e(i.dT,e)}wrapLogger(e,t,r){return this.#e(i.Wb,e,t,r)}measure(e,t){return this.#e(i.V1,e,t)}consent(e){return this.#e(i.Pv,e)}}},4261(e,t,r){"use strict";r.d(t,["BL",0,"finished","CH",0,"recordReplay","Dl",0,"setUserId","Fw",0,"setPageViewName","PA",0,"setCurrentRouteName","Pv",0,"consent","Tb",0,"pauseReplay","U2",0,"addToTrace","V1",0,"measure","Wb",0,"wrapLogger","bt",0,"setErrorHandler","cD",0,"setCustomAttribute","dT",0,"interaction","eY",0,"register","fF",0,"recordCustomEvent","hG",0,"addPageAction","hw",0,"api-ixn-","k6",0,"addRelease","nb",0,"setApplicationVersion","o5",0,"noticeError"])},1738(e,t,r){"use strict";r.d(t,{U:()=>l,Y:()=>u});var n=r(3241),i=r(9908),s=r(1863),a=r(944),o=r(3969),c=r(8362),d=r(860);function u(e,t,r,s){const u=s||r;!u||u[e]&&u[e]!==c.d.prototype[e]||(u[e]=function(){(0,i.p)(o.xV,["API/"+e+"/called"],void 0,d.K7.metrics,r.ee),(0,n.W)({drained:!!r.runtime?.activatedFeatures,type:"data",name:"api",feature:"api-"+e,data:{}});try{return t.apply(this,arguments)}catch(e){(0,a.R)(23,e)}})}function l(e,t,r,n,a){const o=e.info;null===r?delete o.jsAttributes[t]:o.jsAttributes[t]=r,(a||null===r)&&(0,i.p)("api-"+n,[(0,s.t)(),t,r],void 0,"session",e.ee)}},8374(e,t,r){r.nc=(()=>{try{return document?.currentScript?.nonce}catch(e){}return""})()},860(e,t,r){"use strict";const n="events",i="jserrors",s="browser/blobs",a="browser/logs",o={ajax:"ajax",genericEvents:"generic_events",jserrors:i,logging:"logging",metrics:"metrics",pageAction:"page_action",pageViewEvent:"page_view_event",pageViewTiming:"page_view_timing",sessionReplay:"session_replay",sessionTrace:"session_trace",softNav:"soft_navigations"},c={[o.pageViewEvent]:1,[o.pageViewTiming]:2,[o.metrics]:3,[o.jserrors]:4,[o.softNav]:5,[o.ajax]:6,[o.sessionTrace]:7,[o.sessionReplay]:8,[o.logging]:9,[o.genericEvents]:10},d={[o.pageViewEvent]:"rum",[o.pageViewTiming]:n,[o.ajax]:n,[o.softNav]:n,[o.metrics]:i,[o.jserrors]:i,[o.sessionTrace]:s,[o.sessionReplay]:s,[o.logging]:a,[o.genericEvents]:"ins"};r.d(t,["$J",0,d,"K7",0,o,"P3",0,c,"XX",0,i,"Yy",0,a,"df",0,s,"hg",0,"connect"])}};const t={};function r(n){const i=t[n];if(void 0!==i)return i.exports;const s=t[n]={exports:{}};return e[n](s,s.exports,r),s.exports}r.m=e,r.d=(e,t)=>{if(Array.isArray(t))for(var n=0;n<t.length;){var i=t[n++],s=t[n++];r.o(e,i)?0===s&&n++:0===s?Object.defineProperty(e,i,{enumerable:!0,value:t[n++]}):Object.defineProperty(e,i,{enumerable:!0,get:s})}else for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((t,n)=>(r.f[n](e,t),t),[])),r.u=e=>({212:"nr-spa-compressor",238:"nr-spa-iframe-message-handler",249:"nr-spa-recorder",478:"nr-spa"}[e]+"-1.322.0.min.js"),r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{const e={},t="NRBA-1.322.0.PROD:";r.l=(n,i,s,a)=>
5{if(e[n])return void e[n].push(i);let o,c;if(void 0!==s){const e=document.getElementsByTagName("script");for(var d=0;d<e.length;d++){const r=e[d];if(r.getAttribute("src")==n||r.getAttribute("data-webpack")==t+s){o=r;break}}}if(!o){c=!0;var u={478:"sha512-SARROoir04gFygxlr/+mTI+PyMeFL+epi1vnUA2G5mTQ7s3G44ItQBJKbOaIMdIE5OBzuaKhb4k5JYta0NICxg==",249:"sha512-6DKQo5WP7YlpzX6l9UfYkgGLGK2oITdoaYTcZtgl5Foa22NY63lED/0z1hBBLeD7qZCz/YdlY19/r8drlol1tg==",212:"sha512-CYxuvSpDx6ErqWJwAvpoHn54eY37YdMcFao/axOumlw3BsSL7yioOeB2LWViY/Njs818sQG10dSglUhYCMfLPw==",238:"sha512-mvbMILWa3ohYst+u6TLfNrmtY2swUIB3rbJFN/uzt3W62KhgvUVwB81UEZ1R0/it5bfrQ5EVz864pi/CAmucNQ=="};o=document.createElement("script"),o.charset="utf-8",r.nc&&o.setAttribute("nonce",r.nc),o.setAttribute("data-webpack",t+s),o.src=n,0!==o.src.indexOf(window.location.origin+"/")&&(o.crossOrigin="anonymous"),u[a]&&(o.integrity=u[a])}e[n]=[i];const l=(t,r)=>{o.onerror=o.onload=null,clearTimeout(f);const i=e[n];if(delete e[n],o.parentNode?.removeChild(o),i?.forEach(e=>e(r)),t)return t(r)},f=setTimeout(l.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=l.bind(null,o.onerror),o.onload=l.bind(null,o.onload),c&&document.head.appendChild(o)}})(),r.r=e=>{Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="https://js-agent.newrelic.com/",(()=>{const e={38:0,788:0};r.f.j=(t,n)=>{let i=r.o(e,t)?e[t]:void 0;if(0!==i)if(i)n.push(i[2]);else{const s=new Promise((r,n)=>i=e[t]=[r,n]);n.push(i[2]=s);const a=r.p+r.u(t),o=new Error,c=n=>{if(r.o(e,t)&&(i=e[t],0!==i&&(e[t]=void 0),i)){const e=n&&("load"===n.type?"missing":n.type),r=n&&n.target&&n.target.src;o.message="Loading chunk "+t+" failed: ("+e+": "+r+")",o.name="ChunkLoadError",o.type=e,o.request=r,i[1](o)}};r.l(a,c,"chunk-"+t,t)}};const t=(t,n)=>{let[i,s,a]=n;var o,c,d=0;if(i.some(t=>0!==e[t])){for(o in s)r.o(s,o)&&(r.m[o]=s[o]);if(a)a(r)}for(t&&t(n);d<i.length;d++)c=i[d],r.o(e,c)&&e[c]&&e[c][0](),e[c]=0},n=self["webpackChunk:NRBA-1.322.0.PROD"]=self["webpackChunk:NRBA-1.322.0.PROD"]||[];n.forEach(t.bind(null,0)),n.push=t.bind(null,n.push.bind(n))})(),(()=>{"use strict";r(8374);var e=r(8362),t=r(860);const n=Object.values(t.K7);var i=r(384),s=r(1741);var a=r(2555),o=r(3333),c=r(9300);const d=e=>{if(!e||"string"!=typeof e)return!1;try{document.createDocumentFragment().querySelector(e)}catch{return!1}return!0};var u=r(2614),l=r(944),f=r(8122);const h="[data-nr-mask]",p=e=>(0,f.a)(e,(()=>{const e={feature_flags:[],experimental:{register:!1,resources:!1,iframe_bridge:!1,iframe_domains:[]},mask_selector:"*",block_selector:"[data-nr-block]",mask_input_options:{color:!1,date:!1,"datetime-local":!1,email:!1,month:!1,number:!1,range:!1,search:!1,tel:!1,text:!1,time:!1,url:!1,week:!1,textarea:!1,select:!1,password:!0}};return{ajax:{deny_list:void 0,block_internal:!0,enabled:!0,autoStart:!0,capture_payloads:c.mo.NONE},api:{register:{get enabled(){return e.feature_flags.includes(o.$v.REGISTER)||e.experimental.register},set enabled(t){e.experimental.register=t},duplicate_data_to_container:!1,get allow_iframe_bridge(){return e.feature_flags.includes(o.$v.IFRAME_BRIDGE)||e.experimental.iframe_bridge},set allow_iframe_bridge(t){e.experimental.iframe_bridge=t},get iframe_domains(){return e.experimental.iframe_domains},set iframe_domains(t){Array.isArray(t)?e.experimental.iframe_domains=t:(0,l.R)(1,t)}}},browser_consent_mode:{enabled:!1},distributed_tracing:{enabled:void 0,exclude_newrelic_header:void 0,cors_use_newrelic_header:void 0,cors_use_tracecontext_headers:void 0,allowed_origins:void 0},get feature_flags(){return e.feature_flags},set feature_flags(t){e.feature_flags=t},generic_events:{enabled:!0,autoStart:!0},harvest:{interval:30},jserrors:{enabled:!0,autoStart:!0},logging:{enabled:!0,autoStart:!0},metrics:{enabled:!0,autoStart:!0},obfuscate:void 0,page_action:{enabled:!0},page_view_event:{enabled:!0,autoStart:!0},page_view_timing:{enabled:!0,autoStart:!0},performance:{capture_marks:!1,capture_measures:!1,capture_detail:!0,resources:{get enabled(){return e.feature_flags.includes(o.$v.RESOURCES)||e.experimental.resources},set enabled(t){e.experimental.resources=t},asset_types:[],first_party_domains:[],ignore_newrelic:!0}},privacy:{cookies_enabled:!0},proxy:{assets:void 0,beacon:void 0},session:{expiresMs:u.wk,inactiveMs:u.BB},session_replay:{autoStart:!0,enabled:!1,preload:!1,sampling_rate:10,error_sampling_rate:100,collect_fonts:!1,inl
5ine_images:!1,fix_stylesheets:!0,mask_all_inputs:!0,get mask_text_selector(){return e.mask_selector},set mask_text_selector(t){d(t)?e.mask_selector="".concat(t,",").concat(h):""===t||null===t?e.mask_selector=h:(0,l.R)(5,t)},get block_class(){return"nr-block"},get ignore_class(){return"nr-ignore"},get mask_text_class(){return"nr-mask"},get block_selector(){return e.block_selector},set block_selector(t){d(t)?e.block_selector+=",".concat(t):""!==t&&(0,l.R)(6,t)},get mask_input_options(){return e.mask_input_options},set mask_input_options(t){t&&"object"==typeof t?e.mask_input_options={...t,password:!0}:(0,l.R)(7,t)}},session_trace:{enabled:!0,autoStart:!0},soft_navigations:{enabled:!0,autoStart:!0},ssl:void 0,user_actions:{enabled:!0,elementAttributes:["id","className","tagName","type"]},web_sockets:{enabled:!1}}})());var g=r(6154),m=r(9324);let v=0;const y={buildEnv:"PROD",distMethod:"CDN",version:m.x,originTime:g.WN},b={consented:!1},E={activatedFeatures:void 0,appMetadata:{},configured:!1,get consented(){return this.session?.state?.consent||b.consented},set consented(e){b.consented=e},customTransaction:void 0,denyList:[],disabled:!1,drainRegistry:new Map,connector:void 0,harvester:void 0,v2Target:void 0,isolatedBacklog:!1,isRecording:!1,loaderType:void 0,maxBytes:3e4,obfuscator:void 0,onerror:void 0,ptid:void 0,releaseIds:{},session:void 0,timeKeeper:void 0,registeredEntities:[],jsAttributesMetadata:{bytes:0},get harvestCount(){return++v},listeningForIframeMessages:!1};var w=r(9232);var R=r(7836),T=r(3241);const A={accountID:void 0,trustKey:void 0,agentID:void 0,licenseKey:void 0,applicationID:void 0,xpid:void 0};var S=r(9908),x=r(9752);function O(e,t={},n,o){let{init:c,info:d,loader_config:u,runtime:l={},exposed:h=!0}=t;if(!d){const e=(0,i.pV)();c=e.init,d=e.info,u=e.loader_config}var m;e.init=p(c||{}),e.loader_config=(m=u||{},(0,f.a)(m,A)),d.jsAttributes??={},g.bv&&(d.jsAttributes.isWorker=!0),e.info=(0,a.D)(d);const v=e.init;e.runtime??=(e=>{const t=(0,f.a)(e,E),r=Object.keys(y).reduce((e,t)=>(e[t]={value:y[t],writable:!1,configurable:!0,enumerable:!0},e),{});return Object.defineProperties(t,r)})(l),e.runtime.v2Target??={type:w.f.BA,instance:e.agentIdentifier,get id(){return e.runtime.appMetadata?.agents?.[0]?.entityGuid},get attributes(){return{"entity.guid":this.id,appId:e.info.applicationID}}},v.proxy.assets&&(e=>{const t=e.startsWith("http");e+="/",r.p=t?e:"https://"+e})(v.proxy.assets),e.runtime.configured||(Object.defineProperty(e,"beacons",{get:()=>[e.info.beacon,e.info.errorBeacon,e.init.proxy.assets,e.init.proxy.beacon].filter(Boolean)}),Object.defineProperty(e.runtime,"denyList",{get:()=>[...e.init.ajax.deny_list||[],...e.init.ajax.block_internal?e.beacons:[]]}),e.runtime.ptid=e.agentIdentifier,function(e){const t=(0,i.pV)();Object.getOwnPropertyNames(s.W.prototype).forEach(r=>{const n=s.W.prototype[r];if("function"!=typeof n||"constructor"===n)return;let i=t[r];e[r]&&!1!==e.exposed&&"micro-agent"!==e.runtime?.loaderType&&(t[r]=(...t)=>{const n=e[r](...t);return i?i(...t):n})})}(e),e.runtime.loaderType=n,e.ee=R.ee.get(e.agentIdentifier),e.exposed=h,(0,T.W)({drained:!!e.runtime.activatedFeatures,type:"lifecycle",name:"initialize",feature:void 0,data:e.config}),e.init.api.register.allow_iframe_bridge&&g.gm.addEventListener("message",t=>{"string"==typeof t.data?.type&&t.data.type.startsWith(x.Pl)&&(0,S.p)("iframe-message",[t],void 0,"IFRAME",e.ee)}),e.runtime.configured=!0)}var _=r(1863),k=r(4261),N=r(1738);var L=r(1687),M=r(4234),P=r(5289),I=r(5270),j=r(7767),C=r(6389),D=r(7699);const B=new WeakSet,H=new WeakMap;class W extends M.W{constructor(e,t){super(e,t),this.abortHandler=void 0,this.featAggregate=void 0,this.loadedSuccessfully=void 0,this.onAggregateImported=new Promise(e=>{this.loadedSuccessfully=e}),this.deferred=Promise.resolve(),!1===e.init[this.featureName].autoStart?this.deferred=new Promise((t,r)=>{this.ee.on("manual-start-all",(0,C.J)(()=>{(0,L.Ak)(e,this.featureName),t()}))}):(0,L.Ak)(e,t)}importAggregator(e,t,n={}){if(this.featAggregate)return;const i=async()=>{if(await this.deferred,this.#t(e),!(0,a.f)(e.info))return(0,l.R)(43),e.ee.abort(),void this.loadedSuccessfully(!1);try{await async function(e,t,n){if(H.has(e))return H.get(e);const i=(async()=>
5{if((0,j.V)(e.init))try{const{setupAgentSession:t}=await r.e(478).then(r.bind(r,8766));t(e)}catch(e){(0,l.R)(20,e),t.emit("internal-error",[e]),(0,S.p)(D.qh,[e],void 0,n,t)}if(e.init.api.register.allow_iframe_bridge)try{const{setupIframeMFEMessageListener:t}=await r.e(238).then(r.bind(r,6417));t(e)}catch(e){(0,l.R)(23,e)}const[{Connector:i},{Harvester:s}]=await Promise.all([r.e(478).then(r.bind(r,6589)),r.e(478).then(r.bind(r,5237))]);e.runtime.connector=new i(e),e.runtime.harvester=new s(e);const a=Object.values(e.features).filter(t=>e.runtime.drainRegistry.has(t.featureName));Promise.all(a.map(e=>e.onAggregateImported)).then(()=>e.runtime.harvester.startTimer())})();H.set(e,i),await i}(e,this.ee,this.featureName)}catch(e){return(0,l.R)(79,e),this.ee.abort(),void this.loadedSuccessfully(!1)}try{if(!this.#r(this.featureName,e.runtime.session,e.init))return this.abortHandler?.(),(0,L.Ze)(this.agentRef,this.featureName),void this.loadedSuccessfully(!1);const{Aggregate:r}=await t();this.featAggregate=new r(e,n),e.runtime.harvester.initializedAggregates.push(this.featAggregate),this.loadedSuccessfully(!0)}catch(e){(0,l.R)(34,e),this.abortHandler?.(),(0,L.Ze)(this.agentRef,this.featureName,!0),this.loadedSuccessfully(!1)}};g.RI?(0,P.GG)(()=>i(),!0):i()}#r(e,r,n){if(this.blocked)return!1;switch(e){case t.K7.sessionReplay:return(0,I.SR)(n)&&!!r;case t.K7.sessionTrace:return!!r;default:return!0}}#t(e){if(!B.has(e)&&(B.add(e),!(0,a.f)(e.info))){const t=(0,i.pV)();let r={...t.info?.jsAttributes};try{r={...r,...e.info?.jsAttributes}}catch(e){}O(e,{...t,info:{...t.info,jsAttributes:r},runtime:e.runtime},e.runtime.loaderType)}}}var U=r(6630);class K extends W{static featureName=U.T;constructor(e){var t;super(e,U.T),this.setupInspectionEvents(),t=e,(0,N.Y)(k.Fw,function(e,r){"string"==typeof e&&("/"!==e.charAt(0)&&(e="/"+e),t.runtime.customTransaction=(r||"http://custom.transaction")+e,(0,S.p)("api-"+k.Fw,[(0,_.t)()],void 0,void 0,t.ee))},t),this.importAggregator(e,()=>e.init.feature_flags.includes("rum_v2")?r.e(478).then(r.bind(r,908)):r.e(478).then(r.bind(r,7927)))}setupInspectionEvents(){const e=(e,t)=>{e&&(0,T.W)({timeStamp:e.timeStamp,loaded:"complete"===e.target.readyState,type:"window",name:t,data:e.target.location+""})};(0,P.sB)(t=>{e(t,"DOMContentLoaded")}),(0,P.GG)(t=>{e(t,"load")}),(0,P.Qr)(t=>{e(t,"navigate")}),this.ee.on(u.tS.UPDATE,(e,t)=>{(0,T.W)({type:"lifecycle",name:"session",data:t})})}}class F extends e.d{constructor(e){var t;(super(),g.gm)?(this.features={},(0,i.bQ)(this.agentIdentifier,this),this.desiredFeatures=new Set(e.features||[]),this.desiredFeatures.add(K),O(this,e,e.loaderType||"agent"),t=this,(0,N.Y)(k.cD,function(e,r,n=!1){if("string"==typeof e){if(["string","number","boolean"].includes(typeof r)||null===r)return(0,N.U)(t,e,r,k.cD,n);(0,l.R)(40,typeof r)}else(0,l.R)(39,typeof e)},t),function(e){(0,N.Y)(k.Dl,function(t,r=!1){if("string"!=typeof t&&null!==t)return void(0,l.R)(41,typeof t);const n=e.info.jsAttributes["enduser.id"];r&&null!=n&&n!==t?(0,S.p)("api-setUserIdAndResetSession",[t],void 0,"session",e.ee):(0,N.U)(e,"enduser.id",t,k.Dl,!0)},e)}(this),function(e){(0,N.Y)(k.nb,function(t){if("string"==typeof t||null===t)return(0,N.U)(e,"application.version",t,k.nb,!1);(0,l.R)(42,typeof t)},e)}(this),function(e){(0,N.Y)("start",function(){e.ee.emit("manual-start-all")},e)}(this),function(e){(0,N.Y)(k.Pv,function(t=!0){if("boolean"==typeof t){if((0,S.p)("api-"+k.Pv,[t],void 0,"session",e.ee),e.runtime.consented=t,t){const t=e.features.page_view_event;t.onAggregateImported.then(e=>{const r=t.featAggregate;e&&!r.sentRum&&r.sendRum?.()})}}else(0,l.R)(65,typeof t)},e)}(this),this.run()):(0,l.R)(21)}get config(){return{info:this.info,init:this.init,loader_config:this.loader_config,runtime:this.runtime}}get api(){return this}run(){try{const e=function(e){const t={};return n.forEach(r=>{t[r]=!!e[r]?.enabled}),t}(this.init),r=[...this.desiredFeatures],i=this.init.feature_flags.includes("rum_v2");r.sort((e,r)=>t.P3[e.featureName]-t.P3[r.featureName]),r.forEach(r=>{if(!e[r.featureName]&&(i||r.featureName!==t.K7.pageViewEvent))return;const n=function(e){switch(e){case t.K7.ajax:return[t.K7.jserrors];case t.K7.sessionTrace:return[t.K7.ajax,t.K7.pageViewEvent];case t.K7.sessionReplay:return[t.K7.sessionTrace];case t.K7.pageViewTiming:return[t.K7.pageViewEvent];default:return[]}}(r.featureName).filter(e=>!(e in this.features));n.length>0&&(0,l.R)(36,{targetFeature:r.featureName,missingDependencies:n}),this.features[r.featureName]=new r(this)})}catch(e){(0,l.R)(22,e);for(const e in this.features)this.features[e].abortHandler?.();const t=(0,i.Zm)();delete t.initializedAgents[this.agentIdentifier]?.features,delete this.sharedAggregator;return t.ee.get(this.agentIdentifier).abort(),!1}}}var V=r(2843),z=r(782);class G extends W{static featureName=z.T;constructor(e){super(e,z.T),g.RI&&((0,V.u)(()=>(0,S.p)("docHidden",[(0,_.t)()],void 0,z.T,this.ee),!0),(0,V.G)(()=>(0,S.p)("winPagehide",[(0,_.t)()],void 0,z.T,this.ee)),this.importAggregator(e,()=>r.e(478).then(r.bind(r,9917))))}}var q=r(3969);class Y extends W{static featureName=q.TZ;constructor(e){super(e,q.TZ),this.importAggregator(e,()=>r.e(478).then(r.bind(r,6555)))}}var Z=r(6774),J=r(3878),X=r(3304);class Q{constructor(e,t,r,n,i){this.name="UncaughtError",this.message="string"==typeof e?e:(0,X.A)(e),this.sourceURL=t,this.line=r,this.column=n,this.__newrelic=i}}function ee(e){return ne(e)?e:new Q(void 0!==e?.message?e.message:e,e?.filename||e?.sourceURL,e?.lineno||e?.line,e?.colno||e?.col,e?.__newrelic,e?.cause)}function te(e){const t="Unhandled Promise Rejection: ";if(!e?.reason)return;if(ne(e.reason)){try{e.reason.message.startsWith(t)||(e.reason.message=t+e.reason.message)}catch(e){}return ee(e.reason)}const r=ee(e.reason);return(r.message||"").startsWith(t)||(r.message=t+r.message),r}function re(e){if(e.error instanceof SyntaxError&&!/:\d+$/.test(e.error.stack?.trim())){const t=new Q(e.message,e.filename,e.lineno,e.colno,e.error.__newrelic,e.cause);return t.name=SyntaxError.name,t}return ne(e.error)?e.error:ee(e)}function ne(e){return e instanceof Error&&!!e.stack}function ie(e,r,n,i,s=(0,_.t)()){"string"==typeof e&&(e=new Error(e)),(0,S.p)("err",[e,s,!1,r,n.runtime.isRecording,void 0,i],void 0,t.K7.jserrors,n.ee),(0,S.p)("uaErr",[],void 0,t.K7.genericEvents,n.ee)}var se=r(993),ae=r(3785);function oe(e,{customAttributes:t={},level:r=se.p_.INFO}={},n,i,s=(0,_.t)()){(0,ae.R)(n.ee,e,t,r,!1,i,s)}function ce(e,r,n,i,s=(0,_.t)()){(0,S.p)("api-"+k.hG,[s,e,r,i],void 0,t.K7.genericEvents,n.ee)}function de(e,r,n,i,s=(0,_.t)()){const{start:a,end:o,customAttributes:c}=r||{},d={customAttributes:c||{}};if("object"!=typeof d.customAttributes||"string"!=typeof e||0===e.length)return void(0,l.R)(57);const u=(e,t)=>
5null==e?t:"number"==typeof e?e:e instanceof PerformanceMark?e.startTime:Number.NaN;if(d.start=u(a,0),d.end=u(o,s),Number.isNaN(d.start)||Number.isNaN(d.end))(0,l.R)(57);else{if(d.duration=d.end-d.start,!(d.duration<0))return(0,S.p)("api-"+k.V1,[d,e,i],void 0,t.K7.genericEvents,n.ee),d;(0,l.R)(58)}}function ue(e,r={},n,i,s=(0,_.t)()){(0,S.p)("api-"+k.fF,[s,e,r,i],void 0,t.K7.genericEvents,n.ee)}var le=r(5718);const fe=(e,t)=>e?.nrMfeId===t.id&&(!e?.nrMfeObserved||e?.nrMfeObserved===t.instance),he=e=>{try{return g.gm.CSS.escape(e)}catch(t){return e}},pe=(e,t={})=>{const r=t.id,n=t.instance;if(!e||!r||!n)return!1;try{let r=1===e.nodeType?e:e.parentElement;for(;r?.tagName;){if(fe(r.dataset,t))return r.dataset.nrMfeObserved=n,!0;r=r.parentNode}}catch(e){}return!1},ge=(e,t)=>{const r=g.gm.document?.querySelectorAll('[data-nr-mfe-id="'.concat(he(e.id),'"]')),n=(Array.from(r)||[]).find(t=>fe(t.dataset,e));let i=!!n;i&&(n.dataset.nrMfeObserved??=e.instance);const s=new g.gm.MutationObserver(r=>{r.forEach(r=>{r.addedNodes.forEach(r=>{const n=1===r.nodeType?r:null;!i&&fe(n?.dataset,e)&&(s.disconnect(),s.observe(n,{childList:!0,subtree:!0}),i=!0,n.dataset.nrMfeObserved=e.instance),(e=>{try{return e?.textContent?.trim()||["img","video","canvas","svg"].includes(e?.nodeName?.toLowerCase())}catch(e){return!1}})(r)&&(i||pe(r,e))&&t(r,s)})})});return s.observe(n||g.gm.document,{childList:!0,subtree:!0}),s},me=(e,t,r)=>{try{const n=new g.gm.PerformanceObserver(e=>{e.getEntries().forEach(r)});return n.observe(t),e.push(n),n}catch(e){return null}};function ve(e,t){let r,n,i=!1;const s=e=>null==e?e:e-(t?.scriptStart||t?.registeredAt||0),a={fcp:{get value(){const e=s(r);return e>1e4?void 0:e},set value(e){i||(r=e)}},lcp:{get value(){if(void 0!==a.fcp.value)return s(n)},set value(e){i||(n=e)}},cls:{value:void 0},inp:{value:void 0},disconnect:()=>{}};if(!(e&&g.RI&&g.gm.MutationObserver&&g.gm.PerformanceObserver))return a;const o=[];setTimeout(()=>{r||a.disconnect()},1e4);const c=()=>{if(!i){if(null==r){const e=(0,_.t)();if(s(e)>1e4)return void a.disconnect();r=e}n??=(0,_.t)(),a.cls.value??=0}},d=ge(e,(e,t)=>{c(),t.disconnect()});o.push(d);let u=0,l=null;const f=new Set;try{l=new g.gm.ResizeObserver(e=>{i||e.forEach(e=>{try{const t=e.contentRect.width*e.contentRect.height;t>u&&(u=t,a.lcp.value=(0,_.t)()),l.unobserve(e.target)}catch(e){}})})}catch(e){}const h=ge(e,e=>{if(c(),l)try{const t=1===e.nodeType?e:e.parentElement;t&&!f.has(t)&&(f.add(t),"IMG"===t.tagName?t.complete?l.observe(t):t.addEventListener("load",()=>{l.observe(t)},{once:!0}):"VIDEO"===t.tagName?t.readyState>=2?l.observe(t):t.addEventListener("loadeddata",()=>{l.observe(t)},{once:!0}):l.observe(t))}catch(e){}});l&&o.push(l),o.push(h),me(o,{type:"layout-shift",buffered:!0},t=>{i||t.hadRecentInput||(t.sources||[]).some(r=>!!pe(r.node,e)&&(c(),a.cls.value+=t.value,!0))}),me(o,{type:"event",buffered:!0,durationThreshold:40},t=>{!i&&t.interactionId&&pe(t.target,e)&&(void 0===a.inp.value||t.duration>a.inp.value)&&(c(),a.inp.value=t.duration)});const p=["pointerdown","keydown"],m=()=>{p.forEach(e=>{g.gm.removeEventListener(e,v,{passive:!0})})};a.disconnect=()=>{i=!0,o.forEach(e=>{try{e?.disconnect()}catch(e){}}),m(),["visibilitychange","pagehide"].forEach(e=>{g.gm.removeEventListener(e,a.disconnect,{once:!0,passive:!0})})};const v=t=>{pe(t?.target,e)&&((()=>{try{h?.disconnect(),l?.disconnect()}catch(e){}})(),m())};p.forEach(e=>{g.gm.addEventListener(e,v,{passive:!0})}),["visibilitychange","pagehide"].forEach(e=>{g.gm.addEventListener(e,a.disconnect,{once:!0,passive:!0})});const y=g.gm.document?.querySelectorAll('[data-nr-mfe-id="'.concat(he(e.id),'"]'));return Array.from(y||[]).some(t=>fe(t.dataset,e))&&c(),a}var ye=r(9566),be=r(7560),Ee=r(9119);const we=["entry","scripts","all"],Re=["name","id","type"],Te=new Map([[ce,"addPageAction"],[oe,"log"],[de,"measure"],[ie,"noticeError"],[ue,"recordCustomEvent"]]),Ae={experimental:(0,C.J)(()=>(0,l.R)(54,"newrelic.register")),disabled:(0,C.J)(()=>(0,l.R)(55)),invalidTarget:(0,C.J)(e=>(0,l.R)(48,e)),deregistered:(0,C.J)(()=>(0,l.R)(68)),invalidTimingMethod:(0,C.J)(e=>(0,l.R)(80,e)),duplicateName:(0,C.J)(e=>(0,l.R)(81,e)),duplicateId:(0,C.J)(e=>(0,l.R)(82,e))};function Se(e){(0,N.Y)(k.eY,function(t){return xe(e,t)},e)}function xe(e,r){Ae.experimental(),r||={},r.instance=(0,ye.LA)(8),r.type=w.f.MFE,r.licenseKey||=e.info.licenseKey,r.blocked=!1,("object"!=typeof r.tags||null===r.tags||Array.isArray(r.tags))&&(r.tags={}),r.parent??={get id(){return e.runtime.appMetadata.agents?.[0].entityGuid},type:w.f.BA},r.manifest=(0,be.N)(r.manifest),void 0===r.timingMethod||we.includes(r.timingMethod)||(Ae.invali
5dTimingMethod(r.timingMethod),r.timingMethod=void 0);const n=(0,le.Qr)(r);r.manifest&&(0,le.xo)(n,r);const i=ve(r,n),s=(0,Ee.L)(""+location),a={};Object.prototype.hasOwnProperty.call(r,"attributes")||Object.defineProperty(r,"attributes",{get:()=>({...a,"source.id":r.id,"source.name":r.name,"source.type":r.type,"parent.type":r.parent?.type||w.f.BA,"parent.id":r.parent?.id})}),Object.entries(r.tags).forEach(([e,t])=>{Re.includes(e)||(a["source.".concat(e)]=t)});let o=()=>{};const c=e.runtime.registeredEntities,d=e=>{r.blocked=!0,o=e};function u(e){return"string"==typeof e&&!!e.trim()&&e.trim().length<501}e.init.api.register.enabled||d(Ae.disabled),u(r.id)&&u(r.name)||d(()=>Ae.invalidTarget(r)),c.forEach(e=>{try{const{name:t,id:n}=e.metadata.target;t===r.name&&n!==r.id&&Ae.duplicateName(r),n===r.id&&t!==r.name&&Ae.duplicateId(r)}catch(e){}});const f={addPageAction:(t,n={})=>m(ce,[t,{...a,...n},e],r),deregister:()=>{p(),d(Ae.deregistered)},log:(t,n={})=>m(oe,[t,{...n,customAttributes:{...a,...n.customAttributes||{}}},e],r),measure:(t,n={})=>m(de,[t,{...n,customAttributes:{...a,...n.customAttributes||{}}},e],r),noticeError:(t,n={})=>m(ie,[t,{...a,...n},e],r),recordCustomEvent:(t,n={})=>m(ue,[t,{...a,...n},e],r),setApplicationVersion:e=>g("application.version",e),setCustomAttribute:(e,t)=>g(e,t),setUserId:e=>g("enduser.id",e),metadata:{get customAttributes(){return a},target:r,timings:n,vitals:i,events:{latestTimestamp:void 0}}},h=()=>(r.blocked&&o(),r.blocked);function p(){if(n.reportedAt)return;n.reportedAt=(0,_.t)(),i.disconnect(),n.fcp=i.fcp,n.lcp=i.lcp,n.cls=i.cls,n.inp=i.inp;const e=n.fetchEnd-n.fetchStart,t=n.scriptEnd-n.scriptStart,r={assetUrl:n.asset,assetType:n.type,registerUrl:s,deregisterUrl:(0,Ee.L)(""+location),pageUrl:void 0,currentUrl:void 0,timeAlive:n.reportedAt-n.registeredAt,timeToBeRequested:n.fetchStart,timeToExecute:t,timeToFetch:e,timeToLoad:e+t,t
5imeToRegister:n.registeredAt,totalWeight:n.totalWeight,...void 0!==n.renderBlocking&&{renderBlocking:n.renderBlocking},...i.fcp.value>=0&&{"vitals.fcp.value":i.fcp.value},...i.lcp.value>=0&&{"vitals.lcp.value":i.lcp.value},...i.cls.value>=0&&{"vitals.cls.value":i.cls.value},...i.inp.value>=0&&{"vitals.inp.value":i.inp.value}};f.recordCustomEvent("MicroFrontEndTiming",r)}h()||(c.push(f),(0,V.G)(p));const g=(e,t)=>{h()||(a[e]=t)},m=(r,n,i)=>{if(h()&&r!==xe)return;const s=f.metadata.events.latestTimestamp??(0,_.t)();f.metadata.events.latestTimestamp=void 0;const a=Te.get(r)||"unknown";(0,S.p)(q.xV,["API/register/".concat(a,"/called")],void 0,t.K7.metrics,e.ee);try{return r(...n,i,s)}catch(e){(0,l.R)(50,e)}};return f}class Oe extends W{static featureName=Z.T;constructor(e){var t;super(e,Z.T),t=e,(0,N.Y)(k.o5,(e,r)=>ie(e,r,t),t),function(e){(0,N.Y)(k.bt,function(t){e.runtime.onerror=t},e)}(e),function(e){let t=0;(0,N.Y)(k.k6,function(e,r){++t>10||(this.runtime.releaseIds[e.slice(-200)]=(""+r).slice(-200))},e)}(e),Se(e);try{this.removeOnAbort=new AbortController}catch(e){}this.ee.on("internal-error",(t,r)=>{this.abortHandler&&(0,S.p)("ierr",[ee(t),(0,_.t)(),!0,{},e.runtime.isRecording,r],void 0,this.featureName,this.ee)}),g.gm.addEventListener("unhandledrejection",t=>{this.abortHandler&&(0,S.p)("err",[te(t),(0,_.t)(),!1,{unhandledPromiseRejection:1},e.runtime.isRecording],void 0,this.featureName,this.ee)},(0,J.jT)(!1,this.removeOnAbort?.signal)),g.gm.addEventListener("error",t=>{this.abortHandler&&(0,S.p)("err",[re(t),(0,_.t)(),!1,{},e.runtime.isRecording],void 0,this.featureName,this.ee)},(0,J.jT)(!1,this.removeOnAbort?.signal)),this.abortHandler=this.#n,this.importAggregator(e,()=>r.e(478).then(r.bind(r,9377)))}#n(){this.removeOnAbort?.abort(),this.abortHandler=void 0}}var _e=r(8990);let ke=1;function Ne(e){const t=typeof e;return!e||"object"!==t&&"function"!==t?-1:e===g.gm?0:(0,_e.I)(e,"nr@id",function(){return ke++})}function Le(e){if("string"==typeof e)return e.length;if("object"==typeof e){if("undefined"!=typeof ArrayBuffer&&e instanceof ArrayBuffer&&e.byteLength)return e.byteLength;if("undefined"!=typeof Blob&&e instanceof Blob&&e.size)return e.size;if(!("undefined"!=typeof FormData&&e instanceof FormData))try{return(0,X.A)(e).length}catch(e){return}}}var Me=r(8139),Pe=r(3434),Ie=r(5732);const je={},Ce=["open","send","setRequestHeader"];function De(e,t){var r=e||R.ee;const n=function(e){return(e||R.ee).get("xhr")}(r);if(void 0===g.gm.XMLHttpRequest)return n;if(je[n.debugId]++)return n;je[n.debugId]=1,(0,Me.u)(r,t);var i=(0,Pe.YM)(n,void 0,t),s=g.gm.XMLHttpRequest,a=g.gm.MutationObserver,o=g.gm.Promise,c=g.gm.setInterval,d="readystatechange",u=["onload","onerror","onabort","onloadstart","onloadend","onprogress","ontimeout"],f=[],h=g.gm.XMLHttpRequest=function(e){const r=new s(e),a=n.context(r);a.targets=(0,Ie.$5)(t);try{n.emit("new-xhr",[r],a),r.addEventListener(d,(o=a,function(){var e=this;e.readyState>3&&!o.resolved&&(o.resolved=!0,n.emit("xhr-resolved",[],e)),i.inPlace(e,u,"fn-",E)}),(0,J.jT)(!1))}catch(e){(0,l.R)(15,e);try{n.emit("internal-error",[e])}catch(e){}}var o;return r};function p(e,t){i.inPlace(t,["onreadystatechange"],"fn-",E)}if(function(e,t){for(var r in e)t[r]=e[r]}(s,h),h.prototype=s.prototype,i.inPlace(h.prototype,Ce,"-xhr-",E),n.on("send-xhr-start",function(e,t){p(e,t),function(e){f.push(e),a&&(m?m.then(b):c?c(b):(v=-v,y.data=v))}(t)}),n.on("open-xhr-start",p),a){var m=o&&o.resolve();if(!c&&!o){var v=1,y=document.createTextNode(v);new a(b).observe(y,{characterData:!0})}}else r.on("fn-end",function(e){e[0]&&e[0].type===d||b()});function b(){for(var e=0;e<f.length;e++)p(0,f[e]);f.length&&(f=[])}function E(e,t){return t}return n}var Be="fetch-",He=Be+"body-",We=["arrayBuffer","blob","json","text","formData"],Ue=g.gm.Request,Ke=g.gm.Response,Fe="prototype";const Ve={};function ze(e,t){const r=function(e){return(e||R.ee).get("fetch")}(e);if(!(Ue&&Ke&&g.gm.fetch))return r;if(Ve[r.debugId]++)return r;function n(e,n,i){var s=e[n];"function"==typeof s&&(e[n]=function(){var e=[...arguments];const n={},a=(0,Ie.$5)(t);var o;r.emit(i+"before-start",[e],n),n[R.P]&&n[R.P].dt&&(o=n[R.P].dt);var c=s.apply(this,e);return r.emit(i+"start",[e,o],c),c.then(function(e){return r.emit(i+"end",[null,e,a],c),e},function(e){throw r.emit(i+"end",[e,void 0,a],c),e})})}
5return Ve[r.debugId]=1,We.forEach(e=>{n(Ue[Fe],e,He),n(Ke[Fe],e,He)}),n(g.gm,"fetch",Be),r.on(Be+"end",function(e,n,i){var s=this;if(s.targets=i||[t.runtime.v2Target],n){var a=n.headers.get("content-length");null!==a&&(s.rxSize=a),r.emit(Be+"done",[null,n],s)}else r.emit(Be+"done",[e],s)}),r}var Ge=r(7485);class qe{constructor(e){this.agentRef=e}generateTracePayload(e){const t=this.agentRef.loader_config;if(!this.shouldGenerateTrace(e)||!t)return null;var r=(t.accountID||"").toString()||null,n=(t.agentID||"").toString()||null,i=(t.trustKey||"").toString()||null;if(!r||!n)return null;var s=(0,ye.ZF)(),a=(0,ye.el)(),o=Date.now(),c={spanId:s,traceId:a,timestamp:o};return(e.sameOrigin||this.isAllowedOrigin(e)&&this.useTraceContextHeadersForCors())&&(c.traceContextParentHeader=this.generateTraceContextParentHeader(s,a),c.traceContextStateHeader=this.generateTraceContextStateHeader(s,o,r,n,i)),(e.sameOrigin&&!this.excludeNewrelicHeader()||!e.sameOrigin&&this.isAllowedOrigin(e)&&this.useNewrelicHeaderForCors())&&(c.newrelicHeader=this.generateTraceHeader(s,a,o,r,n,i)),c}generateTraceContextParentHeader(e,t){return"00-"+t+"-"+e+"-01"}generateTraceContextStateHeader(e,t,r,n,i){return i+"@nr=0-1-"+r+"-"+n+"-"+e+"----"+t}generateTraceHeader(e,t,r,n,i,s){if(!("function"==typeof g.gm?.btoa))return null;var a={v:[0,1],d:{ty:"Browser",ac:n,ap:i,id:e,tr:t,ti:r}};return s&&n!==s&&(a.d.tk=s),btoa((0,X.A)(a))}shouldGenerateTrace(e){return this.agentRef.init?.distributed_tracing?.enabled&&this.isAllowedOrigin(e)}isAllowedOrigin(e){var t=!1;const r=this.agentRef.init?.distributed_tracing;if(e.sameOrigin)t=!0;else if(r?.allowed_origins instanceof Array)for(var n=0;n<r.allowed_origins.length;n++){var i=(0,Ge.D)(r.allowed_origins[n]);if(e.hostname===i.hostname&&e.protocol===i.protocol&&e.port===i.port){t=!0;break}}return t}excludeNewrelicHeader(){var e=this.agentRef.init?.distributed_tracing;return!!e&&!!e.exclude_newrelic_header}useNewrelicHeaderForCors(){var e=this.agentRef.init?.distributed_tracing;return!!e&&!1!==e.cors_use_newrelic_header}useTraceContextHeadersForCors(){var e=this.agentRef.init?.distributed_tracing;return!!e&&!!e.cors_use_tracecontext_headers}}var Ye=r(7295);function Ze(e){return"string"==typeof e?e:e instanceof(0,i.dV)().o.REQ?e.url:g.gm?.URL&&e instanceof URL?e.href:void 0}function $e(e,t){var r=(0,Ge.D)(t),n=e.params||e;n.hostname=r.hostname,n.port=r.port,n.protocol=r.protocol,n.host=r.hostname+":"+r.port,n.pathname=r.pathname,e.parsedOrigin=r,e.sameOrigin=r.sameOrigin}var Je=["load","error","abort","timeout"],Xe=Je.length,Qe=(0,i.dV)().o.REQ,et=(0,i.dV)().o.XHR;const tt="X-NewRelic-App-Data",rt="internal-error";class nt extends W{static featureName=c.TZ;constructor(e){super(e,c.TZ),this.dt=new qe(e),this.handler=(e,t,r,n)=>(0,S.p)(e,t,r,n,this.ee);try{const e={xmlhttprequest:"xhr",fetch:"fetch",beacon:"beacon"};g.gm?.performance?.getEntriesByType("resource").forEach(r=>{if(r.initiatorType in e&&0!==r.responseStatus){const n={status:r.responseStatus},i={rxSize:r.transferSize,duration:Math.floor(r.duration),cbTime:0};$e(n,r.name),this.handler("xhr",[n,i,r.startTime,r.responseEnd,e[r.initiatorType]],void 0,t.K7.ajax)}})}catch(e){}ze(this.ee,e),De(this.ee,e),function(e,r,n,i){const s=[c.mo.ALL,c.mo.FAILURES].includes(e.init.ajax?.capture_payloads);function a(e){var t=this;t.totalCbs=0,t.called=0,t.cbTime=0,t.end=A,t.ended=!1,t.xhrGuids={},t.lastSize=null,t.loadCaptureCalled=!1,t.params=this.params||{},t.metrics=this.metrics||{},t.latestLongtaskEnd=0,e.addEventListener("load",function(r){O(t,e)},(0,J.jT)(!1)),g.lR||e.addEventListener("progress",function(e){t.lastSize=e.loaded},(0,J.jT)(!1))}function o(e){this.params={method:e[0]},$e(this,e[1]),this.metrics={}}function d(t,r){e.loader_config.xpid&&this.sameOrigin&&r.setRequestHeader("X-NewRelic-ID",e.loader_config.xpid);var n=i.generateTracePayload(this.parsedOrigin);if(n){var s=!1;n.newrelicHeader&&(r.setRequestHeader("newrelic",n.newrelicHeader),s=!0),n.traceContextParentHeader&&(r.setRequestHeader("traceparent",n.traceContextParentHeader),n.traceContextStateHeader&&r.setRequestHeader("tracestate",n.traceContextStateHeader),s=!0),s&&(this.dt=n)}}function u(e,t){s&&e.length>=2&&(this.requestHeaders??={},this.requestHeaders[e[0].toLowerCase()]=e[1])}function l(e,t){var n=this.metrics,i=e[0],s=this;if(n&&i){var a=Le(i);a&&(n.txSize=a)}this.startTime=(0,_.t)(),this.requestBody=i,this.listener=function(e){try{"abort"!==e.type||s.loadCaptureCalled||(s.params.aborted=!0),("load"
5!==e.type||s.called===s.totalCbs&&(s.onloadCalled||"function"!=typeof t.onload)&&"function"==typeof s.end)&&s.end(t)}catch(e){try{r.emit(rt,[e])}catch(e){}}};for(var o=0;o<Xe;o++)t.addEventListener(Je[o],this.listener,(0,J.jT)(!1))}function f(e,t,r){this.cbTime+=e,t?this.onloadCalled=!0:this.called+=1,this.called!==this.totalCbs||!this.onloadCalled&&"function"==typeof r.onload||"function"!=typeof this.end||this.end(r)}function h(e,t){var r=""+Ne(e)+!!t;this.xhrGuids&&!this.xhrGuids[r]&&(this.xhrGuids[r]=!0,this.totalCbs+=1)}function p(e,t){var r=""+Ne(e)+!!t;this.xhrGuids&&this.xhrGuids[r]&&(delete this.xhrGuids[r],this.totalCbs-=1)}function m(){this.endTime=(0,_.t)()}function v(e,t){t instanceof et&&"load"===e[0]&&r.emit("xhr-load-added",[e[1],e[2]],t)}function y(e,t){t instanceof et&&"load"===e[0]&&r.emit("xhr-load-removed",[e[1],e[2]],t)}function b(e,t,r){t instanceof et&&("onload"===r&&(this.onload=!0),("load"===(e[0]&&e[0].type)||this.onload)&&(this.xhrCbStart=(0,_.t)()))}function E(e,t){this.xhrCbStart&&r.emit("xhr-cb-time",[(0,_.t)()-this.xhrCbStart,this.onload,t],t)}function w(e){var t,r=e[1]||{};if("string"==typeof e[0]?0===(t=e[0]).length&&g.RI&&(t=""+g.gm.location.href):e[0]&&e[0].url?t=e[0].url:g.gm?.URL&&e[0]&&e[0]instanceof URL?t=e[0].href:"function"==typeof e[0].toString&&(t=e[0].toString()),"string"==typeof t&&0!==t.length){t&&(this.parsedOrigin=(0,Ge.D)(t),this.sameOrigin=this.parsedOrigin.sameOrigin);var n=i.generateTracePayload(this.parsedOrigin);if(n&&(n.newrelicHeader||n.traceContextParentHeader))if(e[0]&&e[0].headers)o(e[0].headers,n)&&(this.dt=n);else{var s={};for(var a in r)s[a]=r[a];s.headers=new Headers(r.headers||{}),o(s.headers,n)&&(this.dt=n),e.length>1?e[1]=s:e.push(s)}}function o(e,t){var r=!1;return t.newrelicHeader&&(e.set("newrelic",t.newrelicHeader),r=!0),t.traceContextParentHeader&&(e.set("traceparent",t.traceContextParentHeader),t.traceContextStateHeader&&e.set("tracestate",t.traceContextStateHeader),r=!0),r}}function R(e,t){this.params={},this.metrics={},this.startTime=(0,_.t)(),this.dt=t;let[r,n={}]=e;$e(this,Ze(r));const i=(""+(r&&r instanceof Qe&&r.method||n.method||"GET")).toUpperCase();this.params.method=i,this.txSize=Le(n.body||r?.body)||0;try{var a=n.headers||r?.headers;if(s&&a)if(this.requestHeaders??={},a instanceof Headers)a.forEach(function(e,t){this.requestHeaders[t.toLowerCase()]=e}.bind(this));else if("object"==typeof a)for(var o in a)this.requestHeaders[o.toLowerCase()]=a[o]}catch(e){}this.requestBody=n.body||r?.body}function T(e,t){if(this.endTime=(0,_.t)(),this.params||(this.params={}),(0,Ye.iW)(this.params))return;this.params.status=t?t.status:0;const n=()=>{const e=+this.rxSize,t=null==this.rxSize||isNaN(e)?void 0:e,r={txSize:this.txSize,rxSize:t,duration:this.endTime-this.startTime},n=[this.params,r,this.startTime,this.endTime,"fetch"];this.targets.forEach(e=>x(n,this,e))};t&&s?t.clone().text().then(e=>{this.responseBody=e,this.rxSize&&"0"!==this.rxSize&&0!==this.rxSize||void 0===e||0===this.params.status||(this.rxSize=Le(e)),t?.headers&&(this.responseHeaders={},t.headers.forEach(function(e,t){this.responseHeaders[t.toLowerCase()]=e}.bind(this)))}).catch(e=>{r.emit(rt,[e])}).finally(()=>{n()}):n()}function A(e){const t=this.params,n=this.metrics;if(this.ended)return;this.ended=!0;for(let t=0;t<Xe;t++)e.removeEventListener(Je[t],this.listener,!1);if(t.aborted)return;if((0,Ye.iW)(t))return;if(n.duration=this.endTime-this.startTime,this.loadCaptureCalled||4!==e.readyState?null==t.status&&(t.status=0):O(this,e),n.cbTime=this.cbTime,s){try{this.responseBody=e.responseText}catch(t){this.responseBody=e.response}if((!n.rxSize||0===n.rxSize)&&void 0!==this.responseBody&&0!==t.status){const e=Le(this.responseBody);void 0!==e&&(n.rxSize=e)}try{this.responseHeaders=function(e){const t={};return e?(e.split("\r\n").forEach(function(e){const r=e.indexOf(": ");if(r>0){const n=e.substring(0,r),i=e.substring(r+2);t[n.toLowerCase()]=i}}),t):t}(e.getAllResponseHeaders())}catch(e){r.emit(rt,[e])}}const i=[t,n,this.startTime,this.endTime,"xhr"];this.targets.forEach(e=>x(i,this,e))}function x(e,r,i){n("xhr",[...e,i],r,t.K7.ajax)}function O(e,n){e.params.status=n.status;var i=function(e,t){var r=e.responseType;return"json"===r&&null!==t?t:"arraybuffer"===r||"blob"===r||"json"===r?Le(e.response):"text"===r||""===r||void 0===r?Le(e.responseText):void 0}(n,e.lastSize);if(void 0!==i&&0!==n.status&&(e.metrics.rxSize=i),e.sameOrigin&&n.getAllResponseHeaders().indexOf(tt)>=0){var s=n.getResponseHeader(tt);s&&((0,S.p)("sm",["Ajax/CrossApplicationTracing/Header/Seen"],void 0,t.K7.metrics,r),e.params.cat=s.split(", ").pop())}e.loadCaptureCalled=!0}r.on("new-xhr",a),r.on("open-xhr-start",o),r.on("open-xhr-end",d),r.on("send-xhr-start",l),r.on("setRequestHeader-xhr-start",u),r.on("xhr-cb-time",f),r.on("xhr-load-added",h),r.on("xhr-load-removed",p),r.on("xhr-resolved",m),r.on("addEventListener-end",v),r.on("removeEventListener-end",y),r.on("fn-end",E),r.on("fetch-before-start",w),r.on("fetch-start",R),r.on("fn-start",b),r.on("fetch-done",T)}(e,this.ee,this.handler,this.dt),this.importAggregator(e,()=>r.e(478).then(r.bind(r,3845)))}}const it={},st=["pushState","replaceState"];function at(e,t){const r=function(e){return(e||R.ee).get("history")}(e);return!g.RI||it[r.debugId]++||(it[r.debugId]=1,(0,Pe.YM)(r,void 0,t).inPlace(window.history,st,"-")),r}var ot=r(3738);function ct(e){(0,N.Y)(k.BL,function(r=Date.now()){const n=r-g.WN;n<0&&(0,l.R)(62,r),(0,S.p)(q.XG,[k.BL,{time:n}],void 0,t.K7.metrics,e.ee),e.addToTrace({name:k.BL,start:r,origin:"nr"}),(0,S.p)("api-"+k.hG,[n,k.BL],void 0,t.K7.genericEvents,e.ee)},e)}const{He:dt,bD:ut,d3:lt,Kp:ft,TZ:ht,Lc:pt,uP:gt,Rz:mt}=ot;class vt extends W{static featureName=ht;constructor(e){var n;super(e,ht),n=e,(0,N.Y)(k.U2,function(e){if(!(e&&"object"==typeof e&&e.name&&e.start))return;const r={n:e.name,s:e.start-g.WN,e:(e.end||e.start)-g.WN,o:e.origin||"",t:"api"};r.s<0||r.e<0||r.e<r.s?(0,l.R)(61,{start:r.s,end:r.e}):(0,S.p)("bstApi",[r],void 0,t.K7.sessionTrace,n.ee)},n),ct(e);
5if(!(0,j.V)(e.init))return void this.deregisterDrain();const i=this.ee;let s;at(i,e),this.eventsEE=(0,Me.u)(i,e),this.eventsEE.on(gt,function(e,t){this.bstStart=(0,_.t)()}),this.eventsEE.on(pt,function(e,r){(0,S.p)("bst",[e[0],r,this.bstStart,(0,_.t)()],void 0,t.K7.sessionTrace,i)}),i.on(mt+lt,function(e){this.time=(0,_.t)(),this.startPath=location.pathname+location.hash}),i.on(mt+ft,function(e){(0,S.p)("bstHist",[location.pathname+location.hash,this.startPath,this.time],void 0,t.K7.sessionTrace,i)});try{s=new PerformanceObserver(e=>{const r=e.getEntries();(0,S.p)(dt,[r],void 0,t.K7.sessionTrace,i)}),s.observe({type:ut,buffered:!0})}catch(e){}this.importAggregator(e,()=>r.e(478).then(r.bind(r,6974)),{resourceObserver:s})}}var yt=r(733),bt=r(6344);class Et extends W{static featureName=bt.TZ;#i;recorder;constructor(e){var n;let i;super(e,bt.TZ),n=e,(0,N.Y)(k.CH,function(){(0,S.p)(k.CH,[],void 0,t.K7.sessionReplay,n.ee)},n),function(e){(0,N.Y)(k.Tb,function(){(0,S.p)(k.Tb,[],void 0,t.K7.sessionReplay,e.ee)},e)}(e);const s="".concat(u.Wt).concat((0,yt.Y)(e.info.licenseKey,e.info.applicationID));try{i=JSON.parse(localStorage.getItem(s))}catch(e){}(0,I.SR)(e.init)&&this.ee.on(k.CH,()=>this.#s()),this.#a(i)&&this.importRecorder().then(e=>{e.startRecording(bt.Qb.PRELOAD,i?.sessionReplayMode)}),this.abortHandler=this.#n,this.importAggregator(this.agentRef,()=>r.e(478).then(r.bind(r,6167)),this),this.ee.on("err",e=>{this.blocked||this.agentRef.runtime.isRecording&&(this.errorNoticed=!0,(0,S.p)(bt.Vh,[e],void 0,this.featureName,this.ee))})}#a(e){return e&&(e.sessionReplayMode===u.g.FULL||e.sessionReplayMode===u.g.ERROR)||(0,I.Aw)(this.agentRef.init)}importRecorder(){return this.recorder?Promise.resolve(this.recorder):(this.#i??=Promise.all([r.e(478),r.e(249)]).then(r.bind(r,4866)).then(({Recorder:e})=>(this.recorder=new e(this),this.recorder)).catch(e=>{throw this.ee.emit("internal-error",[e]),this.blocked=!0,e}),this.#i)}#s(){this.blocked||(this.featAggregate?this.featAggregate.mode!==u.g.FULL&&this.featAggregate.initializeRecording(u.g.FULL,!0,bt.Qb.API):this.importRecorder().then(()=>{this.recorder.startRecording(bt.Qb.API,u.g.FULL)}))}#n(){this.recorder?.stopRecording()}}var wt=r(3962);class Rt extends W{static featureName=wt.TZ;constructor(e){if(super(e,wt.TZ),function(e){const r=e.ee.get("tracer");function n(){}(0,N.Y)(k.dT,function(e){return(new n).get("object"==typeof e?e:{})},e);const i=n.prototype={createTracer:function(n,i){var s={},a=this,o="function"==typeof i;return(0,S.p)(q.xV,["API/createTracer/called"],void 0,t.K7.metrics,e.ee),function(){if(r.emit((o?"":"no-")+"fn-start",[(0,_.t)(),a,o],s),o)try{return i.apply(this,arguments)}catch(e){const t="string"==typeof e?new Error(e):e;throw r.emit("fn-err",[arguments,this,t],s),t}finally{r.emit("fn-end",[(0,_.t)()],s)}}}};["actionText","setName","setAttribute","save","ignore","onEnd","getContext","end","get"].forEach(r=>{N.Y.apply(this,[r,function(){return(0,S.p)(k.hw+r,[performance.now(),...arguments],this,t.K7.softNav,e.ee),this},e,i])}),(0,N.Y)(k.PA,function(){(0,S.p)(k.hw+"routeName",[performance.now(),...arguments],void 0,t.K7.softNav,e.ee)},e)}(e),!g.RI||!(0,i.dV)().o.MO)return;const n=at(this.ee,e);try{this.removeOnAbort=new AbortController}catch(e){}wt.tC.forEach(e=>{(0,J.sp)(e,e=>{c(e)},!0,this.removeOnAbort?.signal)}
5);const s=()=>(0,S.p)("newURL",[(0,_.t)(),""+window.location],void 0,this.featureName,this.ee);n.on("pushState-end",s),n.on("replaceState-end",s),(0,J.sp)(wt.OV,e=>{c(e),(0,S.p)("newURL",[e.timeStamp,""+window.location],void 0,this.featureName,this.ee)},!0,this.removeOnAbort?.signal);let a=!1;const o=new((0,i.dV)().o.MO)((e,t)=>{a||(a=!0,requestAnimationFrame(()=>{(0,S.p)("newDom",[(0,_.t)()],void 0,this.featureName,this.ee),a=!1}))}),c=(0,C.s)(e=>{"loading"!==document.readyState&&((0,S.p)("newUIEvent",[e],void 0,this.featureName,this.ee),o.observe(document.body,{attributes:!0,childList:!0,subtree:!0,characterData:!0}))},100,{leading:!0});this.abortHandler=function(){this.removeOnAbort?.abort(),o.disconnect(),this.abortHandler=void 0},this.importAggregator(e,()=>r.e(478).then(r.bind(r,4393)),{domObserver:o})}}var Tt=r(981);const At={},St=new Set;function xt(e){return"string"==typeof e?{type:"string",size:(new TextEncoder).encode(e).length}:e instanceof ArrayBuffer?{type:"ArrayBuffer",size:e.byteLength}:e instanceof Blob?{type:"Blob",size:e.size}:e instanceof DataView?{type:"DataView",size:e.byteLength}:ArrayBuffer.isView(e)?{type:"TypedArray",size:e.byteLength}:{type:"unknown",size:0}}class Ot{constructor(e,t){this.timestamp=(0,_.t)(),this.currentUrl=(0,Ee.L)(window.location.href),this.socketId=(0,ye.LA)(8),this.requestedUrl=(0,Ee.L)(e),this.requestedProtocols=Array.isArray(t)?t.join(","):t||"",this.openedAt=void 0,this.protocol=void 0,this.extensions=void 0,this.binaryType=void 0,this.messageOrigin=void 0,this.messageCount=0,this.messageBytes=0,this.messageBytesMin=0,this.messageBytesMax=0,this.messageTypes=void 0,this.sendCount=0,this.sendBytes=0,this.sendBytesMin=0,this.sendBytesMax=0,this.sendTypes=void 0,this.closedAt=void 0,this.closeCode=void 0,this.closeReason="unknown",this.closeWasClean=void 0,this.connectedDuration=0,this.hasErrors=void 0}}class _t extends W{static featureName=o.TZ;constructor(e){super(e,o.TZ);const n=e.init.feature_flags.includes("websockets")||e.init.web_sockets?.enabled,s=!e.init.feature_flags.includes("no_spv"),a=[e.init.page_action.enabled,e.init.performance.capture_marks,e.init.performance.capture_measures,e.init.performance.resources.enabled,e.init.user_actions.enabled,n,s];var c;let d;if(c=e,(0,N.Y)(k.hG,(e,t)=>ce(e,t,c),c),function(e){(0,N.Y)(k.fF,(t,r)=>ue(t,r,e),e)}(e),ct(e),Se(e),function(e){(0,N.Y)(k.V1,(t,r)=>de(t,r,e),e)}(e),this.removeOnAbort=new AbortController,this.abortHandler=()=>{this.removeOnAbort.abort(),this.abortHandler=void 0},n){const u=function(e,t){if(!(0,i.dV)().o.WS)return e;const r=e.get("websockets");if(At[r.debugId]++)return r;At[r.debugId]=1,(0,V.G)(()=>{const e=(0,_.t)();St.forEach(t=>{t.nrData.closedAt=e,t.nrData.closeCode=1001,t.nrData.closeReason="Page navigating away",t.nrData.closeWasClean=!1,t.nrData.openedAt&&(t.nrData.connectedDuration=e-t.nrData.openedAt),r.emit("ws",[t.nrData,t.targets],t)})});
5class n extends WebSocket{static name=Tt.R.WS;static toString(){return"function WebSocket() { [native code] }"}toString(){return"[object WebSocket]"}get[Symbol.toStringTag](){return n.name}#o(e){(e.__newrelic??={}).socketId=this.nrData.socketId,this.nrData.hasErrors??=!0}constructor(...e){super(...e),this.nrData=new Ot(e[0],e[1]),this.targets=(0,Ie.$5)(t),this.addEventListener("open",()=>{this.nrData.openedAt=(0,_.t)(),["protocol","extensions","binaryType"].forEach(e=>{this.nrData[e]=this[e]}),St.add(this)}),this.addEventListener("message",e=>{const{type:t,size:r}=xt(e.data);this.nrData.messageOrigin??=(0,Ee.L)(e.origin),this.nrData.messageCount++,this.nrData.messageBytes+=r,this.nrData.messageBytesMin=Math.min(this.nrData.messageBytesMin||1/0,r),this.nrData.messageBytesMax=Math.max(this.nrData.messageBytesMax,r),(this.nrData.messageTypes??"").includes(t)||(this.nrData.messageTypes=this.nrData.messageTypes?"".concat(this.nrData.messageTypes,",").concat(t):t)}),this.addEventListener("close",e=>{this.nrData.closedAt=(0,_.t)(),this.nrData.closeCode=e.code,e.reason&&(this.nrData.closeReason=e.reason),this.nrData.closeWasClean=e.wasClean,this.nrData.connectedDuration=this.nrData.closedAt-this.nrData.openedAt,St.delete(this),r.emit("ws",[this.nrData,this.targets],this)})}addEventListener(e,t,...r){const n=this,i="function"==typeof t?function(...e){try{return t.apply(this,e)}catch(e){throw n.#o(e),e}}:t?.handleEvent?{handleEvent:function(...e){try{return t.handleEvent.apply(t,e)}catch(e){throw n.#o(e),e}}}:t;return super.addEventListener(e,i,...r)}send(e){if(this.readyState===WebSocket.OPEN){const{type:t,size:r}=xt(e);this.nrData.sendCount++,this.nrData.sendBytes+=r,this.nrData.sendBytesMin=Math.min(this.nrData.sendBytesMin||1/0,r),this.nrData.sendBytesMax=Math.max(this.nrData.sendBytesMax,r),(this.nrData.sendTypes??"").includes(t)||(this.nrData.sendTypes=this.nrData.sendTypes?"".concat(this.nrData.sendTypes,",").concat(t):t)}try{return super.send(e)}catch(e){throw this.#o(e),e}}close(...e){try{super.close(...e)}catch(e){throw this.#o(e),e}}}return g.gm.WebSocket=n,r}(this.ee,e);u.on("ws",(e,t)=>{(0,S.p)("ws-complete",[e,t],void 0,this.featureName,this.ee)})}if(s){if("function"==typeof g.gm.ReportingObserver){const l=new g.gm.ReportingObserver(e=>{e.forEach(e=>{(0,S.p)("spv",[kt(e),(0,_.t)()],void 0,t.K7.genericEvents,this.ee)}),l.disconnect()},{types:["csp-violation"],buffered:!0});l.observe()}g.gm.addEventListener("securitypolicyviolation",e=>{(0,S.p)("spv",[kt(e),e.timeStamp],void 0,t.K7.genericEvents,this.ee)},(0,J.jT)(!1,this.removeOnAbort.signal))}if(g.RI){if(ze(this.ee,e),De(this.ee,e),d=at(this.ee,e),e.init.user_actions.enabled){function f(t){const r=(0,Ge.D)(t);return e.beacons.includes(r.hostname+":"+r.port)}function h(){d.emit("navChange")}o.Zp.forEach(e=>(0,J.sp)(e,e=>(0,S.p)("ua",[e],void 0,this.featureName,this.ee),!0)),o.qN.forEac
5h(e=>{const t=(0,C.s)(e=>{(0,S.p)("ua",[e],void 0,this.featureName,this.ee)},500,{leading:!0});(0,J.sp)(e,t)}),g.gm.addEventListener("error",()=>{(0,S.p)("uaErr",[],void 0,t.K7.genericEvents,this.ee)},(0,J.jT)(!1,this.removeOnAbort.signal)),this.ee.on("open-xhr-start",(e,r)=>{f(e[1])||r.addEventListener("readystatechange",()=>{2===r.readyState&&(0,S.p)("uaXhr",[],void 0,t.K7.genericEvents,this.ee)},(0,J.jT)(void 0,this.removeOnAbort.signal))}),this.ee.on("fetch-start",e=>{e.length>=1&&!f(Ze(e[0]))&&(0,S.p)("uaXhr",[],void 0,t.K7.genericEvents,this.ee)}),d.on("pushState-end",h),d.on("replaceState-end",h),window.addEventListener("hashchange",h,(0,J.jT)(!0,this.removeOnAbort.signal)),window.addEventListener("popstate",h,(0,J.jT)(!0,this.removeOnAbort.signal))}if(e.init.performance.resources.enabled&&g.gm.PerformanceObserver?.supportedEntryTypes.includes("resource")){new PerformanceObserver(e=>{e.getEntries().forEach(e=>{(0,S.p)("browserPerformance.resource",[e],void 0,this.featureName,this.ee)})}).observe({type:"resource",buffered:!0})}}a.some(e=>e)?this.importAggregator(e,()=>r.e(478).then(r.bind(r,8019))):this.deregisterDrain()}}function kt(e){const t=e.body??e;return{blockedUrl:t.blockedURL??e.blockedURI,documentUrl:t.documentURL??e.documentURI,effectiveDirective:t.effectiveDirective,originalPolicy:t.originalPolicy,sourceFile:t.sourceFile,statusCode:t.statusCode,lineNumber:t.lineNumber,columnNumber:t.columnNumber,disposition:"reporting"===t.disposition?"report":t.disposition,sample:t.sample,referrer:t.referrer}}var Nt=r(2646);const Lt=new Map;function Mt(e,t,r,n,i=!0,s){if("object"!=typeof t||!t||"string"!=typeof r||!r||"function"!=typeof t[r])return(0,l.R)(29);const a=function(e){return(e||R.ee).get("logger")}(e),o=(0,Pe.YM)(a,void 0,s),c=new Nt.y(R.P);c.level=n.level,c.customAttributes=n.customAttributes,c.autoCaptured=i;const d=t[r]?.[Pe.Jt]||t[r];return Lt.set(d,c),o.inPlace(t,[r],"wrap-logger-",()=>Lt.get(d),void 0,!0),a}var Pt=r(1910);class It extends W{static featureName=se.TZ;constructor(e){var t;super(e,se.TZ),t=e,(0,N.Y)("log",(e,r)=>oe(e,r,t),t),function(e){(0,N.Y)(k.Wb,(t,r,{customAttributes:n={},level:i=se.p_.INFO}={})=>{Mt(e.ee,t,r,{customAttributes:n,level:i},!1,e)},e)}(e),Se(e);const n=this.ee;["log","error","warn","info","debug","trace"].forEach(t=>{(0,Pt.i)(g.gm.console[t]),Mt(n,g.gm.console,t,{level:"log"===t?"info":t},void 0,e)}),this.ee.on("wrap-logger-end",function([t],r,i,s=[e.runtime.v2Target]){const{level:a,customAttributes:o,autoCaptured:c}=this;s.forEach(e=>{(0,ae.R)(n,t,o,a,c,e)})}),this.importAggregator(e,()=>r.e(478).then(r.bind(r,5288)))}}new F({features:[nt,K,G,vt,Et,Y,Oe,_t,It,Rt],loaderType:"spa"})})()})();</script>
5 6<meta name="title" content="Customer Login"/> 7<meta name="description" content="We are Canadaâs favourite chocolatier. We offer online shopping with quick delivery and in-shop experiences. We use premium ingredients and sustainable cocoa."/> 8<meta name="keywords" content="Chocolate Shop, Premium Chocolate, Purdys Chocolatier"/> 9<meta name="robots" content="NOINDEX, NOFOLLOW"/> 10<meta name="viewport" content="width=device-width, initial-scale=1"/> 11<title>Customer Login</title> 12<link rel="stylesheet" type="text/css" media="all" href="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/css/styles.css" /> 13<link rel="stylesheet" type="text/css" media="all" href="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Amasty_Base/vendor/slick/amslick.min.css" /> 14<link rel="stylesheet" type="text/css" media="all" href="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Amasty_Storelocator/vendor/chosen/chosen.min.css" /> 15<link rel="stylesheet" type="text/css" media="all" href="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/FME_FreeShippingBar/css/fme.shipping.bar.css" /> 16<link rel="stylesheet" type="text/css" media="all" href="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Squareup_Omni/css/loyalty-main.css" /> 17<link rel="stylesheet" type="text/css" media="all" href="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Amasty_CustomTabs/css/source/mkcss/amtabs.css" />
18<script type="text/javascript" src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/MW2Consulting_Csr/js/custom.js"></script>
vendor: 1 bytes, line 18
18
19<script type="text/javascript" src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Purdys_CustomWeltPixelEvents/js/custom-weltpixel-utils.js"></script>
vendor: 1 bytes, line 19
19
20<script type="text/javascript" defer="defer" src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Hyva_MagentoDataServices/js/storefront-event-collector.js"></script>
vendor: 1 bytes, line 20
20
21<script type="text/javascript" defer="defer" src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Hyva_MagentoDataServices/js/storefront-events-sdk.js"></script>
vendor: 1 bytes, line 21
21
22<script type="text/javascript" defer="defer" src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Hyva_MagentoLiveSearch/js/sdk.js"></script>
22 23<link rel="icon" type="image/x-icon" href="https://www.purdys.com/media/favicon/stores/1/favicon-1.png" /> 24<link rel="shortcut icon" type="image/x-icon" href="https://www.purdys.com/media/favicon/stores/1/favicon-1.png" /> 25<!-- InApp Redirect script -->
26<script 27 id="iar" 28 src="https://rum.auditzy.com/mhpFPEJo-www.purdys.com-iar.js" 29 async 30></script>
30 31 <!-- Cookie Banner A/B Testing -->
32<script> 33if (posthog.getFeatureFlag('cookie-banner-overlay') === 'new-cookie-script-overlay') { 34 // OPTION B - New Cookie-Script with overlay 35 var geoScript = document.createElement('script'); 36 geoScript.type = 'text/javascript'; 37 geoScript.charset = 'UTF-8'; 38 geoScript.src = '//geo.cookie-script.com/s/139842e979acded53b241b42b2d1563f.js?country=us'; 39 40 var cdnScript = document.createElement('script'); 41 cdnScript.type = 'text/javascript'; 42 cdnScript.charset = 'UTF-8'; 43 cdnScript.src = '//cdn.cookie-script.com/s/a60af434dfb8b4ecd1abb4af42ea0c3e.js'; 44 document.head.appendChild(geoScript); document.head.appendChild(cdnScript); 45} else { 46 //OPTION A - Current Cookie-Script 47 //Default/control behaviour 48 var geoScript = document.createElement('script'); 49 geoScript.type = 'text/javascript'; 50 geoScript.charset = 'UTF-8'; 51 geoScript.src = '//geo.cookie-script.com/s/6b73d7e24bd549d182dfed45d6c65c4c.js?country=us'; 52 var cdnScript = 53 document.createElement('script'); 54 cdnScript.type = 'text/javascript'; 55 cdnScript.charset = 'UTF-8'; 56 cdnScript.src = '//cdn.cookie-script.com/s/b13066c855c83cfcb1e83d6450f5d8b9.js'; 57 document.head.appendChild(geoScript); document.head.appendChild(cdnScript); 58} 59</script>
59 60<!-- Start of Cookies Banner for purdys.com -->
61<script type="text/javascript" charset="UTF-8" src="//geo.cookie-script.com/s/6b73d7e24bd549d182dfed45d6c65c4c.js?country=us"></script>
61
61<script type="text/javascript" charset="UTF-8" src="//cdn.cookie-script.com/s/b13066c855c83cfcb1e83d6450f5d8b9.js"></script>
61 62<!-- End of Cookies Banner for purdys.com --> 63<!-- FontAwesome --> 64<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" /> 65<!-- Richmond Display Font --> 66<link rel="stylesheet" href="https://use.typekit.net/esu7mmm.css"> 67<!--WKbyAfcH8dlUUeMcZ10DKLsSNGJNBU5u--> 68
69<script type="text/javascript" src="//script.crazyegg.com/pages/scripts/0125/3606.js" async="async" ></script>
69 70 71<style> 72/*Mega Menu */ 73a.mega-menu-promo-img{ 74margin-top:-40px; 75} 76/*Accessibility fixed */ 77.sr-only{color: #fff;} 78.page-header .sr-only span{color: #000;} 79/* LOGO */ 80.logo img { 81 max-height: 60px !important; 82} 83.logo { 84margin: auto !important; 85} 86@media only screen and (max-width: 1023px) { 87 #header .logo{ 88 margin-left: 0px !important; 89 } 90 } 91/* ===== Blog ====== */ 92#maincontent:has(.amblog-grid-list){ 93max-width: 1660px; 94} 95.columns:has(.amblog-grid-list){ 96max-width: 100%; 97padding: 0px; 98} 99.amblog-index-index .breadcrumbs, .amblog-index-category .breadcrumbs{ 100max-width: 1660px !important; 101} 102.amblog-index-index h1.page-title { 103 font-size: 48px; 104 color: #141414; 105} 106.amblog-index-category h1.page-title { 107 font-size: 48px; 108 color: #141414; 109} 110.amblog-grid-list .amblog-title { 111 padding: 0px; 112 padding-bottom: 20px; 113} 114.amblog-grid-list .amblog-link{ 115font-size: 28px; 116color: #141414; 117line-height: normal; 118text-decoration: none; 119} 120.amblog-grid-list a.amblog-link:hover{ 121color: #532E91; 122} 123.amblog-grid-list a.amblog-link:active{ 124color: #532E91; 125} 126.amblog-grid-list .amblog-date{ 127 font-size: 16px; 128} 129.amblog-footer{ 130 padding-bottom: 20px; 131} 132.amblog-footer .amblog-ref{ 133color: #532E91 !important; 134} 135 /* ==========Yotpo Review ========= */ 136 .yotpo-reviews-carousel-container .glide__slide::before{ 137 display: none; 138 } 139 .yotpo-reviews-carousel-container .glide__slide .review-content{ 140 text-align: left; 141 } 142 .yotpo-reviews-carousel-container .review-product { 143 height: auto; 144 } 145 .yotpo-reviews-carousel-container .review-product img{ 146 width: 70px; 147 height: auto; 148 } 149/* -- Footer icon size error -- */ 150.footer-location img { 151 width: 20px; 152} 153/* end */ 154/* ---- Related Product under PDP --- */ 155 156.product-item .product-image-container { 157 margin: auto; 158} 159.block-content .products.grid .product-item { 160 margin: 0 10px; 161} 162.towishlist-wrap { 163 display: inline-block; 164} 165.block-actions { 166 padding-bottom: 30px; 167} 168@media only screen and (max-width: 849px) { 169.product-item>.product-item-info{ 170padding: 0px; 171} 172} 173/* --- End --- */ 174a#yotpo_testimonials_btn { 175 display: none; 176} 177.checkout-payment-method .payment-method-title > .label > img { 178 width: auto; 179} 180.amblog-list{ 181padding-bottom: 40px; 182} 183.logo-section .logo-image { 184 max-width: 136px; 185 margin: auto; 186} 187.landingpage.sec-1 .image-content p a span img { 188 width: 24px; 189 display: inline; 190} 191.legend-thumb img { 192 width: initial; 193 display: inline; 194} 195.legend img { 196 width: auto; 197 display: inline-block; 198} 199.img-container img { 200 margin: auto; 201} 202 203/** Max Updated for yotpo review on Apr 7 2025 **/ 204.yotpo-reviews-carousel-widget-container{ 205 width: 100%; 206} 207#yotpo-reviews-star-ratings-widget { 208 margin: auto; 209 justify-content: center !important; 210} 211span.yotpo-sr-bottom-line-right-panel { 212 text-transform: capitalize; 213} 214button.yotpo-sr-bottom-line-summary.yotpo-sr-bottom-line-button { 215 max-width: 150px; 216 flex-wrap: wrap; 217 justify-content: center; 218} 219@media only screen and (max-width: 849px) { 220.yotpo-seo-page-layout{ 221margin: 0px !important; 222} 223} 224 </style> 225
225<script src="https://www.googleoptimize.com/optimize.js?id=OPT-M5XRBZB"></script>
225 226
226<script type="text/javascript"> 227 (function(c,l,a,r,i,t,y){ 228 c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; 229 t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; 230 y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); 231 })(window, document, "clarity", "script", "btt9ev0vns"); 232 </script>
232 233 234<!-- TikTok Pixel Code Start -->
234<script> !function (w, d, t) { w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];ttq.methods=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie","holdConsent","revokeConsent","grantConsent"],ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);ttq.instance=function(t){for( var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e},ttq.load=function(a,e){var n="https://gw.stape.ws/load/data.js";ttq._i=ttq._i||{},ttq._i[a]=[],ttq._i[a]._u=n,ttq._t=ttq._t||{},ttq._t[a]=+new Date,ttq._o=ttq._o||{},ttq._o[a]=e||{};e=document.createElement("script");e.type="text/javascript",e.async=!0,e.src=n+"?sdkid="+a+"&lib="+t,e.onerror=function(){var e=document.createElement("script");e.type="text/javascript", e.async=!0,e.src="https://analytics.tiktok.com/i18n/pixel/events.js?sdkid="+a+"&lib="+t;var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)};n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)}; ttq.load('C65B038U4DKUD0OQHOQG'); ttq.page(); }(window, document, 'ttq'); </script>
234 <!-- TikTok Pixel Code End --> 235 236<!-- Google tag (gtag.js) -->
vendor: 66 bytes, lines 236-237
236 237<script async src="https://www.googletagmanager.com/gtag/js?id=
237AW-1068516049
vendor: 12 bytes, line 237
237"></script>
237<script>
vendor: 127 bytes, line 237
237window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '
237AW-1068516049
vendor: 4 bytes, line 237
237');
237</script>
237 238 239 240<!-- Event snippet for Purchase (Magento) conversion page -->
241<script> 242 gtag('event', 'conversion', { 243 'send_to': 'AW-1068516049/AxvUCLi6ho4DENGFwf0D', 244 'value': 1.0, 245 'currency': 'CAD', 246 'transaction_id': '' 247 }); 248</script>
248 249 250
251<script id="cookieScript-cookieBlockScript"> 252 (function () { 253 "use strict"; 254 255 // Optimized configuration 256 const CONFIG = { 257 DELAY_MS: 1000, 258 EXPIRE_DATE: "expires=Thu, 01 Jan 1970 00:00:00 GMT", 259 CONSENT_COOKIE: "CookieConsent=", 260 }; 261 262 // Pre-compiled cookie definitions for faster lookup 263 const cookieDefs = [ 264 { name: "_sp_id.", hasVariations: true, category: "performance" }, 265 { name: "_sp_ses.", hasVariations: true, category: "performance" }, 266 { name: "_ce.s", hasVariations: false, category: "performance" }, 267 { name: "_fbp", hasVariations: false, category: "targeting" }, 268 { name: "ct23492", hasVariations: false, category: "targeting" }, 269 { name: "_tt_enable_cookie", hasVariations: false, category: "marketing" }, 270 ]; 271 272 // Create lookup maps for O(1) access 273 const exactMatches = new Map(); 274 const prefixMatches = new Map(); 275 cookieDefs.forEach((def) => { 276 if (def.hasVariations) { 277 prefixMatches.set(def.name, def); 278 } else { 279 exactMatches.set(def.name, def); 280 } 281 }); 282 283 // State management 284 const state = { 285 pendingCookies: [], 286 firstRun: true, 287 consentPreviouslySet: false, 288 previousConsentData: null, 289 isRemovingCookie: false, 290 }; 291 292 // Optimized utility functions 293 const getCookieName = (str) => str.split("=")[0].trim(); 294 295 const findCookieDef = (name) => 296 exactMatches.get(name) || 297 Array.from(prefixMatches.entries()).find(([prefix]) => 298 name.startsWith(prefix) 299 )?.[1]; 300 301 const checkOptionalOptin = (cookieScript) => { 302 return cookieScript?.responseMode === "optionaloptin" && 303 cookieScript.consent?.stamp === '0'; 304 }; 305 306 const shouldBlockCookie = (name) => { 307 // First check if cookie name is in our block list 308 const isInBlockList = exactMatches.has(name) || 309 Array.from(prefixMatches.keys()).some((prefix) => 310 name.startsWith(prefix) 311 ); 312 313 if (!isInBlockList) return false; 314 315 // Only check optional opt-in for cookies that are in our block list 316 const cookieScript = window.CookieScript || {}; 317 return !checkOptionalOptin(cookieScript); 318 }; 319 320 const hasConsentForCategory = (category) => { 321 const cookieScript = window.CookieScript || {}; 322 if (!cookieScript?.consent) return false; 323 if (checkOptionalOptin(cookieScript)) return true; 324 return cookieScript.consent[category] || false; 325 }; 326 327 const removeCookie = (name) => { 328 const domain = window.location.hostname; 329 const expireDate = CONFIG.EXPIRE_DATE; 330 const baseCookie = `${name}=; ${expireDate}`; 331 332 // Set flag to bypass proxy during cookie removal 333 state.isRemovingCookie = true; 334 335 try { 336 // Remove with different domain/path combinations 337 document.cookie = `${baseCookie}; path=/`; 338 document.cookie = `${baseCookie}; path=/; domain=${domain}`; 339 document.cookie = `${baseCookie}; path=/; domain=.${domain}`; 340 341 // Handle www and non-www variations 342 if (domain.startsWith('www.')) { 343 const nonWwwDomain = domain.substring(4); 344 document.cookie = `${baseCookie}; path=/; domain=${nonWwwDomain}`; 345 document.cookie = `${baseCookie}; path=/; domain=.${nonWwwDomain}`; 346 } else { 347 const wwwDomain = `www.${domain}`; 348 document.cookie = `${baseCookie}; path=/; domain=${wwwDomain}`; 349 document.cookie = `${baseCookie}; path=/; domain=.${wwwDomain}`; 350 } 351 352 // Handle parent domain cookies (e.g., for newfundraising.purdys.com, also remove .purdys.com cookies) 353 const domainParts = domain.split('.'); 354 if (domainParts.length > 2) { 355 // For subdomains like newfundraising.purdys.com, also try to remove from parent domain 356 const parentDomain = domainParts.slice(-2).join('.'); 357 const parentDomainWithDot = `.${parentDomain}`; 358 document.cookie = `${baseCookie}; path=/; domain=${parentDomain}`; 359 document.cookie = `${baseCookie}; path=/; domain=${parentDomainWithDot}`; 360 } 361 362 // Additional secure and sameSite variations 363 document.cookie = `${baseCookie}; path=/; secure`; 364 document.cookie = `${baseCookie}; path=/; domain=${domain}; secure`; 365 document.cookie = `${baseCookie}; path=/; domain=.${domain}; secure`; 366 367 // Secure variations for parent domain 368 if (domainParts.length > 2) { 369 const parentDomain = domainParts.slice(-2).join('.'); 370 document.cookie = `${baseCookie}; path=/; domain=${parentDomain}; secure`; 371 document.cookie = `${baseCookie}; path=/; domain=.${parentDomain}; secure`; 372 } 373 } catch (e) { 374 // Silent fail for cookie removal errors 375 } finally { 376 // Reset flag after removal attempts 377 state.isRemovingCookie = false; 378 } 379 }; 380 381 // Optimized consent detection 382 const checkPreviousConsent = () => { 383 try { 384 // Check for CookieConsent cookie and parse its value 385 const cookieConsent = document.cookie 386 .split(";") 387 .find(cookie => cookie.trim().startsWith(CONFIG.CONSENT_COOKIE)); 388 389 if (cookieConsent) { 390 // Extract the consent value (URL-encoded) 391 const consentValue = cookieConsent.split("=")[1]; 392 if (consentValue && consentValue !== "") { 393 try { 394 // Decode the URL-encoded value 395 const decodedValue = decodeURIComponent(consentValue); 396 397 // Manually parse the consent data by extracting values 398 const consentData = {}; 399 400 // Extract boolean values for CookieScript categories 401 consentData.functionality = decodedValue.includes('functionality:true'); 402 consentData.statistics = decodedValue.includes('statistics:true'); 403 consentData.marketing = decodedValue.includes('marketing:true'); 404 consentData.necessary = decodedValue.includes('necessary:true'); 405 consentData.targeting = decodedValue.includes('targeting:true'); 406 consentData.performance = decodedValue.includes('performance:true'); 407 408 // Extract stamp if present 409 const stampMatch = decodedValue.match(/stamp:'([^']*)'/); 410 if (stampMatch) { 411 consentData.stamp = stampMatch[1]; 412 } 413 414 // Extract method if present 415 const methodMatch = decodedValue.match(/method:'([^']*)'/); 416 if (methodMatch) { 417 consentData.method = methodMatch[1]; 418 } 419 420 // Check if consent has been given (any category true or stamp exists) 421 const hasConsent = consentData.functionality || 422 consentData.statistics || 423 consentData.marketing || 424 consentData.targeting || 425 consentData.performance || 426 (consentData.stamp && consentData.stamp !== '0'); 427 428 if (hasConsent) { 429 return consentData; 430 } 431 } catch (parseError) { 432 // If we can't parse, but cookie exists, return a default consent object 433 return { functionality: true, statistics: true, marketing: true, targeting: true, performance: true }; 434 } 435 } 436 } 437 438 return null; 439 } catch (e) { 440 return null; 441 } 442 }; 443 444 // Optimized cookie processing 445 const processExistingCookies = () => { 446 const cookies = document.cookie 447 .split(";") 448 .map((c) => c.trim()) 449 .filter(Boolean); 450
451 cookies.forEach((cookieString) => { 452 const name = getCookieName(cookieString); 453 if (shouldBlockCookie(name)) { 454 const def = findCookieDef(name); 455 if (!def || !hasConsentForCategory(def.category)) { 456 state.pendingCookies.push(cookieString); 457 removeCookie(name); 458 } 459 } 460 }); 461 }; 462 463 const processPendingCookies = () => { 464 const cookieScript = window.CookieScript; 465 if (!cookieScript?.consent || state.pendingCookies.length === 0) return; 466 467 const cookiesToSet = state.pendingCookies.filter((cookieString) => { 468 const name = getCookieName(cookieString); 469 const def = findCookieDef(name); 470 return def ? hasConsentForCategory(def.category) : false; 471 }); 472 473 if (cookiesToSet.length > 0) { 474 cookiesToSet.forEach((cookie) => { 475 try { 476 document.cookie = cookie; 477 } catch (e) { 478 // Silent fail for cookie setting errors 479 } 480 }); 481 482 const toRemove = new Set(cookiesToSet); 483 state.pendingCookies = state.pendingCookies.filter( 484 (cookie) => !toRemove.has(cookie) 485 ); 486 } 487 }; 488 489 // Optimized initialization 490 const initCookies = () => { 491 if (!state.firstRun) return; 492 state.firstRun = false; 493 494 const previousConsentData = checkPreviousConsent(); 495 state.consentPreviouslySet = previousConsentData !== null; 496 state.previousConsentData = previousConsentData; 497 498 if (state.consentPreviouslySet) { 499 setTimeout(processExistingCookies, CONFIG.DELAY_MS); 500 } else { 501 processExistingCookies(); 502 } 503 }; 504 505 // Optimized cookie proxy 506 const cookieProxy = new Proxy(Reflect.getPrototypeOf(document), { 507 set(target, key, value, thisArg) { 508 if (key === "cookie") { 509 // Bypass proxy during cookie removal to prevent circular blocking 510 if (state.isRemovingCookie) { 511 return Reflect.set(...arguments); 512 } 513 514 const name = getCookieName(value); 515 if (shouldBlockCookie(name)) { 516 const def = findCookieDef(name); 517 518 let hasConsent = false; 519 if (state.previousConsentData && def) { 520 hasConsent = state.previousConsentData[def.category] || false; 521 } else if (def) { 522 hasConsent = hasConsentForCategory(def.category); 523 } 524 525 if (hasConsent) { 526 return Reflect.set(...arguments); 527 } else { 528 state.pendingCookies.push(value); 529 return true; 530 } 531 } 532 } 533 return Reflect.set(...arguments); 534 }, 535 }); 536 537 // Optimized event handling 538 const setupEvents = () => { 539 const cookieScript = window.CookieScript; 540 541 if (cookieScript?.consent) { 542 processPendingCookies(); 543 } else { 544 window.addEventListener("CookieScriptLoaded", () => { 545 if (window.CookieScript?.consent) { 546 processPendingCookies(); 547 } else { 548 window.addEventListener("CookieScriptAcceptAll", processPendingCookies); 549 window.addEventListener("CookieScriptAccept", processPendingCookies); 550 } 551 }); 552 } 553 554 // CookieScript event listeners 555 window.addEventListener("CookieScriptAcceptAll", processPendingCookies); 556 window.addEventListener("CookieScriptAccept", processPendingCookies); 557 window.addEventListener("CookieScriptReject", processExistingCookies); 558 window.addEventListener("CookieScriptClose", processExistingCookies); 559 window.addEventListener("CookieScriptLoaded", processExistingCookies); 560 561 // Category-specific events 562 window.addEventListener("CookieScriptCategory-functionality", processPendingCookies); 563 window.addEventListener("CookieScriptCategory-strict", processPendingCookies); 564 window.addEventListener("CookieScriptCategory-targeting", processPendingCookies); 565 window.addEventListener("CookieScriptCategory-performance", processPendingCookies); 566 window.addEventListener("CookieScriptCategory-unclassified", processPendingCookies); 567 568 document.addEventListener("DOMContentLoaded", () => { 569 if (window.CookieScript?.consent) { 570 processPendingCookies(); 571 } 572 }); 573 }; 574 575 // Initialize 576 Reflect.setPrototypeOf(document, cookieProxy); 577 initCookies(); 578 setupEvents(); 579 })(); 580</script>
580 581 582<!-- A/B testing script -->
583<script> 584 !function(t,e){var o,n,p,r;e.__SV||(window.posthog && window.posthog.__loaded)||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init ns hs wi ls ds rs os capture calculateEventProperties fs register register_once register_for_session unregister unregister_for_session bs getFeatureFlag getFeatureFlagPayload getFeatureFlagResult isFeatureEnabled reloadFeatureFlags updateFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey displaySurvey cancelPendingSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException startExceptionAutocapture stopExceptionAutocapture loadToolbar get_property getSessionProperty gs cs createPersonProfile setInternalOrTestUser ts ys opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing get_explicit_consent_status is_capturing clear_opt_in_out_capturing vs debug M ps getPageViewId captureTraceFeedback captureTraceMetric Xr".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]); 585 posthog.init('phc_hJ4E8POShJYsN0nhtxizH2pnkCsN9w9i7uOW6sd2CXJ', { 586 api_host: 'https://us.i.posthog.com', 587 defaults: '2026-01-30', 588 person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well 589 }) 590</script>
590 591 592
593<script> 594 window.addEventListener('load', function () { 595 window.magentoStorefrontEvents = magentoStorefrontEvents; 596 magentoStorefrontEvents.context.setStorefrontInstance( 597 {"storeUrl":"https:\/\/www.purdys.com\/","websiteId":1,"websiteCode":"base","storeId":1,"storeCode":"main_website_store","storeViewId":1,"storeViewCode":"default","websiteName":"Main Website","storeName":"Main Website Store","storeViewName":"Default Store View","baseCurrencyCode":"CAD","storeViewCurrencyCode":"CAD","catalogExtensionVersion":"103.4.10","environmentId":"2cc69165-2270-494c-893f-c00d0bb35bd7","environment":"Production","storefrontTemplate":"Hyva"} ); 598 magentoStorefrontEvents.context.setMagentoExtension({ 599 magentoExtensionVersion: "7.7.0", 600 }); 601 magentoStorefrontEvents.context.setDataServicesExtension({ 602 version: "7.7.0", 603 }); 604 magentoStorefrontEvents.context.setPage({ 605 pageType: "Default" 606 }); 607 magentoStorefrontEvents.context.setContext("pageExtended", { 608 action: "page-view" 609 }); 610 611 let shopperId = "guest"; 612 magentoStorefrontEvents.context.setShopper({ shopperId: shopperId }); 613 614 615 magentoStorefrontEvents.publish.pageView(); 616 }); 617 618 function getCustomerIdFromDataServices() { 619 let customerId = hyva.getCookie("dataservices_customer_id"); 620 return customerId ? decodeURI(customerId).replace(/[^\w\s]/gi, "") : null; 621 } 622 623 function getCustomerGroupFromDataServices() { 624 try { 625 const decodedString = decodeURIComponent(hyva.getCookie("dataservices_customer_group")); 626 return decodedString ? JSON.parse(decodedString) : null; 627 } catch (error) { 628 console.error("Failed to parse customer group data:", error); 629 return null; 630 } 631 } 632</script>
632 633
634<script nonce=""> 635 (function () { 636 // Donât load twice 637 if (window.__MLS_SDK_LOADING__ || window.MagentoLiveSearch) return; 638 window.__MLS_SDK_LOADING__ = true; 639 640 // Temporarily disable AMD define to prevent âmismatched anonymous define()â 641 var savedDefine = window.define; 642 var savedDefineAmd = savedDefine && savedDefine.amd; 643 try { if (savedDefine) { savedDefine.amd = undefined; } } catch (e) {} 644 645 // Dynamically inject the SDK script 646 var script = document.createElement('script'); 647 script.type = 'text/javascript'; 648 script.async = true; 649 script.src = 'https\u003A\u002F\u002Fwww.purdys.com\u002Fstatic\u002Fversion1790083580\u002Ffrontend\u002FPurdys2\u002Fhyva\u002Fen_US\u002FHyva_MagentoLiveSearch\u002Fjs\u002Fsdk.js'; 650 651 script.addEventListener('load', function () { 652 // restore AMD ASAP 653 if (savedDefine) { 654 window.define = savedDefine; window.define.amd = savedDefineAmd; 655 } 656 }); 657 658 document.head.appendChild(script); 659 })(); 660</script>
660 661
662<script nonce="aHhkaGVuNDFoY2Y2eHhyZTY4bzN2c3BycmM3d3FoNnI="> 663 window.getWpGA4Cookie = function(name) { 664 match = document.cookie.match(new RegExp(name + '=([^;]+)')); 665 if (match) return decodeURIComponent(match[1].replace(/\+/g, ' ')) ; 666 }; 667 668 window.ga4AllowServices = false; 669 670 var isCookieRestrictionModeEnabled = 0, 671 currentWebsiteId = 1, 672 cookieRestrictionName = 'user_allowed_save_cookie'; 673 674 if (isCookieRestrictionModeEnabled) { 675 allowedCookies = window.getWpGA4Cookie(cookieRestrictionName); 676 677 if (allowedCookies !== undefined) { 678 allowedWebsites = JSON.parse(allowedCookies); 679 680 if (allowedWebsites[currentWebsiteId] === 1) { 681 window.ga4AllowServices = true; 682 } 683 } 684 } else { 685 window.ga4AllowServices = true; 686 } 687</script>
687 688 <!-- Google Tag Manager -->
689<script nonce="aHhkaGVuNDFoY2Y2eHhyZTY4bzN2c3BycmM3d3FoNnI=" > if (window.ga4AllowServices) {
vendor: 331 bytes, lines 689-693
689(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 690new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 691j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 692'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 693})(window,document,'script','dataLayer','
693GTM-W9VB8VD
vendor: 3 bytes, line 693
693');
693} </script>
693 694<!-- End Google Tag Manager --> 695
695<script nonce="aHhkaGVuNDFoY2Y2eHhyZTY4bzN2c3BycmM3d3FoNnI="> 696 if (window.ga4AllowServices) { 697 window.dataLayer = window.dataLayer || []; 698 var dl4Objects = [{"event":"general","page_type":"other","store_view":"Default Store View","language":"English","locale":"en_US","user_login_state":"Logged out","user_id":null,"customer_email":"","customer_email_hash":"","function_source":"CustomWeltPixelEvents\/Plugin\/WeltPixel\/GA4\/Block\/Core::afterGetDataLayerAsJson"},{"pageName":"Customer Login","pageType":"other","google_tag_params":{"ecomm_pagetype":"other"}}]; 699 for (var i in dl4Objects) { 700 window.dataLayer.push({ecommerce: null}); 701 window.dataLayer.push(dl4Objects[i]); 702 } 703 var wpGA4Cookies = ['wp_ga4_user_id','wp_ga4_customerGroup']; 704 wpGA4Cookies.map(function (cookieName) { 705 var cookieValue = window.getWpGA4Cookie(cookieName); 706 if (cookieValue) { 707 var dl4Object = {}; 708 dl4Object[cookieName.replace('wp_ga4_', '')] = cookieValue; 709 window.dataLayer.push(dl4Object); 710 } 711 }); 712 } 713 </script>
713 714<!-- NO Pixel ID is configured, please goto Admin -->
715<script type="text/javascript"> console.log('No Meta pixel is configured, please log in as a admin and then visit Stores -> Meta -> Setup -> Get Started');</script>
715<script type="text/javascript" data-routing="commerce=rum" defer="defer" src="https://rum.hlx.page/.rum/@adobe/helix-rum-js@^2/dist/rum-standalone.js"></script>
715 716 717 718 719 720
721<script> 722 var BASE_URL = 'https://www.purdys.com/'; 723 var THEME_PATH = 'https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US'; 724 var COOKIE_CONFIG = { 725 "expires": null, 726 "path": "\u002F", 727 "domain": ".purdys.com", 728 "secure": true, 729 "lifetime": "604800", 730 "cookie_restriction_enabled": false }; 731 var CURRENT_STORE_CODE = 'default'; 732 var CURRENT_WEBSITE_ID = '1'; 733 734 window.hyva = window.hyva || {} 735 736 window.cookie_consent_groups = window.cookie_consent_groups || {} 737 window.cookie_consent_groups['necessary'] = true; 738 739 window.cookie_consent_config = window.cookie_consent_config || {}; 740 window.cookie_consent_config['necessary'] = [].concat( 741 window.cookie_consent_config['necessary'] || [], 742 [ 743 'user_allowed_save_cookie', 744 'form_key', 745 'mage-messages', 746 'private_content_version', 747 'mage-cache-sessid', 748 'last_visited_store', 749 'section_data_ids' 750 ] 751 ); 752</script>
vendor: 1 bytes, line 752
752
753<script> 754 'use strict'; 755 (function( hyva, undefined ) { 756 757 function lifetimeToExpires(options, defaults) { 758 759 const lifetime = options.lifetime || defaults.lifetime; 760 761 if (lifetime) { 762 const date = new Date; 763 date.setTime(date.getTime() + lifetime * 1000); 764 return date; 765 } 766 767 return null; 768 } 769 770 function generateRandomString() { 771 772 const allowedCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 773 length = 16; 774 775 let formKey = '', 776 charactersLength = allowedCharacters.length; 777 778 for (let i = 0; i < length; i++) { 779 formKey += allowedCharacters[Math.round(Math.random() * (charactersLength - 1))] 780 } 781 782 return formKey; 783 } 784 785 const sessionCookieMarker = {noLifetime: true} 786 787 const cookieTempStorage = {}; 788 789 const internalCookie = { 790 get(name) { 791 const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); 792 return v ? v[2] : null; 793 }, 794 set(name, value, days, skipSetDomain) { 795 let expires, 796 path, 797 domain, 798 secure, 799 samesite; 800 801 const defaultCookieConfig = { 802 expires: null, 803 path: '/', 804 domain: null, 805 secure: false, 806 lifetime: null, 807 samesite: 'lax' 808 }; 809 810 const cookieConfig = window.COOKIE_CONFIG || {}; 811 812 expires = days && days !== sessionCookieMarker 813 ? lifetimeToExpires({lifetime: 24 * 60 * 60 * days, expires: null}, defaultCookieConfig) 814 : lifetimeToExpires(window.COOKIE_CONFIG, defaultCookieConfig) || defaultCookieConfig.expires; 815 816 path = cookieConfig.path || defaultCookieConfig.path; 817 domain = !skipSetDomain && (cookieConfig.domain || defaultCookieConfig.domain); 818 secure = cookieConfig.secure || defaultCookieConfig.secure; 819 samesite = cookieConfig.samesite || defaultCookieConfig.samesite; 820 821 document.cookie = name + "=" + encodeURIComponent(value) + 822 (expires && days !== sessionCookieMarker ? '; expires=' + expires.toGMTString() : '') + 823 (path ? '; path=' + path : '') + 824 (domain ? '; domain=' + domain : '') + 825 (secure ? '; secure' : '') + 826 (samesite ? '; samesite=' + samesite : 'lax'); 827 }, 828 isWebsiteAllowedToSaveCookie() { 829 const allowedCookies = this.get('user_allowed_save_cookie'); 830 if (allowedCookies) { 831 const allowedWebsites = JSON.parse(unescape(allowedCookies)); 832 833 return allowedWebsites[CURRENT_WEBSITE_ID] === 1; 834 } 835 return false; 836 }, 837 getGroupByCookieName(name) { 838 const cookieConsentConfig = window.cookie_consent_config || {}; 839 let group = null; 840 for (let prop in cookieConsentConfig) { 841 if (!cookieConsentConfig.hasOwnProperty(prop)) continue; 842 if (cookieConsentConfig[prop].includes(name)) { 843 group = prop; 844 break; 845 } 846 } 847 return group; 848 }, 849 isCookieAllowed(name) { 850 const cookieGroup = this.getGroupByCookieName(name); 851 return cookieGroup 852 ? window.cookie_consent_groups[cookieGroup] 853 : this.isWebsiteAllowedToSaveCookie(); 854 }, 855 saveTempStorageCookies() { 856 for (const [name, data] of Object.entries(cookieTempStorage)) { 857 if (this.isCookieAllowed(name)) { 858 this.set(name, data['value'], data['days'], data['skipSetDomain']); 859 delete cookieTempStorage[name]; 860 } 861 } 862 } 863 }; 864 865 hyva.getCookie = (name) => { 866 const cookieConfig = window.COOKIE_CONFIG || {}; 867 868 if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) { 869 return cookieTempStorage[name] ? cookieTempStorage[name]['value'] : null; 870 } 871 872 return internalCookie.get(name); 873 } 874 875 hyva.setCookie = (name, value, days, skipSetDomain) => { 876 const cookieConfig = window.COOKIE_CONFIG || {}; 877 878 if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) { 879 cookieTempStorage[name] = {value, days, skipSetDomain}; 880 return; 881 } 882 return internalCookie.set(name, value, days, skipSetDomain); 883 } 884 885 886 hyva.setSessionCookie = (name, value, skipSetDomain) => { 887 return hyva.setCookie(name, value, sessionCookieMarker, skipSetDomain) 888 } 889 890 hyva.getBrowserStorage = () => { 891 const browserStorage = window.localStorage || window.sessionStorage; 892 if (!browserStorage) { 893 console.warn('Browser Storage is unavailable'); 894 return false;
895 } 896 try { 897 browserStorage.setItem('storage_test', '1'); 898 browserStorage.removeItem('storage_test'); 899 } catch (error) { 900 console.warn('Browser Storage is not accessible', error); 901 return false; 902 } 903 return browserStorage; 904 } 905 906 hyva.postForm = (postParams) => { 907 const form = document.createElement("form"); 908 909 let data = postParams.data; 910 911 if (! postParams.skipUenc && ! data.uenc) { 912 data.uenc = btoa(window.location.href); 913 } 914 form.method = "POST"; 915 form.action = postParams.action; 916 917 Object.keys(postParams.data).map(key => { 918 const field = document.createElement("input"); 919 field.type = 'hidden' 920 field.value = postParams.data[key]; 921 field.name = key; 922 form.appendChild(field); 923 }); 924 925 const form_key = document.createElement("input"); 926 form_key.type = 'hidden'; 927 form_key.value = hyva.getFormKey(); 928 form_key.name="form_key"; 929 form.appendChild(form_key); 930 931 document.body.appendChild(form); 932 933 form.submit(); 934 } 935 936 hyva.getFormKey = function () { 937 let formKey = hyva.getCookie('form_key'); 938 939 if (!formKey) { 940 formKey = generateRandomString(); 941 hyva.setCookie('form_key', formKey); 942 } 943 944 return formKey; 945 } 946 947 hyva.formatPrice = (value, showSign, options = {}) => { 948 const groupSeparator = options.groupSeparator; 949 const decimalSeparator = options.decimalSeparator 950 delete options.groupSeparator; 951 delete options.decimalSeparator; 952 const formatter = new Intl.NumberFormat( 953 'en\u002DUS', 954 Object.assign({ 955 style: 'currency', 956 currency: 'CAD', 957 signDisplay: showSign ? 'always' : 'auto' 958 }, options) 959 ); 960 return (typeof Intl.NumberFormat.prototype.formatToParts === 'function') ? 961 formatter.formatToParts(value).map(({type, value}) => { 962 switch (type) { 963 case 'currency': 964 return '\u0024' || value; 965 case 'minusSign': 966 return '- '; 967 case 'plusSign': 968 return '+ '; 969 case 'group': 970 return groupSeparator !== undefined ? groupSeparator : value; 971 case 'decimal': 972 return decimalSeparator !== undefined ? decimalSeparator : value; 973 default : 974 return value; 975 } 976 }).reduce((string, part) => string + part) : 977 formatter.format(value); 978 } 979 980 const formatStr = function (str, nStart) { 981 const args = Array.from(arguments).slice(2); 982 983 return str.replace(/(%+)([0-9]+)/g, (m, p, n) => { 984 const idx = parseInt(n) - nStart; 985 986 if (args[idx] === null || args[idx] === void 0) { 987 return m; 988 } 989 return p.length % 2 990 ? p.slice(0, -1).replace('%%', '%') + args[idx] 991 : p.replace('%%', '%') + n; 992 }) 993 } 994 995 hyva.str = function (string) { 996 const args = Array.from(arguments); 997 args.splice(1, 0, 1); 998 999 return formatStr.apply(undefined, args); 1000 } 1001 1002 hyva.strf = function () { 1003 const args = Array.from(arguments); 1004 args.splice(1, 0, 0); 1005 1006 return formatStr.apply(undefined, args); 1007 } 1008 1009 /** 1010 * Take a html string as `content` parameter and 1011 * extract an element from the DOM to replace in 1012 * the current page under the same selector, 1013 * defined by `targetSelector` 1014 */ 1015 hyva.replaceDomElement = (targetSelector, content) => { 1016 // Parse the content and extract the DOM node using the `targetSelector` 1017 const parser = new DOMParser(); 1018 const doc = parser.parseFromString(content, 'text/html');
1019 const contentNode = doc.querySelector(targetSelector); 1020 1021 // Bail if content or target can't be found 1022 if (!contentNode || !document.querySelector(targetSelector)) { 1023 return; 1024 } 1025 1026 hyva.activateScripts(contentNode); 1027 1028 // Replace the old DOM node with the new content 1029 document.querySelector(targetSelector).replaceWith(contentNode); 1030 1031 // Reload customerSectionData and display cookie-messages if present 1032 window.dispatchEvent(new CustomEvent("reload-customer-section-data")); 1033 hyva.initMessages(); 1034 } 1035 1036 hyva.removeScripts = (contentNode) => { 1037 const scripts = contentNode.getElementsByTagName('script'); 1038 for (let i = 0; i < scripts.length; i++) { 1039 scripts[i].parentNode.removeChild(scripts[i]); 1040 } 1041 const templates = contentNode.getElementsByTagName('template'); 1042 for (let i = 0; i < templates.length; i++) { 1043 const container = document.createElement('div'); 1044 container.innerHTML = templates[i].innerHTML; 1045 hyva.removeScripts(container); 1046 templates[i].innerHTML = container.innerHTML; 1047 } 1048 } 1049 1050 hyva.activateScripts = (contentNode) => { 1051 // Create new array from HTMLCollection to avoid mutation of collection while manipulating the DOM. 1052 const scripts = Array.from(contentNode.getElementsByTagName('script')); 1053 1054 // Iterate over all script tags to duplicate+inject each into the head 1055 for (const original of scripts) { 1056 const script = document.createElement('script'); 1057 original.type && (script.type = original.type); 1058 script.innerHTML = original.innerHTML; 1059 1060 // Remove the original (non-executing) script from the contentNode 1061 original.parentNode.removeChild(original) 1062 1063 // Add script to head 1064 document.head.appendChild(script); 1065 } 1066 1067 return contentNode; 1068 } 1069 1070 const replace = {['+']: '-', ['/']: '_', ['=']: ','}; 1071 hyva.getUenc = () => btoa(window.location.href).replace(/[+/=]/g, match => replace[match]); 1072 1073 let currentTrap; 1074 1075 const focusableElements = (rootElement) => { 1076 const selector = 'button, [href], input, select, textarea, details, [tabindex]:not([tabindex="-1"]'; 1077 return Array.from(rootElement.querySelectorAll(selector)) 1078 .filter(el => { 1079 return el.style.display !== 'none' 1080 && !el.disabled 1081 && el.tabIndex !== -1 1082 && (el.offsetWidth || el.offsetHeight || el.getClientRects().length) 1083 }) 1084 } 1085 1086 const focusTrap = (e) => { 1087 const isTabPressed = e.key === 'Tab' || e.keyCode === 9; 1088 if (!isTabPressed) return; 1089 1090 const focusable = focusableElements(currentTrap) 1091 const firstFocusableElement = focusable[0] 1092 const lastFocusableElement = focusable[focusable.length - 1] 1093 1094 e.shiftKey 1095 ? document.activeElement === firstFocusableElement && (lastFocusableElement.focus(), e.preventDefault()) 1096 : document.activeElement === lastFocusableElement && (firstFocusableElement.focus(), e.preventDefault()) 1097 }; 1098 1099 hyva.releaseFocus = (rootElement) => { 1100 if (currentTrap && (!rootElement || rootElement === currentTrap)) { 1101 currentTrap.removeEventListener('keydown', focusTrap) 1102 currentTrap = null 1103 } 1104 } 1105 hyva.trapFocus = (rootElement) => { 1106 if (!rootElement) return; 1107 hyva.releaseFocus() 1108 currentTrap = rootElement 1109 rootElement.addEventListener('keydown', focusTrap) 1110 const firstElement = focusableElements(rootElement)[0] 1111 firstElement && firstElement.focus() 1112 } 1113 1114 hyva.safeParseNumber = (rawValue) => { 1115 const number = rawValue ? parseFloat(rawValue) : null; 1116 1117 return Array.isArray(number) || isNaN(number) ? rawValue : number; 1118 } 1119 1120 const toCamelCase = s => s.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(''); 1121 hyva.createBooleanObject = (name, value = false, additionalMethods = {}) => { 1122 1123 const camelCase = toCamelCase(name);
1124 const key = '__hyva_bool_' + name 1125 1126 return new Proxy(Object.assign( 1127 additionalMethods, 1128 { 1129 [key]: !!value, 1130 [name]() {return !!this[key]}, 1131 ['!' + name]() {return !this[key]}, // @deprecated This does not work with non-CSP Alpine 1132 ['not' + camelCase]() {return !this[key]}, 1133 ['toggle' + camelCase]() {this[key] = !this[key]}, 1134 [`set${camelCase}True`]() {this[key] = true}, 1135 [`set${camelCase}False`]() {this[key] = false}, 1136 } 1137 ), { 1138 set(target, prop, value) { 1139 return prop === name 1140 ? (target[key] = !!value) 1141 : Reflect.set(...arguments); 1142 } 1143 }) 1144 } 1145 1146 hyva.alpineInitialized = (fn) => window.addEventListener('alpine:initialized', fn, {once: true}) 1147 window.addEventListener('alpine:init', () => Alpine.data('{}', () => ({})), {once: true}); 1148 window.addEventListener('user-allowed-save-cookie', () => internalCookie.saveTempStorageCookies()) 1149 1150 }( window.hyva = window.hyva || {} )); 1151</script>
vendor: 1 bytes, line 1151
1151
1152<script> 1153 if (!window.IntersectionObserver) { 1154 window.IntersectionObserver = function (callback) { 1155 this.observe = el => el && callback(this.takeRecords()); 1156 this.takeRecords = () => [{isIntersecting: true, intersectionRatio: 1}]; 1157 this.disconnect = () => {}; 1158 this.unobserve = () => {}; 1159 } 1160 } 1161</script>
vendor: 1 bytes, line 1161
1161
1162<script> 1163 'use strict'; 1164 1165 (function () { 1166 window.initAmAppendLabels = { 1167 selectors: { 1168 labelWrapper: '.amlabel-position-wrapper' 1169 }, 1170 /** 1171 * We use approach from hyva.replaceDomElement method to replace labels and init components 1172 * 1173 * @param {HTMLElement} labelContainer 1174 * @param {string} labelHtml 1175 * @return {void} 1176 */ 1177 appendLabels: function (labelContainer, labelHtml) { 1178 const parser = new DOMParser(); 1179 const doc = parser.parseFromString(labelHtml, 'text/html'); 1180 const nodes = doc.querySelectorAll(this.selectors.labelWrapper); 1181 1182 if (!nodes) { 1183 return; 1184 } 1185 1186 // Extract all the script tags from the new content. 1187 // Script tags won't execute when inserted into a dom-element directly, 1188 // therefore we need to inject them to the head of the document. 1189 const tmpScripts = doc.getElementsByTagName('script'); 1190 1191 if (tmpScripts.length > 0) { 1192 // Push all script tags into an array 1193 // (to prevent dom manipulation while iterating over dom nodes) 1194 const scripts = [...tmpScripts]; 1195 1196 // Iterate over all script tags and duplicate+inject each into the head 1197 scripts.forEach(script => { 1198 let scriptNode = document.createElement('script'); 1199 scriptNode.innerHTML = script.innerHTML; 1200 document.head.appendChild(scriptNode); 1201 1202 // Remove the original (non-executing) node from the page 1203 script.parentNode.removeChild(script); 1204 }); 1205 } 1206 1207 labelContainer.append(...nodes); 1208 } 1209 }; 1210 })(); 1211</script>
1211 1212
1213<script> 1214 window.addEventListener('DOMContentLoaded', () => { 1215 if (window._amSwiperLoaded) { 1216 return; 1217 } 1218 1219 // Load Swiper script asynchronously 1220 const script = document.createElement('script'); 1221 script.src = 'https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Amasty_ShopByBrandHyvaCompatibility/js/swiper.min.js'; 1222 script.async = true; 1223 document.body.appendChild(script); 1224 1225 window._amSwiperLoaded = true; 1226 1227 script.onload = () => { 1228 // Dispatch custom event when Swiper is loaded 1229 window.dispatchEvent(new CustomEvent('amSwiperLoaded', {})); 1230 } 1231 1232 // Load Swiper CSS file 1233 const styles = document.createElement('link'); 1234 styles.rel = 'stylesheet'; 1235 styles.href = 'https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Amasty_ShopByBrandHyvaCompatibility/css/swiper.min.css'; 1236 1237 document.head.appendChild(styles); 1238 }); 1239</script>
vendor: 1 bytes, line 1239
1239
1240<script> 1241function initDeviceTypeVariable() { 1242 const mobileMatchMedia = window.matchMedia("(max-width: 767px)"); 1243 const tabletMatchMedia = window.matchMedia("(min-width: 768px) and (max-width: 1023px)"); 1244 const desktopMatchMedia = window.matchMedia("(min-width: 1024px)"); 1245 1246 function updateDeviceStore() { 1247 const isMobile = mobileMatchMedia.matches; 1248 const isTablet = tabletMatchMedia.matches; 1249 const isDesktop = desktopMatchMedia.matches; 1250 1251 window.isMobile = isMobile; 1252 window.isTablet = isTablet; 1253 window.isDesktop = isDesktop; 1254 1255 Alpine.store('device').isMobile = isMobile; 1256 Alpine.store('device').isTablet = isTablet; 1257 Alpine.store('device').isDesktop = isDesktop; 1258 } 1259 1260 // Initial check 1261 updateDeviceStore(); 1262 1263 // Attach change listeners 1264 const matchMediaList = [mobileMatchMedia, tabletMatchMedia, desktopMatchMedia]; 1265 matchMediaList.forEach((mediaQuery) => { 1266 if (typeof mediaQuery.onchange !== 'object') { 1267 mediaQuery.addListener(updateDeviceStore); // Legacy 1268 } else { 1269 mediaQuery.addEventListener("change", updateDeviceStore); 1270 } 1271 }); 1272} 1273 1274document.addEventListener("alpine:init", () => { 1275 Alpine.store('device', { 1276 isMobile: window.isMobile, 1277 isTablet: window.isTablet, 1278 isDesktop: window.isDesktop 1279 }); 1280 1281 initDeviceTypeVariable(); 1282}); 1283</script>
1283</head> 1284<body id="html-body" class="customer-account-login page-layout-1column"> 1285<!-- Google Tag Manager (noscript) -->
vendor: 69 bytes, lines 1285-1286
1285 1286<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
1286GTM-W9VB8VD
vendor: 85 bytes, lines 1286-1287
1286" 1287height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
1288<!-- End Google Tag Manager (noscript) --> 1289
1289<script> 1290 (function () { 1291 'use strict'; 1292 1293 window.amRecaptchaModel = { 1294 formsToProtect: "form\u005Baction\u002A\u003D\u0022multishipping\u002Fcheckout\u002Foverview\u0022\u005D,form\u005Baction\u002A\u003D\u0022multishipping\u002Fcheckout\u002FoverviewPost\u0022\u005D,\u005Baction\u002A\u003D\u0022recpatcha\u002Dholder\u0022\u005D,form\u005Baction\u002A\u003D\u0022customer\u002Faccount\u002Fcreatepost\u0022\u005D,form\u005Baction\u002A\u003D\u0022customer\u002Faccount\u002FloginPost\u0022\u005D,form\u005Baction\u002A\u003D\u0022newsletter\u002Fsubscriber\u002Fnew\u0022\u005D,form\u005Baction\u002A\u003D\u0022contact\u002Findex\u002Fpost\u0022\u005D,form\u005Baction\u002A\u003D\u0022customer\u002Faccount\u002Fforgotpasswordpost\u0022\u005D,form\u005Bx\u002Ddata\u003D\u0022initReviewForm\u0028\u0029\u0022\u005D,form\u005Bx\u002Ddata\u003D\u0022initReviewForm\u0022\u005D,form\u005Baction\u002A\u003D\u0022checkout_payment_captcha\u0022\u005D", 1295 isEnabledOnPayments: "1", 1296 checkoutRecaptchaValidateUrl: "https://www.purdys.com/amcapthca/checkout/validate/", 1297 invisibleCaptchaCustomForm: "0", 1298 recaptchaConfig: { 1299 lang: "hl\u003Den" || 'hl=en', 1300 theme: "light", 1301 badge: "bottomright", 1302 sitekey: "6LfD4jsqAAAAAL0vSLeq92I6r50A87vMaCVUy2qB", 1303 size: "invisible" 1304 }, 1305 url: 'https://www.google.com/recaptcha/api.js', 1306 isCaptchaScriptLoaded: false, 1307 isInvisible: "invisible" === 'invisible', 1308 1309 /** 1310 * Submit event 1311 * 1312 * @param {Object} $event 1313 * @return {void} 1314 */ 1315 execute: function ($event) { 1316 const eventTarget = $event?.target; 1317 if (this.isCaptchaScriptLoaded) { 1318 this.executeCaptcha(eventTarget); 1319 } else { 1320 window.addEventListener('amcaptcha-script-loaded', () => { 1321 this.isCaptchaScriptLoaded = true; 1322 this.executeCaptcha(eventTarget); 1323 this.initAmcaptchaBadges(); 1324 }); 1325 1326 this.loadRecaptchaScript(); 1327 } 1328 }, 1329 1330 /** 1331 * Execute original submit 1332 * 1333 * @param {Object} $event 1334 * @param {HTMLElement} $el 1335 * @return {void} 1336 */ 1337 recaptchaCallback: function ($event, $el) { 1338 this.shouldExecuteCallback($event, $el) && $el.dispatchEvent(new CustomEvent('amrecaptcha-executable-callback')); 1339 }, 1340 1341 /** 1342 * Reset captcha 1343 * 1344 * @param {Object} $event 1345 * @param {HTMLElement} $el 1346 */ 1347 expiredCallback: function($event, $el) { 1348 this.shouldExecuteCallback($event, $el) && grecaptcha.reset($event.detail.widgetId); 1349 }, 1350 1351 /** 1352 * Checks if event is fired on submitted element 1353 * 1354 * @param {Object} $event 1355 * @param {HTMLElement} $el 1356 * @return {boolean} 1357 */ 1358 shouldExecuteCallback: function($event, $el) { 1359 const eventWidgetId = $event.detail.widgetId; 1360 const elementWidgetId = $el.dataset.amCaptchaWidgetId || $el.closest('form')?.dataset.amCaptchaWidgetId; 1361 1362 return eventWidgetId === elementWidgetId; 1363 }, 1364 1365 /** 1366 * Run captcha for widget by id 1367 * 1368 * @param {HTMLElement} eventTarget 1369 * @return {void} 1370 */ 1371 executeCaptcha: function(eventTarget) { 1372 if (!eventTarget.dataset.hasOwnProperty('amCaptchaRendered')) { 1373 this.bindCaptchaGlobalCallbacks(eventTarget); 1374 this.renderCaptcha(eventTarget); 1375 eventTarget.dataset.amCaptchaRendered = ''; 1376 } 1377 1378 const widgetId = this.getWidgetId(eventTarget); 1379 1380 if (!widgetId) { 1381 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages([ 1382 { 1383 type: "error", 1384 text: 'Recaptcha\u0020cannot\u0020properly\u0020validate\u0020form.' 1385 } 1386 ]); 1387 return; 1388 } 1389 1390 window.dispatchEvent(new CustomEvent('amcaptcha-loading-start')); 1391 grecaptcha.reset(widgetId); 1392 1393 if (!this.isInvisible) { 1394 this.stopAmcaptchaLoading(); 1395 return; 1396 } 1397 1398 grecaptcha.execute(widgetId) 1399 .then(() => this.stopAmcaptchaLoading()) 1400 .catch(() => this.stopAmcaptchaLoading()); 1401 }, 1402 1403 /** 1404 * Add recaptcha script to page 1405 * 1406 * @return {void} 1407 */ 1408 loadRecaptchaScript: function () { 1409 const lang = window.amRecaptchaModel.recaptchaConfig.lang; 1410 const url = `${window.amRecaptchaModel.url}?onload=amRecaptchaLoadCallback&render=explicit&${lang}`; 1411 const script = document.createElement('script'); 1412 1413 script.type = 'text/javascript'; 1414 script.src = url; 1415 script.async = true; 1416 document.head.appendChild(script); 1417 }, 1418 1419 /** 1420 * Stop loader on page 1421 * 1422 * @return {void} 1423 */ 1424 stopAmcaptchaLoading: function () { 1425 window.dispatchEvent(new CustomEvent('amcaptcha-loading-stop')); 1426 }, 1427 1428 /** 1429 * Add attributes to hide captcha badge 1430 * 1431 * @return {void} 1432 */ 1433 initAmcaptchaBadges: function () { 1434 if (!this.isInvisible) { 1435 return; 1436 } 1437 1438 document.querySelectorAll('.grecaptcha-badge')?.forEach(captchaBadge => { 1439 captchaBadge?.setAttribute('x-data', 'amGrecaptchaBadge'); 1440 captchaBadge?.setAttribute('x-on:amcaptcha-loading-start.window', 'loadingStart'); 1441 captchaBadge?.setAttribute('x-on:amcaptcha-loading-stop.window', 'loadingStop'); 1442 captchaBadge?.setAttribute(':class', 'getVisibility'); 1443 }); 1444 }, 1445 1446 /** 1447 * Get form widgetId 1448 * 1449 * @param {HTMLElement} eventTarget 1450 * @return {string} 1451 */ 1452 getWidgetId: function (eventTarget) { 1453 return eventTarget.dataset.amCaptchaWidgetId 1454 || eventTarget.closest('form')?.dataset.amCaptchaWidgetId; 1455 }, 1456 1457 /** 1458 * Add captcha badge or checkbox to protected form. 1459 * Store widgetId in dataset to pass this info in callbacks 1460 * 1461 * @param {HTMLElement} eventTarget 1462 * @return {void} 1463 */ 1464 renderCaptcha: function (eventTarget) { 1465 const captchaElement = document.createElement('div'); 1466 const formContent = eventTarget.closest('form')?.querySelector('[data-element="form-content"]') 1467 ?? eventTarget.closest('form'); 1468 formContent?.appendChild(captchaElement); 1469 1470 eventTarget.dataset.amCaptchaWidgetId = grecaptcha.render(captchaElement, { 1471 ...window.amRecaptchaModel?.recaptchaConfig, 1472 callback: () => { 1473 window.dispatchEvent(new CustomEvent( 1474 'amrecaptcha-callback', { 1475 detail: this.getCaptchaCallbackDetail(eventTarget) 1476 })); 1477 }, 1478 'expired-callback': () => { 1479 window.dispatchEvent(new CustomEvent( 1480 'amrecaptcha-expired-callback', { 1481 detail: this.getCaptchaCallbackDetail(eventTarget) 1482 })); 1483 } 1484 }); 1485 }, 1486 1487 /** 1488 * Get detail property for callback event 1489 * 1490 * @param {HTMLElement} eventTarget 1491 * @return {object} 1492 */ 1493 getCaptchaCallbackDetail: function (eventTarget) { 1494 return { 1495 widgetId: eventTarget.dataset.amCaptchaWidgetId, 1496 form: eventTarget 1497 } 1498 }, 1499 1500 /** 1501 * Add functions that will be called on the global state 1502 * 1503 * @param {HTMLElement} eventTarget 1504 * @return {void} 1505 */ 1506 bindCaptchaGlobalCallbacks: function (eventTarget) { 1507 eventTarget.setAttribute( 1508 'x-on:amrecaptcha-callback.window', 1509 `amRecaptchaModelRecaptchaCallback` 1510 ); 1511 eventTarget.setAttribute( 1512 'x-on:amrecaptcha-expired-callback.window', 1513 'amRecaptchaModelExpiredCallback' 1514 ); 1515 } 1516 }; 1517 })(window.amRecaptchaModel = window.amRecaptchaModel || {}); 1518 1519 window.amRecaptchaLoadCallback = function () { 1520 window.dispatchEvent(new CustomEvent('amcaptcha-script-loaded')); 1521 } 1522 1523 function amGrecaptchaBadge() { 1524 return { 1525 visible: true, 1526 1527 loadingStart () { 1528 this.visible = true; 1529 }, 1530 1531 loadingStop () { 1532 this.visible = false;
1533 }, 1534 1535 getVisibility() { 1536 return { 'invisible': !this.visible }; 1537 } 1538 }; 1539 } 1540 1541 window.addEventListener( 1542 'alpine:init', () => Alpine.data('amGrecaptchaBadge', amGrecaptchaBadge), {once: true} 1543 ) 1544 </script>
1544 1545 1546<div 1547 class="ambanners ambanner-10" 1548 data-position="10" 1549 data-bannerid="" 1550 data-role="amasty-banner-container" 1551></div> 1552
1553<script> 1554 const HOME_3_IMAGE_CTA_BLOCK_BREAKPOINT = { 1555 1: { 1556 enabled: true, 1557 slidesPerView: 1.3, 1558 spaceBetween: 16, 1559 }, 1560 576: { 1561 enabled: true, 1562 slidesPerView: 2.2, 1563 spaceBetween: 16, 1564 }, 1565 768: { 1566 enabled: true, 1567 slidesPerView: 2, 1568 spaceBetween: 20, 1569 }, 1570 1024: { 1571 enabled: false, 1572 slidesPerView: 3, 1573 spaceBetween: 20, 1574 }, 1575 }; 1576 const EXPLORE_CATEGORIES_SLIDER_BREAKPOINT = { 1577 1: { 1578 slidesPerView: 2.6, 1579 allowTouchMove: true, 1580 spaceBetween: 16, 1581 }, 1582 768: { 1583 allowTouchMove: true, 1584 slidesPerView: 4, 1585 spaceBetween: 20, 1586 }, 1587 1024: { 1588 allowTouchMove: false, 1589 slidesPerView: 6, 1590 spaceBetween: 20, 1591 }, 1592 }; 1593 const FEATURE_SLIDER_BLOCK_BREAKPOINT = { 1594 1024: { 1595 slidesPerView: 5, 1596 spaceBetween: 20, 1597 }, 1598 1650: { 1599 slidesPerView: 6.7, 1600 spaceBetween: 20, 1601 } 1602 }; 1603 const INSTAGRAM_BLOCK = { 1604 1: { 1605 slidesPerView: 'auto', 1606 spaceBetween: 16, 1607 loop: false, 1608 }, 1609 768: { 1610 slidesPerView: 4.6, 1611 spaceBetween: 20, 1612 centeredSlides: false, 1613 slidesOffsetBefore: 100, 1614 loop: false, 1615 grabCursor: true, 1616 initialSlide: 2, 1617 }, 1618 1650: { 1619 slidesPerView: 'auto', 1620 spaceBetween: 20, 1621 slidesOffsetBefore: 20, 1622 loop: false, 1623 grabCursor: true, 1624 initialSlide: 1, 1625 }, 1626 2000: { 1627 slidesPerView: 'auto', 1628 spaceBetween: 20, 1629 slidesOffsetBefore: 0, 1630 loop: false, 1631 grabCursor: true, 1632 }, 1633 }; 1634 window.swiperInitializing = false; 1635 window.swiperPromise = null; 1636 1637 function initSwiperSlider(swiperElement, options = {}) { 1638 const addSwiperJs = (callback) => { 1639 const script = document.createElement('script'); 1640 script.type = 'text/javascript'; 1641 script.addEventListener('load', callback); 1642 script.src = 'https\u003A\u002F\u002Fwww.purdys.com\u002Fstatic\u002Fversion1790083580\u002Ffrontend\u002FPurdys2\u002Fhyva\u002Fen_US\u002FScandiweb_ContentTypes\u002Fjs\u002Fswiper.min.js'; 1643 document.head.appendChild(script); 1644 } 1645 1646 const addSwiperCss = () => { 1647 const link = document.createElement('link'); 1648 link.type = 'text/css'; 1649 link.rel = 'stylesheet'; 1650 link.href = 'https\u003A\u002F\u002Fwww.purdys.com\u002Fstatic\u002Fversion1790083580\u002Ffrontend\u002FPurdys2\u002Fhyva\u002Fen_US\u002FScandiweb_ContentTypes\u002Fcss\u002Fswiper.min.css'; 1651 document.head.appendChild(link); 1652 } 1653 1654 const initSlider = () => { 1655 let customOptions = {...options}; 1656 if (swiperElement.closest('.home-3image-cta-block')) { 1657 customOptions.breakpoints = HOME_3_IMAGE_CTA_BLOCK_BREAKPOINT; 1658 } 1659 if (swiperElement.closest('.instagram-block')) { 1660 customOptions.breakpoints = INSTAGRAM_BLOCK; 1661 } 1662 1663 if (swiperElement.closest('.explore-categories-slider')) { 1664 // Start with base breakpoints and override slidesPerView only when custom values > 0 1665 customOptions.breakpoints = {...EXPLORE_CATEGORIES_SLIDER_BREAKPOINT}; 1666 1667 if (options.breakpoints) { 1668 Object.keys(options.breakpoints).forEach(breakpoint => { 1669 const customBreakpoint = options.breakpoints[breakpoint]; 1670 if (customBreakpoint.slidesPerView && customBreakpoint.slidesPerView > 0) { 1671 // If breakpoint exists in base, preserve its properties; if not, add default properties 1672 const baseBreakpoint = customOptions.breakpoints[breakpoint] || {};
1673 const defaultBreakpointProps = { 1674 spaceBetween: 20, // Default spaceBetween for larger breakpoints 1675 allowTouchMove: false // Default for larger breakpoints 1676 }; 1677 1678 customOptions.breakpoints[breakpoint] = { 1679 ...defaultBreakpointProps, 1680 ...baseBreakpoint, 1681 slidesPerView: customBreakpoint.slidesPerView 1682 }; 1683 } 1684 }); 1685 } 1686 } 1687 1688 if (swiperElement.closest('[data-content-type="featured-products-slider"]')) { 1689 customOptions.breakpoints = FEATURE_SLIDER_BLOCK_BREAKPOINT; 1690 } 1691 1692 const swiperInstance = new Swiper(swiperElement, { 1693 slidesPerView: 'auto', 1694 spaceBetween: 16, 1695 mousewheel: { 1696 forceToAxis: true 1697 }, 1698 ...customOptions 1699 }); 1700 return swiperInstance; 1701 } 1702 1703 if (!window.Swiper) { 1704 if (!window.swiperPromise) { 1705 window.swiperPromise = new Promise((resolve) => { 1706 window.swiperInitializing = true; 1707 addSwiperCss(); 1708 addSwiperJs(() => { 1709 resolve(); 1710 }); 1711 }); 1712 } 1713 1714 return window.swiperPromise.then(() => { 1715 return initSlider(); 1716 }); 1717 } else { 1718 return initSlider(); 1719 } 1720 } 1721</script>
1721 1722<input name="form_key" type="hidden" value="cbEPL7j02Kuo6BSa" /> 1723 <noscript> 1724 <section class="message global noscript border-b-2 border-blue-500 bg-blue-50 shadow-none m-0 px-0 rounded-none font-normal"> 1725 <div class="container text-center"> 1726 <p> 1727 <strong>JavaScript seems to be disabled in your browser.</strong> 1728 <span> 1729 For the best experience on our site, be sure to turn on Javascript in your browser. </span> 1730 </p> 1731 </div> 1732 </section> 1733 </noscript> 1734 1735
1736<script> 1737 document.body.addEventListener('touchstart', () => {}, {passive: true}) 1738</script>
1738 1739 1740
1740<script> 1741 window.amRecaptchaModel = window.amRecaptchaModel || {}; 1742 </script>
1742 1743
1743<script> 1744 'use strict'; 1745 1746 (() => { 1747 window.addEventListener( 1748 'ga-initialized', 1749 () => { 1750 window.addEventListener('cart-item-added', (event) => { 1751 let products = []; 1752 1753 event.detail.forEach((item) => { 1754 products.push({ 1755 id: item['product_sku'], 1756 name: item['product_name'], 1757 price: item['product_price_value'], 1758 quantity: item['qty'], 1759 }); 1760 }); 1761 1762 dataLayer.push({ 1763 event: 'addToCart', 1764 ecommerce: { 1765 currencyCode: 'CAD', 1766 add: { 1767 products: products, 1768 }, 1769 }, 1770 }); 1771 }); 1772 }, 1773 { once: true } 1774 ); 1775 })(); 1776</script>
vendor: 1 bytes, line 1776
1776
1777<script> 1778 'use strict'; 1779 1780 (() => { 1781 window.addEventListener( 1782 'ga-initialized', 1783 () => { 1784 window.addEventListener('cart-item-removed', (event) => { 1785 let products = []; 1786 1787 event.detail.forEach((item) => { 1788 products.push({ 1789 id: item['product_sku'], 1790 name: item['product_name'], 1791 price: item['product_price_value'], 1792 quantity: item['qty'], 1793 }); 1794 }); 1795 1796 dataLayer.push({ 1797 event: 'removeFromCart', 1798 ecommerce: { 1799 currencyCode: 'CAD', 1800 remove: { 1801 products: products, 1802 }, 1803 }, 1804 }); 1805 }); 1806 }, 1807 { once: true } 1808 ); 1809 })(); 1810</script>
vendor: 1 bytes, line 1810
1810
1811<script> 1812 'use strict'; 1813 1814 (() => { 1815 window.addEventListener( 1816 'ga-initialized', 1817 () => { 1818 const banners = document.querySelectorAll('\u005Bdata\u002Dbanner\u002Did\u005D'); 1819 let promotions = []; 1820 1821 banners.forEach((banner) => { 1822 banner.addEventListener('click', () => { 1823 window.dataLayer.push({ 1824 event: 'promotionClick', 1825 ecommerce: { 1826 promoClick: { 1827 promotions: [{ 1828 id: banner.dataset.bannerId, 1829 name: banner.dataset.bannerName, 1830 creative: '', 1831 position: 'customer_account_login', 1832 }], 1833 }, 1834 }, 1835 }); 1836 }); 1837 1838 promotions.push({ 1839 id: banner.dataset.bannerId, 1840 name: banner.dataset.bannerName, 1841 creative: '', 1842 position: 'customer_account_login', 1843 }); 1844 }); 1845 1846 if (promotions.length) { 1847 window.dataLayer.push({ 1848 event: 'promotionView', 1849 ecommerce: { 1850 promoView: { 1851 promotions: promotions, 1852 }, 1853 }, 1854 }); 1855 } 1856 }, 1857 { once: true } 1858 ); 1859 })(); 1860</script>
1860 1861
1862<script> 1863 'use strict'; 1864 (function(hyva) { 1865 const formValidationRules = { 1866 required(value, options, field, context) { 1867 const el = field.element.type === 'hidden' ? createTextInputFrom(field.element) : field.element, 1868 msg = 'This\u0020is\u0020a\u0020required\u0020field.'; 1869 1870 if (el.type === 'radio' || el.type === 'checkbox') { 1871 return (value === undefined || value.length === 0) ? msg : true; 1872 } 1873 1874 el.setAttribute('required', ''); 1875 el.checkValidity(); 1876 1877 return el.validity.valueMissing ? msg : true; 1878 }, 1879 maxlength(value, options, field, context) { 1880 const n = Number(options) 1881 if (value.length > n) { 1882 return n === 1 1883 ? hyva.strf('Please\u0020enter\u0020no\u0020more\u0020than\u00201\u0020character.') 1884 : hyva.strf('Please\u0020enter\u0020no\u0020more\u0020than\u0020\u00250\u0020characters.', options) 1885 } 1886 return true; 1887 }, 1888 minlength(value, options, field, context) { 1889 const n = Number(options) 1890 if (value.length > 0 && value.length < n) { 1891 return n === 1 1892 ? hyva.strf('Please\u0020enter\u0020at\u0020least\u00201\u0020character.') 1893 : hyva.strf('Please\u0020enter\u0020at\u0020least\u0020\u00250\u0020characters.', options) 1894 } 1895 return true; 1896 }, 1897 max(value, options, field, context) { 1898 field.element.setAttribute('max', options); 1899 field.element.checkValidity(); 1900 if (field.element.validity.rangeOverflow) { 1901 return hyva.strf('Please\u0020enter\u0020a\u0020value\u0020less\u0020than\u0020or\u0020equal\u0020to\u0020\u0022\u00250\u0022.', options); 1902 } 1903 return true; 1904 }, 1905 min(value, options, field, context) { 1906 field.element.setAttribute('min', options); 1907 field.element.checkValidity(); 1908 if (field.element.validity.rangeUnderflow) { 1909 return hyva.strf('Please\u0020enter\u0020a\u0020value\u0020greater\u0020than\u0020or\u0020equal\u0020to\u0020\u0022\u00250\u0022.', options); 1910 } 1911 return true; 1912 }, 1913 step(value, options, field, context) { 1914 field.element.setAttribute('step', options); 1915 field.element.checkValidity(); 1916 if (field.element.validity.stepMismatch) { 1917 const val = Number(value); 1918 const step = Number(options); 1919 const msg = 'Please\u0020enter\u0020a\u0020valid\u0020value.\u0020The\u0020two\u0020nearest\u0020valid\u0020values\u0020are\u0020\u0022\u00250\u0022\u0020and\u0020\u0022\u00251\u0022.'; 1920 return hyva.strf(msg, Math.floor(val / step) * step, Math.ceil(val / step) * step); 1921 } 1922 return true; 1923 }, 1924 pattern(value, options, field, context) { 1925 field.element.setAttribute('pattern', options); 1926 field.element.checkValidity(); 1927 if (field.element.validity.patternMismatch) { 1928 return field.element.title 1929 ? hyva.strf('Please\u0020match\u0020the\u0020requested\u0020format\u003A\u0020\u00250.', field.element.title) 1930 : 'Please\u0020match\u0020the\u0020requested\u0020format.' 1931 } 1932 return true; 1933 }, 1934 email(value, options, field, context) { 1935 const rule = /^([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; 1936 if (value.length > 0 && !rule.test(value)) { 1937 return 'Please\u0020enter\u0020a\u0020valid\u0020email\u0020address.'; 1938 } 1939 return true; 1940 }, 1941 password(value, options, field, context) { 1942 const rule = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/; 1943 if (value.length > 0 && !rule.test(value)) { 1944 return 'Please\u0020provide\u0020at\u0020least\u0020one\u0020upper\u0020case,\u0020one\u0020lower\u0020case,\u0020one\u0020digit\u0020and\u0020one\u0020special\u0020character\u0020\u0028\u0023\u003F\u0021\u0040\u0024\u0025\u005E\u0026\u002A\u002D\u0029'; 1945 } 1946 return true; 1947 }, 1948 equalTo(value, options, field, context) { 1949 const dependencyField = context.fields[options].element; 1950 if (value !== dependencyField.value) { 1951 const dependencyFieldName = 1952 dependencyField.label || 1953 dependencyField.title || 1954 (dependencyField.labels && dependencyField.labels[0] && dependencyField.labels[0].innerText) || 1955 dependencyField.name; 1956 return hyva.strf('This\u0020field\u0020value\u0020must\u0020be\u0020the\u0020same\u0020as\u0020\u0022\u00250\u0022.', dependencyFieldName); 1957 } 1958 return true; 1959 } 1960 }; 1961 1962 function raceSome(promises, pred) { 1963 return new Promise((resolve, reject) => { 1964 1965 if (promises.length === 0) { 1966 return resolve(); 1967 } 1968 1969 let settled = false, nDone = 0; 1970 1971 const resolveIf = v => { 1972 if (!settled && (pred(v) || ++nDone === promises.length)) { 1973 settled = true; 1974 resolve(v); 1975 } 1976 return v; 1977 } 1978 1979 promises.map(promise => {
1980 promise.then(resolveIf).catch(reason => { 1981 settled = true; 1982 reject(reason) 1983 }); 1984 return promise; 1985 }); 1986 }); 1987 } 1988 1989 const INPUT_ATTRIBUTE_RULES = {min: 'min', max: 'max', required: 'required', minlength: 'minlength', maxlength: 'maxlength', step: 'step', pattern: 'pattern'} 1990 const INPUT_TYPE_RULES = {email: 'email'} 1991 1992 function getRules(element) { 1993 let rules = {}; 1994 Object.keys(INPUT_ATTRIBUTE_RULES).forEach(attrName => { 1995 if (element.hasAttribute(attrName)) { 1996 rules[INPUT_ATTRIBUTE_RULES[attrName]] = element.getAttribute(attrName); 1997 } 1998 }) 1999 if (INPUT_TYPE_RULES[element.type]) { 2000 rules[INPUT_TYPE_RULES[element.type]] = true; 2001 } 2002 2003 if (element.dataset.validate) { 2004 try { 2005 Object.assign(rules, JSON.parse(element.dataset.validate)); 2006 } catch (error) { 2007 console.error('Validator error. Cannot parse data-validate attribute of element:\n', element); 2008 } 2009 } 2010 2011 return rules; 2012 } 2013 2014 function isInvalidRuleResult(ruleState) { 2015 return typeof ruleState === 'string' || !ruleState || (ruleState.type && ruleState.content); 2016 } 2017 2018 async function runValidateFn(rule, options, value, field) { 2019 return formValidationRules[rule](value, options, field, this); 2020 } 2021 2022 function generateId() { 2023 let id; 2024 do { 2025 id = `${this.idPrefix}-${++this.idSeq}`; 2026 } while (document.getElementById(id)); 2027 return id; 2028 } 2029 2030 function isVisible(element) { 2031 const el = element.type !== 'hidden' ? element : (element.parentElement || {}); 2032 return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length) 2033 } 2034 2035 function elementWillValidate(element) { 2036 return (element.willValidate || element.type === 'hidden') 2037 && element.tagName !== 'BUTTON' 2038 && element.disabled === false 2039 && !(element.tagName === 'INPUT' && element.type === 'submit') 2040 && (element.hasAttribute('data-validate-hidden') || isVisible(element)) 2041 } 2042 2043 function createMessageContainer(el, fieldWrapperClassName) { 2044 if (! el.parentElement) { 2045 return; 2046 } 2047 const refocus = document.activeElement === el; 2048 const wrapper = document.createElement('div'); 2049 wrapper.classList.add.apply(wrapper.classList, fieldWrapperClassName.split(' ')); 2050 el.parentElement.insertBefore(wrapper, el); 2051 wrapper.appendChild(el); 2052 refocus && document.activeElement !== el && el.focus(); 2053 return wrapper; 2054 } 2055 2056 function containerNotFound(selector, el) { 2057 const msg = `Cannot find message container element ${selector} of ${el.name}`; 2058 console.error(msg, el); 2059 throw msg; 2060 } 2061 2062 function createTextInputFrom(el) { 2063 const text = document.createElement('INPUT'); 2064 text.type = 'text'; 2065 text.value = el.value; 2066 return text; 2067 } 2068 2069 function classNamesToSelector(classNames) { 2070 return classNames.split(' ') 2071 .filter(className => className.length > 0) 2072 .map(className => `.${className}`) 2073 .join('') 2074 } 2075 2076 function hasMessagesWrapper(field, messagesWrapperClassName) { 2077 return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName)); 2078 } 2079 2080 function getMessagesWrapper(field, messagesWrapperClassName) { 2081 if (hasMessagesWrapper.call(this, field, messagesWrapperClassName)) { 2082 return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName)); 2083 } 2084 2085 const msgWrapper = document.createElement('ul'); 2086 const msgId = generateId.call(this); 2087 msgWrapper.id = msgId; 2088 field.element.setAttribute('aria-errormessage', msgId); 2089 field.element.setAttribute('aria-describedby', msgId); 2090 msgWrapper.classList.add.apply(msgWrapper.classList, messagesWrapperClassName.split(' ')); 2091 if (field.validateOnChange) { 2092 msgWrapper.setAttribute('aria-live', 'polite'); 2093 } 2094 this.getMessageContainer(field).appendChild(msgWrapper); 2095 2096 return msgWrapper; 2097 } 2098 2099 function getCheckedValues(field) { 2100 const name = field.element.name.replace(/([\\"])/g, '\\$1'); 2101 const elements = field.element.form.querySelectorAll('input[name="' + name + '"]:checked'); 2102 return Array.from(elements).map(el => el.value); 2103 } 2104 2105 function escapeHtml(s) { 2106 const div = document.createElement('div') 2107 div.innerText = s; 2108 return div.innerHTML; 2109 } 2110 2111 2112 function formValidation(form, options = {}) { 2113 const formElement = form || this.$el; 2114 2115 if (formElement.dataset && formElement.dataset.options) { 2116 try { 2117 options = Object.assign(options, JSON.parse(formElement.dataset.options || '{}')); 2118 } catch (e) { 2119 throw new Error('Cannot read the form options from the data-options attribute: not val
2119id JSON'); 2120 } 2121 } 2122 2123 // Disable browser default validation 2124 if (formElement.tagName === 'FORM') { 2125 formElement.setAttribute('novalidate', ''); 2126 } else { 2127 console.error('formValidation can be initialized only on FORM element', formElement); 2128 return; 2129 } 2130 2131 options = Object.assign({ 2132 fieldWrapperClassName: 'field field-reserved', 2133 messagesWrapperClassName: 'messages', 2134 validClassName: 'field-success', 2135 invalidClassName: 'field-error', 2136 pageMessagesWrapperSelector: null, 2137 scrollToFirstError: true, 2138 }, options || {}); 2139 2140 return { 2141 state: { 2142 valid: false, 2143 }, 2144 fields: {}, 2145 idSeq: 0, 2146 idPrefix: formElement.id || 'vld-msg', 2147 setupFields(elements) { 2148 this.fields = {}; 2149 Array.from(elements).forEach(element => { 2150 if (elementWillValidate(element)) { 2151 this.setupField(element); 2152 } 2153 }); 2154 }, 2155 setupField(element) { 2156 if (! element) return; 2157 const onChange = !!element.dataset.onChange; 2158 if (elementWillValidate(element)) { 2159 const rules = getRules(element); 2160 if (Object.keys(rules).length > 0) { 2161 if (this.fields[element.name]) { 2162 Object.assign(this.fields[element.name].rules, rules); 2163 } else { 2164 this.fields[element.name] = { 2165 element, 2166 rules: rules, 2167 validateOnChange: onChange, 2168 state: { 2169 valid: null, 2170 rules: {} 2171 } 2172 } 2173 } 2174 } 2175 } else { 2176 console.error('Element will not validate', element); 2177 } 2178 }, 2179 onSubmit(event) { 2180 if (event.target.tagName === 'FORM') { 2181 event.preventDefault(); 2182 2183 this.validate() 2184 .then(() => event.target.submit()) 2185 .catch(invalidElements => {}); 2186 } 2187 }, 2188 onChange(event) { 2189 event.target.dataset.onChange = 'true'; 2190 if (!Object.keys(this.fields).length) { 2191 this.setupFields(formElement.elements); 2192 } 2193 if (!Object.keys(this.fields).includes(event.target.name)) { 2194 this.setupField(event.target); 2195 } 2196 const field = this.fields[event.target.name]; 2197 2198 this.validateField(field); 2199 field && field.element.removeAttribute('data-on-change') 2200 }, 2201 validateSafe() { 2202 return new Promise(resolve => this.validate().then(() => resolve(true)).catch(() => {})) 2203 }, 2204 validate() { 2205 if (!Object.keys(this.fields).length || !Object.keys(this.fields).length !== formElement.elements.length) { 2206 this.setupFields(formElement.elements); 2207 } 2208 return new Promise(async (resolve, reject) => { 2209 if (formElement.elements) { 2210 await raceSome(this.validateFields(), result => result !== true) 2211 const invalidFields = Object.values(this.fields).filter(field => !field.state.valid); 2212 this.state.valid = invalidFields.length === 0; 2213 if (this.state.valid) { 2214 resolve(); 2215 } else { 2216 if (options.scrollToFirstError && invalidFields.length > 0) { 2217 invalidFields[0].element.focus() 2218 invalidFields[0].element.select && invalidFields[0].element.select(); 2219 } 2220 reject(invalidFields.map(field => field.element)); 2221 } 2222 } 2223 }); 2224 }, 2225 validateFields() { 2226 const fields = Object.values(this.fields); 2227 2228 fields.forEach(field => { 2229 this.getMessageContainer(field).classList.remove(options.validClassName, options.invalidClassName) 2230 }); 2231 return fields.map(field => this.validateField(field)) 2232 }, 2233 validateField(field) { 2234 if (! field || ! elementWillValidate(field.element)) { 2235 return new Promise(resolve => resolve(true)) 2236 } 2237 2238 let value; 2239 if (field.element.type === 'checkbox') { 2240 value = getCheckedValues(field); 2241 } else if (field.element.type === 'radio') { 2242 value = getCheckedValues(field)[0] || undefined; 2243 } else if (field.element.tagName === 'SELECT' && field.element.multiple) { 2244 value = Array.from(field.element.selectedOptions).map(opt => opt.value); 2245 } else { 2246 value = field.element.value; 2247 } 2248 2249 const rules = field.rules || {}; 2250 2251 field.state.valid = true;
2252 this.showFieldState(field); 2253 2254 2255 const fieldValidations = Object.keys(rules).filter(rule => formValidationRules[rule]).map(async rule => { 2256 return runValidateFn.call(this, rule, rules[rule], value, field).then(result => { 2257 field.state.rules[rule] = result; 2258 return result; 2259 }) 2260 }); 2261 2262 return new Promise(resolve => { 2263 Promise.all(fieldValidations).then(results => { 2264 field.state.valid = !elementWillValidate(field.element) || rules.length === 0 || !results.some(isInvalidRuleResult) 2265 this.showFieldState(field); 2266 resolve(field.state.valid); 2267 }) 2268 }); 2269 }, 2270 getMessagesByField(field) { 2271 const messages = []; 2272 const invalidRules = Object.keys(field.state.rules).filter(rule => isInvalidRuleResult(field.state.rules[rule])); 2273 2274 field.rules && Object.keys(field.rules).forEach((rule) => { 2275 if (invalidRules.includes(rule)) { 2276 const customMessage = field.element.getAttribute('data-msg-' + rule); 2277 const message = customMessage ? customMessage : field.state.rules[rule]; 2278 const ruleOptions = JSON.parse(JSON.stringify(field.rules[rule])); 2279 2280 if (typeof message === 'undefined' || message === null || (typeof message !== 'string' && ! message.type)) { 2281 messages.push(hyva.strf('Validation rule "%0" failed.', rule)); 2282 } else if (Array.isArray(ruleOptions)) { 2283 ruleOptions.unshift(message.type ? message.content : message); 2284 const content = hyva.strf.apply(null, ruleOptions); 2285 messages.push(message.type ? {type: message.type, content} : content); 2286 } else { 2287 const content = hyva.strf(message.type ? message.content : message, ruleOptions) 2288 messages.push(message.type ? {type: message.type, content} : content); 2289 } 2290 } 2291 }); 2292 return messages; 2293 }, 2294 /** @deprecated */ 2295 getFieldWrapper(field) { 2296 return this.getMessageContainer(field) 2297 }, 2298 getMessageContainer(field) { 2299 let container; 2300 const pageSelector = field.element.getAttribute('data-validation-container') || options.pageMessagesContainerSelector; 2301 if (pageSelector) { 2302 container = document.querySelector(pageSelector) 2303 || containerNotFound(pageSelector, field.element) 2304 } else { 2305 const containerSelector = classNamesToSelector(options.fieldWrapperClassName); 2306 container = field.element.closest(containerSelector) 2307 || createMessageContainer(field.element, options.fieldWrapperClassName) 2308 || containerNotFound(containerSelector, field.element); 2309 } 2310 2311 return container; 2312 }, 2313 showFieldState(field) { 2314 const container = this.getMessageContainer(field), 2315 hasErrorMessages = hasMessagesWrapper.call(this, field, options.messagesWrapperClassName), 2316 messages = this.getMessagesByField(field).map(m => { 2317 return m.type !== 'html' ? escapeHtml(m.type ? m.content : m) : m.content; 2318 }); 2319 container.classList.toggle(options.validClassName, field.state.valid && ! hasErrorMessages); 2320 container.classList.toggle(options.invalidClassName, !field.state.valid || hasErrorMessages); 2321 this.createHtmlErrorMessage(field, messages); 2322 2323 if (field.state.valid) { 2324 field.element.removeAttribute('aria-invalid'); 2325 } else { 2326 field.element.setAttribute('aria-invalid', 'true'); 2327 if (! document.activeElement) { 2328 field.element.focus(); 2329 } 2330 } 2331 }, 2332 removeMessages(field, messagesClass) { 2333 if (! hasMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName)) { 2334 return; 2335 } 2336 2337 const msgWrapper = getMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName); 2338 const messages = msgWrapper.querySelectorAll(`[data-msg-field='${field.element.name}']`);
2339 Array.from(messages).forEach(msg => msg.remove()); 2340 if (msgWrapper && msgWrapper.childElementCount === 0) { 2341 field.element.removeAttribute('aria-errormessage'); 2342 field.element.removeAttribute('aria-describedby'); 2343 msgWrapper.remove(); 2344 } 2345 }, 2346 createErrorMessage(field, messages) { 2347 const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml) 2348 this.createHtmlErrorMessage(field, htmlMessages); 2349 }, 2350 createHtmlErrorMessage(field, messages) { 2351 this.removeMessages(field, options.messagesWrapperClassName); 2352 field.element.removeAttribute('aria-errormessage'); 2353 field.element.removeAttribute('aria-describedby'); 2354 2355 if (!field.state.valid) { 2356 const msgWrapper = this.addHtmlMessages(field, options.messagesWrapperClassName, messages); 2357 field.element.setAttribute('aria-errormessage', msgWrapper.id); 2358 field.element.setAttribute('aria-describedby', msgWrapper.id); 2359 } 2360 }, 2361 /** @deprecated */ 2362 createMessage(field, message) { 2363 return this.addMessages(field, options.messagesWrapperClassName, message); 2364 }, 2365 addMessages(field, messagesClass, messages) { 2366 const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml) 2367 return this.addHtmlMessages(field, messagesClass, htmlMessages); 2368 }, 2369 addHtmlMessages(field, messagesClass, htmlMessages) { 2370 const msgWrapper = getMessagesWrapper.call(this, field, messagesClass); 2371 2372 (Array.isArray(htmlMessages) ? htmlMessages : [htmlMessages]).forEach((htmlMessage) => { 2373 const li = document.createElement('li'); 2374 li.innerHTML = htmlMessage; 2375 li.setAttribute('data-msg-field', field.element.name); 2376 msgWrapper.appendChild(li); 2377 }); 2378 2379 return msgWrapper; 2380 }, 2381 setField(name, value) { 2382 this.fields[name].element.value = value; 2383 this.fields[name].element.dispatchEvent((new Event('input'))); 2384 this.validateField(this.fields[name]); 2385 } 2386 } 2387 } 2388 2389 hyva.formValidation = formValidation; 2390 hyva.formValidation.rules = formValidationRules; 2391 hyva.formValidation.setInputAttributeRuleName = (attrName, ruleName) => INPUT_ATTRIBUTE_RULES[attrName] = ruleName || attrName; 2392 hyva.formValidation.setInputTypeRuleName = (typeName, ruleName) => INPUT_TYPE_RULES[typeName] = ruleName || typeName; 2393 hyva.formValidation.addRule = (name, validator) => formValidationRules[name] = validator; 2394 }(window.hyva = window.hyva || {})); 2395 2396 window.addEventListener('alpine:init', () => { 2397 Alpine.data('hyva.formValidation', hyva.formValidation); 2398 Alpine.data('hyva.formValidation($el)', hyva.formValidation); 2399 }, {once: true}); 2400</script>
2400 2401<div class="page-wrapper"><header class="page-header"><a class="action skip sr-only focus:not-sr-only focus:absolute focus:z-40 focus:bg-white 2402 contentarea" 2403 href="#contentarea"> 2404 <span> 2405 Skip to Content </span> 2406</a> 2407
2407<script type="application/javascript" async 2408 src="https://static.klaviyo.com/onsite/js/TzSV8X/klaviyo.js"></script>
2408 2409 2410 <div x-data="initKlaviyoCustomerTracking()" 2411 @private-content-loaded.window="sendCustomerDataToKlaviyo(event.detail.data)"></div> 2412
2412<script> 2413 !function(){if(!window.klaviyo){window._klOnsite=window._klOnsite||[];try{window.klaviyo=new Proxy({},{get:function(n,i){return"push"===i?function(){var n;(n=window._klOnsite).push.apply(n,arguments)}:function(){for(var n=arguments.length,o=new Array(n),w=0;w<n;w++)o[w]=arguments[w];var t="function"==typeof o[o.length-1]?o.pop():void 0,e=new Promise((function(n){window._klOnsite.push([i].concat(o,[function(i){t&&t(i),n(i)}]))}));return e}}})}catch(n){window.klaviyo=window.klaviyo||[],window.klaviyo.push=function(){var n;(n=window._klOnsite).push.apply(n,arguments)}}}}(); 2414 2415 function initKlaviyoCustomerTracking() { 2416 return { 2417 sendCustomerDataToKlaviyo(data) { 2418 window.klaviyo = window.klaviyo || []; 2419 const customer = data.customer; 2420 2421 window.klaviyo.isIdentified().then((identified)=> { 2422 if(customer && customer.email && !identified) { 2423 window.klaviyo.identify({ 2424 '$email': customer.email, 2425 '$first_name': customer.firstname ? customer.firstname : '', 2426 '$last_name': customer.lastname ? customer.lastname : '' 2427 }); 2428 } 2429 }); 2430 } 2431 } 2432 } 2433 </script>
vendor: 1 bytes, line 2433
2433
2434<script> 2435 function initHeader() { 2436 return { 2437 searchOpen: false, 2438 cart: {}, 2439 isCartOpen: false, 2440 isMobile: true, 2441 // Defined in px 2442 downScrollThreshold: 300, 2443 upScrollThreshold: 150, 2444 headerTagElement: undefined, 2445 lastScrollPosition: window.scrollY, 2446 lastScrollDirection: undefined, 2447 2448 init() { 2449 this.headerTagElement = document.querySelector('header.page-header'); 2450 2451 this.onChangeMedia(this.$store.device.isDesktop); 2452 this.handleNotificationHeight(); 2453 2454 this.$watch('$store.device.isDesktop', (isDesktop) => { 2455 this.onChangeMedia(isDesktop); 2456 this.handleNotificationHeight(); 2457 }) 2458 2459 }, 2460 handleNotificationHeight() { 2461 this.headerTagElement.style.setProperty('--header-notification-height', `${this.$refs.headerNotification.offsetHeight}px`); 2462 }, 2463 handleScrollThreshold() { 2464 const { 2465 scrollY 2466 } = window; 2467 2468 // Determine if we are scrolling up or down 2469 const isScrollingDown = this.lastScrollPosition < scrollY; 2470 const isScrollingUp = this.lastScrollPosition > scrollY; 2471 2472 if (isScrollingDown && this.lastScrollDirection !== 'down') { 2473 this.lastScrollDirection = 'down'; 2474 this.scrollUpDistance = 0; 2475 } 2476 2477 if (isScrollingUp && this.lastScrollDirection !== 'up') { 2478 this.lastScrollDirection = 'up'; 2479 this.scrollDownDistance = 0; 2480 } 2481 2482 // If scrolling down and exceeding the down scroll threshold, hide notification 2483 if (isScrollingDown && scrollY > this.downScrollThreshold && !this.isNotificationHidden) { 2484 this.handleHideNotification(true, scrollY); 2485 this.lastScrollPosition = scrollY; 2486 return; 2487 } 2488 2489 // If scrolling up and exceeding the up scroll threshold (100px), show notification again 2490 if (isScrollingUp && (scrollY < this.lastScrollPosition - this.upScrollThreshold)) { 2491 this.handleHideNotification(false, scrollY); 2492 this.lastScrollPosition = scrollY; 2493 return; 2494 } 2495 2496 // Handle the scrolling up or down thresholds with reset behavior 2497 if (isScrollingDown) { 2498 this.scrollDownDistance += scrollY - this.lastScrollPosition; 2499 if (this.scrollDownDistance > this.downScrollThreshold) { 2500 this.handleHideNotification(true, scrollY); 2501 } 2502 } 2503 2504 if (isScrollingUp) { 2505 this.scrollUpDistance += this.lastScrollPosition - scrollY; 2506 if (this.scrollUpDistance > 100) { 2507 this.handleHideNotification(false, scrollY); 2508 } 2509 } 2510 2511 // Update the last scroll position 2512 this.lastScrollPosition = scrollY; 2513 }, 2514 handleHideNotification(isHidden, scrollPosition) { 2515 if (isHidden) { 2516 this.headerTagElement.classList.add('page-header-hidden-notification'); 2517 } else { 2518 this.headerTagElement.classList.remove('page-header-hidden-notification'); 2519 } 2520 2521 this.isNotificationHidden = isHidden; 2522 this.lastScrollPosition = scrollPosition; 2523 }, 2524 getData(data) { 2525 if (data.cart) { 2526 this.cart = data.cart 2527 } 2528 }, 2529 isCartEmpty() { 2530 return !this.cart.summary_count 2531 }, 2532 toggleCart(event) { 2533 if (event.detail && event.detail.isOpen !== undefined) { 2534 this.isCartOpen = event.detail.isOpen 2535 if (!this.isCartOpen && this.$refs && this.$refs.cartButton) { 2536 this.$refs.cartButton.focus() 2537 } 2538 } else { 2539 this.isCartOpen = true 2540 } 2541 }, 2542 onChangeMedia(isDesktop) { 2543 if (!isDesktop) { 2544 this.$refs.searchContainerMobile.appendChild(this.$refs.searchForm); 2545 } else { 2546 const desktopSearchContainer = this.$refs.searchContainerDesktop; 2547 if (desktopSearchContainer.firstElementChild) { 2548 desktopSearchContainer.replaceChild(this.$refs.searchForm, desktopSearchContainer.firstElementChild); 2549 } else { 2550 desktopSearchContainer.appendChild(this.$refs.searchForm); 2551 } 2552 } 2553 }, 2554 toggleSearch() { 2555 this.searchOpen = !this.searchOpen; 2556 if (this.searchOpen) { 2557 2558 this.$dispatch('open-search-popup', {}); 2559 2560 // Focus on search input when opened 2561 setTimeout(() => { 2562 const searchInput = this.$refs.searchContainerMobile.querySelector('input[type="text"]'); 2563 if (searchInput) { 2564 searchInput.focus(); 2565 } 2566 }, 100); 2567 } 2568 }, 2569 closeSearch() { 2570 this.searchOpen = false;
2571 } 2572 } 2573 } 2574 2575 function initCompareHeader() { 2576 return { 2577 compareProducts: null, 2578 itemCount: 0, 2579 receiveCompareData(data) { 2580 if (data['compare-products']) { 2581 this.compareProducts = data['compare-products']; 2582 this.itemCount = this.compareProducts.count; 2583 } 2584 } 2585 } 2586 } 2587</script>
2587 2588<div id="header" 2589 x-data="initHeader()" 2590 @scroll.window="handleScrollThreshold" 2591 @private-content-loaded.window="getData(event.detail.data)" 2592> 2593 <div class="w-full h-full" x-ref="headerNotification"> 2594 <div class="relative"> 2595 <div class="header-notification [&_*]:!max-h-[50px] [&_*]:md:!max-h-10 [&_.pagebuilder-overlay-wrapper]:flex 2596 [&_.pagebuilder-overlay-wrapper]:justify-center [&_.pagebuilder-overlay-wrapper_*]:!flex 2597 [&_.pagebuilder-overlay-wrapper_*]:max-md:flex-wrap [&_.pagebuilder-overlay-wrapper_*]:!gap-x-1 2598 [&_.pagebuilder-overlay-wrapper_*]:!items-center [&_.pagebuilder-overlay-wrapper_*]:justify-center 2599 [&_.pagebuilder-poster-content>div]:w-full [&_.pagebuilder-poster-content>div]:justify-center" 2600 > 2601 <style>#html-body [data-pb-style=WLJFXII]{justify-content:center;display:flex;flex-direction:column;background-color:#532e91;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;text-align:center}#html-body [data-pb-style=VM4HDKS]{text-align:center;min-height:40px}#html-body [data-pb-style=EEQ7T1Q]{background-position:left top;background-size:cover;background-repeat:no-repeat;text-align:center;min-height:40px}#html-body [data-pb-style=S71GEM0]{min-height:40px;padding:0;background-color:transparent}#html-body [data-pb-style=SRABRMM]{background-position:left top;background-size:cover;background-repeat:no-repeat;text-align:center;min-height:40px}#html-body [data-pb-style=UH6V9IJ]{min-height:40px;padding:0;background-color:transparent}</style><div data-content-type="row" data-appearance="full-bleed" data-enable-parallax="0" data-parallax-speed="0.5" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-background-lazy-load="false" data-element="main" data-pb-style="WLJFXII"><div class="pagebuilder-slider" data-content-type="slider" data-appearance="default" data-autoplay="true" data-autoplay-speed="4000" data-fade="true" data-infinite-loop="true" data-show-arrows="false" data-show-dots="false" data-element="main" data-pb-style="VM4HDKS"><div data-content-type="slide" data-slide-name="" data-appearance="poster" data-show-button="never" data-show-overlay="never" data-element="main"><div data-element="empty_link"><div class="pagebuilder-slide-wrapper" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-background-lazy-load="false" data-element="wrapper" data-pb-style="EEQ7T1Q"><div class="pagebuilder-overlay-wrapper"><div class="pagebuilder-overlay pagebuilder-poster-overlay" data-overlay-color="" aria-label="" title="" data-element="overlay" data-pb-style="S71GEM0"><div class="pagebuilder-poster-content"><div data-element="content"><p style="text-align: center;"><span style="color: rgb(0, 0, 0); font-size: 15px;"><strong><span style="color: rgb(255, 255, 255); font-size: 14px;"><span data-teams="true">Get Free Shipping for Canadian deliveries on orders above </span>$99! <span style="color: rgb(255, 255, 255); text-decoration: underline;">Shop now</span></span></strong></span></p></div></div></div></div></div></div></div><div data-content-type="slide" data-slide-name="" data-appearance="poster" data-show-button="never" data-show-overlay="never" data-element="main"><div data-element="empty_link"><div class="pagebuilder-slide-wrapper" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-background-lazy-load="false" data-element="wrapper" data-pb-style="SRABRMM"><div class="pagebuilder-overlay-wrapper"><div class="pagebuilder-overlay pagebuilder-poster-overlay" data-overlay-color="" aria-label="" title="" data-element="overlay" data-pb-style="UH6V9IJ"><div class="pagebuilder-poster-content"><div data-element="content"><p style="text-align: center;"><span style="font-size: 14px; color: rgb(255, 255, 255);"><strong>Canada’s Chocolatier since 1907. <a style="color: rgb(255, 255, 255);" tabindex="0" href="https://www.purdys.com/purdys-heritage" target="_blank" rel="noopener">LEARN MORE</a></strong></span></p></div></div></div></div></div></div></div></div></div> </div> 2602 </div> 2603 </div> 2604 2605 <div class="bg-white lg:relative py-3 md:py-4 border-b border-primary-lightest lg:border-b-0" :class="{ 'border-transparent': searchOpen }"> 2606 <div class="px-4 flex gap-5 md:px-5 2xl:px-10 lg:gap-10 justify-between items-center m-auto min-[1660px]:!max-w-[1740px]"> 2607 <div 2608 class="header-left-links text-sm flex-1 hidden lg:block [&_ul]:ml-0 [&_ul]:gap-x-5 [&_ul]:flex [&_li]:before:content-[''] 2609 [&_li]:pl-0 [&_li]:m-0 [&_a]:no-underline [&_a]:font-normal [&_a]:text-primary-dark" 2610 > 2611 <div data-content-type="html" data-appearance="default" data-element="main" data-decoded="true"><ul id="nav-top"> 2612 <li><a href="/corporate-gifts"><b>CORPORATE GIFTS</b></a></li> 2613 <li><a href="https://fundraising.purdys.com/"><b>FUNDRAISING</b></a></li> 2614 <li><a href="https://group.purdys.com/"><b>GROUP SAVINGS</b></a></li> 2615 <!--<li><a href="/doordash-redirect?utm_source=purdys&utm_medium=website&utm_campaign=doordashlink"><b>DOORDASH</b></a></li>--> 2616</ul> 2617</div> </div> 2618 2619 <!--Logo--> 2620 <a class="logo block !m-0" href="https://www.purdys.com/" 2621 aria-label="Go to Home page"> 2622 <img src="https://www.purdys.com/media/logo/stores/1/purdys-chocolatier-logo.png" 2623 alt="Purdys Chocolatier Logo" 2624 class="!h-[60px]" 2625 style="width: auto" 2626 width="auto" /> 2627 </a> 2628 2629 <div class="flex gap-6 lg:gap-5 items-center lg:flex-1"> 2630 2631 <!--Search desktop--> 2632 <div x-ref="searchContainerDesktop" class="header-search-container hidden lg:block lg:flex-1"> 2633 <div> 2634 <div x-data="initLiveSearch" 2635 x-init="initPopover" 2636 @close-search.window="closePopover" 2637 @open-search.window="openPopover" 2638 @keydown.escape="closePopover" 2639 class="relative" 2640> 2641 <search role="search"> 2642 <form class="form minisearch w-full flex md:relative lg:max-w-80 ml-auto" 2643 id="search_mini_form" 2644 action="https://www.purdys.com/catalogsearch/result/" 2645 method="get" 2646 > 2647 <div class="relative w-full"> 2648 <label class="hidden" for="search" data-role="minisearch-label">
2649 <span>Search</span> 2650 </label> 2651 <input id="search" 2652 class="form-input flex-1 px-3 py-2 rounded-sm outline-none w-full min-h-12 hover:border-primary focus:border-primary focus:shadow-none" 2653 type="text" 2654 x-ref="searchRef" 2655 :value="queryPhrase" 2656 name="q" 2657 value="" 2658 placeholder="Type to search..." 2659 maxlength="128" 2660 role="combobox" 2661 aria-haspopup="false" 2662 aria-autocomplete="both" 2663 autocomplete="off" 2664 aria-expanded="false" 2665 @input="handleInput" 2666 @click.away="closePopover" 2667 /> 2668 <button type='submit' 2669 title="Search" 2670 x-show="queryPhrase === ''" 2671 class='bg-primary text-white rounded-e-sm px-3 outline-none absolute end-0 top-0 h-full 2672 rounded-r border-0 hover:bg-primary hover:border-0' 2673 aria-label="Search"> 2674 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"> 2675<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75049 1.49969C4.74642 1.49969 1.50049 4.74563 1.50049 8.74969C1.50049 12.7538 4.74642 15.9997 8.75049 15.9997C12.7546 15.9997 16.0005 12.7538 16.0005 8.74969C16.0005 4.74563 12.7546 1.49969 8.75049 1.49969ZM0.000488281 8.74969C0.000488281 3.9172 3.918 -0.000305176 8.75049 -0.000305176C13.583 -0.000305176 17.5005 3.9172 17.5005 8.74969C17.5005 13.5822 13.583 17.4997 8.75049 17.4997C3.918 17.4997 0.000488281 13.5822 0.000488281 8.74969Z" fill="white"/> 2676<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4001 14.3993C14.7108 14.0887 15.2144 14.0887 15.5251 14.3993L19.7675 18.6418C20.0781 18.9524 20.0781 19.4561 19.7675 19.7667C19.4569 20.0774 18.9532 20.0774 18.6426 19.7667L14.4001 15.5243C14.0895 15.2136 14.0895 14.71 14.4001 14.3993Z" fill="white"/> 2677</svg> 2678 </button> 2679 <button 2680 @click.prevent="queryString = ''; clearInput()" 2681 x-cloak 2682 x-show="queryPhrase !== ''" 2683 title="Clear" 2684 class="bg-primary text-white rounded-e-sm px-3 outline-none absolute 2685 end-0 top-0 h-full rounded-r border-0 hover:bg-primary hover:border-0 hover:text-white" 2686 aria-label="Clear"> 2687 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"> 2688<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 2689<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 2690</svg> 2691 </button> 2692 <template x-if="queryPhrase != ''"> 2693 <a id="clearInput" class="absolute right-[60px] top-[11px] hover:text-primary" @click.prevent="clearInput" href="#">Clear</a> 2694 </template> 2695 </div> 2696 </form> 2697 </search> 2698 <div x-show="searchOpen" 2699 class="bg-black opacity-[0.6] fixed w-[100vw] h-[100vh] left-0 mt-[18px]" 2700 style="display: none;" 2701 @click="closePopup"></div> 2702 <section 2703 x-show="searchOpen" 2704 class="livesearch-overlay search-autocomplete overflow-y-scroll !h-[calc(100dvh-var(--height-header))] md:!h-auto md:overflow-y-auto" 2705 :class="getSearchClasses" 2706 style="display: none;" 2707 aria-label="Search Suggestions" 2708 x-transition:enter='transition-all duration-300 ease-out' 2709 x-transition:enter-start='opacity-0' 2710 x-transition:enter-end='opacity-100' 2711 x-transition:leave='transition-all duration-200 ease-in' 2712 x-transition:leave-start='opacity-100' 2713 x-transition:leave-end='opacity-0' 2714 > 2715 <!-- No Results Header --> 2716 <template x-if="showResults && !liveSearchTotalCount && !isLoading"> 2717 <div class="max-w-[1660px] mx-auto px-4 md:px-0"> 2718 <div class="flex items-center justify-between"> 2719 <h3 class="font-bold text-xl md:text-3xl" 2720 x-text="'No\u0020results\u0020found\u0020for\u003A ' + '\'' + queryPhrase + '\''"></h3> 2721 <a href="#" @click.prevent='closePopup'> 2722 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="none" class="text-black" role="img"> 2723<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 2724<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 2725<title>x</title></svg> 2726 </a> 2727 </div> 2728 <p class="pt-3">
2728We can't find any item matching your search. Please try another search.</p> 2729 </div> 2730 </template> 2731 <!-- Results Header --> 2732 <template x-if="showResults"> 2733 <div class="flex items-center justify-between max-w-[1660px] mx-auto px-4 md:px-0"> 2734 <h3 class="font-bold text-xl md:text-3xl !m-0" x-text="'Search\u0020results\u0020for\u003A ' + '\'' + queryPhrase + '\''"></h3> 2735 <a href="#" @click.prevent='closePopup'> 2736 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="none" class="text-black" role="img"> 2737<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 2738<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 2739<title>x</title></svg> 2740 </a> 2741 </div> 2742 </template> 2743 <div x-show="liveSearchTotalCount && !isLoading" class="livesearch-overlay-content" 2744 x-transition:enter='transition-all duration-300 ease-out' 2745 x-transition:enter-start='opacity-0' 2746 x-transition:enter-end='opacity-100' 2747 x-transition:leave='transition-all duration-200 ease-in' 2748 x-transition:leave-start='opacity-100' 2749 x-transition:leave-end='opacity-0'> 2750 <template x-if="showSuggestions"> 2751 <div class="flex flex-col md:max-h-[393px]"> 2752 <p class="text-lg pb-[10px] border-b border-grey-muted"> 2753 <strong> 2754 Suggestions </strong> 2755 </p> 2756 <ul class="-mx-2 space-y-1 !border-0 pl-[8px]" role="list"> 2757 <template x-for="suggestion in liveSearchSuggestions"> 2758 <li class="before:hidden !border-0 !p-0 pl-0 hover:!bg-transparent"> 2759 <button 2760 x-html="getSuggestionText(suggestion)" 2761 @click="searchSuggestion" 2762 @mouseenter="onSuggestionHover(suggestion)" 2763 type="button" 2764 class="w-full p-2 text-left capitalize border-0 pl-0 2765 hover:text-primary-hover hover:underline 2766 hover:cursor-pointer hover:bg-transparent hover:border-0" 2767 ></button> 2768 </li> 2769 </template> 2770 </ul> 2771 <div class="pt-3 md:pt-5 mt-auto pb-2.5 md:pb-0"> 2772 <a href='#' @click.prevent="redirectToSearchPage()" class='flex gap-1 items-center !text-primary'>
2773 <span x-text="getSeeResultsText()"></span> 2774 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="rtl:rotate-180" width="20" height="20" aria-hidden="true"> 2775 <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/> 2776</svg> 2777 </a> 2778 </div> 2779 </div> 2780 </template> 2781 <template x-if="showResults"> 2782 <div class="lg:col-span-2"> 2783 <p class="text-lg pb-[10px] border-b-[1px] border-grey-muted"> 2784 <strong> 2785 Product Suggestions </strong> 2786 </p> 2787 <ul class="livesearch-overlay-content-products !border-0" role="list"> 2788 <template x-for="product in liveSearchProducts"> 2789 <li class="before:hidden pl-0 !p-0 !border-0 hover:!bg-transparent"> 2790 <button 2791 class="w-full flex gap-4 text-left cursor-pointer 2792 product-suggestion-button border-0 hover:bg-transparent hover:border-0" 2793 @click="handleProductClick" 2794 > 2795 <img 2796 :src="product.image" 2797 :alt="product.name" 2798 width="80" 2799 height="80" 2800 class="w-[80px] h-[80px] shrink-0 object-contain" 2801 > 2802 <div class="flex flex-col justify-center w-full"> 2803 <div class="flex flex-row justify-between gap-6"> 2804 <p><strong class="product-name max-w-[287px]" x-text="product.name"></strong></p> 2805 <template x-if="product.price"> 2806 <p class="font-bold" x-text="showProductPrice"></p> 2807 </template> 2808 </div> 2809 </div> 2810 </button> 2811 </li> 2812 </template> 2813 </ul> 2814 <div class="py-5 md:pb-0"> 2815 <a href="#" @click.prevent="redirectToSearchPage(true)" class="flex gap-1 items-center !text-primary">
2816 <span x-text="getSeeResultsText(true)"></span> 2817 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="rtl:rotate-180" width="20" height="20" aria-hidden="true"> 2818 <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/> 2819</svg> 2820 </a> 2821 </div> 2822 </div> 2823 </template> 2824 </div> 2825 </section> 2826</div> 2827
2828<script> 2829 'use strict'; 2830 2831 // Global search instance storage to persist across Alpine re-initializations 2832 window.liveSearchInstance = null; 2833 window.liveSearchInitialized = false; 2834 2835 window.addEventListener( 2836 'alpine:init', 2837 () => Alpine.data('initLiveSearch', () => ({ 2838 queryPhrase: '', 2839 liveSearchProducts: [], 2840 liveSearchSuggestions: [], 2841 liveSearchTotalCount: 0, 2842 isLoading: false, 2843 minQueryLength: 3, 2844 pageSize: 8, 2845 searchOpen: false, 2846 search: null, 2847 searchInitialized: false, 2848 isSuggestionHoverMode: false, 2849 suggestionHover: '', 2850 searchId: "2cc69165-2270-494c-893f-c00d0bb35bd7_" + 2851 "base_" + 2852 "main_website_store_" + 2853 "default", 2854 2855 initPopover() { 2856 this.initSearch(); 2857 this.initTogglePopover(); 2858 2859 const self = this; 2860 2861 window.addEventListener('open-search-popup', function (event) { 2862 if (self.liveSearchTotalCount > 0) { 2863 self.openPopover(); 2864 } 2865 }); 2866 2867 }, 2868 2869 async initSearch() { 2870 // Check if we already have a global instance 2871 if (window.liveSearchInitialized && window.liveSearchInstance) { 2872 this.search = window.liveSearchInstance; 2873 this.searchInitialized = true; 2874 return; 2875 } 2876 2877 if (this.searchInitialized || this.searchInitializing) { 2878 return; 2879 } 2880 2881 this.searchInitializing = true; 2882 2883 try { 2884 // Set up storefront events 2885 if (window.magentoStorefrontEvents) { 2886 window.magentoStorefrontEvents.context.setSearchExtension({ 2887 version: "4.4.1" 2888 }); 2889 } 2890 2891 // Wait for MagentoLiveSearch to be available (with shorter timeout) 2892 await this.waitForMagentoLiveSearch(); 2893 2894 if (!window.MagentoLiveSearch || typeof window.MagentoLiveSearch !== 'function') { 2895 throw new Error('MagentoLiveSearch is not available'); 2896 } 2897 2898 this.search = new window.MagentoLiveSearch({ 2899 environmentId: "2cc69165-2270-494c-893f-c00d0bb35bd7", 2900 websiteCode: "base", 2901 storeCode: "main_website_store", 2902 storeViewCode: "default", 2903 apiKey: "search_gql", 2904 }); 2905 2906 if (!this.search) { 2907 throw new Error('MagentoLiveSearch constructor returned null/undefined'); 2908 } 2909 2910 // Store globally to persist across Alpine re-initializations 2911 window.liveSearchInstance = this.search; 2912 window.liveSearchInitialized = true; 2913 this.searchInitialized = true; 2914 } catch (error) { 2915 console.error('MagentoLiveSearch could not be initialized:', error); 2916 this.searchInitialized = false; 2917 this.search = null; 2918 } finally { 2919 this.searchInitializing = false; 2920 } 2921 }, 2922 2923 waitForMagentoLiveSearch() { 2924 return new Promise((resolve) => { 2925 // If already available, resolve immediately 2926 if (window.MagentoLiveSearch) { 2927 resolve(); 2928 return; 2929 } 2930 2931 // Otherwise set up a watcher with faster polling 2932 const checkInterval = setInterval(() => { 2933 if (window.MagentoLiveSearch) { 2934 clearInterval(checkInterval); 2935 resolve(); 2936 } 2937 }, 50); // Faster polling - 50ms instead of 100ms 2938
2939 // Shorter timeout - 3 seconds instead of 10 2940 setTimeout(() => { 2941 clearInterval(checkInterval); 2942 resolve(); 2943 }, 3000); 2944 }); 2945 }, 2946 2947 clearInput() { 2948 this.$refs.searchRef.value = ''; 2949 this.liveSearchProducts = []; 2950 this.liveSearchSuggestions = []; 2951 this.liveSearchTotalCount = 0; 2952 this.queryPhrase = ''; 2953 2954 this.closePopover(); 2955 }, 2956 2957 redirectToSearchPage(isSuggestion = false) { 2958 let searchTerm = this.queryPhrase; 2959 2960 if (isSuggestion) { 2961 // Use suggestionHover if available, otherwise fall back to queryPhrase 2962 searchTerm = this.suggestionHover || this.queryPhrase; 2963 } 2964 2965 window.location.href = '/catalogsearch/result/?q=' + encodeURIComponent(searchTerm); 2966 }, 2967 2968 buildSearch() { 2969 window.magentoStorefrontEvents.context.setSearchExtension({ 2970 version: "4.4.1" 2971 }); 2972 2973 // Wait for MagentoLiveSearch to be available 2974 return new Promise((resolve) => { 2975 const checkForMagentoLiveSearch = () => { 2976 if (window.MagentoLiveSearch) { 2977 resolve(new MagentoLiveSearch({ 2978 environmentId: "2cc69165-2270-494c-893f-c00d0bb35bd7", 2979 websiteCode: "base", 2980 storeCode: "main_website_store", 2981 storeViewCode: "default", 2982 apiKey: "search_gql", 2983 })); 2984 } else { 2985 setTimeout(checkForMagentoLiveSearch, 100); 2986 } 2987 }; 2988 2989 checkForMagentoLiveSearch(); 2990 }); 2991 2992 }, 2993 2994 // this will open the popover, whenever the search-input has focus 2995 initTogglePopover() { 2996 const popover = document.getElementById('search'); 2997 2998 popover.addEventListener('focus', () => { 2999 if (this.isQueryLengthValid()) { 3000 this.openPopover(); 3001 } else { 3002 this.closePopover(); 3003 } 3004 }); 3005 }, 3006 3007 closePopover() { 3008 this.searchOpen = false; 3009 // Restore body scroll when closing search overlay 3010 document.body.style.overflow = ''; 3011 }, 3012 3013 closePopup() { 3014 this.searchOpen = false; 3015 // Restore body scroll when closing search overlay 3016 document.body.style.overflow = ''; 3017 }, 3018 3019 openPopover() { 3020 this.searchOpen = true; 3021 3022 if (window.innerWidth <= 768) { 3023 // Lock body scroll when opening search overlay 3024 document.body.style.overflow = 'hidden'; 3025 } 3026 }, 3027 3028 handleInput() { 3029 this.queryPhrase = this.$el.value; 3030 this.suggestionHover = ''; 3031 this.isSuggestionHoverMode = false; 3032 3033 if (!this.isQueryLengthValid()) { 3034 if (this.searchOpen){ 3035 this.closePopover(); 3036 } 3037 return; 3038 } 3039 3040 this.getProducts(); 3041 this.$nextTick(() => { 3042 this.openPopover(); 3043 }); 3044 }, 3045 3046 showProductPrice(){ 3047 return this.product.price ? hyva.formatPrice(this.product.price) : ''; 3048 }, 3049 3050 handleProductClick() { 3051 window.magentoStorefrontEvents.context.setSearchResults( 3052 window.magentoStorefrontEvents.context.getSearchResults() 3053 ); 3054 3055 window.magentoStorefrontEvents.publish.searchProductClick("search-bar", `${this.product.sku}`); 3056 3057 // Timeout to prevent submission before event has fired. 3058 setTimeout(() => window.location.href = this.product.url, 500); 3059 }, 3060 3061 // this will submit the form with the clicked suggestion as the search-phrase 3062 searchSuggestion() { 3063 document.getElementById('search').value = this.suggestion; 3064 document.getElementById('search_mini_form').submit(); 3065 }, 3066 3067 showResults(){ 3068 return this.liveSearchProducts.length >= 1 && this.isQueryLengthValid() || this.isSuggestionHoverMode; 3069 }, 3070 3071 showSuggestions(){ 3072 return this.liveSearchSuggestions.length >= 1 && this.isQueryLengthValid(); 3073 }, 3074 3075 isQueryLengthValid() { 3076 return this.queryPhrase.length >= this.minQueryLength; 3077 }, 3078 3079 getSearchClasses() { 3080 return this.searchOpen ? 'block' : 'hidden'; 3081 }, 3082 getSuggestionText(suggestion) { 3083 const matches = suggestion.match(new RegExp(this.queryPhrase, 'gmi')); 3084 3085 matches.map((match) => { 3086 suggestion = suggestion.replaceAll(match, '<b>' + match + '</b>') 3087 }); 3088 3089 return suggestion; 3090 }, 3091 async onSuggestionHover(suggestion) { 3092 this.isSuggestionHoverMode = true; 3093 this.suggestionHover = suggestion; 3094 3095 await this.getProducts(); 3096 3097 this.queryPhrase = suggestion; 3098 }, 3099 onSuggestionUnhover() { 3100 this.isSuggestionHoverMode = false;
3101 this.suggestionHover = ''; 3102 }, 3103 getSeeResultsText(isSuggestion = false) { 3104 const text = 'See\u0020\u0025total\u0020results\u0020for\u0020\u0022\u0025term\u0022'; 3105 3106 if (isSuggestion && this.isSuggestionHoverMode) { 3107 return text.replace('%total', this.liveSearchTotalCount).replace('%term', this.suggestionHover); 3108 } 3109 3110 return text.replace('%total', this.liveSearchTotalCount).replace('%term', this.queryPhrase); 3111 }, 3112 3113 // get products from fetch-request and update liveSearchProducts and liveSearchSuggestions 3114 async getProducts() { 3115 this.searchOpen = true; 3116 3117 if (!this.isSuggestionHoverMode) { 3118 this.isLoading = true; 3119 } 3120 3121 // Make sure search is initialized 3122 if (!this.searchInitialized && !this.searchInitializing) { 3123 await this.initSearch(); 3124 } else if (this.searchInitializing) { 3125 // Wait for ongoing initialization to complete (reduced timeout) 3126 let attempts = 0; 3127 while (this.searchInitializing && attempts < 20) { // 2 seconds max instead of 5 3128 await new Promise(resolve => setTimeout(resolve, 100)); 3129 attempts++; 3130 } 3131 } 3132 3133 // If still not initialized, try to use global instance as fallback 3134 if (!this.search) { 3135 if (window.liveSearchInstance) { 3136 this.search = window.liveSearchInstance; 3137 this.searchInitialized = true; 3138 } else { 3139 console.error('MagentoLiveSearch could not be initialized'); 3140 this.isLoading = false; 3141 return; 3142 } 3143 } 3144 3145 const result = await this.getLiveSearchProducts(); 3146 this.isLoading = false; 3147 3148 this.liveSearchProducts = result.data.productSearch.items.map(item => ({ 3149 name: item.product.name, 3150 price: item.product.price_range.maximum_price.regular_price.value, 3151 currency: item.product.price_range.maximum_price.regular_price.currency, 3152 image: item.product.thumbnail?.url || item.product.image?.url || 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 3153 url: item.product.canonical_url, 3154 id: item.product.id, 3155 sku: item.product.sku 3156 })); 3157 3158 this.liveSearchSuggestions = result.data.productSearch.suggestions 3159 this.liveSearchTotalCount = result.data.productSearch.total_count; 3160 3161 //search-results-view 3162 }, 3163 3164 // retrieve products from magento live search service 3165 async getLiveSearchProducts() { 3166 this.searchRequestId = Math.random().toString(16).slice(2); 3167 3168 if (!this.search) { 3169 return { 3170 data: { 3171 productSearch: { 3172 items: [], 3173 suggestions: [], 3174 total_count: 0, 3175 page_info: { 3176 current_page: 1, 3177 page_size: this.pageSize 3178 }, 3179 facets: [] 3180 } 3181 } 3182 }; 3183 } 3184 3185 const searchQuery = this.isSuggestionHoverMode ? this.suggestionHover : this.queryPhrase; 3186 3187 const searchCtx = { 3188 units: [{ 3189 searchUnitId: "search-bar", 3190 queryTypes: ["products"], 3191 phrase: searchQuery, 3192 currentPage: 1, 3193 pageSize: this.pageSize, 3194 filter:[ 3195 {attribute:"visibility",in:["Search","Catalog, Search"]}, 3196 {attribute:"inStock",eq:"true"} 3197 ], 3198 sort: [{attribute: "relevance", direction: "DESC"}], 3199 searchRequestId: this.searchRequestId 3200 }] 3201 }; 3202 3203 window.magentoStorefrontEvents.context.setSearchInput(searchCtx); 3204 window.magentoStorefrontEvents.publish.searchRequestSent("search-bar"); 3205 const before = Date.now(); 3206 let result; 3207 3208 try { 3209 // Here we call the actual search method 3210 result = await this.search.productSearch({ 3211 phrase: searchQuery, 3212 page_size: this.pageSize, 3213 sort: [{attribute: "relevance", direction: "DESC"}], 3214 filter: [ 3215 {attribute: "visibility", in: ["Search", "Catalog, Search"]}, 3216 {attribute: "inStock", eq: "true"} 3217 ], 3218 current_page: 1, 3219 data: { 3220 products: true, 3221 facets: true, 3222 suggestions: true 3223 }, 3224 context: { 3225 customerGroup: "b6589fc6ab0dc82cf12099d1c2d40ab994e8410c" 3226 } 3227 }); 3228 } catch (error) { 3229 return { data: { productSearch: { items: [], suggestions: [], total_count: 0, page_info: { current_page: 1, page_size: this.pageSize } } } }; 3230 } 3231 3232 const after = Date.now(); 3233 3234 const productItems = result.data.productSearch.items || []; 3235 const suggestions = result.data.productSearch.suggestions || []; 3236 const facets = result.data.productSearch.facets || []; 3237 3238 const formattedProductItems = productItems.map((item, key) => ({ 3239 name: item.product.name, 3240 sku: item.product.sku, 3241 url: item.product.canonical_url, 3242 imageUrl: item.product.image ? item.product.image.url : 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 3243 price: item.product.price_range.minimum_price.final_price.value, 3244 rank: key, 3245 })); 3246 3247 const formattedSuggestions = suggestions.map((item, key) => ({ 3248 suggestion: item, 3249 rank: key, 3250 })); 3251 3252 const formattedFacets = facets.map((facet) => ({ 3253 attribute: facet.attribute, 3254 title: facet.title, 3255 type: facet.type || "PINNED", 3256 buckets: facet.buckets.map((bucket) => bucket), 3257 })); 3258 3259 const searchCtxUpdated = { 3260 units: [{ 3261 searchUnitId: "search-bar", 3262 searchRequestId: this.searchRequestId, 3263 executionTime: after - before, 3264 products: formattedProductItems, 3265 categories: [], 3266 suggestions: this.isSuggestionHoverMode ? this.suggestions : formattedSuggestions, 3267 page: result.data.productSearch.page_info.current_page, 3268 perPage: result.data.productSearch.page_info.page_size, 3269 facets: formattedFacets 3270 }] 3271 }; 3272 3273 if (this.isSuggestionHoverMode) { 3274 this.liveSearchProducts = result.data.productSearch.items.map(item => ({ 3275 name: item.product.name, 3276 price: item.product.price_range.maximum_price.regular_price.value, 3277 currency: item.product.price_range.maximum_price.regular_price.currency, 3278 image: item.product.thumbnail?.url || item.product.image?.url || 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 3279 url: item.product.canonical_url, 3280 id: item.product.id, 3281 sku: item.product.sku 3282 })); 3283 } 3284 3285 window.magentoStorefrontEvents.context.setSearchResults(searchCtxUpdated); 3286 window.magentoStorefrontEvents.publish.searchResponseReceived("search-bar"); 3287 3288 return result; 3289 } 3290 })), 3291 {once: true} 3292 ) 3293</script>
3293 3294 </div> 3295 </div> 3296 3297 <a href="https://www.purdys.com/shops/" class="header-shops items-center gap-x-2 no-underline text-primary-dark hidden lg:flex hover:no-underline hover:text-primary-dark"> 3298 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 24" fill="none" aria-hidden="true"> 3299<path fill-rule="evenodd" clip-rule="evenodd" d="M19.847 11.7852C19.9882 10.9933 19.9999 10.4413 19.9999 10.1893V10.0453C20.0235 3.23 13.2696 -2.00149 6.10395 0.746241C2.37403 2.1861 0.00900526 5.97774 0.00900526 10.0453C-0.00276104 10.1533 -0.0380599 10.8733 0.197266 12.0132C0.199722 12.0207 0.202178 12.0277 0.204526 12.0343C0.213431 12.0597 0.220799 12.0807 0.220799 12.1091C0.267864 12.3491 0.326695 12.5771 0.397293 12.8171C0.467891 13.0571 0.550255 13.321 0.644385 13.597C1.62099 16.3327 4.05661 20.2564 9.98682 24H9.99859C15.6464 20.4363 18.1056 16.7167 19.1881 14.005C19.1881 13.981 19.1998 13.957 19.2116 13.945C19.494 13.261 19.7176 12.5411 19.847 11.7852ZM17.4037 13.0815C17.6191 12.5433 17.7811 11.9997 17.8757 11.4476L17.878 11.4341C17.9934 10.7868 17.9999 10.3569 17.9999 10.1893V10.0384C18.0188 4.58613 12.6344 0.385611 6.82215 2.61284C3.9457 3.7243 2.00901 6.71462 2.00901 10.0453V10.154L2.00075 10.2298C2.0012 10.2338 1.99618 10.3343 2.00725 10.5286C2.02013 10.755 2.05389 11.0886 2.13639 11.5113C2.15876 11.5893 2.18143 11.6842 2.19764 11.7939C2.22774 11.9356 2.26584 12.0822 2.31599 12.2526C2.37873 12.4659 2.45103 12.6977 2.53268 12.9378C3.29637 15.0685 5.20582 18.3241 9.9929 21.6102C14.464 18.5417 16.4117 15.5032 17.286 13.3736C17.3182 13.2724 17.3577 13.1748 17.4037 13.0815Z" fill="#2E1950"/> 3300<path fill-rule="evenodd" clip-rule="evenodd" d="M10 12C11.1046 12 12 11.1046 12 10C12 8.89543 11.1046 8 10 8C8.89543 8 8 8.89543 8 10C8 11.1046 8.89543 12 10 12ZM10 14C12.2091 14 14 12.2091 14 10C14 7.79086 12.2091 6 10 6C7.79086 6 6 7.79086 6 10C6 12.2091 7.79086 14 10 14Z" fill="#2E1950"/> 3301</svg> 3302 <span class="text-sm font-bold uppercase text-primary-dark hidden xl:block"> 3303 Shops </span> 3304 </a> 3305 3306 <!--Customer Icon & Dropdown--> 3307 3308<div 3309 class="header-customer relative hidden lg:inline-block" 3310 x-data="{ 3311 open: false, 3312 isLoggedIn: false, 3313 customerName: '' 3314 }" 3315 @keyup.escape="open = false" 3316 @click.outside="open = false" 3317 @private-content-loaded.window=" 3318 if (event.detail.data.customer && event.detail.data.customer.signin_token) { 3319 isLoggedIn = true; 3320 customerName = event.detail.data.customer.firstname || ''; 3321 } else { 3322 isLoggedIn = false; 3323 customerName = ''; 3324 } 3325 " 3326> 3327 <button 3328 type="button" 3329 id="customer-menu" 3330 class="block rounded !border-0 m-0 !bg-transparent !shadow-none" 3331 @click="open = !open" 3332 :aria-expanded="open ? 'true' : 'false'" 3333 aria-label="My Account" 3334 aria-haspopup="true" 3335 > 3336 <div class="flex items-center gap-x-2"> 3337 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"> 3338<path fill-rule="evenodd" clip-rule="evenodd" d="M12 10C14.2091 10 16 8.20914 16 6C16 3.79086 14.2091 2 12 2C9.79086 2 8 3.79086 8 6C8 8.20914 9.79086 10 12 10ZM12 12C15.3137 12 18 9.31371 18 6C18 2.68629 15.3137 0 12 0C8.68629 0 6 2.68629 6 6C6 9.31371 8.68629 12 12 12Z" fill="#2E1950"/> 3339<path fill-rule="evenodd" clip-rule="evenodd" d="M15 16H9C5.68629 16 3 18.6863 3 22H21C21 18.6863 18.3137 16 15 16ZM9 14C4.58172 14 1 17.5817 1 22V24H23V22C23 17.5817 19.4183 14 15 14H9Z" fill="#2E1950"/> 3340</svg> 3341 <span class="text-sm font-bold uppercase text-primary-dark hidden xl:block"> 3342 <template x-if="isLoggedIn"> 3343 <span x-text="'Hi ' + customerName"></span> 3344 </template> 3345 <template x-if="!isLoggedIn"> 3346 <span>Sign in</span> 3347 </template> 3348 </span> 3349 </div> 3350 </button> 3351 <nav 3352 class="absolute top-full mt-2.5 max-2xl:end-0 ltr:start-1/2 rtl:end-1/2 -translate-x-1/2 z-30 w-fit" 3353 x-cloak 3354 x-show="open" 3355 x-transition:enter="transition ease-out" 3356 x-transition:enter-start="opacity-0" 3357 x-transition:enter-end="opacity-100" 3358 x-transition:leave="transition ease-in" 3359 x-transition:leave-start="opacity-100" 3360 x-transition:leave-end="opacity-0" 3361 aria-labelledby="customer-menu" 3362 > 3363 <div class="bg-transparent w-full flex justify-center"> 3364 <svg xmlns="http://www.w3.org/2000/svg" width="18" height="12" viewBox="0 0 18 12" fill="none" class="text-white" role="img">
3365 <path d="M9.98882 0.503861C9.52814 -0.167954 8.3657 -0.167954 7.90503 0.503861L0 12H18L9.98882 0.503861Z" fill="currentColor"/> 3366<title>tooltip-indicator</title></svg> 3367 </div> 3368 <div class="relative"> 3369 <div class="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-full w-0 h-0 border-l-[9px] border-l-transparent border-r-[9px] border-r-transparent border-b-[12px] border-b-primary"></div> 3370 <div class="customer-popup bg-white border-t border-primary shadow-[0_8px_24px_-8px_rgba(20,20,20,0.25)] py-2"> 3371 3372<a id="customer.header.sign.in.link" 3373 x-data="{}" 3374 @click="$store.device.isDesktop ? ($event.preventDefault(), $dispatch('open-auth-modal', { tab: 'login' })) : null" 3375 :href="$store.device.isDesktop ? '#' : 'https://www.purdys.com/customer/account/login/'" 3376 title="Sign In" 3377> 3378 Sign In</a> 3379 3380 <a id="customer.header.register.link" 3381 x-data="{}" 3382 @click="$store.device.isDesktop ? ($event.preventDefault(), $dispatch('open-auth-modal', { tab: 'register' })) : null" 3383 :href="$store.device.isDesktop ? '#' : 'https://www.purdys.com/customer/account/create/'" 3384 title="Create an Account" 3385 > 3386 Create an Account </a> 3387 </div> 3388 3389 </div> 3390 </nav> 3391</div> 3392 <button 3393 class="lg:hidden relative outline-none border-0 bg-transparent hover:bg-transparent hover:border-0" 3394 @click="toggleSearch()" 3395 title="Search" 3396 :aria-expanded="searchOpen" 3397 aria-haspopup="dialog"> 3398 <span class="sr-only label"> 3399 Toggle search </span> 3400 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="none" class="[&_path]:fill-primary-dark" aria-hidden="true"> 3401<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75049 1.49969C4.74642 1.49969 1.50049 4.74563 1.50049 8.74969C1.50049 12.7538 4.74642 15.9997 8.75049 15.9997C12.7546 15.9997 16.0005 12.7538 16.0005 8.74969C16.0005 4.74563 12.7546 1.49969 8.75049 1.49969ZM0.000488281 8.74969C0.000488281 3.9172 3.918 -0.000305176 8.75049 -0.000305176C13.583 -0.000305176 17.5005 3.9172 17.5005 8.74969C17.5005 13.5822 13.583 17.4997 8.75049 17.4997C3.918 17.4997 0.000488281 13.5822 0.000488281 8.74969Z" fill="white"/> 3402<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4001 14.3993C14.7108 14.0887 15.2144 14.0887 15.5251 14.3993L19.7675 18.6418C20.0781 18.9524 20.0781 19.4561 19.7675 19.7667C19.4569 20.0774 18.9532 20.0774 18.6426 19.7667L14.4001 15.5243C14.0895 15.2136 14.0895 14.71 14.4001 14.3993Z" fill="white"/> 3403</svg> 3404 </button> 3405 3406 3407 <!--Cart Icon--> 3408 <button 3409 id="menu-cart-icon" 3410 class="relative outline-none border-0 hover:bg-transparent hover:border-0 focus:bg-transparent focus:border-0" 3411 x-ref="cartButton" 3412 :aria-disabled="isCartEmpty()" 3413 title="Cart" 3414 @click.prevent.stop="() => { 3415 $dispatch('toggle-cart', { isOpen: true }) 3416 }" 3417 @toggle-cart.window="toggleCart($event)" 3418 :aria-expanded="isCartOpen" 3419 aria-haspopup="dialog" 3420 > 3421 <span 3422 class="sr-only label" 3423 x-text="` 3424 Toggle minicart, 3425 ${isCartEmpty() ? 3426 'Cart is empty' : 3427 hyva.str( 3428 'You\u0020have\u0020\u00251\u0020product\u00252\u0020in\u0020your\u0020cart.', 3429 cart.summary_count, 3430 cart.summary_count === 1 ? '' : 's' 3431 )}` 3432 "> 3433 </span> 3434 3435 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" role="img"> 3436<path fill-rule="evenodd" clip-rule="evenodd" d="M2 8V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V8H2ZM1 6C0.447715 6 0 6.44772 0 7V20C0 22.2091 1.79086 24 4 24H20C22.2091 24 24 22.2091 24 20V7C24 6.44771 23.5523 6 23 6H1Z" fill="#2E1950"/> 3437<path fill-rule="evenodd" clip-rule="evenodd" d="M16 11C15.4477 11 15 10.5523 15 10L15 5C15 3.34314 13.6569 2 12 2C10.3431 2 9 3.34315 9 5L9 10C9 10.5523 8.55229 11 8 11C7.44772 11 7 10.5523 7 10L7 5C7 2.23858 9.23857 2.41411e-07 12 0C14.7614 -2.41411e-07 17 2.23858 17 5V10C17 10.5523 16.5523 11 16 11Z" fill="#2E1950"/> 3438<title>bag</title></svg> 3439
3440 <span 3441 x-text="cart.summary_count" 3442 x-show="!isCartEmpty()" 3443 x-cloak 3444 class=" 3445 absolute top-0 end-0 px-0.5 h-6 w-6 -mt-5 -mr-3 flex justify-center items-center text-xs font-bold 3446 leading-none text-center text-white uppercase transform 3447 translate-y-1/2 rounded-full bg-primary-dark 3448 border-2 border-white 3449 " 3450 aria-hidden="true"></span> 3451 </button> 3452 3453 <!-- Mobile menu --> 3454 <div class="min-w-6 z-30 lg:hidden"> 3455 3456
3456<script> 3457 'use strict'; 3458 </script>
3458 3459
3459<script> 3460 const menuData_6ab638176cfc6 = [{"id":"2263","title":"Fall","url":"https:\/\/www.purdys.com\/fall","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2331","title":"Discover Thanksgiving","url":"https:\/\/www.purdys.com\/fall-gift-ideas","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"3773","title":"Discover Halloween","url":"https:\/\/www.purdys.com\/halloween-gift-ideas","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"3769","title":"Maritimes Collection","url":"https:\/\/www.purdys.com\/share-joy-share-chocolate","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"3752","title":"Ice Cream & Ice Cream Bars","url":"https:\/\/www.purdys.com\/ice-cream","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2266","title":"Canadian Souvenirs","url":"https:\/\/www.purdys.com\/gifts\/canadian-collection","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2281","title":"Product Type","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2284","title":"Boxed Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/boxed-chocolates","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2287","title":"Gift Baskets and Sets","url":"https:\/\/www.purdys.com\/gifts\/gift-baskets-and-sets","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2290","title":"Mix and Match Chocolates","url":"https:\/\/www.purdys.com\/chocolate\/mix-and-match-chocolates","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"2293","title":"By Price","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2296","title":"under $20","url":"https:\/\/www.purdys.com\/fall\/fall-low","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2299","title":"$20 to $50","url":"https:\/\/www.purdys.com\/fall\/fall-medium","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2302","title":"Over $50","url":"https:\/\/www.purdys.com\/fall\/over-50","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"2305","title":"Others","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2308","title":"Build-a-box","url":"https:\/\/www.purdys.com\/build-a-box","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2311","title":"Shipping and Delivery","url":"https:\/\/www.purdys.com\/shipping-gift-guarantee","classes":null,"hasChildren":false,"image":null,"children":[]}]}]},{"id":"1811","title":"Chocolate","url":"https:\/\/www.purdys.com\/chocolate","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2155","title":"Canadian Souvenirs","url":"https:\/\/www.purdys.com\/gifts\/canadian-collection","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1817","title":"Boxed Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/boxed-chocolates","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2066","title":"Gift Baskets","url":"https:\/\/www.purdys.com\/gifts\/gift-baskets-and-sets","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1820","title":"Build-a-Box","url":"https:\/\/www.purdys.com\/build-a-box","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1829","title":"Free Shipping ","url":"https:\/\/www.purdys.com\/chocolate\/send-chocolates-free-shipping","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1832","title":"Online Exclusive","url":"https:\/\/www.purdys.com\/chocolate\/online-exclusive-chocolates","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1838","title":"Bestsellers","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1841","title":"All Bestsellers","url":"https:\/\/www.purdys.com\/chocolate\/bestsellers","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1844","title":"Sweet Georgia Browns","url":"https:\/\/www.purdys.com\/chocolate\/chocolate-sweet-georgia-browns","classes":null,"hasChildren":false,"image":null,"children":[]}
3460,{"id":"1847","title":"Hedgehogs","url":"https:\/\/www.purdys.com\/chocolate\/chocolate-hedgehogs","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1850","title":"Himalayan Pink Salt Caramels","url":"https:\/\/www.purdys.com\/chocolate\/salted-caramel-chocolates","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1853","title":"CHOCOLATE TYPE","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1856","title":"Plant-based Chocolate","url":"https:\/\/www.purdys.com\/plant-based-chocolate","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1865","title":"Dark Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/dark-chocolate","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1868","title":"Milk Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/milk-chocolate","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1871","title":"White Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/white-chocolate","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1862","title":"No Sugar Added","url":"https:\/\/www.purdys.com\/chocolate\/no-sugar-added","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1859","title":"Fruit Flavours","url":"https:\/\/www.purdys.com\/chocolate\/fruit-flavoured-chocolate","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1874","title":"COLLECTIONS","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1877","title":"Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/see-all","classes":"menu-second-column","hasChildren":false,"image":null,"children":[]},{"id":"1880","title":"Bars and Snacks","url":"https:\/\/www.purdys.com\/chocolate\/chocolate-bars-snacks","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1883","title":"Mint Chocolates","url":"https:\/\/www.purdys.com\/chocolate\/mint-chocolates","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1886","title":"Nuts and Caramels","url":"https:\/\/www.purdys.com\/chocolate\/nuts-and-caramels","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1889","title":"Assorted Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/assorted-chocolate","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1892","title":"Peanut Butter","url":"https:\/\/www.purdys.com\/chocolate\/peanut-butter","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1895","title":"Fruits, Ginger and Spices","url":"https:\/\/www.purdys.com\/chocolate\/fruits-ginger-and-spices","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1898","title":"Chocolate Truffles","url":"https:\/\/www.purdys.com\/chocolate\/chocolate-truffles","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1901","title":"Chocolate Bark, Brittle and Toffee","url":"https:\/\/www.purdys.com\/chocolate\/brittle-bark-and-toffee","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1904","title":"From The Chocolate Kitchen","url":"https:\/\/www.purdys.com\/chocolate\/from-the-chocolate-kitchen","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1907","title":"Chocolate Favours","url":"https:\/\/www.purdys.com\/gifts\/chocolate-favours","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1913","title":"Kosher Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/kosher","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1916","title":"SALE & SPECIALS","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1919","title":"Special Offers","url":"https:\/\/www.purdys.com\/chocolate\/special-offers","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1922","title":"Bulk Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/bulk-chocolate","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1925","title":"Mix and Match 3 for $11.75","url":"https:\/\/www.purdys.com\/chocolate\/mix-match-low","classes":null,"hasChildren":false,"image":null,"children":[]}
3460,{"id":"1928","title":"Mix and Match 3 for $12.50","url":"https:\/\/www.purdys.com\/chocolate\/mix-match-medium","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1931","title":"Mix and Match 3 for $23","url":"https:\/\/www.purdys.com\/chocolate\/mix-match-high","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1934","title":"OTHER","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2343","title":"Ice Cream","url":"https:\/\/www.purdys.com\/ice-cream","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1937","title":"Chocolate Legend ","url":"https:\/\/www.purdys.com\/chocolate-legend","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1940","title":"New","url":"https:\/\/www.purdys.com\/chocolate\/new","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1943","title":"Shipping Deadlines","url":"https:\/\/www.purdys.com\/shipping-gift-guarantee","classes":null,"hasChildren":false,"image":null,"children":[]}]}]},{"id":"1301","title":"Gifts","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2158","title":"Thanksgiving Gifts Ideas","url":"https:\/\/www.purdys.com\/fall-gift-ideas","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"3776","title":"Halloween Gift Ideas","url":"https:\/\/www.purdys.com\/halloween-gift-ideas","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1322","title":"Canadian Souvenirs","url":"https:\/\/www.purdys.com\/canadian-souvenirs","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1539","title":"Build-a-Box","url":"https:\/\/www.purdys.com\/build-a-box","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"2078","title":"Chocolate Covered Dates","url":"https:\/\/www.purdys.com\/chocolate-covered-dates","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1334","title":"By Occasion","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1337","title":"Birthday Gifts","url":"https:\/\/www.purdys.com\/gifts\/birthday","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1340","title":"Thank You Gifts","url":"https:\/\/www.purdys.com\/gifts\/thank-you","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1343","title":"See All Gifts","url":"https:\/\/www.purdys.com\/gifts\/see-all-gifts","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1346","title":"Anniversary Gifts","url":"https:\/\/www.purdys.com\/gifts\/anniversary","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1352","title":"Guide to Purdys Wedding Favours","url":"https:\/\/www.purdys.com\/discover\/wedding-bridal-favours","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1361","title":"Product Type","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1364","title":"Gift Baskets and Sets","url":"https:\/\/www.purdys.com\/gifts\/gift-baskets-and-sets","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1367","title":"Gift Boxes","url":"https:\/\/www.purdys.com\/chocolate\/boxed-chocolates","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1370","title":"Gift Card ","url":"https:\/\/www.purdys.com\/gift-cards","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1373","title":"Vegan Gifts","url":"https:\/\/www.purdys.com\/vegan","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1519","title":"Kosher Chocolate","url":"https:\/\/www.purdys.com\/chocolate\/kosher","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1376","title":"Corporate Gifts ","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1379","title":"Corporate Gifts","url":"https:\/\/www.purdys.com\/corporate-gifts","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1382","title":"Employee Gifts and Snacks","url":"https:\/\/www.purdys.com\/gifts\/employee-gifts","classes":null,"hasChildren":false,"image":null,"children":[]}
3460,{"id":"1991","title":"Branded Corporate Gifts","url":"https:\/\/www.purdys.com\/corporate-gifting","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1385","title":"By Price","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1388","title":"Gift Under 20","url":"https:\/\/www.purdys.com\/gifts\/gifts-low","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1391","title":"Gifts $20 to $50","url":"https:\/\/www.purdys.com\/gifts\/gifts-medium","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1394","title":"Gifts Over $50","url":"https:\/\/www.purdys.com\/gifts\/gifts-high","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1397","title":"Other","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2346","title":"Ice Cream","url":"https:\/\/www.purdys.com\/ice-cream","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1400","title":"Online Exclusive","url":"https:\/\/www.purdys.com\/chocolate\/online-exclusive-chocolates","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1403","title":"Chocolate Legend ","url":"https:\/\/www.purdys.com\/chocolate-legend","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1406","title":"Shipping and Delivery","url":"#","classes":null,"hasChildren":false,"image":null,"children":[]}]}]},{"id":"1199","title":"Custom","url":"https:\/\/www.purdys.com\/custom","classes":"custom","hasChildren":true,"image":null,"children":[{"id":"1542","title":"Build-a-Box","url":"https:\/\/www.purdys.com\/build-a-box","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1205","title":"Custom Corporate Gifts ","url":"https:\/\/www.purdys.com\/corporate-gifting","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1208","title":"Wedding & Anniversaries","url":"https:\/\/www.purdys.com\/wedding-and-bridal-favours","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1220","title":"Discover","url":"https:\/\/www.purdys.com\/discover","classes":null,"hasChildren":true,"image":null,"children":[{"id":"2349","title":"Ice Cream","url":"https:\/\/www.purdys.com\/ice-cream","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1223","title":"Purdys Blog","url":"https:\/\/www.purdys.com\/blog.html","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1226","title":"Purple Partnerships","url":"https:\/\/www.purdys.com\/discover\/purple-partnerships","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1229","title":"Shipping Deadlines","url":"https:\/\/www.purdys.com\/shipping-gift-guarantee","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1232","title":"Sustainability","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1235","title":"Purdys' Sustainable Cocoa Program","url":"https:\/\/www.purdys.com\/sustainable-cocoa","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1238","title":"Sustainable Farms","url":"https:\/\/www.purdys.com\/sustainable-farms","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1241","title":"Sustainable Living","url":"https:\/\/www.purdys.com\/sustainable-living","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1244","title":"Clean Water Project","url":"https:\/\/www.purdys.com\/clean-water-project","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1247","title":"Corporate Gifts ","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1250","title":"Corporate Gifts Guides","url":"https:\/\/www.purdys.com\/corporate-gifts","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1253","title":"Branded Corporate Gifts","url":"https:\/\/www.purdys.com\/corporate-gifting","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1256","title":"Our Stories","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1259","title":"About Us","url":"https:\/\/www.purdys.com\/discover\/about-us","classes":null,"hasChildren":false,"image":null,"children":[]}
3460,{"id":"1262","title":"Purdys Heritage","url":"https:\/\/www.purdys.com\/discover\/purdys-heritage","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1265","title":"Tips & Guides","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1268","title":"Is Chocolate Vegan","url":"https:\/\/www.purdys.com\/blog\/is-chocolate-vegan-your-guide-to-vegan-chocolate.html","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1271","title":"The Tasty Art of Giving","url":"https:\/\/www.purdys.com\/blog\/celebrate-with-the-perfect-chocolate-gifts.html","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1274","title":"Guide to Purdys Wedding Favours","url":"https:\/\/www.purdys.com\/wedding-and-bridal-favours","classes":null,"hasChildren":false,"image":null,"children":[]}]},{"id":"1277","title":"Marketing & Tasting","url":"#","classes":null,"hasChildren":true,"image":null,"children":[{"id":"1280","title":"Chocolate Legend","url":"https:\/\/www.purdys.com\/discover\/chocolate-legend","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1283","title":"Wine and Chocolate Pairings","url":"https:\/\/www.purdys.com\/discover\/wine-and-chocolate-pairings","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1286","title":"Recipes","url":"https:\/\/www.purdys.com\/discover\/recipes","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1289","title":"Gift Ideas","url":"https:\/\/www.purdys.com\/discover\/gift-ideas","classes":null,"hasChildren":false,"image":null,"children":[]},{"id":"1292","title":"Blog","url":"https:\/\/www.purdys.com\/blog.html","classes":null,"hasChildren":false,"image":null,"children":[]}]}]}]; 3461 const initSlidingMenu_6ab638176cfc6 = () => ({ 3462 open: false, 3463 stack: [{ title: '', nodes: menuData_6ab638176cfc6 }], 3464 openMenu() { 3465 this.open = true; 3466 this.stack = [{ title: '', nodes: menuData_6ab638176cfc6 }]; 3467 this.$nextTick(() => hyva.trapFocus(this.$refs['menu-dialog'])); 3468 document.body.classList.add('fixed', 'w-full'); 3469 }, 3470 closeMenu() { 3471 document.body.classList.remove('fixed', 'w-full'); 3472 this.open = false; 3473 this.$nextTick(() => this.$refs['open-menu-button'].focus() || hyva.releaseFocus()); 3474 }, 3475 goTo(node) { 3476 this.stack.push({ title: node.title, nodes: node.children }); 3477 }, 3478 goBack() { 3479 if (this.stack.length > 1) this.stack.pop(); 3480 }, 3481 getLeafNodes(nodes) { 3482 return nodes.filter(n => !n.hasChildren); 3483 }, 3484 getParentNodes(nodes) { 3485 return nodes.filter(n => n.hasChildren); 3486 } 3487 }); 3488 </script>
3488 3489 3490 <nav 3491 class="snowdog-topmenu-mobile z-20 order-2 text-primary-dark sm:order-1 navigation bg-transparent lg:hidden" 3492 x-data="initSlidingMenu_6ab638176cfc6()" 3493 @keydown.escape="open && closeMenu()" 3494 @click.outside="open && closeMenu()" 3495 aria-label="Main menu" 3496 role="navigation" 3497 > 3498 <button x-ref="open-menu-button" 3499 @click="openMenu()" 3500 type="button" 3501 aria-label="Open menu" 3502 class="bg-transparent border-0 hover:border-0 hover:bg-transparent focus:bg-transparent focus:border-0 3503 active:bg-transparent active:border-0" 3504 :aria-expanded="open" 3505 aria-haspopup="menu" 3506 > 3507 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true"> 3508<path d="M1 4H23" stroke="#2E1950" stroke-width="2" stroke-linecap="round"/> 3509<path d="M1 20H23" stroke="#2E1950" stroke-width="2" stroke-linecap="round"/> 3510<path d="M1 12H23" stroke="#2E1950" stroke-width="2" stroke-linecap="round"/> 3511</svg> 3512 </button> 3513 <div class="fixed inset-0 bg-black/50 z-40" x-show="open" x-transition.opacity @click="closeMenu()" x-cloak></div> 3514 <div 3515 class="fixed z-50 top-0 left-0 w-full h-full shadow-2xl overflow-hidden transition-transform duration-300 bg-black bg-opacity-25" 3516 :class="{ '-translate-x-full': !open, 'translate-x-0': open }" 3517 x-cloak 3518 x-ref="menu-dialog" 3519 role="dialog" 3520 aria-modal="true" 3521 aria-label="Main menu" 3522 :aria-hidden="open ? 'false' : 'true'" 3523 > 3524 <template x-for="(panel, idx) in stack" :key="idx"> 3525 <div 3526 class="absolute top-0 left-0 w-full bg-white transition-transform duration-300 flex flex-col h-full" 3527 :class="{ 'translate-x-0': idx === stack.length - 1, 'translate-x-full': idx < stack.length - 1 }" 3528 x-show="idx === stack.length - 1" 3529 > 3530 <div class="flex items-center justify-between px-4 py-[15p
3530x] bg-white flex-shrink-0 h-[52px]"> 3531 <template x-if="stack.length > 1"> 3532 <button @click="goBack()" aria-label="Back" class="border-0"> 3533 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="none" class="rotate-90" aria-hidden="true"> 3534<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 3535</svg> 3536 </button> 3537 </template> 3538 <template x-if="stack.length === 1"> 3539 <div class="flex-1 flex justify-center"> 3540 <!-- Search bar from header.phtml --> 3541 <div class="w-full"> 3542 <div x-data="initMobileLiveSearch()" 3543 x-init="initPopover" 3544 @close-search.window="closePopover" 3545 @open-search.window="openPopover" 3546 @keydown.escape="closePopover" 3547> 3548 <form 3549 class="form minisearch w-full flex items-center" 3550 id="search_mini_form_menu" 3551 action="https://www.purdys.com/catalogsearch/result/" 3552 method="get" 3553 role="search"> 3554 <label class="sr-only" for="search-menu" data-role="minisearch-label"> 3555 <span>Search</span> 3556 </label> 3557 <div class="flex items-center flex-1"> 3558 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" class="mr-3 [&>path]:fill-black" aria-hidden="true"> 3559<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75049 1.49969C4.74642 1.49969 1.50049 4.74563 1.50049 8.74969C1.50049 12.7538 4.74642 15.9997 8.75049 15.9997C12.7546 15.9997 16.0005 12.7538 16.0005 8.74969C16.0005 4.74563 12.7546 1.49969 8.75049 1.49969ZM0.000488281 8.74969C0.000488281 3.9172 3.918 -0.000305176 8.75049 -0.000305176C13.583 -0.000305176 17.5005 3.9172 17.5005 8.74969C17.5005 13.5822 13.583 17.4997 8.75049 17.4997C3.918 17.4997 0.000488281 13.5822 0.000488281 8.74969Z" fill="white"/> 3560<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4001 14.3993C14.7108 14.0887 15.2144 14.0887 15.5251 14.3993L19.7675 18.6418C20.0781 18.9524 20.0781 19.4561 19.7675 19.7667C19.4569 20.0774 18.9532 20.0774 18.6426 19.7667L14.4001 15.5243C14.0895 15.2136 14.0895 14.71 14.4001 14.3993Z" fill="white"/> 3561</svg> 3562 <input id="search-menu" 3563 type="search" 3564 autocomplete="off" 3565 name="q" 3566 :value="queryPhrase" 3567 value="" 3568 placeholder="Search..." 3569 maxlength="128" 3570 class="flex-1 bg-transparent !border-0 outline-none !text-base text-gray-900 placeholder-gray-400 !p-0" 3571 role="combobox" 3572 aria-haspopup="false" 3573 aria-autocomplete="both" 3574 aria-expanded="false" 3575 x-ref="searchRef" 3576 @input="handleInput" 3577 @click.away="closePopover" /> 3578 </div> 3579 <button 3580 @click.prevent="clearInput" 3581 x-cloak 3582 x-show="queryPhrase !== ''" 3583 title="Clear" 3584 class="flex items-center justify-center w-6 h-6 body-1 text-primary font-bold underline mr-3 !border-0" 3585 aria-label="Clear"> 3586 <span>Clear</span> 3587 </button> 3588 </form> 3589 3590 <div x-show="searchOpen" 3591 class="bg-black opacity-[0.6] fixed w-[100vw] h-[100vh] left-0 mt-[18px] z-20" 3592 style="display: none;" 3593 @click="closePopup"></div> 3594 <section 3595 x-show="searchOpen" 3596 class="livesearch-overlay search-autocomplete overflow-y-scroll !h-[calc(100dvh-52px)] !top-[52px] !mt-0 !left-0" 3597 :class="getSearchClasses" 3598 style="display: none;" 3599 aria-label="Search Suggestions" 3600 x-transition:enter='transition-all duration-300 ease-out' 3601 x-transition:enter-start='opacity-0' 3602 x-transition:enter-end='opacity-100' 3603 x-transition:leave='transition-all duration-200 ease-in' 3604 x-transition:leave-start='opacity-100' 3605 x-transition:leave-end='opacity-0' 3606 > 3607 <!-- No Results Header --> 3608 <template x-if="showResults && !liveSearchTotalCount && !isLoading"> 3609 <div class="max-w-[1660px] mx-auto px-4 md:px-0"> 3610 <div class="flex items-center justify-between"> 3611 <h3 class="font-bold text-xl md:text-3xl !m-0" 3612 x-text="'No\u0020results\u0020found\u0020for\u003A ' + '\'' + queryPhrase + '\''"></h3> 3613 <a href="#" @click.prevent='closePopup'> 3614 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="none" class="text-black" role="img"> 3615<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 3616<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 3617<title>x</title></svg> 3618 </a> 3619 </div> 3620 <p class="pt-3">
3620We can't find any item matching your search. Please try another search.</p> 3621 </div> 3622 </template> 3623 <!-- Results Header --> 3624 <template x-if="showResults"> 3625 <div class="flex items-center px-4 md:px-0 justify-between max-w-[1660px] mx-auto "> 3626 <h3 class="font-bold text-xl md:text-3xl !m-0" x-text="'Search\u0020results\u0020for\u003A ' + '\'' + queryPhrase + '\''"></h3> 3627 <a href="#" @click.prevent='closePopup'> 3628 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="none" class="text-black" role="img"> 3629<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 3630<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 3631<title>x</title></svg> 3632 </a> 3633 </div> 3634 </template> 3635 <div x-show="liveSearchTotalCount && !isLoading" class="livesearch-overlay-content" 3636 x-transition:enter='transition-all duration-300 ease-out' 3637 x-transition:enter-start='opacity-0' 3638 x-transition:enter-end='opacity-100' 3639 x-transition:leave='transition-all duration-200 ease-in' 3640 x-transition:leave-start='opacity-100' 3641 x-transition:leave-end='opacity-0'> 3642 <template x-if="showSuggestions"> 3643 <div class="flex flex-col md:max-h-[393px]"> 3644 <p class="text-lg pb-[10px] border-b border-grey-muted"> 3645 <strong> 3646 Suggestions </strong> 3647 </p> 3648 <ul class="m-0 space-y-1" role="list"> 3649 <template x-for="suggestion in liveSearchSuggestions"> 3650 <li class="before:hidden pl-0"> 3651 <button 3652 x-html="getSuggestionText(suggestion)" 3653 @click="searchSuggestion" 3654 @mouseenter="onSuggestionHover(suggestion)" 3655 type="button" 3656 class="w-full py-2 text-left hover:text-primary-hover hover:underline hover:cursor-pointer capitalize" 3657 ></button> 3658 </li> 3659 </template> 3660 </ul> 3661 <div class="pt-3 md:pt-5 mt-auto pb-2.5 md:pb-0"> 3662 <a href='#' @click.prevent="redirectToSearchPage()" class='flex gap-1 items-center !text-primary !underline'>
3663 <span x-text="getSeeResultsText()"></span> 3664 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="rtl:rotate-180" width="20" height="20" aria-hidden="true"> 3665 <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/> 3666</svg> 3667 </a> 3668 </div> 3669 </div> 3670 </template> 3671 <template x-if="showResults"> 3672 <div class="lg:col-span-2"> 3673 <p class="text-lg pb-[10px] border-b-[1px] border-grey-muted"> 3674 <strong> 3675 Product Suggestions </strong> 3676 </p> 3677 <ul class="livesearch-overlay-content-products" role="list"> 3678 <template x-for="product in liveSearchProducts"> 3679 <li class="before:hidden pl-0"> 3680 <button 3681 class="w-full flex gap-4 text-left cursor-pointer product-suggestion-button" 3682 @click="handleProductClick" 3683 > 3684 <img 3685 :src="product.image" 3686 :alt="product.name" 3687 width="80" 3688 height="80" 3689 class="w-[80px] h-[80px] shrink-0 object-contain" 3690 > 3691 <div class="flex flex-col justify-center w-full"> 3692 <div class="flex flex-row justify-between gap-6"> 3693 <p><strong class="product-name max-w-[287px]" x-text="product.name"></strong></p> 3694 <template x-if="product.price"> 3695 <p class="font-bold" x-text="showProductPrice"></p> 3696 </template> 3697 </div> 3698 </div> 3699 </button> 3700 </li> 3701 </template> 3702 </ul> 3703 <div class="pt-5"> 3704 <a href="#" @click="redirectToSearchPage(true)" class="flex gap-1 items-center !text-primary !underline">
3705 <span x-text="getSeeResultsText(true)"></span> 3706 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="rtl:rotate-180" width="20" height="20" aria-hidden="true"> 3707 <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/> 3708</svg> 3709 </a> 3710 </div> 3711 </div> 3712 </template> 3713 </div> 3714 </section> 3715</div> 3716
3717<script> 3718 'use strict'; 3719 3720 function initMobileLiveSearch() { 3721 return { 3722 queryPhrase: '', 3723 liveSearchProducts: [], 3724 liveSearchSuggestions: [], 3725 liveSearchTotalCount: 0, 3726 isLoading: false, 3727 minQueryLength: 3, 3728 pageSize: 8, 3729 searchOpen: false, 3730 search: null, 3731 isSuggestionHoverMode: false, 3732 suggestionHover: '', 3733 searchRequestId: null, 3734 searchId: "2cc69165-2270-494c-893f-c00d0bb35bd7_" + 3735 "base_" + 3736 "main_website_store_" + 3737 "default", 3738 3739 initPopover() { 3740 this.search = this.buildSearch(); 3741 this.initTogglePopover(); 3742 3743 const self = this; 3744 3745 window.addEventListener('open-search-popup', function (event) { 3746 if (self.liveSearchTotalCount > 0) { 3747 self.openPopover(); 3748 } 3749 }); 3750 }, 3751 3752 clearInput() { 3753 this.$refs.searchRef.value = ''; 3754 this.liveSearchProducts = []; 3755 this.liveSearchSuggestions = []; 3756 this.liveSearchTotalCount = 0; 3757 this.queryPhrase = ''; 3758 3759 this.closePopover(); 3760 }, 3761 3762 redirectToSearchPage(isSuggestion = false) { 3763 window.location.href = '/catalogsearch/result/?q=' + (isSuggestion ? this.suggestionHover.replaceAll(' ', '+') : this.queryPhrase); 3764 }, 3765 3766 buildSearch() { 3767 window.magentoStorefrontEvents.context.setSearchExtension({ 3768 version: "4.4.1" 3769 }); 3770 return new MagentoLiveSearch({ 3771 environmentId: "2cc69165-2270-494c-893f-c00d0bb35bd7", 3772 websiteCode: "base", 3773 storeCode: "main_website_store", 3774 storeViewCode: "default", 3775 apiKey: "search_gql", 3776 }); 3777 }, 3778 3779 // this will open the popover, whenever the search-input has focus 3780 initTogglePopover() { 3781 const popover = document.getElementById('search-menu'); 3782 3783 if (popover) { 3784 popover.addEventListener('focus', () => { 3785 if (this.isQueryLengthValid()) { 3786 this.openPopover(); 3787 } else { 3788 this.closePopover(); 3789 } 3790 }); 3791 } 3792 }, 3793 3794 closePopover() { 3795 this.searchOpen = false; 3796 // Restore body scroll when closing search overlay 3797 document.body.style.overflow = ''; 3798 }, 3799 3800 closePopup() { 3801 this.searchOpen = false; 3802 // Restore body scroll when closing search overlay 3803 document.body.style.overflow = ''; 3804 }, 3805 3806 openPopover() { 3807 this.searchOpen = true; 3808 // Lock body scroll when opening search overlay 3809 document.body.style.overflow = 'hidden'; 3810 }, 3811 3812 handleInput() { 3813 this.queryPhrase = this.$refs.searchRef.value; 3814 this.suggestionHover = ''; 3815 this.isSuggestionHoverMode = false; 3816 3817 if (!this.isQueryLengthValid()) { 3818 if (this.searchOpen){ 3819 this.closePopover(); 3820 } 3821 return; 3822 } 3823 3824 this.getProducts(); 3825 this.$nextTick(() => { 3826 this.openPopover(); 3827 }); 3828 }, 3829 3830 showProductPrice(){ 3831 return this.product.price ? hyva.formatPrice(this.product.price) : ''; 3832 }, 3833 3834 handleProductClick() { 3835 window.magentoStorefrontEvents.context.setSearchResults( 3836 window.magentoStorefrontEvents.context.getSearchResults() 3837 ); 3838 3839 window.magentoStorefrontEvents.publish.searchProductClick("search-bar", `${this.product.sku}`); 3840 3841 // Timeout to prevent submission before event has fired. 3842 setTimeout(() => window.location.href = this.product.url, 500); 3843 }, 3844 3845 // this will submit the form with the clicked suggestion as the search-phrase 3846 searchSuggestion() { 3847 document.getElementById('search-menu').value = this.suggestion; 3848 document.getElementById('search_mini_form_menu').submit(); 3849 }, 3850 3851 showResults(){ 3852 return this.liveSearchProducts.length >= 1 && this.isQueryLengthValid() || this.isSuggestionHoverMode; 3853 }, 3854 3855 showSuggestions(){ 3856 return this.liveSearchSuggestions.length >= 1 && this.isQueryLengthValid(); 3857 }, 3858 3859 isQueryLengthValid() { 3860 return this.queryPhrase.length >= this.minQueryLength; 3861 }, 3862 3863 getSearchClasses() { 3864 return this.searchOpen ? 'block' : 'hidden'; 3865 }, 3866 3867 getSuggestionText(suggestion) { 3868 const matches = suggestion.match(new RegExp(this.queryPhrase, 'gmi')); 3869 3870 if (matches) { 3871 matches.map((match) => { 3872 suggestion = suggestion.replaceAll(match, '<b>' + match + '</b>') 3873 }); 3874 } 3875 3876 return suggestion; 3877 }, 3878 3879 async onSuggestionHover(suggestion) { 3880 this.isSuggestionHoverMode = true; 3881 this.suggestionHover = suggestion; 3882 3883 await this.getProducts(); 3884 3885 this.queryPhrase = suggestion; 3886 }, 3887 3888 onSuggestionUnhover() { 3889 this.isSuggestionHoverMode = false;
3890 this.suggestionHover = ''; 3891 }, 3892 3893 getSeeResultsText(isSuggestion = false) { 3894 const text = 'See\u0020\u0025total\u0020results\u0020for\u0020\u0022\u0025term\u0022'; 3895 3896 if (isSuggestion && this.isSuggestionHoverMode) { 3897 return text.replace('%total', this.liveSearchTotalCount).replace('%term', this.suggestionHover); 3898 } 3899 3900 return text.replace('%total', this.liveSearchTotalCount).replace('%term', this.queryPhrase); 3901 }, 3902 3903 // get products from fetch-request and update liveSearchProducts and liveSearchSuggestions 3904 async getProducts() { 3905 this.searchOpen = true; 3906 3907 if (!this.isSuggestionHoverMode) { 3908 this.isLoading = true; 3909 } 3910 3911 const result = await this.getLiveSearchProducts(); 3912 3913 this.isLoading = false; 3914 3915 this.liveSearchProducts = result.data.productSearch.items.map(item => ({ 3916 name: item.product.name, 3917 price: item.product.price_range.maximum_price.regular_price.value, 3918 currency: item.product.price_range.maximum_price.regular_price.currency, 3919 image: item.product.thumbnail?.url || item.product.image?.url || 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 3920 url: item.product.canonical_url, 3921 id: item.product.id, 3922 sku: item.product.sku 3923 })); 3924 3925 this.liveSearchSuggestions = result.data.productSearch.suggestions 3926 this.liveSearchTotalCount = result.data.productSearch.total_count; 3927 3928 //search-results-view 3929 }, 3930 3931 // retrieve products from magento live search service 3932 async getLiveSearchProducts() { 3933 this.searchRequestId = Math.random().toString(16).slice(2); 3934 3935 if (!this.search) { 3936 return { 3937 data: { 3938 productSearch: { 3939 items: [], 3940 suggestions: [], 3941 total_count: 0, 3942 page_info: { 3943 current_page: 1, 3944 page_size: this.pageSize 3945 }, 3946 facets: [] 3947 } 3948 } 3949 }; 3950 } 3951 3952 const searchQuery = this.isSuggestionHoverMode ? this.suggestionHover : this.queryPhrase; 3953 3954 const searchCtx = { 3955 units: [{ 3956 searchUnitId: "search-bar", 3957 queryTypes: ["products"], 3958 phrase: searchQuery, 3959 currentPage: 1, 3960 pageSize: this.pageSize, 3961 filter:[ 3962 {attribute:"visibility",in:["Search","Catalog, Search"]}, 3963 {attribute:"inStock",eq:"true"} 3964 ], 3965 sort: [{attribute: "relevance", direction: "DESC"}], 3966 searchRequestId: this.searchRequestId 3967 }] 3968 }; 3969 3970 window.magentoStorefrontEvents.context.setSearchInput(searchCtx); 3971 window.magentoStorefrontEvents.publish.searchRequestSent("search-bar"); 3972 3973 const before = Date.now(); 3974 3975 const result = await this.search.productSearch({ 3976 phrase: searchQuery, 3977 page_size: this.pageSize, 3978 sort: [{attribute: "relevance", direction: "DESC"}], 3979 filter:[ 3980 {attribute:"visibility",in:["Search","Catalog, Search"]}, 3981 {attribute:"inStock",eq:"true"} 3982 ], 3983 current_page: 1, 3984 data: { 3985 products: true, 3986 facets: true, 3987 suggestions: true 3988 }, 3989 context: { 3990 customerGroup: "b6589fc6ab0dc82cf12099d1c2d40ab994e8410c" 3991 } 3992 }); 3993 3994 const after = Date.now(); 3995 3996 const productItems = result.data.productSearch.items || []; 3997 const suggestions = result.data.productSearch.suggestions || []; 3998 const facets = result.data.productSearch.facets || []; 3999 4000 const formattedProductItems = productItems.map((item, key) => ({ 4001 name: item.product.name, 4002 sku: item.product.sku, 4003 url: item.product.canonical_url, 4004 imageUrl: item.product.image ? item.product.image.url : 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 4005 price: item.product.price_range.minimum_price.final_price.value, 4006 rank: key, 4007 })); 4008 4009 const formattedSuggestions = suggestions.map((item, key) => ({ 4010 suggestion: item, 4011 rank: key, 4012 })); 4013 4014 const formattedFacets = facets.map((facet) => ({ 4015 attribute: facet.attribute, 4016 title: facet.title, 4017 type: facet.type || "PINNED", 4018 buckets: facet.buckets.map((bucket) => bucket), 4019 })); 4020 4021 const searchCtxUpdated = { 4022 units: [{ 4023 searchUnitId: "search-bar", 4024 searchRequestId: this.searchRequestId, 4025 executionTime: after - before, 4026 products: formattedProductItems, 4027 categories: [], 4028 suggestions: this.isSuggestionHoverMode ? this.suggestions : formattedSuggestions, 4029 page: result.data.productSearch.page_info.current_page, 4030 perPage: result.data.productSearch.page_info.page_size, 4031 facets: formattedFacets 4032 }] 4033 }; 4034 4035 if (this.isSuggestionHoverMode) { 4036 this.liveSearchProducts = result.data.productSearch.items.map(item => ({ 4037 name: item.product.name, 4038 price: item.product.price_range.maximum_price.regular_price.value, 4039 currency: item.product.price_range.maximum_price.regular_price.currency, 4040 image: item.product.thumbnail?.url || item.product.image?.url || 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 4041 url: item.product.canonical_url, 4042 id: item.product.id, 4043 sku: item.product.sku 4044 })); 4045 } 4046 4047 window.magentoStorefrontEvents.context.setSearchResults(searchCtxUpdated); 4048 window.magentoStorefrontEvents.publish.searchResponseReceived("search-bar"); 4049 4050 return result; 4051 } 4052 } 4053 } 4054</script>
4054 4055 </div> 4056 </div> 4057 </template> 4058 <template x-if="stack.length > 1"> 4059 <span class="body-1 font-bold flex-1 text-center uppercase" x-text="panel.title"></span> 4060 </template> 4061 <button @click="closeMenu()" aria-label="Close" class="ml-3 border-0 hover:border-0 hover:bg-transparent"> 4062 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" class="text-black" aria-hidden="true"> 4063<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 4064<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 4065</svg> 4066 </button> 4067 </div> 4068 <div class="overflow-y-auto min-h-0" style="flex-grow: 1; max-height: calc(100dvh - 52px - env(safe-area-inset-bottom)); -webkit-overflow-scrolling: touch;"> 4069 <ul :class="{ 'ml-0': true,'!pt-3': stack.length <= 1}" class="!pb-10 pb-safe"> 4070 <!-- All menu items in natural order --> 4071 <template x-for="node in panel.nodes" :key="'menu-' + node.id"> 4072 <li class="before:content-[''] pl-0 border-b border-primary-lightest flex items-center justify-between" :class="node.classes"> 4073 <a :href="node.url" class="!flex items-center !px-4 !py-[15px] no-underline uppercase font-bold text-left"> 4074 <!-- Menu item image --> 4075 <template x-if="node.image"> 4076 <img :src="node.image" :alt="node.title" class="mr-2 w-5 h-5 object-cover"> 4077 </template> 4078 <span x-text="node.title" class="text-primary-dark"></span> 4079 </a> 4080 <template x-if="node.hasChildren"> 4081 <button @click="goTo(node)" 4082 class="flex flex-1 items-center justify-end px-4 py-[15p
4082x] w-6/12 border-0 4083 hover:border-0 hover:bg-transparent" 4084 aria-label="Open submenu"> 4085 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="none" class="text-primary-dark" aria-hidden="true" style="transform: rotate(-90deg);"> 4086<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 4087</svg> 4088 </button> 4089 </template> 4090 </li> 4091 </template> 4092 4093 <!-- Hardcoded Shops and Account items (only on first level) --> 4094 <template x-if="idx === 0"> 4095 <div> 4096 4097 <!-- Account item --> 4098 <li class="before:content-[''] pl-0 border-b border-primary-lightest flex items-center"> 4099 <button 4100 @click="goTo({ title: 'Sign\u0020in', children: [] })" 4101 class="flex items-center flex-1 px-4 py-[15p
4101x] no-underline uppercase font-bold 4102 text-left text-primary-dark bg-transparent border-0 cursor-pointer 4103 hover:bg-transparent hover:border-0" 4104 > 4105 <span class="mr-2 text-primary-dark"> 4106 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" class="inline-block" aria-hidden="true"> 4107<path fill-rule="evenodd" clip-rule="evenodd" d="M12 10C14.2091 10 16 8.20914 16 6C16 3.79086 14.2091 2 12 2C9.79086 2 8 3.79086 8 6C8 8.20914 9.79086 10 12 10ZM12 12C15.3137 12 18 9.31371 18 6C18 2.68629 15.3137 0 12 0C8.68629 0 6 2.68629 6 6C6 9.31371 8.68629 12 12 12Z" fill="#2E1950"/> 4108<path fill-rule="evenodd" clip-rule="evenodd" d="M15 16H9C5.68629 16 3 18.6863 3 22H21C21 18.6863 18.3137 16 15 16ZM9 14C4.58172 14 1 17.5817 1 22V24H23V22C23 17.5817 19.4183 14 15 14H9Z" fill="#2E1950"/> 4109</svg> 4110 </span> 4111 <span>Sign in</span> 4112 </button> 4113 <button @click="goTo({ title: 'Sign\u0020in', children: [] }
4113)" class="flex items-center justify-end px-4 py-[15px] w-6/12 bg-transparent border-0 hover:bg-transparent hover:border-0 cursor-pointer" aria-label="Open account menu"> 4114 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 20 20" fill="none" aria-hidden="true" style="transform: rotate(-90deg);"> 4115<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 4116</svg> 4117 </button> 4118 </li> 4119 </div> 4120 </template> 4121 <template x-if="idx === 0"> 4122 <div 4123 class="[&_ul]:ml-0 [&_ul]:gap-y-6 [&_ul]:flex [&_ul]:flex-col [&_ul]:px-4 [&_ul]:pt-6 [&_li]:before:content-[''] 4124 [&_li]:pl-0 [&_a]:no-underline [&_a]:font-normal [&_a]:text-left [&_a]:!text-primary-dark [&_a]:!p-0" 4125 > 4126 <div data-content-type="html" data-appearance="default" data-element="main" data-decoded="true"><ul id="nav-top"> 4127 <li><a href="/corporate-gifts"><b>CORPORATE GIFTS</b></a></li> 4128 <li><a href="https://fundraising.purdys.com/"><b>FUNDRAISING</b></a></li> 4129 <li><a href="https://group.purdys.com/"><b>GROUP SAVINGS</b></a></li> 4130 <!--<li><a href="/doordash-redirect?utm_source=purdys&utm_medium=website&utm_campaign=doordashlink"><b>DOORDASH</b></a></li>--> 4131</ul> 4132</div> </div> 4133 </template> 4134 4135 <!-- Customer links for account menu layer --> 4136 <template x-if="panel.title === 'Sign\u0020in' || panel.title === 'Account'"> 4137 <div class="py-3 border-y border-primary-lightest"> 4138 <!-- Logged out customer links --> 4139 <div class="[&_a]:block [&_a]:px-4 [&_a]:py-3 [&_a]:no-underline [&_a]:text-primary-dark [&_a]:hover:bg-transparent"> 4140 4141<a id="customer.header.sign.in.link" 4142 x-data="{}" 4143 @click="$store.device.isDesktop ? ($event.preventDefault(), $dispatch('open-auth-modal', { tab: 'login' })) : null" 4144 :href="$store.device.isDesktop ? '#' : 'https://www.purdys.com/customer/account/login/'" 4145 title="Sign In" 4146> 4147 Sign In</a> 4148 4149 <a id="customer.header.register.link" 4150 x-data="{}" 4151 @click="$store.device.isDesktop ? ($event.preventDefault(), $dispatch('open-auth-modal', { tab: 'register' })) : null" 4152 :href="$store.device.isDesktop ? '#' : 'https://www.purdys.com/customer/account/create/'" 4153 title="Create an Account" 4154 > 4155 Create an Account </a> 4156 </div> 4157 </div> 4158 </template> 4159 </ul> 4160 </div> 4161 </div> 4162 </template> 4163 </div> 4164 </nav> 4165 </div> 4166 </div> 4167 </div> 4168 </div> 4169 4170 <div class="bg-container-darker"> 4171 </div> 4172 4173 <!--Search mobile--> 4174 <div 4175 x-ref="searchContainerMobile" 4176 class="absolute w-full bg-white border-b border-primary-lightest lg:hidden pb-4 px-4 md:px-5 transition-all duration-300 ease-in-out" 4177 x-show="searchOpen" 4178 x-transition:enter="transition ease-out duration-300" 4179 x-transition:enter-start="opacity-0 transform -translate-y-2" 4180 x-transition:enter-end="opacity-100 transform translate-y-0" 4181 x-transition:leave="transition ease-in duration-200" 4182 x-transition:leave-start="opacity-100 transform translate-y-0" 4183 x-transition:leave-end="opacity-0 transform -translate-y-2" 4184 @click.away="closeSearch()" 4185 x-cloak> 4186 <div x-ref="searchForm"> 4187 <div x-data="initLiveSearch" 4188 x-init="initPopover" 4189 @close-search.window="closePopover" 4190 @open-search.window="openPopover" 4191 @keydown.escape="closePopover" 4192 class="relative" 4193> 4194 <search role="search"> 4195 <form class="form minisearch w-full flex md:relative lg:max-w-80 ml-auto" 4196 id="search_mini_form" 4197 action="https://www.purdys.com/catalogsearch/result/" 4198 method="get" 4199 > 4200 <div class="relative w-full"> 4201 <label class="hidden" for="search" data-role="minisearch-label">
4202 <span>Search</span> 4203 </label> 4204 <input id="search" 4205 class="form-input flex-1 px-3 py-2 rounded-sm outline-none w-full min-h-12 hover:border-primary focus:border-primary focus:shadow-none" 4206 type="text" 4207 x-ref="searchRef" 4208 :value="queryPhrase" 4209 name="q" 4210 value="" 4211 placeholder="Type to search..." 4212 maxlength="128" 4213 role="combobox" 4214 aria-haspopup="false" 4215 aria-autocomplete="both" 4216 autocomplete="off" 4217 aria-expanded="false" 4218 @input="handleInput" 4219 @click.away="closePopover" 4220 /> 4221 <button type='submit' 4222 title="Search" 4223 x-show="queryPhrase === ''" 4224 class='bg-primary text-white rounded-e-sm px-3 outline-none absolute end-0 top-0 h-full 4225 rounded-r border-0 hover:bg-primary hover:border-0' 4226 aria-label="Search"> 4227 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"> 4228<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75049 1.49969C4.74642 1.49969 1.50049 4.74563 1.50049 8.74969C1.50049 12.7538 4.74642 15.9997 8.75049 15.9997C12.7546 15.9997 16.0005 12.7538 16.0005 8.74969C16.0005 4.74563 12.7546 1.49969 8.75049 1.49969ZM0.000488281 8.74969C0.000488281 3.9172 3.918 -0.000305176 8.75049 -0.000305176C13.583 -0.000305176 17.5005 3.9172 17.5005 8.74969C17.5005 13.5822 13.583 17.4997 8.75049 17.4997C3.918 17.4997 0.000488281 13.5822 0.000488281 8.74969Z" fill="white"/> 4229<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4001 14.3993C14.7108 14.0887 15.2144 14.0887 15.5251 14.3993L19.7675 18.6418C20.0781 18.9524 20.0781 19.4561 19.7675 19.7667C19.4569 20.0774 18.9532 20.0774 18.6426 19.7667L14.4001 15.5243C14.0895 15.2136 14.0895 14.71 14.4001 14.3993Z" fill="white"/> 4230</svg> 4231 </button> 4232 <button 4233 @click.prevent="queryString = ''; clearInput()" 4234 x-cloak 4235 x-show="queryPhrase !== ''" 4236 title="Clear" 4237 class="bg-primary text-white rounded-e-sm px-3 outline-none absolute 4238 end-0 top-0 h-full rounded-r border-0 hover:bg-primary hover:border-0 hover:text-white" 4239 aria-label="Clear"> 4240 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true"> 4241<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 4242<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 4243</svg> 4244 </button> 4245 <template x-if="queryPhrase != ''"> 4246 <a id="clearInput" class="absolute right-[60px] top-[11px] hover:text-primary" @click.prevent="clearInput" href="#">Clear</a> 4247 </template> 4248 </div> 4249 </form> 4250 </search> 4251 <div x-show="searchOpen" 4252 class="bg-black opacity-[0.6] fixed w-[100vw] h-[100vh] left-0 mt-[18px]" 4253 style="display: none;" 4254 @click="closePopup"></div> 4255 <section 4256 x-show="searchOpen" 4257 class="livesearch-overlay search-autocomplete overflow-y-scroll !h-[calc(100dvh-var(--height-header))] md:!h-auto md:overflow-y-auto" 4258 :class="getSearchClasses" 4259 style="display: none;" 4260 aria-label="Search Suggestions" 4261 x-transition:enter='transition-all duration-300 ease-out' 4262 x-transition:enter-start='opacity-0' 4263 x-transition:enter-end='opacity-100' 4264 x-transition:leave='transition-all duration-200 ease-in' 4265 x-transition:leave-start='opacity-100' 4266 x-transition:leave-end='opacity-0' 4267 > 4268 <!-- No Results Header --> 4269 <template x-if="showResults && !liveSearchTotalCount && !isLoading"> 4270 <div class="max-w-[1660px] mx-auto px-4 md:px-0"> 4271 <div class="flex items-center justify-between"> 4272 <h3 class="font-bold text-xl md:text-3xl" 4273 x-text="'No\u0020results\u0020found\u0020for\u003A ' + '\'' + queryPhrase + '\''"></h3> 4274 <a href="#" @click.prevent='closePopup'> 4275 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="none" class="text-black" role="img"> 4276<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 4277<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 4278<title>x</title></svg> 4279 </a> 4280 </div> 4281 <p class="pt-3">
4281We can't find any item matching your search. Please try another search.</p> 4282 </div> 4283 </template> 4284 <!-- Results Header --> 4285 <template x-if="showResults"> 4286 <div class="flex items-center justify-between max-w-[1660px] mx-auto px-4 md:px-0"> 4287 <h3 class="font-bold text-xl md:text-3xl !m-0" x-text="'Search\u0020results\u0020for\u003A ' + '\'' + queryPhrase + '\''"></h3> 4288 <a href="#" @click.prevent='closePopup'> 4289 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="none" class="text-black" role="img"> 4290<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 4291<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 4292<title>x</title></svg> 4293 </a> 4294 </div> 4295 </template> 4296 <div x-show="liveSearchTotalCount && !isLoading" class="livesearch-overlay-content" 4297 x-transition:enter='transition-all duration-300 ease-out' 4298 x-transition:enter-start='opacity-0' 4299 x-transition:enter-end='opacity-100' 4300 x-transition:leave='transition-all duration-200 ease-in' 4301 x-transition:leave-start='opacity-100' 4302 x-transition:leave-end='opacity-0'> 4303 <template x-if="showSuggestions"> 4304 <div class="flex flex-col md:max-h-[393px]"> 4305 <p class="text-lg pb-[10px] border-b border-grey-muted"> 4306 <strong> 4307 Suggestions </strong> 4308 </p> 4309 <ul class="-mx-2 space-y-1 !border-0 pl-[8px]" role="list"> 4310 <template x-for="suggestion in liveSearchSuggestions"> 4311 <li class="before:hidden !border-0 !p-0 pl-0 hover:!bg-transparent"> 4312 <button 4313 x-html="getSuggestionText(suggestion)" 4314 @click="searchSuggestion" 4315 @mouseenter="onSuggestionHover(suggestion)" 4316 type="button" 4317 class="w-full p-2 text-left capitalize border-0 pl-0 4318 hover:text-primary-hover hover:underline 4319 hover:cursor-pointer hover:bg-transparent hover:border-0" 4320 ></button> 4321 </li> 4322 </template> 4323 </ul> 4324 <div class="pt-3 md:pt-5 mt-auto pb-2.5 md:pb-0"> 4325 <a href='#' @click.prevent="redirectToSearchPage()" class='flex gap-1 items-center !text-primary'>
4326 <span x-text="getSeeResultsText()"></span> 4327 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="rtl:rotate-180" width="20" height="20" aria-hidden="true"> 4328 <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/> 4329</svg> 4330 </a> 4331 </div> 4332 </div> 4333 </template> 4334 <template x-if="showResults"> 4335 <div class="lg:col-span-2"> 4336 <p class="text-lg pb-[10px] border-b-[1px] border-grey-muted"> 4337 <strong> 4338 Product Suggestions </strong> 4339 </p> 4340 <ul class="livesearch-overlay-content-products !border-0" role="list"> 4341 <template x-for="product in liveSearchProducts"> 4342 <li class="before:hidden pl-0 !p-0 !border-0 hover:!bg-transparent"> 4343 <button 4344 class="w-full flex gap-4 text-left cursor-pointer 4345 product-suggestion-button border-0 hover:bg-transparent hover:border-0" 4346 @click="handleProductClick" 4347 > 4348 <img 4349 :src="product.image" 4350 :alt="product.name" 4351 width="80" 4352 height="80" 4353 class="w-[80px] h-[80px] shrink-0 object-contain" 4354 > 4355 <div class="flex flex-col justify-center w-full"> 4356 <div class="flex flex-row justify-between gap-6"> 4357 <p><strong class="product-name max-w-[287px]" x-text="product.name"></strong></p> 4358 <template x-if="product.price"> 4359 <p class="font-bold" x-text="showProductPrice"></p> 4360 </template> 4361 </div> 4362 </div> 4363 </button> 4364 </li> 4365 </template> 4366 </ul> 4367 <div class="py-5 md:pb-0"> 4368 <a href="#" @click.prevent="redirectToSearchPage(true)" class="flex gap-1 items-center !text-primary">
4369 <span x-text="getSeeResultsText(true)"></span> 4370 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="rtl:rotate-180" width="20" height="20" aria-hidden="true"> 4371 <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/> 4372</svg> 4373 </a> 4374 </div> 4375 </div> 4376 </template> 4377 </div> 4378 </section> 4379</div> 4380
4381<script> 4382 'use strict'; 4383 4384 // Global search instance storage to persist across Alpine re-initializations 4385 window.liveSearchInstance = null; 4386 window.liveSearchInitialized = false; 4387 4388 window.addEventListener( 4389 'alpine:init', 4390 () => Alpine.data('initLiveSearch', () => ({ 4391 queryPhrase: '', 4392 liveSearchProducts: [], 4393 liveSearchSuggestions: [], 4394 liveSearchTotalCount: 0, 4395 isLoading: false, 4396 minQueryLength: 3, 4397 pageSize: 8, 4398 searchOpen: false, 4399 search: null, 4400 searchInitialized: false, 4401 isSuggestionHoverMode: false, 4402 suggestionHover: '', 4403 searchId: "2cc69165-2270-494c-893f-c00d0bb35bd7_" + 4404 "base_" + 4405 "main_website_store_" + 4406 "default", 4407 4408 initPopover() { 4409 this.initSearch(); 4410 this.initTogglePopover(); 4411 4412 const self = this; 4413 4414 window.addEventListener('open-search-popup', function (event) { 4415 if (self.liveSearchTotalCount > 0) { 4416 self.openPopover(); 4417 } 4418 }); 4419 4420 }, 4421 4422 async initSearch() { 4423 // Check if we already have a global instance 4424 if (window.liveSearchInitialized && window.liveSearchInstance) { 4425 this.search = window.liveSearchInstance; 4426 this.searchInitialized = true; 4427 return; 4428 } 4429 4430 if (this.searchInitialized || this.searchInitializing) { 4431 return; 4432 } 4433 4434 this.searchInitializing = true; 4435 4436 try { 4437 // Set up storefront events 4438 if (window.magentoStorefrontEvents) { 4439 window.magentoStorefrontEvents.context.setSearchExtension({ 4440 version: "4.4.1" 4441 }); 4442 } 4443 4444 // Wait for MagentoLiveSearch to be available (with shorter timeout) 4445 await this.waitForMagentoLiveSearch(); 4446 4447 if (!window.MagentoLiveSearch || typeof window.MagentoLiveSearch !== 'function') { 4448 throw new Error('MagentoLiveSearch is not available'); 4449 } 4450 4451 this.search = new window.MagentoLiveSearch({ 4452 environmentId: "2cc69165-2270-494c-893f-c00d0bb35bd7", 4453 websiteCode: "base", 4454 storeCode: "main_website_store", 4455 storeViewCode: "default", 4456 apiKey: "search_gql", 4457 }); 4458 4459 if (!this.search) { 4460 throw new Error('MagentoLiveSearch constructor returned null/undefined'); 4461 } 4462 4463 // Store globally to persist across Alpine re-initializations 4464 window.liveSearchInstance = this.search; 4465 window.liveSearchInitialized = true; 4466 this.searchInitialized = true; 4467 } catch (error) { 4468 console.error('MagentoLiveSearch could not be initialized:', error); 4469 this.searchInitialized = false; 4470 this.search = null; 4471 } finally { 4472 this.searchInitializing = false; 4473 } 4474 }, 4475 4476 waitForMagentoLiveSearch() { 4477 return new Promise((resolve) => { 4478 // If already available, resolve immediately 4479 if (window.MagentoLiveSearch) { 4480 resolve(); 4481 return; 4482 } 4483 4484 // Otherwise set up a watcher with faster polling 4485 const checkInterval = setInterval(() => { 4486 if (window.MagentoLiveSearch) { 4487 clearInterval(checkInterval); 4488 resolve(); 4489 } 4490 }, 50); // Faster polling - 50ms instead of 100ms 4491
4492 // Shorter timeout - 3 seconds instead of 10 4493 setTimeout(() => { 4494 clearInterval(checkInterval); 4495 resolve(); 4496 }, 3000); 4497 }); 4498 }, 4499 4500 clearInput() { 4501 this.$refs.searchRef.value = ''; 4502 this.liveSearchProducts = []; 4503 this.liveSearchSuggestions = []; 4504 this.liveSearchTotalCount = 0; 4505 this.queryPhrase = ''; 4506 4507 this.closePopover(); 4508 }, 4509 4510 redirectToSearchPage(isSuggestion = false) { 4511 let searchTerm = this.queryPhrase; 4512 4513 if (isSuggestion) { 4514 // Use suggestionHover if available, otherwise fall back to queryPhrase 4515 searchTerm = this.suggestionHover || this.queryPhrase; 4516 } 4517 4518 window.location.href = '/catalogsearch/result/?q=' + encodeURIComponent(searchTerm); 4519 }, 4520 4521 buildSearch() { 4522 window.magentoStorefrontEvents.context.setSearchExtension({ 4523 version: "4.4.1" 4524 }); 4525 4526 // Wait for MagentoLiveSearch to be available 4527 return new Promise((resolve) => { 4528 const checkForMagentoLiveSearch = () => { 4529 if (window.MagentoLiveSearch) { 4530 resolve(new MagentoLiveSearch({ 4531 environmentId: "2cc69165-2270-494c-893f-c00d0bb35bd7", 4532 websiteCode: "base", 4533 storeCode: "main_website_store", 4534 storeViewCode: "default", 4535 apiKey: "search_gql", 4536 })); 4537 } else { 4538 setTimeout(checkForMagentoLiveSearch, 100); 4539 } 4540 }; 4541 4542 checkForMagentoLiveSearch(); 4543 }); 4544 4545 }, 4546 4547 // this will open the popover, whenever the search-input has focus 4548 initTogglePopover() { 4549 const popover = document.getElementById('search'); 4550 4551 popover.addEventListener('focus', () => { 4552 if (this.isQueryLengthValid()) { 4553 this.openPopover(); 4554 } else { 4555 this.closePopover(); 4556 } 4557 }); 4558 }, 4559 4560 closePopover() { 4561 this.searchOpen = false; 4562 // Restore body scroll when closing search overlay 4563 document.body.style.overflow = ''; 4564 }, 4565 4566 closePopup() { 4567 this.searchOpen = false; 4568 // Restore body scroll when closing search overlay 4569 document.body.style.overflow = ''; 4570 }, 4571 4572 openPopover() { 4573 this.searchOpen = true; 4574 4575 if (window.innerWidth <= 768) { 4576 // Lock body scroll when opening search overlay 4577 document.body.style.overflow = 'hidden'; 4578 } 4579 }, 4580 4581 handleInput() { 4582 this.queryPhrase = this.$el.value; 4583 this.suggestionHover = ''; 4584 this.isSuggestionHoverMode = false; 4585 4586 if (!this.isQueryLengthValid()) { 4587 if (this.searchOpen){ 4588 this.closePopover(); 4589 } 4590 return; 4591 } 4592 4593 this.getProducts(); 4594 this.$nextTick(() => { 4595 this.openPopover(); 4596 }); 4597 }, 4598 4599 showProductPrice(){ 4600 return this.product.price ? hyva.formatPrice(this.product.price) : ''; 4601 }, 4602 4603 handleProductClick() { 4604 window.magentoStorefrontEvents.context.setSearchResults( 4605 window.magentoStorefrontEvents.context.getSearchResults() 4606 ); 4607 4608 window.magentoStorefrontEvents.publish.searchProductClick("search-bar", `${this.product.sku}`); 4609 4610 // Timeout to prevent submission before event has fired. 4611 setTimeout(() => window.location.href = this.product.url, 500); 4612 }, 4613 4614 // this will submit the form with the clicked suggestion as the search-phrase 4615 searchSuggestion() { 4616 document.getElementById('search').value = this.suggestion; 4617 document.getElementById('search_mini_form').submit(); 4618 }, 4619 4620 showResults(){ 4621 return this.liveSearchProducts.length >= 1 && this.isQueryLengthValid() || this.isSuggestionHoverMode; 4622 }, 4623 4624 showSuggestions(){ 4625 return this.liveSearchSuggestions.length >= 1 && this.isQueryLengthValid(); 4626 }, 4627 4628 isQueryLengthValid() { 4629 return this.queryPhrase.length >= this.minQueryLength; 4630 }, 4631 4632 getSearchClasses() { 4633 return this.searchOpen ? 'block' : 'hidden'; 4634 }, 4635 getSuggestionText(suggestion) { 4636 const matches = suggestion.match(new RegExp(this.queryPhrase, 'gmi')); 4637 4638 matches.map((match) => { 4639 suggestion = suggestion.replaceAll(match, '<b>' + match + '</b>') 4640 }); 4641 4642 return suggestion; 4643 }, 4644 async onSuggestionHover(suggestion) { 4645 this.isSuggestionHoverMode = true; 4646 this.suggestionHover = suggestion; 4647 4648 await this.getProducts(); 4649 4650 this.queryPhrase = suggestion; 4651 }, 4652 onSuggestionUnhover() { 4653 this.isSuggestionHoverMode = false;
4654 this.suggestionHover = ''; 4655 }, 4656 getSeeResultsText(isSuggestion = false) { 4657 const text = 'See\u0020\u0025total\u0020results\u0020for\u0020\u0022\u0025term\u0022'; 4658 4659 if (isSuggestion && this.isSuggestionHoverMode) { 4660 return text.replace('%total', this.liveSearchTotalCount).replace('%term', this.suggestionHover); 4661 } 4662 4663 return text.replace('%total', this.liveSearchTotalCount).replace('%term', this.queryPhrase); 4664 }, 4665 4666 // get products from fetch-request and update liveSearchProducts and liveSearchSuggestions 4667 async getProducts() { 4668 this.searchOpen = true; 4669 4670 if (!this.isSuggestionHoverMode) { 4671 this.isLoading = true; 4672 } 4673 4674 // Make sure search is initialized 4675 if (!this.searchInitialized && !this.searchInitializing) { 4676 await this.initSearch(); 4677 } else if (this.searchInitializing) { 4678 // Wait for ongoing initialization to complete (reduced timeout) 4679 let attempts = 0; 4680 while (this.searchInitializing && attempts < 20) { // 2 seconds max instead of 5 4681 await new Promise(resolve => setTimeout(resolve, 100)); 4682 attempts++; 4683 } 4684 } 4685 4686 // If still not initialized, try to use global instance as fallback 4687 if (!this.search) { 4688 if (window.liveSearchInstance) { 4689 this.search = window.liveSearchInstance; 4690 this.searchInitialized = true; 4691 } else { 4692 console.error('MagentoLiveSearch could not be initialized'); 4693 this.isLoading = false; 4694 return; 4695 } 4696 } 4697 4698 const result = await this.getLiveSearchProducts(); 4699 this.isLoading = false; 4700 4701 this.liveSearchProducts = result.data.productSearch.items.map(item => ({ 4702 name: item.product.name, 4703 price: item.product.price_range.maximum_price.regular_price.value, 4704 currency: item.product.price_range.maximum_price.regular_price.currency, 4705 image: item.product.thumbnail?.url || item.product.image?.url || 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 4706 url: item.product.canonical_url, 4707 id: item.product.id, 4708 sku: item.product.sku 4709 })); 4710 4711 this.liveSearchSuggestions = result.data.productSearch.suggestions 4712 this.liveSearchTotalCount = result.data.productSearch.total_count; 4713 4714 //search-results-view 4715 }, 4716 4717 // retrieve products from magento live search service 4718 async getLiveSearchProducts() { 4719 this.searchRequestId = Math.random().toString(16).slice(2); 4720 4721 if (!this.search) { 4722 return { 4723 data: { 4724 productSearch: { 4725 items: [], 4726 suggestions: [], 4727 total_count: 0, 4728 page_info: { 4729 current_page: 1, 4730 page_size: this.pageSize 4731 }, 4732 facets: [] 4733 } 4734 } 4735 }; 4736 } 4737 4738 const searchQuery = this.isSuggestionHoverMode ? this.suggestionHover : this.queryPhrase; 4739 4740 const searchCtx = { 4741 units: [{ 4742 searchUnitId: "search-bar", 4743 queryTypes: ["products"], 4744 phrase: searchQuery, 4745 currentPage: 1, 4746 pageSize: this.pageSize, 4747 filter:[ 4748 {attribute:"visibility",in:["Search","Catalog, Search"]}, 4749 {attribute:"inStock",eq:"true"} 4750 ], 4751 sort: [{attribute: "relevance", direction: "DESC"}], 4752 searchRequestId: this.searchRequestId 4753 }] 4754 }; 4755 4756 window.magentoStorefrontEvents.context.setSearchInput(searchCtx); 4757 window.magentoStorefrontEvents.publish.searchRequestSent("search-bar"); 4758 const before = Date.now(); 4759 let result; 4760 4761 try { 4762 // Here we call the actual search method 4763 result = await this.search.productSearch({ 4764 phrase: searchQuery, 4765 page_size: this.pageSize, 4766 sort: [{attribute: "relevance", direction: "DESC"}], 4767 filter: [ 4768 {attribute: "visibility", in: ["Search", "Catalog, Search"]}, 4769 {attribute: "inStock", eq: "true"} 4770 ], 4771 current_page: 1, 4772 data: { 4773 products: true, 4774 facets: true, 4775 suggestions: true 4776 }, 4777 context: { 4778 customerGroup: "b6589fc6ab0dc82cf12099d1c2d40ab994e8410c" 4779 } 4780 }); 4781 } catch (error) { 4782 return { data: { productSearch: { items: [], suggestions: [], total_count: 0, page_info: { current_page: 1, page_size: this.pageSize } } } }; 4783 } 4784 4785 const after = Date.now(); 4786 4787 const productItems = result.data.productSearch.items || []; 4788 const suggestions = result.data.productSearch.suggestions || []; 4789 const facets = result.data.productSearch.facets || []; 4790 4791 const formattedProductItems = productItems.map((item, key) => ({ 4792 name: item.product.name, 4793 sku: item.product.sku, 4794 url: item.product.canonical_url, 4795 imageUrl: item.product.image ? item.product.image.url : 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 4796 price: item.product.price_range.minimum_price.final_price.value, 4797 rank: key, 4798 })); 4799 4800 const formattedSuggestions = suggestions.map((item, key) => ({ 4801 suggestion: item, 4802 rank: key, 4803 })); 4804 4805 const formattedFacets = facets.map((facet) => ({ 4806 attribute: facet.attribute, 4807 title: facet.title, 4808 type: facet.type || "PINNED", 4809 buckets: facet.buckets.map((bucket) => bucket), 4810 })); 4811 4812 const searchCtxUpdated = { 4813 units: [{ 4814 searchUnitId: "search-bar", 4815 searchRequestId: this.searchRequestId, 4816 executionTime: after - before, 4817 products: formattedProductItems, 4818 categories: [], 4819 suggestions: this.isSuggestionHoverMode ? this.suggestions : formattedSuggestions, 4820 page: result.data.productSearch.page_info.current_page, 4821 perPage: result.data.productSearch.page_info.page_size, 4822 facets: formattedFacets 4823 }] 4824 }; 4825 4826 if (this.isSuggestionHoverMode) { 4827 this.liveSearchProducts = result.data.productSearch.items.map(item => ({ 4828 name: item.product.name, 4829 price: item.product.price_range.maximum_price.regular_price.value, 4830 currency: item.product.price_range.maximum_price.regular_price.currency, 4831 image: item.product.thumbnail?.url || item.product.image?.url || 'https://www.purdys.com/media/catalog/product/placeholder/default/No-image-available_1.png', 4832 url: item.product.canonical_url, 4833 id: item.product.id, 4834 sku: item.product.sku 4835 })); 4836 } 4837 4838 window.magentoStorefrontEvents.context.setSearchResults(searchCtxUpdated); 4839 window.magentoStorefrontEvents.publish.searchResponseReceived("search-bar"); 4840 4841 return result; 4842 } 4843 })), 4844 {once: true} 4845 ) 4846</script>
4846 4847 </div> 4848 </div> 4849 4850 <!--Cart Drawer--> 4851
4851<script> 4852 function initCartDrawer() { 4853 return { 4854 open: false, 4855 isLoading: false, 4856 cart: {}, 4857 maxItemsToDisplay: 10, 4858 itemsCount: 0, 4859 itemCountSingularText: 'item', 4860 itemCountPluralText: 'items', 4861 updatedQuantities: {}, 4862 4863 init() { 4864 // Initialization without free shipping calculation 4865 }, 4866 4867 getData(data) { 4868 if (data.cart) { 4869 this.cart = data.cart; 4870 this.itemsCount = data.cart.items?.reduce((sum, item) => sum + (parseInt(item.qty) || 0), 0) || 0; 4871 this.setCartItems(); 4872 4873 // Dispatch cart subtotal event for free shipping indicator 4874 if (data.cart.subtotal) { 4875 window.dispatchEvent(new CustomEvent('update-cart-subtotal', { 4876 detail: { 4877 subtotal: parseFloat(data.cart.subtotalAmount) || 0 4878 } 4879 })); 4880 } 4881 } 4882 this.isLoading = false; 4883 }, 4884 4885 cartItems: [], 4886 getItemCountTitle() { 4887 return hyva.strf('\u00250\u0020of\u0020\u00251\u0020products\u0020in\u0020cart\u0020displayed', this.maxItemsToDisplay, this.itemsCount) 4888 }, 4889 setCartItems() { 4890 this.cartItems = this.cart.items && this.cart.items.sort((a, b) => b.item_id - a.item_id) || []; 4891 if (this.maxItemsToDisplay > 0) { 4892 this.cartItems = this.cartItems.slice(0, parseInt(this.maxItemsToDisplay, 10)); 4893 } 4894 }, 4895 updateQuantity(itemId, qty) { 4896 this.updatedQuantities[itemId] = qty; 4897 this.sendUpdateCart(itemId, qty); 4898 }, 4899 sendUpdateCart(itemId, qty) { 4900 this.isLoading = true; 4901 const formKey = hyva.getFormKey(); 4902 const postUrl = BASE_URL + 'checkout/sidebar/updateItemQty/'; 4903 fetch(postUrl, { 4904 "headers": { 4905 "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 4906 }, 4907 "body": "form_key=" + formKey + "&item_id=" + itemId + "&item_qty=" + qty, 4908 "method": "POST", 4909 "mode": "cors", 4910 "credentials": "include" 4911 }).then(response => { 4912 if (response.redirected) { 4913 window.location.href = response.url; 4914 } else if (response.ok) { 4915 return response.json(); 4916 } else { 4917 window.dispatchMessages && window.dispatchMessages([{ type: 'warning', text: 'Could\u0020not\u0020update\u0020item\u0020from\u0020quote.' }]); 4918 } 4919 }).then(result => { 4920 window.dispatchMessages && window.dispatchMessages([{ type: result.success ? 'success' : 'error', text: result.success ? 'You\u0020updated\u0020the\u0020item.' : result.error_message }], result.success ? 5000 : 0); 4921 if (result.success) { 4922 window.dataLayer = window.dataLayer || []; 4923 window.dataLayer.push({ event: result.event, ecommerce: result.ecommerce }); 4924 } 4925 window.dispatchEvent(new CustomEvent('reload-customer-section-data')); 4926 this.$dispatch && this.$dispatch('update-totals', {data: result.checkoutConfig?.totalsData}); 4927 }).finally(() => { 4928 this.isLoading = false; 4929 }); 4930 }, 4931 deleteItemFromCart(itemId) { 4932 this.isLoading = true; 4933 const itemData = this.cart.items.filter((item) => item['item_id'] === itemId); 4934 const formKey = hyva.getFormKey(); 4935 const postUrl = BASE_URL + 'checkout/sidebar/removeItem/'; 4936 fetch(postUrl, { 4937 "headers": { 4938 "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 4939 }, 4940 "body": "form_key=" + formKey + "&item_id=" + itemId, 4941 "method": "POST", 4942 "mode": "cors", 4943 "credentials": "include" 4944 }).then(response => { 4945 if (response.redirected) { 4946 window.location.href = response.url; 4947 } else if (response.ok) { 4948 return response.json(); 4949 } else { 4950 window.dispatchMessages && window.dispatchMessages([{ type: 'warning', text: 'Could\u0020not\u0020remove\u
49500020item\u0020from\u0020quote.' }]); 4951 this.isLoading = false; 4952 } 4953 }).then(result => { 4954 window.dispatchMessages && window.dispatchMessages([{ type: result.success ? 'success' : 'error', text: result.success ? 'You\u0020removed\u0020the\u0020item.' : result.error_message }], result.success ? 5000 : 0); 4955 if (result.success && itemData) { 4956 window.dispatchEvent(new CustomEvent('cart-item-removed', { detail: itemData })); 4957 } 4958 window.dispatchEvent(new CustomEvent('reload-customer-section-data')); 4959 }); 4960 }, 4961 scrollLock(use = true) { 4962 document.body.style.overflow = use ? 'hidden' : ''; 4963 }, 4964 toggleCartDrawer(event) { 4965 if (event.detail && event.detail.isOpen !== undefined) { 4966 if (event.detail.isOpen) { 4967 this.openCartDrawer(); 4968 } else { 4969 this.open = false; 4970 this.scrollLock(false); 4971 this.$refs && this.$refs.cartDialogContent && hyva.releaseFocus(this.$refs.cartDialogContent); 4972 } 4973 } else { 4974 this.openCartDrawer() 4975 } 4976 }, 4977 openCartDrawer() { 4978 this.open = true; 4979 this.scrollLock(true); 4980 this.$nextTick(() => { 4981 this.$refs && this.$refs.cartDialogContent && hyva.trapFocus(this.$refs.cartDialogContent) 4982 }) 4983 }, 4984 closeCartDrawer() { 4985 this.$dispatch('toggle-cart', { isOpen: false }) 4986 }, 4987 getSectionDataExtraActions() { 4988 if (!this.cart.extra_actions) { 4989 return ''; 4990 } 4991 const contentNode = document.createElement('div'); 4992 contentNode.innerHTML = this.cart.extra_actions; 4993 hyva.activateScripts(contentNode); 4994 return contentNode.innerHTML; 4995 }, 4996 getProductEditUrl(item) { 4997 if (item.product_type === 'bundle') { 4998 return BASE_URL + 'build-a-box?item_id=' + item.item_id; 4999 } 5000 5001 return item.configure_url; 5002 }, 5003 getProductImageUrl(item, src) { 5004 if (item.product_type === 'bundle') { 5005 return BASE_URL + 'build-a-box/' 5006 } 5007 5008 return src; 5009 } 5010 } 5011 } 5012</script>
5012 5013<section x-cloak 5014 x-show="cart" 5015 id="cart-drawer" 5016 x-data="initCartDrawer()" 5017 @private-content-loaded.window="getData($event.detail.data)" 5018 @toggle-cart.window="toggleCartDrawer($event)" 5019 @keydown.escape="closeCartDrawer" 5020> 5021 <div role="dialog" 5022 aria-labelledby="cart-drawer-title" 5023 aria-modal="true" 5024 :aria-hidden="!open" 5025 class="fixed inset-y-0 end-0 z-30 flex max-w-full"> 5026 <div class="backdrop" 5027 x-show="open" 5028 x-transition:enter="ease-in-out duration-500" 5029 x-transition:enter-start="opacity-0" 5030 x-transition:enter-end="opacity-100" 5031 x-transition:leave="ease-in-out duration-500" 5032 x-transition:leave-start="opacity-100" 5033 x-transition:leave-end="opacity-0" 5034 role="button" 5035 @click="closeCartDrawer" 5036 aria-label="Close minicart"></div> 5037 <div class="relative w-screen max-w-md bg-white" 5038 x-show="open" 5039 x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700" 5040 x-transition:enter-start="ltr:translate-x-full rtl:-translate-x-full" 5041 x-transition:enter-end="translate-x-0" 5042 x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700" 5043 x-transition:leave-start="translate-x-0" 5044 x-transition:leave-end="ltr:translate-x-full rtl:-translate-x-full" 5045 x-ref="cartDialogContent" 5046 role="region" 5047 :tabindex="open ? 0 : -1" 5048 aria-label="My Shopping Bag" 5049 > 5050 <div class="flex flex-col h-full text-black"> 5051 5052 <header class="relative p-4 border-b border-subtext"> 5053 <h3 id="cart-drawer-title" class="flex items-center gap-2 !m-0"> 5054 <span class="font-bold text-lg uppercase">My Shopping Bag</span> 5055 <span 5056 class="rounded-full bg-primary-dark text-white text-xs px-2 py-1 font-semibold" 5057 x-text="`${itemsCount} ${itemsCount === 1 ? itemCountSingularText : itemCountPluralText}`" 5058 x-show="itemsCount > 0" 5059 ></span> 5060 </h3> 5061 </header> 5062 5063 5064<!-- Free Shipping Indicator --> 5065<div id="minicart-free-shipping-indicator" 5066 class="free-shipping-indicator bg-background-light bg-opacity-50 pt-[11px] pb-3 px-4 bg-transparent" 5067 x-data="initFreeShippingIndicator()" 5068 x-init="init()" 5069 :class="getFreeShippingStateClass()" 5070 x-show="freeShippingVisible" 5071 x-cloak 5072 @shipping-estimation-address-updated.window="handleCountryChange($event.detail?.shippingAddress?.country_id)" 5073> 5074 <p class="text-center mb-1.5 min-h-[24px]" x-html="freeShippingMessage"></p> 5075 5076 <div class="rounded-full h-2 overflow-hidden bg-primary/[.15]"> 5077 <div 5078 class="rounded-full h-2 transition-all duration-300 ease-out bg-primary" 5079 :style="`width: ${freeShippingProgressPercentage}%`" 5080 ></div> 5081 </div> 5082</div> 5083
5084<script> 5085 function initFreeShippingIndicator() { 5086 return { 5087 // Free shipping properties 5088 freeShippingThreshold: 100, 5089 freeShippingSubtotal: 0, 5090 freeShippingMessage: '', 5091 freeShippingProgressPercentage: 0, 5092 freeShippingEnabled: true, 5093 freeShippingVisible: false, 5094 freeShippingAllowedCountries: ["CA"], 5095 freeShippingAchievedMessage: 'Congratulations,\u0020you\u0020get\u0020\u003Cstrong\u003EFree\u0020Shipping\u0021\u003C\u002Fstrong\u003E', 5096 freeShippingRemainingMessage: 'Add\u0020\u003Cstrong\u003E\u0024\u00251\u003C\u002Fstrong\u003E\u0020more\u0020to\u0020get\u0020\u003Cstrong\u003EFree\u0020Shipping\u0021\u003C\u002Fstrong\u003E', 5097 5098 init() { 5099 this.calculateFreeShipping(); 5100 5101 // Listen for cart subtotal updates 5102 window.addEventListener('update-cart-subtotal', (event) => { 5103 this.freeShippingSubtotal = event.detail.subtotal || 0; 5104 this.calculateFreeShipping(); 5105 }); 5106 5107 const sectionData = JSON.parse( 5108 hyva.getBrowserStorage().getItem('mage-cache-storage') 5109 ) || {}; 5110 5111 const checkoutData = sectionData?.['checkout-data'] || {}; 5112 const shippingAddress = checkoutData?.shippingAddressFromData || {}; 5113 // window.checkoutConfig is `false` on an empty cart page and absent on non-cart 5114 // pages â reading .defaultCountryId off it crashed this component's init and left 5115 // the indicator permanently hidden. Guard it and fall back to the store default. 5116 const countryId = shippingAddress.country_id 5117 || (window.checkoutConfig && window.checkoutConfig.defaultCountryId) 5118 || 'CA'; 5119 this.handleCountryChange(countryId); 5120 }, 5121 5122 calculateFreeShipping() { 5123 if (!this.freeShippingEnabled || this.freeShippingThreshold <= 0) { 5124 return; 5125 } 5126 5127 if (this.freeShippingSubtotal >= this.freeShippingThreshold) { 5128 this.freeShippingMessage = this.freeShippingAchievedMessage; 5129 this.freeShippingProgressPercentage = 100; 5130 } else { 5131 const remaining = this.freeShippingThreshold - this.freeShippingSubtotal; 5132 this.freeShippingMessage = this.freeShippingRemainingMessage.replace('%1', remaining.toFixed(2)); 5133 this.freeShippingProgressPercentage = Math.min((this.freeShippingSubtotal / this.freeShippingThreshold) * 100, 100); 5134 } 5135 }, 5136 5137 getFreeShippingStateClass() { 5138 return this.freeShippingSubtotal >= this.freeShippingThreshold ? 'free-shipping-achieved' : ''; 5139 }, 5140 5141 handleCountryChange(newCountryId) { 5142 this.freeShippingVisible = 5143 this.freeShippingAllowedCountries.includes(newCountryId); 5144 } 5145 }; 5146 } 5147</script>
5147 5148 5149 5150 <template x-if="!itemsCount"> 5151 <div class="relative px-4 py-6 bg-white border-bs sm:px-6 border-container"> 5152 Cart is empty </div> 5153 </template> 5154 5155 <template x-if="itemsCount"> 5156 <div class="minicart-item-container relative grid overflow-y-auto overscroll-y-contain"> 5157 <template x-for="item in cartItems"> 5158 <template x-if="item.is_hidden_cart_item !== true"> 5159 <div 5160 class="flex items-start py-4 mx-4 space-x-3 transition duration-150 ease-in-out bg-white 5161 rounded-sm border-b border-grey-checkbox" 5162 > 5163 <!-- Product image as before --> 5164 <template x-if="item.product_type === 'bundle' || (item.product_has_url && item.is_visible_in_site_visibility)"> 5165 <a :href="getProductImageUrl(item, item.product_url)" 5166 class="w-1/4 max-w-24 !ms-0" 5167 :aria-label="hyva.strf('Product\u0020\u0022\u00250\u0022', item.product_name)" 5168 > 5169 <img 5170 :src="item.product_image.src" 5171 :width="item.product_image.width" 5172 :height="item.product_image.height" 5173 loading="lazy" 5174 :alt="item.product_name" 5175 /> 5176 </a> 5177 </template> 5178 <template x-if="item.product_type !== 'bundle' && (!item.product_has_url || !item.is_visible_in_site_visibility)"> 5179 <div class="w-1/4 max-w-24 !ms-0"> 5180 <img 5181 :src="item.product_image.src" 5182 :width="item.product_image.width" 5183 :height="item.product_image.height" 5184 loading="lazy" 5185 :alt="item.product_name" 5186 /> 5187 </div> 5188 </template> 5189 <div class="w-3/4"> 5190 <div> 5191 <p class="font-bold line-clamp-2"> 5192 <span x-html="item.product_name"></span> 5193 </p> 5194 </div> 5195 <div x-data="{ showDetails: false }"> 5196 <template x-if="item.product_type === 'bundle' && item.options?.length"> 5197 <div> 5198 <div x-show="showDetails" class="my-3 flex flex-col gap-2"> 5199 <span class="body-3"> 5200 What's in the box: 5201 </span> 5202 <template x-for="option in item.options"> 5203 <template x-if="option.value"> 5204 <div 5205 class="body-3 flex flex-col gap-y-2 [&_h4]:!text-sm 5206 [&_h4]:!leading-[18px] [&_.product-item-cart-info]:!m-0" 5207 > 5208 <template x-if="Array.isArray(option.value)"> 5209 <template x-for="value in option.value"> 5210 <sp
5210an x-html="option.label === 'Sleeve' ? 'Sleeve: ' + value : value"></span> 5211 </template> 5212 </template> 5213 <template x-if="!Array.isArray(option.value)"> 5214 <span x-html="option.label === 'Sleeve' ? 'Sleeve: ' + option.value : option.value"></span> 5215 </template> 5216 </div> 5217 </template> 5218 </template> 5219 </div> 5220 <button 5221 @click="showDetails = !showDetails" 5222 class="!text-primary body-1 font-bold flex items-center gap-2 mt-2 underline !border-0 !bg-transparent" 5223 type="button"> 5224 <span 5225 x-text="showDetail
5225s ? 'Hide details' : 5226 'Show details'" 5227 ></span> 5228 <span :class="{ 'rotate-180': showDetails }"> 5229 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 20 20" fill="none" class="[&>path]:stroke-[4px]" role="img"> 5230<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 5231<title>chevron-down</title></svg> 5232 </span> 5233 </button> 5234 </div> 5235 </template> 5236 </div> 5237 <div class="mt-3 flex flex-col gap-3"> 5238 <div class="flex justify-between items-center gap-4"> 5239 <!-- Price display --> 5240 <div class="flex flex-col items-end"> 5241 <span 5242 :class="{ 5243 'text-red font-bold text-xl leading-[26px]': item.base_final_price && item.base_final_price > item.product_price_value, 5244 'font-bold text-xl leading-[26px]': !(item.base_final_price && item.base_final_price > item.product_price_value) 5245 }" 5246 x-html="hyva.formatPrice(item.qty * item.product_price_value)" 5247 ></span> 5248 <template x-if="item.base_final_price && item.base_final_price > item.product_price_value"> 5249 <span 5250 class="line-through text-subtext text-sm mr-auto" 5251 x-html="hyva.formatPrice(item.qty * item.base_final_price)" 5252 ></span> 5253 </template> 5254 </div> 5255 <!-- Quantity input --> 5256 <div class="flex items-center border border-grey-muted rounded px-4 py-[9px] h-[42px] max-w-[120px] bg-white"> 5257 <button type="button" 5258 class="flex-1 text-black text-xl font-normal border-0 focus:outline-none 5259 hover:bg-transparent hover:border-0 focus:bg-transparent focus:border-0" 5260 :class="item.qty <= 1 ? 'text-grey-muted cursor-not-allowed' : 'text-black'" 5261 :disabled="item.qty <= 1" 5262 @click="item.qty = Math.max(1, parseInt(item.qty) - 1); updateQuantity(item.item_id, item.qty)" 5263 :aria-label="'Decrease\u0020quantity\u0020for ' + item.product_name" 5264 title="Minus"> 5265 <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 16 16" fill="none" aria-hidden="true"> 5266<path fill-rule="evenodd" clip-rule="evenodd" d="M16 8.86666L0 8.86666L0 7.53333L16 7.53333V8.86666Z" fill="currentColor"/> 5267</svg> 5268 </button> 5269 <input type="number" 5270 :id="'qty-' + item.item_id" 5271 x-model="item.qty" 5272 class="text-center !font-proxima !border-0 focus:ring-0 focus:outline-none body-2 !text-[18px] !py-[0.5rem] !px-[0.75rem] max-w-[54px]"
5273 @input="updateQuantity(item.item_id, item.qty)" 5274 min="1" 5275 aria-label="Quantity" 5276 /> 5277 <button type="button" 5278 class="flex-1 text-black text-xl font-normal border-0 focus:outline-none 5279 hover:bg-transparent hover:border-0 focus:bg-transparent focus:border-0" 5280 @click="item.qty = parseInt(item.qty) + 1; updateQuantity(item.item_id, item.qty)" 5281 :aria-label="'Increase\u0020quantity\u0020for ' + item.product_name" 5282 title="Plus"> 5283 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"> 5284<path fill-rule="evenodd" clip-rule="evenodd" d="M7.19999 16L7.19999 -5.82819e-08L8.53333 0L8.53332 16L7.19999 16Z" fill="#141414"/> 5285<path fill-rule="evenodd" clip-rule="evenodd" d="M16 8.86666L0 8.86666L0 7.53333L16 7.53333V8.86666Z" fill="#141414"/> 5286</svg> 5287 </button> 5288 </div> 5289 </div> 5290 <!-- Edit/Delete as flex row --> 5291 <div class="flex items-center gap-4 justify-end"> 5292 <a 5293 :href="getProductEditUrl(item)" 5294 x-show="item.product_type === 'bundle' || (item.product_type !== 'grouped' && item.is_visible_in_site_visibility)" 5295 class="font-bold text-base pr-4 border-r border-background-light cursor-pointer !text-primary" 5296 :aria-label="hyva.strf('Edit\u0020product\u0020\u0022\u00250\u0022', item.product_name)" 5297 > 5298 Edit product </a> 5299 <button 5300 type="button" 5301 class="font-bold text-base !text-primary cursor-pointer 5302 underline !border-0 5303 hover:bg-transparent active:bg-transparent 5304 focus:bg-transparent" 5305 @click="deleteItemFromCart(item.item_id)" 5306 :aria-label="hyva.strf('Remove\u0020product\u0020\u0022\u00250\u0022\u0020from\u0020cart', item.product_name)" 5307 > 5308 Remove </button> 5309 </div> 5310 </div> 5311 </div> 5312 </div> 5313 </template> 5314 </template> 5315 </div> 5316 </template> 5317 5318 <template x-if="itemsCount"> 5319 <div class="mt-auto"> 5320 5321 <div class="relative grid pb-4 pt-3 px-4 bg-white border-t border-black"> 5322 <p class="flex gap-4 font-bold text-xl leading-[26px] justify-between uppercase"> 5323 <span>Bag subtotal</span><span x-html="cart.subtotal"></span> 5324 </p> 5325 <div class="w-full flex gap-3 justify-between mt-3"> 5326 <a href="https://www.purdys.com/checkout/cart/" 5327 class="btn btn-secondary no-u
5327nderline w-full hover:no-underline" 5328 > 5329 Edit Cart </a> 5330 <a @click.prevent.stop="closeCartDrawer; $dispatch('toggle-authentication', 5331 {url: 'https://www.purdys.com/checkout/cart/'});" 5332 href="https://www.purdys.com/checkout/cart/" 5333 class="inline-flex btn btn-primary w-full hover:no-underline !border-0" 5334 > 5335 Checkout </a> 5336 </div> 5337 <div x-html="getSectionDataExtraActions()"></div> 5338 </div> 5339 </div> 5340 </template> 5341 5342 </div> 5343 5344 <button 5345 type="button" 5346 @click="closeCartDrawer" 5347 aria-label="Close minicart" 5348 class="absolute top-0 end-0 p-4 text-black !border-0 !bg-transparent" 5349 > 5350 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" class="text-black" aria-hidden="true"> 5351<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 5352<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 5353</svg> 5354 </button> 5355 </div> 5356 <div class="flex flex-row justify-center items-center w-full h-full fixed select-none z-50" 5357 style="left: 50%;top: 50%;transform: translateX(-50%) translateY(-50%);background: rgba(255,255,255,0.7);" 5358 x-show="isLoading" 5359 x-cloak 5360 x-transition:enter="ease-out duration-200" 5361 x-transition:enter-start="opacity-0" 5362 x-transition:enter-end="opacity-100" 5363 x-transition:leave="ease-in duration-200" 5364 x-transition:leave-start="opacity-100" 5365 x-transition:leave-end="opacity-0"> 5366 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 56 56" fill="none" class="animate-[spin_2s_linear_infinite] text-primary" role="img"> 5367 <path d="M52.5 28C52.5 31.2174 51.8663 34.4033 50.6351 37.3757C49.4038 40.3482 47.5992 43.0491 45.3241 45.3241C43.0491 47.5992 40.3482 49.4038 37.3757 50.635C34.4033 51.8663 31.2174 52.5 28 52.5C24.7826 52.5 21.5967 51.8663 18.6243 50.635C15.6518 49.4038 12.9509 47.5991 10.6759 45.3241C8.40085 43.0491 6.59619 40.3482 5.36495 37.3757C4.13371 34.4033 3.5 31.2174 3.5 28C3.5 24.7826 4.13371 21.5967 5.36496 18.6242C6.5962 15.6518 8.40086 12.9509 10.6759 10.6759C12.9509 8.40084 15.6518 6.59619 18.6243 5.36495C21.5967 4.13371 24.7826 3.5 28 3.5C31.2174 3.5 34.4033 4.13371 37.3758 5.36496C40.3482 6.5962 43.0491 8.40086 45.3241 10.6759C47.5992
536712.9509 49.4038 15.6518 50.6351 18.6243C51.8663 21.5967 52.5 24.7826 52.5 28L52.5 28Z" stroke="#EEEAF4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> 5368 <path d="M52.5 28C52.5 24.7826 51.8663 21.5967 50.6351 18.6243C49.4038 15.6518 47.5992 12.9509 45.3241 10.6759C43.0491 8.40085 40.3482 6.59619 37.3757 5.36495C34.4033 4.13371 31.2174 3.5 28 3.5" stroke="#532E91" stroke-width="6" stroke-linecap="square"/> 5369<title>loader</title></svg> 5370</div> 5371 </div> 5372 5373</section> 5374 5375 <!--Authentication Pop-Up--> 5376
5376<script> 5377 function initAuthentication() { 5378 return { 5379 open: false, 5380 forceAuthentication: false, 5381 checkoutUrl: 'https://www.purdys.com/checkout/index/', 5382 errors: 0, 5383 hasCaptchaToken: 0, 5384 displayErrorMessage: false, 5385 errorMessages: [], 5386 setErrorMessages: function setErrorMessages(messages) { 5387 this.errorMessages = [messages]; 5388 this.displayErrorMessage = this.errorMessages.length; 5389 }, 5390 submitForm: function() { 5391 // Do not rename $form, the variable is expected to be declared in the recaptcha output 5392 const $form = document.querySelector('#login-form'); 5393 5394 if (this.errors === 0) { 5395 this.dispatchLoginRequest($form); 5396 } 5397 }, 5398 onPrivateContentLoaded: function(data) { 5399 const isLoggedIn = data.customer && data.customer.firstname; 5400 if (data.cart && !isLoggedIn) { 5401 this.forceAuthentication = !data.cart.isGuestCheckoutAllowed; 5402 } 5403 }, 5404 redirectIfAuthenticated: function(event) { 5405 if (event.detail && event.detail.url) { 5406 this.checkoutUrl = event.detail.url; 5407 } 5408 if (!this.forceAuthentication) { 5409 window.location.href = this.checkoutUrl; 5410 } 5411 }, 5412 dispatchLoginRequest: function(form) { 5413 this.isLoading = true; 5414 const username = this.$refs['customer-email'].value; 5415 const password = this.$refs['customer-password'].value; 5416 const formKey = hyva.getFormKey(); 5417 const bodyFields = { 5418 'username': username, 5419 'password': password, 5420 'formKey': formKey 5421 }; 5422 const fieldName = ''; 5423 const recaptchaField = fieldName && form[fieldName]; 5424 if (recaptchaField) { 5425 bodyFields[fieldName] = recaptchaField.value; 5426 } 5427 fetch('https://www.purdys.com/customer/ajax/login/', { 5428 method: 'POST', 5429 headers: { 5430 'Content-Type': 'application/json', 5431 'X-Requested-With': 'XMLHttpRequest' 5432 }, 5433 body: JSON.stringify(bodyFields) 5434 }).then(response => { 5435 return response.json() 5436 }).then(data => { 5437 this.isLoading = false; 5438 if (data.errors) { 5439 dispatchMessages([{ 5440 type: 'error', 5441 text: data.message 5442 }], 5000); 5443 this.errors = 1; 5444 this.hasCaptchaToken = 0; 5445 } else { 5446 window.location.href = this.checkoutUrl; 5447 } 5448 }); 5449 } 5450 } 5451 } 5452</script>
5452 5453<section id="authentication-popup" 5454 x-data="initAuthentication()" 5455 @private-content-loaded.window="onPrivateContentLoaded($event.detail.data)" 5456 @toggle-authentication.window="open = forceAuthentication; redirectIfAuthenticated(event)" 5457 @keydown.window.escape="open = false"> 5458 <div 5459 class="backdrop" 5460 aria-hidden="true" 5461 x-cloak 5462 x-show="open" 5463 x-transition:enter="ease-in-out duration-500" 5464 x-transition:enter-start="opacity-0" 5465 x-transition:enter-end="opacity-100" 5466 x-transition:leave="ease-in-out duration-500" 5467 x-transition:leave-start="opacity-100" 5468 x-transition:leave-end="opacity-0" 5469 @click="open = false"></div> 5470 <div role="dialog" 5471 aria-modal="true" 5472 @click.outside="open = false" 5473 class="inset-y-0 right-0 z-30 flex max-w-full fixed" 5474 x-cloak 5475 x-show="open"> 5476 <div class="relative w-screen max-w-md pt-16 bg-container-lighter" 5477 x-show="open" 5478 x-cloak="" 5479 x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700" 5480 x-transition:enter-start="translate-x-full" 5481 x-transition:enter-end="translate-x-0" 5482 x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700" 5483 x-transition:leave-start="translate-x-0" 5484 x-transition:leave-end="translate-x-full"> 5485 <div 5486 x-show="open" 5487 x-cloak="" 5488 x-transition:enter="ease-in-out duration-500" 5489 x-transition:enter-start="opacity-0" 5490 x-transition:enter-end="opacity-100" 5491 x-transition:leave="ease-in-out duration-500" 5492 x-transition:leave-start="opacity-100" 5493 x-transition:leave-end="opacity-0" class="absolute top-0 right-2 flex p-2 mt-2"> 5494 <button 5495 type="button" 5496 @click="open = false;" 5497 aria-label="Close panel" 5498 class="p-2 text-gray-300 transition duration-150 ease-in-out hover:text-black"> 5499 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="none" role="img"> 5500<path d="M3.3335 16.6667L16.6668 3.33337" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 5501<path d="M16.6668 16.6666L3.3335 3.33329" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/> 5502<title>Close panel</title></svg> 5503 </button> 5504 </div> 5505 <template x-if="open"> 5506 <div class="flex flex-col h-full py-6 space-y-6 bg-white shadow-xl overflow-y-auto"> 5507 <div class="block-customer-login bg-container border border-container mx-4 p-4 shadow-sm"> 5508 <p id="authenticate-customer-login" class="text-lg leading-7 text-gray-900"> 5509 <strong class="font-medium">Checkout using your account</strong> 5510 </p> 5511 5512 <form class="form form-login" 5513 method="post" 5514 @submit.prevent="submitForm();" 5515 id="login-form"> 5516 <div class="fieldset login"> 5517 <div class="field email required"> 5518 <label class="label" for="form-login-username" form="login-form"> 5519 <span>Email Address</span> 5520 </label> 5521 <div class="control"> 5522 <input name="username" 5523 id="form-login-username" 5524 x-ref="customer-email" 5525 @change="errors = 0" 5526 type="email" 5527 required 5528 autocomplete="off" 5529 class="form-input input-text"> 5530 </div> 5531 </div> 5532 <div class="field password required"> 5533 <label for="form-login-password" class="label" form="login-form">
5534 <span>Password</span> 5535 </label> 5536 <div class="control"> 5537 <input name="password" 5538 id="form-login-password" 5539 type="password" 5540 class="form-input input-text" 5541 required 5542 x-ref="customer-password" 5543 autocomplete="off" 5544 @change="errors = 0"> 5545 </div> 5546 </div> 5547 5548 <input name="context" type="hidden" value="checkout" /> 5549 <div class="actions-toolbar flex justify-between pt-6 pb-2 items-center"> 5550 <button type="submit" class="inline-flex btn btn-primary disabled:opacity-75"> 5551 Sign In </button> 5552 <a href="https://www.purdys.com/customer/account/forgotpassword/"> 5553 Forgot Your Password? </a> 5554 </div> 5555 5556 </div> 5557 </form> 5558 </div> 5559 <div class="mx-4"> 5560 </div> 5561 <div class="block-new-customer bg-container border border-container mx-4 p-4 shadow-sm"> 5562 <p id="authenticate-new-customer" class="text-lg mb-2 leading-7 text-gray-900"> 5563 <strong class="font-medium">Checkout as a new customer</strong> 5564 </p> 5565 5566 <div class="block-content"> 5567 <p class="mb-1"> 5568 Creating an account has many benefits: </p> 5569 <ul class="list-disc pl-5"> 5570 <li> See order and shipping status</li> 5571 <li> Track order history</li> 5572 <li> Check out faster</li> 5573 </ul> 5574 <div class="actions-toolbar flex justify-between mt-6 mb-2 items-center"> 5575 <a href="https://www.purdys.com/customer/account/create/" 5576 class="inline-flex btn btn-primary"> 5577 Create an Account </a> 5578 </div> 5579 </div> 5580 </div> 5581 </div> 5582 </template> 5583 </div> 5584 </div> 5585</section> 5586</div> 5587 5588<div class="page messages">
5588<script> 5589 function initMessages() { 5590 "use strict"; 5591 return { 5592 messages: window.mageMessages || [], 5593 isEmpty() { 5594 return this.messages.reduce( 5595 function (isEmpty, message) { 5596 return isEmpty && message === undefined 5597 }, true 5598 ) 5599 }, 5600 removeMessage(messageIndex) { 5601 this.messages[messageIndex] = undefined; 5602 }, 5603 addMessages(messages, hideAfter) { 5604 messages.map((message) => { 5605 this.messages = this.messages.concat(message); 5606 if (hideAfter === undefined && message.type === 'success' && window.defaultSuccessMessageTimeout) { 5607 hideAfter = window.defaultSuccessMessageTimeout; 5608 } 5609 if (hideAfter) { 5610 this.setHideTimeOut(this.messages.length -1, hideAfter); 5611 } 5612 }); 5613 }, 5614 setHideTimeOut(messageIndex, hideAfter) { 5615 setTimeout((messageIndex) => { 5616 this.removeMessage(messageIndex); 5617 }, hideAfter, messageIndex); 5618 }, 5619 getIcon(type) { 5620 switch (type) { 5621 case 'success': 5622 return "<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" role=\"img\">\n<path d=\"M10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20C4.47715 20 0 15.5228 0 10C0 4.47715 4.47715 0 10 0ZM10 1.66667C5.39763 1.66667 1.66667 5.39763 1.66667 10C1.66667 14.6024 5.39763 18.3333 10 18.3333C14.6024 18.3333 18.3333 14.6024 18.3333 10C18.3333 5.39763 14.6024 1.66667 10 1.66667ZM12.8019 7.44548C13.0746 7.1758 13.5145 7.17846 13.7842 7.45117C14.0538 7.72389 14.0512 8.16376 13.7785 8.43343L9.47754 12.6864C9.20708 12.9538 8.77157 12.9537 8.50098 12.6864L6.51937 10.7284C6.24671 10.4589 6.24367 10.0198 6.51286 9.74691C6.78233 9.47413 7.22226 9.47105 7.49512 9.7404L8.98926 11.2158L12.8019 7.44548Z\" fill=\"currentColor\"\/>\n<title>circle-check<\/title><\/svg>\n"; 5623 case 'error': 5624 return "<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" role=\"img\">\n<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M6.03085 6.04273C6.3053 5.77482 6.75026 5.77482 7.02471 6.04273L10.0848 9.02986L12.9753 6.20823C13.2497 5.94032 13.6947 5.94032 13.9692 6.20823C14.2436 6.47614 14.2436 6.9105 13.9692 7.17841L11.0786 10L13.9692 12.8217C14.2436 13.0896 14.2436 13.5239 13.9692 13.7918C13.6947 14.0598 13.2497 14.0598 12.9753 13.7918L10.0848 10.9702L7.02471 13.9574C6.75026 14.2253 6.3053 14.2253 6.03085 13.9574C5.7564 13.6894 5.7564 13.2551 6.03085 12.9872L9.09091 10L6.03085 7.01291C5.7564 6.745 5.7564 6.31063 6.03085 6.04273Z\" fill=\"currentColor\"\/>\n<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M10 18.3333C14.6024 18.3333 18.3333 14.6024 18.3333 10C18.3333 5.39763 14.6024 1.66667 10 1.66667C5.39763 1.66667 1.66667 5.39763 1.66667 10C1.66667 14.6024 5.39763 18.3333 10 18.3333ZM10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20Z\" fill=\"currentColor\"\/>\n<title>circle-x-mark<\/title><\/svg>\n"; 5625 case 'warning': 5626 return "<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" role=\"img\">\n<path d=\"M11.1034 11.8717H8.93425L8.55425 5H11.4676L11.1034 11.8717ZM10.0267 15.735C9.21925 15.735 8.53841 15.0542 8.53841 14.2467C8.53841 13.455 9.21925 12.7742 10.0267 12.7742C10.8342 12.7742 11.4992 13.455 11.4992 14.2467C11.4992 15.0542 10.8342 15.735 10.0267 15.735Z\" fill=\"currentColor\"\/>\n<path d=\"M18.3333 10C18.3333 5.39763 14.6024 1.66667 10 1.66667C5.39763 1.66667 1.66667 5.39763 1.66667 10C1.66667 14.6024 5.39763 18.3333 10 18.3333V20C4.47715 20 0 15.5228 0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10C20 15.5228 15.5228 20 10 20V18.3333C14.6024 18.3333 18.3333 14.6024 18.3333 10Z\" fill=\"currentColor\"\/>\n<title>warning<\/title><\/svg>\n"; 5627 default: 5628 return "<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" role=\"img\">\n<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M10 18.3333C14.6024 18.3333 18.3333 14.6024 18.3333 10C18.3333 5.39763 14.6024 1.66667 10 1.66667C5.39763 1.66667 1.66667 5.39763 1.66667 10C1.66667 14.6024 5.39763 18.3333 10 18.3333ZM10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20Z\" fill=\"currentColor\"\/>
5628\n<path d=\"M9.23913 14.1667V7.5H10.7609V14.1667H9.23913ZM8.33333 14.1667V13.4144C8.33333 13.2933 8.36151 13.2025 8.41787 13.142C8.48229 13.0815 8.57085 13.0383 8.68357 13.0123L9.37198 12.8567L9.52899 14.1667H8.33333ZM10.471 14.1667L10.628 12.8567L11.3164 13.0123C11.4291 13.0383 11.5137 13.0815 11.57 13.142C11.6345 13.2025 11.6667 13.2933 11.6667 13.4144V14.1667H10.471ZM9.52899 7.5L9.37198 8.80999L8.68357 8.65434C8.57085 8.6284 8.48229 8.58517 8.41787 8.52464C8.36151 8.46412 8.33333 8.37332 8.33333 8.25227V7.5H9.52899Z\" fill=\"currentColor\"\/>\n<path d=\"M10.8333 5.83333C10.8333 6.29357 10.4602 6.66667 10 6.66667C9.53976 6.66667 9.16667 6.29357 9.16667 5.83333C9.16667 5.3731 9.53976 5 10 5C10.4602 5 10.8333 5.3731 10.8333 5.83333Z\" fill=\"currentColor\"\/>\n<title>information<\/title><\/svg>\n"; 5629 } 5630 }, 5631 getMessageStyle(type) { 5632 switch (type) { 5633 case 'success': 5634 return '[--message-lighter:theme(colors.green.light)] [--message:theme(colors.green.DEFAULT)] [--message-darker:theme(colors.green.900)]' 5635 case 'error': 5636 return '[--message-lighter:theme(colors.red.light)] [--message:theme(colors.red.DEFAULT)] [--message-darker:theme(colors.red.900)]' 5637 case 'warning': 5638 return '[--message-lighter:theme(colors.orange.light)] [--message:theme(colors.orange.DEFAULT)] [--message-darker:theme(colors.yellow.900)]' 5639 case 'notice': 5640 case 'info': 5641 return '[--message-lighter:theme(colors.background.light)] [--message:theme(colors.primary.DEFAULT)] [--message-darker:theme(colors.blue.900)]' 5642 default: 5643 return '[--message-lighter:theme(colors.background.light)] [--message:theme(colors.primary.DEFAULT)] [--message-darker:theme(colors.slate.900)]' 5644 } 5645 }, 5646 eventListeners: { 5647 ['@messages-loaded.window'](event) { 5648 this.addMessages(event.detail.messages, event.detail.hideAfter) 5649 }, 5650 ['@private-content-loaded.window'](event) { 5651 const data = event.detail.data; 5652 if ( 5653 data.messages && 5654 data.messages.messages && 5655 data.messages.messages.length 5656 ) { 5657 this.addMessages(data.messages.messages); 5658 } 5659 }, 5660 ['@clear-messages.window']() { 5661 this.messages = []; 5662 } 5663 } 5664 } 5665 } 5666</script>
5666 5667<section 5668 id="messages" 5669 x-data="initMessages()" 5670 x-bind="eventListeners" 5671 aria-live="assertive" 5672 role="alert" 5673> 5674 <template x-if="!isEmpty()"> 5675 <div class="messages container py-3"> 5676 <template x-for="(message, index) in messages" :key="index"> 5677 <template x-if="message"> 5678 <div 5679 class="message items-stretch gap-4 rounded text-body-2 mb-6 last-of-type:mb-0 5680 bg-[var(--message-lighter)] text-[var(--message)] px-5 py-4" 5681 :ui-id="'message-' + message.type" 5682 :class="getMessageStyle(message.type)" 5683 > 5684 <div class="grow flex items-center gap-3"> 5685 <span x-html="getIcon(message.type)" class="fill-[var(--message)]"></span> 5686 <span class="text-black font-normal body-2" x-html="message.text"></span> 5687 <button 5688 type="button" 5689 class="ms-auto text-black !border-0 !bg-transparent !opacity-100" 5690 aria-label="Close message" 5691 @click.prevent="removeMessage(index)" 5692 > 5693 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" width="24" height="24" aria-hidden="true"> 5694 <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/> 5695</svg> 5696 </button> 5697 </div> 5698 </div> 5699 </template> 5700 </template> 5701 </div> 5702 </template> 5703</section> 5704</div> 5705
5705<script> 5706 'use strict'; 5707 5708 const initTopmenuDesktop_6ab63817b9359 = () => ({ 5709 openSubmenuId: null, 5710 hoverTimeout: null, 5711 hoverBridgeTimeout: null, 5712 openSubmenuWithDelay(menuNode) { 5713 // Clear any existing timeout 5714 if (this.hoverTimeout) { 5715 clearTimeout(this.hoverTimeout) 5716 } 5717 5718 // Set new timeout to open submenu after 0.2 seconds 5719 this.hoverTimeout = setTimeout(() => { 5720 this.openSubmenuId = menuNode 5721 }, 200) 5722 }, 5723 clearHoverTimeout() { 5724 if (this.hoverTimeout) { 5725 clearTimeout(this.hoverTimeout) 5726 this.hoverTimeout = null 5727 } 5728 }, 5729 onMainMenuMouseLeave() { 5730 // Clear the hover timeout 5731 this.clearHoverTimeout() 5732 5733 // Set a bridge timeout to keep submenu open briefly 5734 // This prevents closing when moving to submenu 5735 if (this.hoverBridgeTimeout) { 5736 clearTimeout(this.hoverBridgeTimeout) 5737 } 5738 this.hoverBridgeTimeout = setTimeout(() => { 5739 this.openSubmenuId = null 5740 }, 100) 5741 }, 5742 onSubmenuMouseEnter() { 5743 // Clear bridge timeout when entering submenu 5744 if (this.hoverBridgeTimeout) { 5745 clearTimeout(this.hoverBridgeTimeout) 5746 this.hoverBridgeTimeout = null 5747 } 5748 }, 5749 onSubmenuMouseLeave() { 5750 // Close submenu when leaving submenu area 5751 this.openSubmenuId = null 5752 }, 5753 toggleSubmenu(menuNode) { 5754 if (menuNode === this.openSubmenuId) { 5755 this.openSubmenuId = null 5756 hyva.releaseFocus() 5757 } else { 5758 this.openSubmenuId = menuNode 5759 this.$nextTick(() => { 5760 hyva.trapFocus(this.$refs['submenu-' + menuNode]) 5761 }) 5762 } 5763 }, 5764 onSubmenuEscape(event) { 5765 event.stopPropagation() 5766 this.$refs['sr-button-' + this.openSubmenuId]?.focus() 5767 this.openSubmenuId = null 5768 } 5769 }) 5770 </script>
5770 5771 5772 <div x-data="initTopmenuDesktop_6ab63817b9359()" 5773 class=" 5774 snowdog-desktop-menu 5775 hidden w-full bg-primary-dark lg:block md:px-5 2xl:px-10 5776 " 5777 > 5778 <nav 5779 class="snowdog-topmenu-desktop lg:block" 5780 aria-label="Main menu" 5781 > 5782 <ul 5783 class="relative flex flex-wrap justify-center w-fit m-auto" 5784 @mouseleave="onMainMenuMouseLeave()" 5785 > 5786 <li class=" 5787 group 5788 flex items-center justify-between px-5 py-3.5 m-0 list-none hover:bg-white before:content-[''] 5789 " 5790 :class="{ 5791 'bg-white shadow-md' : '1' && openSubmenuId === '80' 5792 }" 5793 @mouseenter="openSubmenuWithDelay('80')" 5794 @mouseleave="onMainMenuMouseLeave()" 5795 @keydown.escape="onSubmenuEscape" 5796 > 5797 <div 5798 class="group uppercase [&>a]:font-bold [&>a]:no-underline [&>a]:text-white group-hover:[&>a]:!text-primary-dark" 5799 :class="openSubmenuId === '80' 5800 ? '[&>a]:!text-primary-dark' 5801 : ''" 5802 > 5803 5804<a href="https://www.purdys.com/fall" 5805 class="snowdog-menu-link " 5806 data-menu="menu-80" 5807 > 5808 Fall</a> 5809 </div> 5810 <div class="relative"> 5811 <button 5812 type="button" 5813 class="flex items-center ml-2 transition-transform duration-300 border-0 hover:border-0 hover:bg-transparent" 5814 :class="{ 5815 'rotate-180' : openSubmenuId === '80' 5816 }" 5817 x-ref="sr-button-80" 5818 :aria-expanded="openSubmenuId === '80' ? 'true' : 'false'" 5819 @click="toggleSubmenu('80')" 5820 > 5821 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 20 20" fill="none" class="text-white group-hover:!text-primary-dark [&>path]:stroke-[4px] transition-transform" aria-hidden="true" :class="openSubmenuId === '80' ? '!text-primary-dark group-hover:!text-primary-dark rotate-180' : ''"> 5822<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 5823</svg> 5824 <span class="sr-only"> 5825 Toggle submenu for Fall </span> 5826 </button> 5827 </div> 5828 </li> 5829 <li class=" 5830 group 5831 flex items-center justify-between px-5 py-3.5 m-0 list-none hover:bg-white before:content-[''] 5832 " 5833 :class="{ 5834 'bg-white shadow-md' : '1' && openSubmenuId === '1559' 5835 }" 5836 @mouseenter="openSubmenuWithDelay('1559')" 5837 @mouseleave="onMainMenuMouseLeave()" 5838 @keydown.escape="onSubmenuEscape" 5839 > 5840 <div 5841 class="group uppercase [&>a]:font-bold [&>a]:no-underline [&>a]:text-white group-hover:[&>a]:!text-primary-dark" 5842 :class="openSubmenuId === '1559' 5843 ? '[&>a]:!text-primary-dark' 5844 : ''" 5845 > 5846 5847<a href="https://www.purdys.com/chocolate" 5848 class="snowdog-menu-link " 5849 data-menu="menu-1559" 5850 > 5851 Chocolate</a> 5852 </div> 5853 <div class="relative"> 5854 <button 5855 type="button" 5856 class="flex items-center ml-2 transition-transform duration-300 border-0 hover:border-0 hover:bg-transparent" 5857 :class="{ 5858 'rotate-180' : openSubmenuId === '1559' 5859 }" 5860 x-ref="sr-button-1559" 5861 :aria-expanded="openSubmenuId === '1559' ? 'true' : 'false'" 5862 @click="toggleSubmenu('1559')" 5863 > 5864 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 20 20" fill="none" class="text-white group-hover:!text-primary-dark [&>path]:stroke-[4px] transition-transform" aria-hidden="true" :class="openSubmenuId === '1559' ? '!text-primary-dark group-hover:!text-primary-dark rotate-180' : ''"> 5865<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 5866</svg>
5867 <span class="sr-only"> 5868 Toggle submenu for Chocolate </span> 5869 </button> 5870 </div> 5871 </li> 5872 <li class=" 5873 group 5874 flex items-center justify-between px-5 py-3.5 m-0 list-none hover:bg-white before:content-[''] 5875 " 5876 :class="{ 5877 'bg-white shadow-md' : '1' && openSubmenuId === '11' 5878 }" 5879 @mouseenter="openSubmenuWithDelay('11')" 5880 @mouseleave="onMainMenuMouseLeave()" 5881 @keydown.escape="onSubmenuEscape" 5882 > 5883 <div 5884 class="group uppercase [&>a]:font-bold [&>a]:no-underline [&>a]:text-white group-hover:[&>a]:!text-primary-dark" 5885 :class="openSubmenuId === '11' 5886 ? '[&>a]:!text-primary-dark' 5887 : ''" 5888 > 5889 5890<a href="https://www.purdys.com/gifts" 5891 class="snowdog-menu-link " 5892 data-menu="menu-11" 5893 > 5894 Gifts</a> 5895 </div> 5896 <div class="relative"> 5897 <button 5898 type="button" 5899 class="flex items-center ml-2 transition-transform duration-300 border-0 hover:border-0 hover:bg-transparent" 5900 :class="{ 5901 'rotate-180' : openSubmenuId === '11' 5902 }" 5903 x-ref="sr-button-11" 5904 :aria-expanded="openSubmenuId === '11' ? 'true' : 'false'" 5905 @click="toggleSubmenu('11')" 5906 > 5907 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 20 20" fill="none" class="text-white group-hover:!text-primary-dark [&>path]:stroke-[4px] transition-transform" aria-hidden="true" :class="openSubmenuId === '11' ? '!text-primary-dark group-hover:!text-primary-dark rotate-180' : ''"> 5908<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 5909</svg> 5910 <span class="sr-only"> 5911 Toggle submenu for Gifts </span> 5912 </button> 5913 </div> 5914 </li> 5915 <li class=" 5916 group 5917 flex items-center justify-between px-5 py-3.5 m-0 list-none hover:bg-white before:content-[''] 5918 " 5919 :class="{ 5920 'bg-white shadow-md' : '1' && openSubmenuId === '76' 5921 }" 5922 @mouseenter="openSubmenuWithDelay('76')" 5923 @mouseleave="onMainMenuMouseLeave()" 5924 @keydown.escape="onSubmenuEscape" 5925 > 5926 <div 5927 class="group uppercase [&>a]:font-bold [&>a]:no-underline [&>a]:text-white group-hover:[&>a]:!text-primary-dark" 5928 :class="openSubmenuId === '76' 5929 ? '[&>a]:!text-primary-dark' 5930 : ''" 5931 > 5932 5933<a href="https://www.purdys.com/custom" 5934 class="snowdog-menu-link custom" 5935 data-menu="menu-76" 5936 > 5937 Custom</a> 5938 </div> 5939 <div class="relative"> 5940 <button 5941 type="button" 5942 class="flex items-center ml-2 transition-transform duration-300 border-0 hover:border-0 hover:bg-transparent" 5943 :class="{ 5944 'rotate-180' : openSubmenuId === '76' 5945 }" 5946 x-ref="sr-button-76" 5947 :aria-expanded="openSubmenuId === '76' ? 'true' : 'false'" 5948 @click="toggleSubmenu('76')" 5949 > 5950 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 20 20" fill="none" class="text-white group-hover:!text-primary-dark [&>path]:stroke-[4px] transition-transform" aria-hidden="true" :class="openSubmenuId === '76' ? '!text-primary-dark group-hover:!text-primary-dark rotate-180' : ''"> 5951<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 5952</svg>
5953 <span class="sr-only"> 5954 Toggle submenu for Custom </span> 5955 </button> 5956 </div> 5957 </li> 5958 <li class=" 5959 group 5960 flex items-center justify-between px-5 py-3.5 m-0 list-none hover:bg-white before:content-[''] 5961 " 5962 :class="{ 5963 'bg-white shadow-md' : '1' && openSubmenuId === '2179' 5964 }" 5965 @mouseenter="openSubmenuWithDelay('2179')" 5966 @mouseleave="onMainMenuMouseLeave()" 5967 @keydown.escape="onSubmenuEscape" 5968 > 5969 <div 5970 class="group uppercase [&>a]:font-bold [&>a]:no-underline [&>a]:text-white group-hover:[&>a]:!text-primary-dark" 5971 :class="openSubmenuId === '2179' 5972 ? '[&>a]:!text-primary-dark' 5973 : ''" 5974 > 5975 5976<a href="https://www.purdys.com/discover" 5977 class="snowdog-menu-link " 5978 data-menu="menu-2179" 5979 > 5980 Discover</a> 5981 </div> 5982 <div class="relative"> 5983 <button 5984 type="button" 5985 class="flex items-center ml-2 transition-transform duration-300 border-0 hover:border-0 hover:bg-transparent" 5986 :class="{ 5987 'rotate-180' : openSubmenuId === '2179' 5988 }" 5989 x-ref="sr-button-2179" 5990 :aria-expanded="openSubmenuId === '2179' ? 'true' : 'false'" 5991 @click="toggleSubmenu('2179')" 5992 > 5993 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 20 20" fill="none" class="text-white group-hover:!text-primary-dark [&>path]:stroke-[4px] transition-transform" aria-hidden="true" :class="openSubmenuId === '2179' ? '!text-primary-dark group-hover:!text-primary-dark rotate-180' : ''"> 5994<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> 5995</svg> 5996 <span class="sr-only"> 5997 Toggle submenu for Discover </span> 5998 </button> 5999 </div> 6000 </li> 6001 </ul> 6002 </nav> 6003 <div 6004 x-ref="submenu-80" 6005 class="absolute left-0 top-full w-full z-30 shadow-md bg-white flex overflow-hidden" 6006 style="transform-origin: top;" 6007 x-show="openSubmenuId === '80'" 6008 x-transition:enter="transition-all duration-300 ease-out" 6009 x-transition:enter-start="transform scale-y-0" 6010 x-transition:enter-end="transform scale-y-100" 6011 x-transition:leave="transition-all duration-200 ease-in" 6012 x-transition:leave-start="transform scale-y-100" 6013 x-transition:leave-end="transform scale-y-0" 6014 x-cloak 6015 @mouseenter="onSubmenuMouseEnter()" 6016 @mouseleave="onSubmenuMouseLeave()" 6017> 6018 <div class="min-[1660px]:!max-w-[1740px] mx-auto flex flex-row justify-between w-full min-h-[400px] max-h-[600px] 6019 md:px-5 2xl:px-10 py-10 gap-10 [&_a]:no-underline overflow-y-auto"> 6020 <!-- Left column: items without children and without image --> 6021 <div 6022 class="flex flex-col pr-10 gap-4 justify-start 6023 [&_a]:text-xl [&_a]:leading-[26px] [&_a]:font-bold [&_a]:text-primary-dark [&_a:hover]:text-primary max-w-[278px]" 6024 > 6025 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6026 6027<a class="snowdog-menu-link " 6028 href="https://www.purdys.com/fall-gift-ideas" 6029 data-menu="menu-2328" 6030 > 6031 Discover Thanksgiving</a> 6032 </div> 6033 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6034 6035<a class="snowdog-menu-link " 6036 href="https://www.purdys.com/halloween-gift-ideas" 6037 data-menu="menu-2351" 6038 > 6039 Discover Halloween</a> 6040 </div> 6041 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6042 6043<a class="snowdog-menu-link " 6044 href="https://www.purdys.com/share-joy-share-chocolate" 6045 data-menu="menu-3763" 6046 > 6047 Maritimes Collection</a> 6048 </div> 6049 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6050 <a href="https://www.purdys.com/almost-perfect-chocolates-145-kg" 6051 class="snowdog-menu-link " 6052 data-menu="menu-81" 6053 data-menu="menu-81" > 6054 Almost Perfects </a> 6055 </div> 6056 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6057 6058<a class="snowdog-menu-link " 6059 href="https://www.purdys.com/ice-cream" 6060 data-menu="menu-3749" 6061 > 6062 Ice Cream & Ice Cream Bars</a> 6063 </div> 6064 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6065 6066<a href="https://www.purdys.com/gifts/canadian-collection" 6067 class="snowdog-menu-link " 6068 data-menu="menu-3785" 6069 > 6070 Canadian Collection</a> 6071 </div> 6072 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6073 6074<a class="snowdog-menu-link " 6075 href="https://www.purdys.com/summer-delivery" 6076 data-menu="menu-2350" 6077 > 6078 Summer Shipping</a> 6079 </div> 6080 </div> 6081 6082 <div class="flex flex-wrap xl:flex-nowrap flex-1 gap-x-10 justify-between"> 6083 <!-- Main column: items with children --> 6084 <div class="max-h-[439px] columns-3 column-gap-8 column-fill-auto max-w-[75%]"> 6085 <div class="break-inside-avoid mb-10 inline-block w-full"> 6086 <div 6087 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6088 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6089 >
6090 <span class="flex " 6091 tabindex="0" 6092 data-menu="menu-82" > 6093 Product Type </span> 6094 </div> 6095 <ul class="space-y-3 ml-0"> 6096 <li 6097 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6098 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6099 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6100 > 6101 6102<a href="https://www.purdys.com/chocolate/boxed-chocolates" 6103 class="snowdog-menu-link " 6104 data-menu="menu-286" 6105 > 6106 Boxed Chocolate</a> 6107 </li> 6108 <li 6109 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6110 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6111 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6112 > 6113 6114<a href="https://www.purdys.com/fall/halloween" 6115 class="snowdog-menu-link " 6116 data-menu="menu-3779" 6117 > 6118 Halloween Chocolate</a> 6119 </li> 6120 <li 6121 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6122 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6123 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6124 > 6125 6126<a href="https://www.purdys.com/fall/thanksgiving" 6127 class="snowdog-menu-link " 6128 data-menu="menu-3782" 6129 > 6130 Thanksgiving Chocolate</a> 6131 </li> 6132 <li 6133 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6134 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6135 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6136 > 6137 6138<a href="https://www.purdys.com/gifts/gift-baskets-and-sets" 6139 class="snowdog-menu-link " 6140 data-menu="menu-287" 6141 > 6142 Gift Baskets and Sets</a> 6143 </li> 6144 <li 6145 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6146 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6147 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6148 > 6149 6150<a href="https://www.purdys.com/chocolate/mix-and-match-chocolates" 6151 class="snowdog-menu-link " 6152 data-menu="menu-288" 6153 > 6154 Mix and Match Chocolates</a> 6155 </li> 6156 </ul> 6157 </div> 6158 <div class="break-inside-avoid mb-10 inline-block w-full"> 6159 <div 6160 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6161 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6162 >
6163 <span class="flex " 6164 tabindex="0" 6165 data-menu="menu-83" > 6166 By Price </span> 6167 </div> 6168 <ul class="space-y-3 ml-0"> 6169 <li 6170 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6171 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6172 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6173 > 6174 6175<a href="https://www.purdys.com/fall/fall-low" 6176 class="snowdog-menu-link " 6177 data-menu="menu-290" 6178 > 6179 Under $20</a> 6180 </li> 6181 <li 6182 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6183 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6184 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6185 > 6186 6187<a href="#" 6188 class="snowdog-menu-link " 6189 data-menu="menu-291" 6190 > 6191 $20 to $50</a> 6192 </li> 6193 <li 6194 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6195 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6196 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6197 > 6198 6199<a href="https://www.purdys.com/fall/fall-medium" 6200 class="snowdog-menu-link " 6201 data-menu="menu-2167" 6202 > 6203 Over $50</a> 6204 </li> 6205 </ul> 6206 </div> 6207 <div class="break-inside-avoid mb-10 inline-block w-full"> 6208 <div 6209 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6210 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6211 > 6212 <span class="flex " 6213 tabindex="0" 6214 data-menu="menu-2170" > 6215 Others </span> 6216 </div> 6217 <ul class="space-y-3 ml-0"> 6218 <li 6219 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6220 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6221 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6222 > 6223 <a href="https://www.purdys.com/build-a-box" 6224 class="snowdog-menu-link " 6225 data-menu="menu-2173" 6226 data-menu="menu-2173" > 6227 Build-a-box </a> 6228 </li> 6229 <li 6230 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6231 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6232 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6233 > 6234 6235<a class="snowdog-menu-link " 6236 href="https://www.purdys.com/shipping-gift-guarantee" 6237 data-menu="menu-2176" 6238 > 6239 Shipping and Delivery</a> 6240 </li> 6241 </ul> 6242 </div> 6243 6244 <!-- Items with special class "menu-second-column" --> 6245 </div> 6246 6247 <!-- Right column: feature block if present --> 6248 <div class="flex flex-row gap-6 justify-start [&>div]:max-w-[240px]"> 6249 <div class="flex flex-col justify-start [&>a:first-child]:mb-4 [&>a:nth-child(2)]:text-center [&>a:nth-child(2)]:font-bold [&>a:nth-child(2)]:text-black [&>a:nth-child(2)]:text-xl [&>a:nth-child(2)]:leading-[26px] [&>a:nth-child(2):hover]:text-primary"> 6250 <!-- Render the feature node using the vendor node_type template logic --> 6251 <a href="https://www.purdys.com/halloween-gift-tin-12pc" 6252 class="block mb-2 transition-opacity group-hover:opacity-80 min-h-[364px]" 6253 tabindex="-1" 6254 > 6255 <img src="https://www.purdys.com/media/snowdog/menu/node/b/a/banner-container_1_.png" 6256 alt="Halloween Tins" 6257 loading="lazy" 6258 width="273" 6259 height="364" 6260 /> 6261 </a> 6262 <a href="https://www.purdys.com/halloween-gift-tin-12pc" 6263 class="snowdog-menu-link menu-second-column mega-menu-promo-img" 6264 data-menu="menu-2260" 6265 data-menu="menu-2260" > 6266 Halloween Tins </a> 6267 </div> 6268 </div> 6269 </div> 6270 </div> 6271</div> 6272 <div 6273 x-ref="submenu-1559" 6274 class="absolute left-0 top-full w-full z-30 shadow-md bg-white flex overflow-hidden" 6275 style="transform-origin: top;" 6276 x-show="openSubmenuId === '1559'" 6277 x-transition:enter="transition-all duration-300 ease-out" 6278 x-transition:enter-start="transform scale-y-0" 6279 x-transition:enter-end="transform scale-y-100" 6280 x-transition:leave="transition-all duration-200 ease-in" 6281 x-transition:leave-start="transform scale-y-100" 6282 x-transition:leave-end="transform scale-y-0" 6283 x-cloak 6284 @mouseenter="onSubmenuMouseEnter()" 6285 @mouseleave="onSubmenuMouseLeave()" 6286> 6287 <div class="min-[1660px]:!max-w-[1740px] mx-auto flex flex-row justify-between w-full min-h-[400px] max-h-[600px] 6288 md:px-5 2xl:px-10 py-10 gap-10 [&_a]:no-underline overflow-y-auto"> 6289 <!-- Left column: items without children and without image --> 6290 <div 6291 class="flex flex-col pr-10 gap-4 justify-start 6292 [&_a]:text-xl [&_a]:leading-[26px] [&_a]:font-bold [&_a]:text-primary-dark [&_a:hover]:text-primary max-w-[278px]" 6293 > 6294 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6295 6296<a class="snowdog-menu-link " 6297 href="https://www.purdys.com/fall-gift-ideas" 6298 data-menu="menu-2324" 6299 > 6300 Discover Thanksgiving</a> 6301 </div> 6302 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6303 6304<a class="snowdog-menu-link " 6305 href="https://www.purdys.com/halloween-gift-ideas" 6306 data-menu="menu-2360" 6307 > 6308 Discover Halloween</a> 6309 </div> 6310 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6311 6312<a class="snowdog-menu-link " 6313 href="https://www.purdys.com/share-joy-share-chocolate" 6314 data-menu="menu-3766" 6315 > 6316 Maritimes Collection</a> 6317 </div> 6318 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6319 6320<a href="https://www.purdys.com/chocolate/boxed-chocolates" 6321 class="snowdog-menu-link " 6322 data-menu="menu-1568" 6323 > 6324 Boxed Chocolate</a> 6325 </div> 6326 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6327 <a href="https://www.purdys.com/almost-perfect-chocolates-145-kg" 6328 class="snowdog-menu-link " 6329 data-menu="menu-3788" 6330 data-menu="menu-3788" > 6331 Almost Perfects </a> 6332 </div> 6333 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6334 6335<a class="snowdog-menu-link " 6336 href="https://www.purdys.com/pistachio-chocolate" 6337 data-menu="menu-1721" 6338 > 6339 Pistachio </a> 6340 </div> 6341 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6342 <a href="https://www.purdys.com/build-a-box" 6343 class="snowdog-menu-link " 6344 data-menu="menu-1577" 6345 data-menu="menu-1577" > 6346 Build-a-box </a> 6347 </div> 6348 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6349 6350<a href="https://www.purdys.com/chocolate/send-chocolates-free-shipping" 6351 class="snowdog-menu-link " 6352 data-menu="menu-1583" 6353 > 6354 Free Shipping </a> 6355 </div> 6356 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6357
6358<a href="https://www.purdys.com/chocolate/online-exclusive-chocolates" 6359 class="snowdog-menu-link " 6360 data-menu="menu-1586" 6361 > 6362 Online Exclusive</a> 6363 </div> 6364 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6365 6366<a href="https://www.purdys.com/chocolate/most-reviewed-chocolate" 6367 class="snowdog-menu-link " 6368 data-menu="menu-1589" 6369 > 6370 Most Reviewed</a> 6371 </div> 6372 </div> 6373 6374 <div class="flex flex-wrap xl:flex-nowrap flex-1 gap-x-10 justify-between"> 6375 <!-- Main column: items with children --> 6376 <div class="max-h-[500px] columns-3 column-gap-8 column-fill-auto max-w-[75%]"> 6377 <div class="break-inside-avoid mb-10 inline-block w-full"> 6378 <div 6379 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6380 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6381 > 6382 <span class="flex " 6383 tabindex="0" 6384 data-menu="menu-1595" > 6385 Bestsellers </span> 6386 </div> 6387 <ul class="space-y-3 ml-0"> 6388 <li 6389 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6390 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6391 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6392 > 6393 6394<a href="https://www.purdys.com/chocolate/bestsellers" 6395 class="snowdog-menu-link " 6396 data-menu="menu-1598" 6397 > 6398 All Bestsellers</a> 6399 </li> 6400 <li 6401 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6402 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6403 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6404 > 6405 6406<a href="https://www.purdys.com/chocolate/chocolate-sweet-georgia-browns" 6407 class="snowdog-menu-link " 6408 data-menu="menu-1601" 6409 > 6410 Sweet Georgia Browns</a> 6411 </li> 6412 <li 6413 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6414 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6415 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6416 > 6417 6418<a href="https://www.purdys.com/chocolate/chocolate-hedgehogs" 6419 class="snowdog-menu-link " 6420 data-menu="menu-1604" 6421 > 6422 Hedgehogs</a> 6423 </li> 6424 <li 6425 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6426 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6427 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6428 > 6429 6430<a href="https://www.purdys.com/chocolate/salted-caramel-chocolates" 6431 class="snowdog-menu-link " 6432 data-menu="menu-1607" 6433 > 6434 Himalayan Pink Salt Caramels</a> 6435 </li> 6436 </ul> 6437 </div> 6438 <div class="break-inside-avoid mb-10 inline-block w-full"> 6439 <div 6440 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6441 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6442 >
6443 <span class="flex " 6444 tabindex="0" 6445 data-menu="menu-1610" > 6446 CHOCOLATE TYPE </span> 6447 </div> 6448 <ul class="space-y-3 ml-0"> 6449 <li 6450 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6451 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6452 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6453 > 6454 6455<a href="https://www.purdys.com/chocolate/dark-chocolate" 6456 class="snowdog-menu-link " 6457 data-menu="menu-1616" 6458 > 6459 Dark Chocolate</a> 6460 </li> 6461 <li 6462 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6463 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6464 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6465 > 6466 6467<a href="https://www.purdys.com/chocolate/milk-chocolate" 6468 class="snowdog-menu-link " 6469 data-menu="menu-1619" 6470 > 6471 Milk Chocolate</a> 6472 </li> 6473 <li 6474 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6475 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6476 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6477 > 6478 6479<a href="https://www.purdys.com/chocolate/white-chocolate" 6480 class="snowdog-menu-link " 6481 data-menu="menu-1622" 6482 > 6483 White Chocolate</a> 6484 </li> 6485 <li 6486 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6487 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6488 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6489 > 6490 6491<a href="https://www.purdys.com/chocolate/no-sugar-added" 6492 class="snowdog-menu-link " 6493 data-menu="menu-1625" 6494 > 6495 No Sugar Added</a> 6496 </li> 6497 <li 6498 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6499 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6500 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6501 > 6502 6503<a class="snowdog-menu-link " 6504 href="https://www.purdys.com/plant-based-chocolate" 6505 data-menu="menu-1613" 6506 > 6507 Plant-based Chocolate</a> 6508 </li> 6509 <li 6510 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6511 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6512 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6513 > 6514 6515<a href="https://www.purdys.com/chocolate/fruit-flavoured-chocolate" 6516 class="snowdog-menu-link " 6517 data-menu="menu-1628" 6518 > 6519 Fruit Flavours</a> 6520 </li> 6521 </ul> 6522 </div> 6523 <div class="break-inside-avoid mb-10 inline-block w-full"> 6524 <div 6525 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6526 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6527 >
6528 <span class="flex " 6529 tabindex="0" 6530 data-menu="menu-1724" > 6531 COLLECTIONS </span> 6532 </div> 6533 <ul class="space-y-3 ml-0"> 6534 <li 6535 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6536 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6537 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6538 > 6539 6540<a href="https://www.purdys.com/chocolate/see-all" 6541 class="snowdog-menu-link menu-second-column" 6542 data-menu="menu-1727" 6543 > 6544 Chocolate</a> 6545 </li> 6546 <li 6547 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6548 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6549 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6550 > 6551 6552<a href="https://www.purdys.com/chocolate/chocolate-bars-snacks" 6553 class="snowdog-menu-link " 6554 data-menu="menu-1730" 6555 > 6556 Bars and Snacks</a> 6557 </li> 6558 <li 6559 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6560 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6561 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6562 > 6563 6564<a href="https://www.purdys.com/chocolate/mint-chocolates" 6565 class="snowdog-menu-link " 6566 data-menu="menu-1733" 6567 > 6568 Mint Chocolates</a> 6569 </li> 6570 <li 6571 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6572 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6573 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6574 > 6575 6576<a href="https://www.purdys.com/chocolate/nuts-and-caramels" 6577 class="snowdog-menu-link " 6578 data-menu="menu-1736" 6579 > 6580 Nuts and Caramels</a> 6581 </li> 6582 <li 6583 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6584 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6585 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6586 > 6587 6588<a href="https://www.purdys.com/chocolate/assorted-chocolate" 6589 class="snowdog-menu-link " 6590 data-menu="menu-1739" 6591 > 6592 Assorted Chocolate</a> 6593 </li> 6594 <li 6595 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6596 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6597 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6598 > 6599 6600<a href="https://www.purdys.com/chocolate/peanut-butter" 6601 class="snowdog-menu-link " 6602 data-menu="menu-1742" 6603 > 6604 Peanut Butter</a> 6605 </li> 6606 <li 6607 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6608 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6609 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6610 > 6611 6612<a href="https://www.purdys.com/chocolate/fruits-ginger-and-spices" 6613 class="snowdog-menu-link " 6614 data-menu="menu-1745" 6615 > 6616 Fruits, Ginger and Spices</a> 6617 </li> 6618 <li 6619 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6620 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6621 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6622 > 6623 6624<a href="https://www.purdys.com/chocolate/chocolate-truffles" 6625 class="snowdog-menu-link " 6626 data-menu="menu-1748" 6627 > 6628 Chocolate Truffles</a> 6629 </li> 6630 <li 6631 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6632 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6633 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6634 > 6635 6636<a href="https://www.purdys.com/chocolate/brittle-bark-and-toffee" 6637 class="snowdog-menu-link " 6638 data-menu="menu-1751" 6639 > 6640 Chocolate Bark, Brittle and Toffee</a> 6641 </li> 6642 <li 6643 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6644 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6645 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6646 > 6647 6648<a href="https://www.purdys.com/chocolate/from-the-chocolate-kitchen" 6649 class="snowdog-menu-link " 6650 data-menu="menu-1754" 6651 > 6652 From The Chocolate Kitchen</a> 6653 </li> 6654 <li 6655 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6656 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6657 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6658 > 6659 6660<a href="https://www.purdys.com/chocolate/kosher" 6661 class="snowdog-menu-link " 6662 data-menu="menu-1763" 6663 > 6664 Kosher Chocolate</a> 6665 </li> 6666 </ul> 6667 </div> 6668 <div class="break-inside-avoid mb-10 inline-block w-full"> 6669 <div 6670 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6671 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6672 >
6673 <span class="flex " 6674 tabindex="0" 6675 data-menu="menu-1673" > 6676 SALE & SPECIALS </span> 6677 </div> 6678 <ul class="space-y-3 ml-0"> 6679 <li 6680 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6681 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6682 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6683 > 6684 6685<a href="https://www.purdys.com/chocolate/special-offers" 6686 class="snowdog-menu-link " 6687 data-menu="menu-1676" 6688 > 6689 Special Offers</a> 6690 </li> 6691 <li 6692 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6693 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6694 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6695 > 6696 6697<a href="https://www.purdys.com/chocolate/bulk-chocolate" 6698 class="snowdog-menu-link " 6699 data-menu="menu-1679" 6700 > 6701 Bulk Chocolate</a> 6702 </li> 6703 <li 6704 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6705 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6706 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6707 > 6708 6709<a href="https://www.purdys.com/chocolate/mix-match-low" 6710 class="snowdog-menu-link " 6711 data-menu="menu-1682" 6712 > 6713 Mix and Match 3 for $11.75</a> 6714 </li> 6715 <li 6716 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6717 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6718 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6719 > 6720 6721<a href="https://www.purdys.com/chocolate/mix-match-medium" 6722 class="snowdog-menu-link " 6723 data-menu="menu-1685" 6724 > 6725 Mix and Match 3 for $12.50</a> 6726 </li> 6727 <li 6728 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6729 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6730 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6731 > 6732 6733<a href="https://www.purdys.com/chocolate/mix-match-high" 6734 class="snowdog-menu-link " 6735 data-menu="menu-1688" 6736 > 6737 Mix and Match 3 for $23</a> 6738 </li> 6739 </ul> 6740 </div> 6741 <div class="break-inside-avoid mb-10 inline-block w-full"> 6742 <div 6743 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6744 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6745 >
6746 <span class="flex " 6747 tabindex="0" 6748 data-menu="menu-1766" > 6749 OTHER </span> 6750 </div> 6751 <ul class="space-y-3 ml-0"> 6752 <li 6753 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6754 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6755 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6756 > 6757 6758<a class="snowdog-menu-link " 6759 href="https://www.purdys.com/ice-cream" 6760 data-menu="menu-2334" 6761 > 6762 Ice Cream & Ice Cream Bars</a> 6763 </li> 6764 <li 6765 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6766 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6767 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6768 > 6769 6770<a class="snowdog-menu-link " 6771 href="https://www.purdys.com/chocolate-legend" 6772 data-menu="menu-1769" 6773 > 6774 Chocolate Legend </a> 6775 </li> 6776 <li 6777 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6778 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6779 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6780 > 6781 6782<a href="https://www.purdys.com/chocolate/new" 6783 class="snowdog-menu-link " 6784 data-menu="menu-1772" 6785 > 6786 New</a> 6787 </li> 6788 <li 6789 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6790 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6791 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6792 > 6793 6794<a class="snowdog-menu-link " 6795 href="https://www.purdys.com/shipping-gift-guarantee" 6796 data-menu="menu-1775" 6797 > 6798 Shipping Deadlines</a> 6799 </li> 6800 </ul> 6801 </div> 6802 6803 <!-- Items with special class "menu-second-column" --> 6804 </div> 6805 6806 <!-- Right column: feature block if present --> 6807 <div class="flex flex-row gap-6 justify-start [&>div]:max-w-[240px]"> 6808 <div class="flex flex-col justify-start [&>a:first-child]:mb-4 [&>a:nth-child(2)]:text-center [&>a:nth-child(2)]:font-bold [&>a:nth-child(2)]:text-black [&>a:nth-child(2)]:text-xl [&>a:nth-child(2)]:leading-[26px] [&>a:nth-child(2):hover]:text-primary"> 6809 <!-- Render the feature node using the vendor node_type template logic --> 6810 <a href="https://www.purdys.com/coffin-gift-box" 6811 class="block mb-2 transition-opacity group-hover:opacity-80 min-h-[364px]" 6812 tabindex="-1" 6813 > 6814 <img src="https://www.purdys.com/media/snowdog/menu/node/b/a/banner-container_1.png" 6815 alt="Coffin Box" 6816 loading="lazy" 6817 width="273" 6818 height="364" 6819 /> 6820 </a> 6821 <a href="https://www.purdys.com/coffin-gift-box" 6822 class="snowdog-menu-link menu-second-column mega-menu-promo-img" 6823 data-menu="menu-2122" 6824 data-menu="menu-2122" > 6825 Coffin Box </a> 6826 </div> 6827 </div> 6828 </div> 6829 </div> 6830</div> 6831 <div 6832 x-ref="submenu-11" 6833 class="absolute left-0 top-full w-full z-30 shadow-md bg-white flex overflow-hidden" 6834 style="transform-origin: top;" 6835 x-show="openSubmenuId === '11'" 6836 x-transition:enter="transition-all duration-300 ease-out" 6837 x-transition:enter-start="transform scale-y-0" 6838 x-transition:enter-end="transform scale-y-100" 6839 x-transition:leave="transition-all duration-200 ease-in" 6840 x-transition:leave-start="transform scale-y-100" 6841 x-transition:leave-end="transform scale-y-0" 6842 x-cloak 6843 @mouseenter="onSubmenuMouseEnter()" 6844 @mouseleave="onSubmenuMouseLeave()" 6845> 6846 <div class="min-[1660px]:!max-w-[1740px] mx-auto flex flex-row justify-between w-full min-h-[400px] max-h-[600px] 6847 md:px-5 2xl:px-10 py-10 gap-10 [&_a]:no-underline overflow-y-auto"> 6848 <!-- Left column: items without children and without image --> 6849 <div 6850 class="flex flex-col pr-10 gap-4 justify-start 6851 [&_a]:text-xl [&_a]:leading-[26px] [&_a]:font-bold [&_a]:text-primary-dark [&_a:hover]:text-primary max-w-[278px]" 6852 > 6853 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6854 6855<a class="snowdog-menu-link " 6856 href="https://www.purdys.com/fall-gift-ideas" 6857 data-menu="menu-2363" 6858 > 6859 Fall Gift Ideas</a> 6860 </div> 6861 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6862 6863<a class="snowdog-menu-link " 6864 href="https://www.purdys.com/halloween-gift-ideas" 6865 data-menu="menu-3754" 6866 > 6867 Halloween Gift Ideas</a> 6868 </div> 6869 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6870 6871<a href="https://www.purdys.com/gifts/canadian-collection" 6872 class="snowdog-menu-link " 6873 data-menu="menu-3760" 6874 > 6875 Canadian Souvenir</a> 6876 </div> 6877 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6878 6879<a class="snowdog-menu-link " 6880 href="https://www.purdys.com/pistachio-chocolate" 6881 data-menu="menu-232" 6882 > 6883 Pistachio Chocolates</a> 6884 </div> 6885 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 6886 <a href="https://www.purdys.com/build-a-box" 6887 class="snowdog-menu-link " 6888 data-menu="menu-1528" 6889 data-menu="menu-1528" > 6890 Build-a-box </a> 6891 </div> 6892 </div> 6893 6894 <div class="flex flex-wrap xl:flex-nowrap flex-1 gap-x-10 justify-between"> 6895 <!-- Main column: items with children --> 6896 <div class="max-h-[439px] columns-3 column-gap-8 column-fill-auto max-w-[75%]"> 6897 <div class="break-inside-avoid mb-10 inline-block w-full"> 6898 <div 6899 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6900 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6901 >
6902 <span class="flex " 6903 tabindex="0" 6904 data-menu="menu-12" > 6905 By Occasion </span> 6906 </div> 6907 <ul class="space-y-3 ml-0"> 6908 <li 6909 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6910 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6911 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6912 > 6913 6914<a href="https://www.purdys.com/gifts/see-all-gifts" 6915 class="snowdog-menu-link " 6916 data-menu="menu-267" 6917 > 6918 See All Gifts</a> 6919 </li> 6920 <li 6921 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6922 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6923 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6924 > 6925 6926<a href="https://www.purdys.com/gifts/thank-you" 6927 class="snowdog-menu-link " 6928 data-menu="menu-266" 6929 > 6930 Thank You Gifts</a> 6931 </li> 6932 <li 6933 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6934 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6935 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6936 > 6937 6938<a href="https://www.purdys.com/gifts/birthday" 6939 class="snowdog-menu-link " 6940 data-menu="menu-265" 6941 > 6942 Birthday Gifts</a> 6943 </li> 6944 <li 6945 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6946 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6947 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6948 > 6949 6950<a href="https://www.purdys.com/gifts/anniversary" 6951 class="snowdog-menu-link " 6952 data-menu="menu-268" 6953 > 6954 Anniversary Gifts</a> 6955 </li> 6956 <li 6957 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6958 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6959 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6960 > 6961 6962<a href="https://www.purdys.com/gifts/spread-joy" 6963 class="snowdog-menu-link " 6964 data-menu="menu-269" 6965 > 6966 Spread Joy</a> 6967 </li> 6968 <li 6969 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6970 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6971 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6972 > 6973 6974<a href="https://www.purdys.com/discover/wedding-bridal-favours" 6975 class="snowdog-menu-link " 6976 data-menu="menu-270" 6977 > 6978 Guide to Purdys Wedding Favours</a> 6979 </li> 6980 <li 6981 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 6982 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 6983 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 6984 > 6985 6986<a href="https://www.purdys.com/chocolate/favours" 6987 class="snowdog-menu-link " 6988 data-menu="menu-272" 6989 > 6990 See all Custom Favours</a> 6991 </li> 6992 </ul> 6993 </div> 6994 <div class="break-inside-avoid mb-10 inline-block w-full"> 6995 <div 6996 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 6997 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 6998 >
6999 <span class="flex " 7000 tabindex="0" 7001 data-menu="menu-13" > 7002 Product Type </span> 7003 </div> 7004 <ul class="space-y-3 ml-0"> 7005 <li 7006 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7007 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7008 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7009 > 7010 7011<a href="https://www.purdys.com/gifts/gift-baskets-and-sets" 7012 class="snowdog-menu-link " 7013 data-menu="menu-273" 7014 > 7015 Gift Baskets and Sets</a> 7016 </li> 7017 <li 7018 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7019 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7020 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7021 > 7022 7023<a href="https://www.purdys.com/chocolate/boxed-chocolates" 7024 class="snowdog-menu-link " 7025 data-menu="menu-274" 7026 > 7027 Gift Boxes</a> 7028 </li> 7029 <li 7030 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7031 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7032 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7033 > 7034 7035<a href="https://www.purdys.com/chocolate/bestsellers" 7036 class="snowdog-menu-link " 7037 data-menu="menu-3" 7038 > 7039 Bestsellers</a> 7040 </li> 7041 <li 7042 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7043 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7044 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7045 > 7046 7047<a class="snowdog-menu-link " 7048 href="https://www.purdys.com/gift-cards" 7049 data-menu="menu-275" 7050 > 7051 Gift Card </a> 7052 </li> 7053 <li 7054 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7055 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7056 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7057 > 7058 7059<a href="https://www.purdys.com/chocolate/kosher" 7060 class="snowdog-menu-link " 7061 data-menu="menu-1513" 7062 > 7063 Kosher Chocolate</a> 7064 </li> 7065 </ul> 7066 </div> 7067 <div class="break-inside-avoid mb-10 inline-block w-full"> 7068 <div 7069 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 7070 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 7071 >
7072 <span class="flex " 7073 tabindex="0" 7074 data-menu="menu-14" > 7075 Corporate Gifts </span> 7076 </div> 7077 <ul class="space-y-3 ml-0"> 7078 <li 7079 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7080 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7081 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7082 > 7083 7084<a class="snowdog-menu-link " 7085 href="https://www.purdys.com/corporate-gifts" 7086 data-menu="menu-277" 7087 > 7088 Corporate Gifts</a> 7089 </li> 7090 <li 7091 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7092 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7093 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7094 > 7095 7096<a class="snowdog-menu-link " 7097 href="https://www.purdys.com/corporate-gifting" 7098 data-menu="menu-1988" 7099 > 7100 Branded Corporate Gifts</a> 7101 </li> 7102 <li 7103 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7104 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7105 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7106 > 7107 7108<a href="https://www.purdys.com/gifts/employee-gifts" 7109 class="snowdog-menu-link " 7110 data-menu="menu-278" 7111 > 7112 Employee Gifts and Snacks</a> 7113 </li> 7114 </ul> 7115 </div> 7116 <div class="break-inside-avoid mb-10 inline-block w-full"> 7117 <div 7118 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 7119 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 7120 > 7121 <span class="flex " 7122 tabindex="0" 7123 data-menu="menu-15" > 7124 By Price </span> 7125 </div> 7126 <ul class="space-y-3 ml-0"> 7127 <li 7128 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7129 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7130 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7131 > 7132 7133<a href="https://www.purdys.com/gifts/gifts-low" 7134 class="snowdog-menu-link " 7135 data-menu="menu-279" 7136 > 7137 Gift Under $20</a> 7138 </li> 7139 <li 7140 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7141 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7142 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7143 > 7144 7145<a href="https://www.purdys.com/gifts/gifts-medium" 7146 class="snowdog-menu-link " 7147 data-menu="menu-280" 7148 > 7149 Gifts $20 to $50</a> 7150 </li> 7151 <li 7152 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7153 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7154 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7155 > 7156 7157<a href="https://www.purdys.com/gifts/gifts-high" 7158 class="snowdog-menu-link " 7159 data-menu="menu-281" 7160 > 7161 Gifts Over $50</a> 7162 </li> 7163 </ul> 7164 </div> 7165 <div class="break-inside-avoid mb-10 inline-block w-full"> 7166 <div 7167 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 7168 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 7169 >
7170 <span class="flex " 7171 tabindex="0" 7172 data-menu="menu-16" > 7173 Other </span> 7174 </div> 7175 <ul class="space-y-3 ml-0"> 7176 <li 7177 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7178 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7179 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7180 > 7181 7182<a class="snowdog-menu-link " 7183 href="https://www.purdys.com/ice-cream" 7184 data-menu="menu-2337" 7185 > 7186 Ice Cream & Ice Cream Bars</a> 7187 </li> 7188 <li 7189 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7190 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7191 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7192 > 7193 7194<a href="https://www.purdys.com/chocolate/special-offers" 7195 class="snowdog-menu-link " 7196 data-menu="menu-1545" 7197 > 7198 Special Offers</a> 7199 </li> 7200 <li 7201 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7202 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7203 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7204 > 7205 7206<a href="https://www.purdys.com/chocolate/send-chocolates-free-shipping" 7207 class="snowdog-menu-link " 7208 data-menu="menu-263" 7209 > 7210 Free Shipping </a> 7211 </li> 7212 <li 7213 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7214 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7215 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7216 > 7217 <a href="/chocolate-legend" 7218 class="snowdog-menu-link " 7219 data-menu="menu-283" 7220 data-menu="menu-283" > 7221 Chocolate Legend </a> 7222 </li> 7223 <li 7224 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7225 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7226 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7227 > 7228 <a href="/shipping-gift-guarantee" 7229 class="snowdog-menu-link " 7230 data-menu="menu-284" 7231 data-menu="menu-284" > 7232 Shipping and Delivery </a> 7233 </li> 7234 </ul> 7235 </div> 7236 7237 <!-- Items with special class "menu-second-column" --> 7238 </div> 7239 7240 <!-- Right column: feature block if present --> 7241 <div class="flex flex-row gap-6 justify-start [&>div]:max-w-[240px]"> 7242 <div class="flex flex-col justify-start [&>a:first-child]:mb-4 [&>a:nth-child(2)]:text-center [&>a:nth-child(2)]:font-bold [&>a:nth-child(2)]:text-black [&>a:nth-child(2)]:text-xl [&>a:nth-child(2)]:leading-[26px] [&>a:nth-child(2):hover]:text-primary"> 7243 <!-- Render the feature node using the vendor node_type template logic --> 7244 <a href="https://www.purdys.com/fall-gift-ideas" 7245 class="block mb-2 transition-opacity group-hover:opacity-80 min-h-[364px]" 7246 tabindex="-1" 7247 > 7248 <img src="https://www.purdys.com/media/snowdog/menu/node/b/a/banner-container.png" 7249 alt="Coffin Box" 7250 loading="lazy" 7251 width="273" 7252 height="364" 7253 /> 7254 </a> 7255 <a href="https://www.purdys.com/fall-gift-ideas" 7256 class="snowdog-menu-link menu-second-column mega-menu-promo-img" 7257 data-menu="menu-3770" 7258 data-menu="menu-3770" > 7259 Talhulah Turkey & Leaves </a> 7260 </div> 7261 </div> 7262 </div> 7263 </div> 7264</div> 7265 <div 7266 x-ref="submenu-76" 7267 class="absolute left-0 top-full w-full z-30 shadow-md bg-white flex overflow-hidden" 7268 style="transform-origin: top;" 7269 x-show="openSubmenuId === '76'" 7270 x-transition:enter="transition-all duration-300 ease-out" 7271 x-transition:enter-start="transform scale-y-0" 7272 x-transition:enter-end="transform scale-y-100" 7273 x-transition:leave="transition-all duration-200 ease-in" 7274 x-transition:leave-start="transform scale-y-100" 7275 x-transition:leave-end="transform scale-y-0" 7276 x-cloak 7277 @mouseenter="onSubmenuMouseEnter()" 7278 @mouseleave="onSubmenuMouseLeave()" 7279> 7280 <div class="min-[1660px]:!max-w-[1740px] mx-auto flex flex-row justify-between w-full min-h-[400px] max-h-[600px] 7281 md:px-5 2xl:px-10 py-10 gap-10 [&_a]:no-underline overflow-y-auto"> 7282 <!-- Left column: items without children and without image --> 7283 <div 7284 class="flex flex-col pr-10 gap-4 justify-start 7285 [&_a]:text-xl [&_a]:leading-[26px] [&_a]:font-bold [&_a]:text-primary-dark [&_a:hover]:text-primary max-w-[278px]" 7286 > 7287 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 7288 <a href="/build-a-box" 7289 class="snowdog-menu-link " 7290 data-menu="menu-77" 7291 data-menu="menu-77" > 7292 Build-a-Box </a> 7293 </div> 7294 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 7295 <a href="/corporate-gifting" 7296 class="snowdog-menu-link " 7297 data-menu="menu-345" 7298 data-menu="menu-345" > 7299 Custom Corporate Gifts </a> 7300 </div> 7301 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 7302 <a href="/wedding-and-bridal-favours" 7303 class="snowdog-menu-link " 7304 data-menu="menu-346" 7305 data-menu="menu-346" > 7306 Wedding & Anniversaries </a> 7307 </div> 7308 </div> 7309 7310 <div class="flex flex-wrap xl:flex-nowrap flex-1 gap-x-10 justify-between"> 7311 <!-- Main column: items with children --> 7312 7313 <!-- Right column: feature block if present --> 7314 <div class="flex flex-row gap-6 justify-start [&>div]:max-w-[240px]"> 7315 <div class="flex flex-col justify-start [&>a:first-child]:mb-4 [&>a:nth-child(2)]:text-left [&>a:nth-child(2)]:font-bold [&>a:nth-child(2)]:text-black [&>a:nth-child(2)]:text-xl [&>a:nth-child(2)]:leading-[26px] [&>a:nth-child(2):hover]:text-primary"> 7316 <!-- Render the feature node using the vendor node_type template logic --> 7317 <a href="/build-a-box" 7318 class="block mb-2 transition-opacity group-hover:opacity-80 min-h-[364px]" 7319 tabindex="-1" 7320 > 7321 <img src="https://www.purdys.com/media/snowdog/menu/node/i/m/image_5_.png" 7322 alt="Build you own Box" 7323 loading="lazy" 7324 width="269" 7325 height="359" 7326 /> 7327 </a> 7328 <a href="/build-a-box" 7329 class="snowdog-menu-link mega-menu-promo-img" 7330 data-menu="menu-344" 7331 data-menu="menu-344" > 7332 Build-a-Box </a> 7333 </div> 7334 <div class="flex flex-col justify-start [&>a:first-child]:mb-4 [&>a:nth-child(2)]:text-left [&>a:nth-child(2)]:font-bold [&>a:nth-child(2)]:text-black [&>a:nth-child(2)]:text-xl [&>a:nth-child(2)]:leading-[26px] [&>a:nth-child(2):hover]:text-primary"> 7335 <!-- Render the feature node using the vendor node_type template logic --> 7336 <a href="/corporate-gifting" 7337 class="block mb-2 transition-opacity group-hover:opacity-80 min-h-[364px]" 7338 tabindex="-1" 7339 > 7340 <img src="https://www.purdys.com/media/snowdog/menu/node/i/m/image_3_.png" 7341 alt="Custom Corporate Gifts" 7342 loading="lazy" 7343 width="270" 7344 height="359" 7345 /> 7346 </a> 7347 <a href="/corporate-gifting" 7348 class="snowdog-menu-link mega-menu-promo-img" 7349 data-menu="menu-347" 7350 data-menu="menu-347" > 7351 Custom Corporate Gifts </a> 7352 </div> 7353 <div class="flex flex-col justify-start [&>a:first-child]:mb-4 [&>a:nth-child(2)]:text-left [&>a:nth-child(2)]:font-bold [&>a:nth-child(2)]:text-black [&>a:nth-child(2)]:text-xl [&>a:nth-child(2)]:leading-[26px] [&>a:nth-child(2):hover]:text-primary"> 7354 <!-- Render the feature node using the vendor node_type template logic --> 7355 <a href="/wedding-and-bridal-favours" 7356 class="block mb-2 transition-opacity group-hover:opacity-80 min-h-[364px]" 7357 tabindex="-1" 7358 > 7359 <img src="https://www.purdys.com/media/snowdog/menu/node/i/m/image_4_.png" 7360 alt="Custom Wedding Gifts" 7361 loading="lazy" 7362 width="270" 7363 height="359" 7364 /> 7365 </a> 7366 <a href="/wedding-and-bridal-favours" 7367 class="snowdog-menu-link mega-menu-promo-img" 7368 data-menu="menu-348" 7369 data-menu="menu-348" > 7370 Custom Wedding Gifts </a> 7371 </div> 7372 </div> 7373 </div> 7374 </div> 7375</div> 7376 <div 7377 x-ref="submenu-2179" 7378 class="absolute left-0 top-full w-full z-30 shadow-md bg-white flex overflow-hidden" 7379 style="transform-origin: top;" 7380 x-show="openSubmenuId === '2179'" 7381 x-transition:enter="transition-all duration-300 ease-out" 7382 x-transition:enter-start="transform scale-y-0" 7383 x-transition:enter-end="transform scale-y-100" 7384 x-transition:leave="transition-all duration-200 ease-in" 7385 x-transition:leave-start="transform scale-y-100" 7386 x-transition:leave-end="transform scale-y-0" 7387 x-cloak 7388 @mouseenter="onSubmenuMouseEnter()" 7389 @mouseleave="onSubmenuMouseLeave()" 7390> 7391 <div class="min-[1660px]:!max-w-[1740px] mx-auto flex flex-row justify-between w-full min-h-[400px] max-h-[600px] 7392 md:px-5 2xl:px-10 py-10 gap-10 [&_a]:no-underline overflow-y-auto"> 7393 <!-- Left column: items without children and without image --> 7394 <div 7395 class="flex flex-col pr-10 gap-4 justify-start 7396 [&_a]:text-xl [&_a]:leading-[26px] [&_a]:font-bold [&_a]:text-primary-dark [&_a:hover]:text-primary max-w-[278px]" 7397 > 7398 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 7399 7400<a class="snowdog-menu-link " 7401 href="https://www.purdys.com/ice-cream" 7402 data-menu="menu-2340" 7403 > 7404 Ice Cream & Ice Cream Bars</a> 7405 </div> 7406 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 7407 <a href="/blog.html" 7408 class="snowdog-menu-link " 7409 data-menu="menu-2182" 7410 data-menu="menu-2182" > 7411 Purdys Blog </a> 7412 </div> 7413 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 7414 7415<a href="https://www.purdys.com/discover/purple-partnerships" 7416 class="snowdog-menu-link " 7417 data-menu="menu-2185" 7418 > 7419 Purple Partnerships</a> 7420 </div> 7421 <div class="text-base font-medium text-primary-dark hover:text-primary transition-colors"> 7422 7423<a class="snowdog-menu-link " 7424 href="https://www.purdys.com/shipping-gift-guarantee" 7425 data-menu="menu-2188" 7426 > 7427 Shipping Deadlines</a> 7428 </div> 7429 </div> 7430 7431 <div class="flex flex-wrap xl:flex-nowrap flex-1 gap-x-10 justify-between"> 7432 <!-- Main column: items with children --> 7433 <div class="max-h-[439px] columns-3 column-gap-8 column-fill-auto max-w-[75%]"> 7434 <div class="break-inside-avoid mb-10 inline-block w-full"> 7435 <div 7436 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 7437 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 7438 >
7439 <span class="flex " 7440 tabindex="0" 7441 data-menu="menu-2191" > 7442 Sustainability </span> 7443 </div> 7444 <ul class="space-y-3 ml-0"> 7445 <li 7446 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7447 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7448 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7449 > 7450 7451<a class="snowdog-menu-link " 7452 href="https://www.purdys.com/sustainable-cocoa" 7453 data-menu="menu-2194" 7454 > 7455 Purdys' Sustainable Cocoa Program</a> 7456 </li> 7457 <li 7458 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7459 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7460 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7461 > 7462 7463<a class="snowdog-menu-link " 7464 href="https://www.purdys.com/sustainable-farms" 7465 data-menu="menu-2197" 7466 > 7467 Sustainable Farms</a> 7468 </li> 7469 <li 7470 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7471 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7472 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7473 > 7474 7475<a class="snowdog-menu-link " 7476 href="https://www.purdys.com/sustainable-living" 7477 data-menu="menu-2200" 7478 > 7479 Sustainable Living</a> 7480 </li> 7481 <li 7482 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7483 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7484 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7485 > 7486 7487<a class="snowdog-menu-link " 7488 href="https://www.purdys.com/clean-water-project" 7489 data-menu="menu-2203" 7490 > 7491 Clean Water Project</a> 7492 </li> 7493 </ul> 7494 </div> 7495 <div class="break-inside-avoid mb-10 inline-block w-full"> 7496 <div 7497 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 7498 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 7499 > 7500 <span class="flex " 7501 tabindex="0" 7502 data-menu="menu-2206" > 7503 Corporate Gifts </span> 7504 </div> 7505 <ul class="space-y-3 ml-0"> 7506 <li 7507 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7508 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7509 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7510 > 7511 7512<a class="snowdog-menu-link " 7513 href="https://www.purdys.com/corporate-gifts" 7514 data-menu="menu-2209" 7515 > 7516 Corporate Gifts Guides</a> 7517 </li> 7518 <li 7519 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7520 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7521 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7522 > 7523 <a href="/corporate-gifting" 7524 class="snowdog-menu-link " 7525 data-menu="menu-2212" 7526 data-menu="menu-2212" > 7527 Branded Corporate Gifts </a> 7528 </li> 7529 </ul> 7530 </div> 7531 <div class="break-inside-avoid mb-10 inline-block w-full"> 7532 <div 7533 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 7534 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 7535 >
7536 <span class="flex " 7537 tabindex="0" 7538 data-menu="menu-2215" > 7539 Our Stories </span> 7540 </div> 7541 <ul class="space-y-3 ml-0"> 7542 <li 7543 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7544 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7545 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7546 > 7547 7548<a href="https://www.purdys.com/discover/about-us" 7549 class="snowdog-menu-link " 7550 data-menu="menu-2218" 7551 > 7552 About Us</a> 7553 </li> 7554 <li 7555 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7556 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7557 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7558 > 7559 7560<a href="https://www.purdys.com/discover/purdys-heritage" 7561 class="snowdog-menu-link " 7562 data-menu="menu-2221" 7563 > 7564 Purdys Heritage</a> 7565 </li> 7566 </ul> 7567 </div> 7568 <div class="break-inside-avoid mb-10 inline-block w-full"> 7569 <div 7570 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 7571 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 7572 > 7573 <span class="flex " 7574 tabindex="0" 7575 data-menu="menu-2224" > 7576 Tips & Guides </span> 7577 </div> 7578 <ul class="space-y-3 ml-0"> 7579 <li 7580 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7581 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7582 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7583 > 7584 <a href="/blog/is-chocolate-vegan-your-guide-to-vegan-chocolate.html" 7585 class="snowdog-menu-link " 7586 data-menu="menu-2227" 7587 data-menu="menu-2227" > 7588 Is Chocolate Vegan </a> 7589 </li> 7590 <li 7591 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7592 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7593 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7594 > 7595 <a href="/blog/celebrate-with-the-perfect-chocolate-gifts.html" 7596 class="snowdog-menu-link " 7597 data-menu="menu-2230" 7598 data-menu="menu-2230" > 7599 The Tasty Art of Giving </a> 7600 </li> 7601 <li 7602 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7603 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7604 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7605 > 7606 7607<a class="snowdog-menu-link " 7608 href="https://www.purdys.com/wedding-and-bridal-favours" 7609 data-menu="menu-2233" 7610 > 7611 Guide to Purdys Wedding Favours</a> 7612 </li> 7613 </ul> 7614 </div> 7615 <div class="break-inside-avoid mb-10 inline-block w-full"> 7616 <div 7617 class="uppercase tracking-[0.001em] font-bold text-primary-dark mb-4 text-xl leading-[26px] 7618 [&_a]:text-primary-dark [&_a]:!font-bold [&_a:hover]:text-primary" 7619 >
7620 <span class="flex " 7621 tabindex="0" 7622 data-menu="menu-2236" > 7623 Marketing & Tasting </span> 7624 </div> 7625 <ul class="space-y-3 ml-0"> 7626 <li 7627 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7628 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7629 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7630 > 7631 7632<a href="https://www.purdys.com/discover/chocolate-legend" 7633 class="snowdog-menu-link " 7634 data-menu="menu-2239" 7635 > 7636 Chocolate Legend</a> 7637 </li> 7638 <li 7639 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7640 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7641 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7642 > 7643 7644<a href="https://www.purdys.com/discover/wine-and-chocolate-pairings" 7645 class="snowdog-menu-link " 7646 data-menu="menu-2242" 7647 > 7648 Wine and Chocolate Pairings</a> 7649 </li> 7650 <li 7651 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7652 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7653 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7654 > 7655 7656<a href="https://www.purdys.com/discover/recipes" 7657 class="snowdog-menu-link " 7658 data-menu="menu-2245" 7659 > 7660 Recipes</a> 7661 </li> 7662 <li 7663 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7664 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7665 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7666 > 7667 7668<a href="https://www.purdys.com/discover/gift-ideas" 7669 class="snowdog-menu-link " 7670 data-menu="menu-2248" 7671 > 7672 Gift Ideas</a> 7673 </li> 7674 <li 7675 class="body-1 text-primary-dark body-1 hover:text-primary before:content-[''] pl-0 7676 [&_a]:body-1 [&_a]:text-black [&_a:hover]:text-primary [&_a:hover]:cursor-pointer 7677 [&_span]:text-black [&_span:hover]:text-primary [&_span:hover]:cursor-pointer [&_span]:!p-0" 7678 > 7679 <a href="/blog.html" 7680 class="snowdog-menu-link " 7681 data-menu="menu-2251" 7682 data-menu="menu-2251" > 7683 Blog </a> 7684 </li> 7685 </ul> 7686 </div> 7687 7688 <!-- Items with special class "menu-second-column" --> 7689 </div> 7690 7691 <!-- Right column: feature block if present --> 7692 <div class="flex flex-row gap-6 justify-start [&>div]:max-w-[240px]"> 7693 <div class="flex flex-col justify-start [&>a:first-child]:mb-4 [&>a:nth-child(2)]:text-center [&>a:nth-child(2)]:font-bold [&>a:nth-child(2)]:text-black [&>a:nth-child(2)]:text-xl [&>a:nth-child(2)]:leading-[26px] [&>a:nth-child(2):hover]:text-primary"> 7694 <!-- Render the feature node using the vendor node_type template logic --> 7695 <a href="https://www.purdys.com/share-joy-share-chocolate" 7696 class="block mb-2 transition-opacity group-hover:opacity-80 min-h-[364px]" 7697 tabindex="-1" 7698 > 7699 <img src="https://www.purdys.com/media/snowdog/menu/node/m/a/maritimes-collection-mega-menu.png" 7700 alt="Maritimes" 7701 loading="lazy" 7702 width="273" 7703 height="364" 7704 /> 7705 </a> 7706 <a href="https://www.purdys.com/share-joy-share-chocolate" 7707 class="snowdog-menu-link menu-second-column mega-menu-promo-img" 7708 data-menu="menu-2257" 7709 data-menu="menu-2257" > 7710 Maritimes </a> 7711 </div> 7712 </div> 7713 </div> 7714 </div> 7715</div> 7716 </div> 7717</header><div> 7718</div>
7718<main id="maincontent" class="page-main"><div id="contentarea" tabindex="-1"></div> 7719<div class="page-title-container container flex flex-col md:flex-row flex-wrap my-6 font-bold lg:mt-8 font-semibold text-[24px] leading-7 tracking-tiny lg:text-h3-lg text-black"> 7720 <!-- Desktop title: use h1 as usual --> 7721 <h1 class="page-title font-semibold text-[24px] leading-7 tracking-tiny lg:text-h3-lg text-black" 7722 > 7723 <span class="base" data-ui-id="page-title-wrapper" >Customer Login</span> </h1> 7724 </div> 7725<div class="columns"><div class="column main"> <!-- Yotpo - Add body class --> 7726
7726<script> 7727 document.addEventListener('DOMContentLoaded', function () { 7728 document.body.classList.add('yotpo-yotpo-is-enabled'); 7729 }); 7730 </script>
7730 7731 7732 <!--/ Yotpo - Add body class --> 7733 <!-- Yotpo - Widget Script --> 7734
7734<script> 7735 (function e(){var e=document.createElement("script");e.type="text/javascript",e.async=true,e.src="//staticw2.yotpo.com/Y8hTayDbyjtCoMfQkIIJpq9NdBI0r2HoSN33Q590/widget.js";var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t)})(); 7736 </script>
7736 7737 <!--/ Yotpo - Widget Script -->
7738<script nonce="aHhkaGVuNDFoY2Y2eHhyZTY4bzN2c3BycmM3d3FoNnI="> 7739 var feedId = getUrlParam('ff'), 7740 product = getUrlParam('fp'), 7741 currentDate = new Date(), 7742 session = getCookie('feed_session'); 7743 7744 if (!session) { 7745 session = '' + Math.floor(currentDate.getTime() / 1000) + Math.floor(Math.random() * 10000001); 7746 } 7747 7748 if (session && feedId > 0 && product > 0) { 7749 setCookie('feed_session', session, {expires: 365, path: '/'}); 7750 setCookie('feed_id', feedId, 365); 7751 7752 var xhr = new XMLHttpRequest(), 7753 baseUrl = 'https://www.purdys.com/', 7754 url = BASE_URL + '?rnd=' + Math.floor(Math.random() * 10000001) + "&feed=" + feedId + "&session=" + session + "&product=" + product; 7755 7756 xhr.open('GET', url, true); 7757 xhr.setRequestHeader('Content-Type', 'application/json'); 7758 xhr.send(); 7759 } 7760 7761 function getUrlParam(name) { 7762 let results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href); 7763 7764 if (results === null) { 7765 return ''; 7766 } else { 7767 return results[1] || 0; 7768 } 7769 } 7770 7771 function getCookie(cookieName) { 7772 var name = cookieName + "=", 7773 decodedCookie = decodeURIComponent(document.cookie), 7774 cookieArray = decodedCookie.split(';'); 7775 7776 for (var i = 0; i < cookieArray.length; i++) { 7777 var cookie = cookieArray[i].trim(); 7778 if (cookie.indexOf(name) === 0) { 7779 return cookie.substring(name.length, cookie.length); 7780 } 7781 } 7782 7783 return null; 7784 } 7785 7786 function setCookie(cookieName, cookieValue, expirationDays) { 7787 var d = new Date(); 7788 d.setTime(d.getTime() + (expirationDays * 24 * 60 * 60 * 1000)); 7789 var expires = "expires=" + d.toUTCString(); 7790 document.cookie = cookieName + "=" + cookieValue + ";" + expires + ";path=/"; 7791 } 7792</script>
7792 7793
7794<script> 7795 window.klaviyoTrackEvent = function (eventName, data = {}) { 7796 window._learnq = window._learnq || []; 7797 window._learnq.push(['track', eventName, { 7798 ...data, 7799 Timestamp: new Date().toISOString() 7800 }]); 7801 } 7802</script>
7802 7803<div id="customer-login-container" class="login-container"><div class="bg-background-light/50 rounded py-6 px-4 md:p-10"> 7804 <div aria-labelledby="block-customer-login-heading"> 7805 <div class="flex flex-col gap-6 max-md:gap-4"> 7806 <!-- Sign In Section Header --> 7807 <div class="text-center"> 7808 <h1 class="text-[40px] max-md:text-[28px] font-semibold leading-[1.2] tracking-[0.01em] text-black mb-6 max-md:mb-6"> 7809 Sign in </h1> 7810 <p class="text-base text-black leading-[1.375] mx-auto"> 7811 Experience a smoother checkout, multi-address shipping, order tracking, and more delicious benefits </p> 7812 </div> 7813 7814 <!-- Login Form --> 7815 <form class="form form-login" 7816 action="https://www.purdys.com/customer/account/loginPost/" 7817 method="post" 7818 x-data="initCustomerLoginForm_6ab6cfd600889()" 7819 @submit.prevent="submitForm()" 7820 id="customer-login-form"> 7821 <input name="form_key" type="hidden" value="cbEPL7j02Kuo6BSa" /> <fieldset class="fieldset login"> 7822 <div class="flex flex-col gap-4"> 7823 <!-- Email Field --> 7824 <div class="field email required"> 7825 <label class="flex items-center mb-4 text-base font-normal text-black" 7826 for="email_6ab6cfd600889"> 7827 <span>Email Address</span> 7828 <span class="text-[#C11A25]">*</span> 7829 </label> 7830 <div class="control"> 7831 <input name="login[username]" 7832 class="w-full h-[42px] px-4 py-[10px] border border-gray rounded text-base text-black focus:border-primary focus:outline-none" 7833 required 7834 value="" 7835 autocomplete="off" 7836 id="email_6ab6cfd600889" 7837 type="email" 7838 title="Email Address" 7839 /> 7840 </div> 7841 </div> 7842 7843 <!-- Password Field --> 7844 <div class="field password required"> 7845 <label class="flex items-center mb-4 text-base font-normal text-black" 7846 for="pass_6ab6cfd600889"> 7847 <span>Password</span> 7848 <span class="text-[#C11A25]">*</span> 7849 </label> 7850 <div class="control flex items-center relative"> 7851 <div class="sr-only" aria-live="polite"> 7852 <template x-if="!showPassword"> 7853 <span> 7854 Password hidden </span> 7855 </template> 7856 <template x-if="showPassword"> 7857 <span> 7858 Password shown </span> 7859 </template> 7860 </div> 7861 <input name="login[password]" 7862 class="w-full h-[42px] px-4 py-[10px] border border-gray rounded text-base text-black focus:border-primary focus:outline-none pe-10" 7863 required 7864 :type="showPassword ? 'text' : 'password'" 7865 autocomplete="off" 7866 id="pass_6ab6cfd600889" 7867 title="Password" 7868 /> 7869 <button 7870 type="button" 7871 x-on:click="showPassword = !showPassword" 7872 :aria-pressed="showPassword ? 'true' : 'false'" 7873 class="px-4 py-3 absolute end-0" 7874 :aria-label="showPassword ? 'Hide\u0020Password' : 'Show\u0020Password'" 7875 > 7876 <template x-if="!showPassword"> 7877 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" width="24" height="24" role="img"> 7878 <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/> 7879 <path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/> 7880<title>eye</title></svg> 7881 </template> 7882 <template x-if="showPassword"> 7883 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" width="24" height="24" role="img"> 7884 <path fill-rule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clip-rule="evenodd"/> 7885 <path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z"/> 7886<title>
7886eye-off</title></svg> 7887 </template> 7888 </button> 7889 </div> 7890 </div> 7891 7892 <!-- Keep me signed in checkbox --> 7893 <div class="flex items-center gap-4"> 7894 <div class="flex items-center gap-4"> 7895 <input type="checkbox" 7896 id="keep_signed_in_6ab6cfd600889" 7897 name="persistent_remember_me" 7898 class="w-4 h-4 border border-[#BFBFBF] rounded-[1px] text-primary focus:ring-primary" 7899 value="1"> 7900 <label for="keep_signed_in_6ab6cfd600889" class="text-sm text-black"> 7901 Keep me signed in </label> 7902 </div> 7903 <div class="relative h-5" x-data="{ showTooltip: false }"> 7904 <button type="button" 7905 class="w-5 h-5 text-primary hover:opacity-80 transition-opacity" 7906 @mouseenter="showTooltip = true" 7907 @mouseleave="showTooltip = false" 7908 @focus="showTooltip = true" 7909 @blur="showTooltip = false" 7910 aria-label="Information about keeping you signed in"> 7911 <img src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/images/svg/question-mark-circle.svg" 7912 alt="Help" 7913 class="w-5 h-5"/> 7914 </button> 7915 7916 <!-- Tooltip --> 7917 <div x-show="showTooltip" 7918 x-transition:enter="transition ease-out duration-200" 7919 x-transition:enter-start="opacity-0 transform scale-95" 7920 x-transition:enter-end="opacity-100 transform scale-100" 7921 x-transition:leave="transition ease-in duration-75" 7922 x-transition:leave-start="opacity-100 transform scale-100" 7923 x-transition:leave-end="opacity-0 transform scale-95" 7924 class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-gray-900 text-white text-sm rounded shadow-lg whitespace-nowrap z-10" 7925 style="display: none;"> 7926 Stay logged in for up to 30 days on this device <div class="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div> 7927 </div> 7928 </div> 7929 </div> 7930 7931 <!-- Action Buttons --> 7932 <div class="flex flex-col gap-4 items-center"> 7933 <button type="submit" 7934 class="w-full bg-primary text-white font-bold text-sm uppercase tracking-[0.03em] py-3 px-4 rounded hover:bg-primary/90 transition-colors" 7935 name="send" 7936 > 7937 <span class="leading-3">SIGN IN</span> 7938 </button> 7939 <a class="text-base font-bold text-primary hover:text-primary/80 transition-colors" 7940 href="https://www.purdys.com/customer/account/forgotpassword/"> 7941 <span>Forgot password?</span> 7942 </a> 7943 </div> 7944 </div> 7945 7946 <!-- Error Messages --> 7947 <div> 7948 <template x-if="displayErrorMessage"> 7949 <p class="text-red flex items-center">
7950 <span class="inline-block w-8 h-8 me-3"> 7951 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="24" height="24" aria-hidden="true"> 7952 <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> 7953</svg> 7954 </span> 7955 <template x-for="errorMessage in errorMessages"> 7956 <span x-html="errorMessage"></span> 7957 </template> 7958 </p> 7959 </template> 7960 </div> 7961 </fieldset> 7962 </form> 7963 7964 <!-- Separator Line --> 7965 <div class="w-[50%] m-auto h-px bg-[#BBBBC3]"></div> 7966 7967 <!-- Sign Up Link --> 7968 <div class="text-center"> 7969 <p class="text-base text-black"> 7970 Don't have an account? <a href="https://www.purdys.com/customer/account/create/" 7971 class="text-primary hover:text-primary-hover transition-colors"> 7972 Sign Up </a> 7973 </p> 7974 </div> 7975 </div> 7976 </div> 7977
7977<script> 7978 function initCustomerLoginForm_6ab6cfd600889() { 7979 return { 7980 errors: 0, 7981 hasCaptchaToken: 0, 7982 showPassword: false, 7983 displayErrorMessage: false, 7984 errorMessages: [], 7985 setErrorMessages(messages) { 7986 this.errorMessages = [messages] 7987 this.displayErrorMessage = this.errorMessages.length 7988 }, 7989 submitForm() { 7990 // do not rename $form, the variable is the expected name in the recaptcha output 7991 const $form = document.querySelector('#customer-login-form'); 7992 7993 if (this.errors === 0) { 7994 $form.submit(); 7995 } 7996 } 7997 } 7998 } 7999 </script>
7999 8000</div> 8001 8002<!-- Mobile "Or" Separator --> 8003<div class="flex items-center gap-4 lg:hidden"> 8004 <div class="flex-1 h-px bg-black"></div> 8005 <span class="text-base text-black font-normal leading-[22px]">Or</span> 8006 <div class="flex-1 h-px bg-black"></div> 8007</div> 8008 <div class="bg-background-light/50 rounded py-6 px-4 md:p-10 h-fit"> 8009 <div class="flex flex-col gap-4 text-center"> 8010 <!-- New Customers Header --> 8011 <div> 8012 <h1 class="text-[40px] max-md:text-[28px] font-semibold leading-[1.2] tracking-[0.01em] text-black mb-6 max-md:mb-4"> 8013 New Customers </h1> 8014 <p class="text-base text-black leading-[1.375]"> 8015 Creating an account has many benefits: check out faster, keep more than one address, track orders and more. </p> 8016 </div> 8017 8018 <!-- Create Account Button --> 8019 <div class="md:mt-4"> 8020 <a href="https://www.purdys.com/customer/account/create/" 8021 class="inline-block w-full bg-primary text-white font-bold text-sm uppercase tracking-[0.03em] py-3 px-4 rounded hover:bg-primary/90 transition-colors no-underline"> 8022 <span>Create An Account</span> 8023 </a> 8024 </div> 8025 </div> 8026 </div> 8027</div>
8028<script> 8029 const rawRequest = ""; 8030 const searchRequest = rawRequest ? JSON.parse(rawRequest) : null; 8031 8032 const rawResponse = ""; 8033 const searchResponse = rawResponse ? JSON.parse(rawResponse) : null; 8034 8035 const rawExecutionTime = "0"; 8036 const executionTime = rawExecutionTime ? parseFloat(rawExecutionTime) : null; 8037 8038 const requestId = "684475d6\u002D1990\u002D4ada\u002Dbba5\u002D6c43b49cfb56"; 8039 8040 const rawAttributes = ""; 8041 const attributes = rawAttributes ? JSON.parse(rawAttributes) : null; 8042 8043 const rawPageSize = "24"; 8044 const pageSize = rawPageSize ? parseInt(rawPageSize) : null; 8045 8046 const customerGroup = "b6589fc6ab0dc82cf12099d1c2d40ab994e8410c"; 8047 8048 window.LiveSearchMetrics = { 8049 searchRequest, 8050 searchResponse, 8051 requestId, 8052 executionTime, 8053 attributes, 8054 pageSize, 8055 context: { 8056 'customerGroup' : customerGroup 8057 } 8058 }; 8059 8060 window.addEventListener('load', function () { 8061 magentoStorefrontEvents.context.setSearchExtension({ 8062 version: "4.4.1" 8063 }); 8064 }) 8065</script>
8065 8066</div></div></main><footer class="footer"><div class="footer content"><div><style>#html-body [data-pb-style=RSAB68Q]{justify-content:flex-start;display:flex;flex-direction:column;background-color:#532e91;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;text-align:center;padding-top:0;padding-bottom:0}#html-body [data-pb-style=Q1MUREV]{text-align:center;padding-top:8px;padding-bottom:8px}#html-body [data-pb-style=LG8IL8Y],#html-body [data-pb-style=U4H5N6T]{background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll}#html-body [data-pb-style=U4H5N6T]{justify-content:flex-start;display:flex;flex-direction:column;background-color:#2e1950;padding:60px 40px}#html-body [data-pb-style=LG8IL8Y]{align-self:stretch}#html-body [data-pb-style=KW4LBT0]{display:flex;width:100%}#html-body [data-pb-style=EE5JGGA],#html-body [data-pb-style=SSJH2YL]{justify-content:flex-start;display:flex;flex-direction:column;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;width:50%;align-self:stretch}#html-body [data-pb-style=MUYXYOX]{display:none}#html-body [data-pb-style=OML5YU9],#html-body [data-pb-style=SLEH9ES]{background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll}#html-body [data-pb-style=OML5YU9]{justify-content:flex-start;display:flex;flex-direction:column;background-color:#2e1950;padding:40px}#html-body [data-pb-style=SLEH9ES]{align-self:stretch}#html-body [data-pb-style=FQHBO9B]{display:flex;width:100%}#html-body [data-pb-style=K2IN203]{justify-content:flex-start;display:flex;flex-direction:column;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;width:25%;align-self:stretch}#html-body [data-pb-style=IX9PFCI]{margin:0;padding:0}#html-body [data-pb-style=FUXQTVN]{justify-content:flex-start;display:flex;flex-direction:column;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;width:25%;align-self:stretch}#html-body [data-pb-style=LWQ7T9S]{margin:0;padding:0}#html-body [data-pb-style=AOFKXMR]{justify-content:flex-start;display:flex;flex-direction:column;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;width:25%;align-self:stretch}#html-body [data-pb-style=QX4A3JA]{margin:0;padding:0}#html-body [data-pb-style=C6W2PBQ]{justify-content:flex-start;display:flex;flex-direction:column;width:25%;align-self:stretch}#html-body [data-pb-style=BKE0357],#html-body [data-pb-style=C6W2PBQ],#html-body [data-pb-style=LYHHH0M]{background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll}#html-body [data-pb-style=BKE0357]{justify-content:flex-start;display:flex;flex-direction:column;background-color:#2e1950;padding:0 40px 40px}#html-body [data-pb-style=LYHHH0M]{align-self:stretch}#html-body [data-pb-style=XN0IGTB]{display:flex;width:100%}#html-body [data-pb-style=G112RCT]{justify-content:flex-start;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;width:50%}#html-body [data-pb-style=G112RCT],#html-body [data-pb-style=UNQSC74],#html-body [data-pb-style=V56ANT6]{display:flex;flex-direction:column;align-self:stretch}#html-body [data-pb-style=V56ANT6]{justify-content:flex-start;width:50%}#html-body [data-pb-style=HDQ4Y37],#html-body [data-pb-style=J3XVEBX],#html-body [data-pb-style=V56ANT6]{background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll}#html-body [data-pb-style=J3XVEBX]{justify-content:flex-start;display:flex;flex-direction:column;background-color:#2e1950;padding:16px 40px}#html-body [data-pb-style=HDQ4Y37]{align-self:stretch}#html-body [data-pb-style=WOWMXKM]{display:flex;width:100%}#html-body [data-pb-style=JLA0JXQ],#html-body [data-pb-style=PA43XLI],#html-body [data-pb-style=VVP4G3W]{justify-content:flex-start;display:flex;flex-direction:column;background-position:left top;background-size:cover;background-repeat:no-repeat;background-attachment:scroll;width:33.3333%;align-self:stretch}</style><div data-content-type="row" data-appearance="full-width" data-enable-parallax="0" data-parallax-speed="0.5" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-background-lazy-load="false" data-element="main" data-pb-style="RSAB68Q"><div class="row-full-width-inner" data-element="inner"><div data-content-type="html" data-appearance="default" data-element="main" data-pb-style="Q1MUREV" data-decoded="true"><div class="flex justify-center flex-wrap gap-x-1 gap-y-2 !text-white"> 8067 <div class="flex items-center gap-2"> 8068 <div class="stars flex items-center"> 8069 <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewbox="0 0 29 28" fill="none" role="img"> 8070<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#B09FCC"></path> 8071<g clip-path="url(#clip0_26550_6322)"> 8072<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#EEEAF4"></path> 8073</g> 8074<defs> 8075<clippath id="clip0_26550_6322"> 8076<rect width="28" height="28" fill="white" transform="translate(0.5 0.00195312)"></rect> 8077</clippath> 8078</defs> 8079<title>star</title></svg> 8080 <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewbox="0 0 29 28" fill="none" role="img"> 8081<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#B09FCC"></path> 8082<g clip-path="url(#clip0_26550_6322_2)"> 8083<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#EEEAF4"></path> 8084</g> 8085<defs> 8086<clippath id="clip0_26550_6322_2"> 8087<rect width="28" height="28" fill="white" transform="translate(0.5 0.00195312)"></rect> 8088</clippath> 8089</defs> 8090<title>star</title></svg> 8091 <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewbox="0 0 29 28" fill="none" role="img"> 8092<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#B09FCC"></path> 8093<g clip-path="url(#clip0_26550_6322_3)"> 8094<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#EEEAF4"></path> 8095</g> 8096<defs> 8097<clippath id="clip0_26550_6322_3"> 8098<rect width="28" height="28" fill="white" transform="translate(0.5 0.00195312)"></rect> 8099</clippath> 8100</defs> 8101<title>star</title></svg> 8102 <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewbox="0 0 29 28" fill="none" role="img"> 8103<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#B09FCC"></path> 8104<g clip-path="url(#clip0_26550_6322_4)"> 8105<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#EEEAF4"></path> 8106</g> 8107<defs> 8108<clippath id="clip0_26550_6322_4"> 8109<rect width="28" height="28" fill="white" transform="translate(0.5 0.00195312)"></rect> 8110</clippath> 8111</defs> 8112<title>star</title></svg> 8113 <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewbox="0 0 29 28" fill="none" role="img"> 8114<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#B09FCC"></path> 8115<g clip-path="url(#clip0_26550_6322_5)"> 8116<path d="M13.7354 4.75765C13.976 4.01698 15.0239 4.01698 15.2645 4.75765L17.0269 10.1816C17.1345 10.5128 17.4432 10.7371 17.7915 10.7371H23.4945C24.2733 10.7371 24.5971 11.7336 23.967 12.1914L19.3532 15.5435C19.0714 15.7483 18.9535 16.1111 19.0611 16.4424L20.8235 21.8663C21.0641 22.6069 20.2164 23.2228 19.5863 22.7651L14.9725 19.4129C14.6907 19.2082 14.3092 19.2082 14.0274 19.4129L9.41357 22.7651C8.78351 23.2228 7.93578 22.6069 8.17644 21.8663L9.93877 16.4424C10.0464 16.1111 9.9285 15.7483 9.64673 15.5435L5.03289 12.1914C4.40283 11.7336 4.72664 10.7371 5.50542 10.7371H11.2084C11.5567 10.7371 11.8654 10.5128 11.973 10.1816L13.7354 4.75765Z" fill="#EEEAF4"></path> 8117</g> 8118<defs> 8119<clippath id="clip0_26550_6322_5"> 8120<rect width="28" height="28" fill="white" transform="translate(0.5 0.00195312)"></rect> 8121</clippath> 8122</defs> 8123<title>star</title></svg> 8124 </div>
8125 <span class="body-2 font-bold"> 8126 4.9 based on </span> 8127 </div> 8128 8129 <div class="flex items-center gap-1"> 8130 <span class="body-2 font-bold"> 8131 24307 reviews. </span> 8132 <a href="/reviews" class="reviews-link font-bold body-1 flex items-center gap-1 !text-white"> 8133 <span>VIEW ALL REVIEWS</span> 8134 <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewbox="0 0 20 20" fill="none" class="-rotate-90 text-white [&>path]:stroke-[4px]" aria-hidden="true"> 8135<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> 8136</svg> 8137 </a> 8138 </div> 8139</div> 8140</div></div></div><div class="footer-newsletter" data-content-type="row" data-appearance="full-bleed" data-enable-parallax="0" data-parallax-speed="0.5" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-background-lazy-load="false" data-element="main" data-pb-style="U4H5N6T"><div class="pagebuilder-column-group" data-background-images="{}" data-content-type="column-group" data-appearance="default" data-grid-size="12" data-background-lazy-load="" data-element="main" data-pb-style="LG8IL8Y"><div class="pagebuilder-column-line" data-content-type="column-line" data-element="main" data-pb-style="KW4LBT0"><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="EE5JGGA"><div data-content-type="text" data-appearance="default" data-element="main"><h2 style="text-align: left;"><span style="color: rgb(255, 255, 255); font-size: 40px;">Want FREE Sweet Georgia Browns on your next order</span></h2> 8141 <p style="text-align: left;"><span style="color: rgb(217, 220, 241); font-size: 16px;">Sign up for our tasty emails and we'll send you a promo code!</span></p></div></div><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="SSJH2YL"><div data-content-type="html" data-appearance="default" data-element="main" data-pb-style="MUYXYOX" data-decoded="true"><div class="w-full max-lg:mt-3"> 8142 <form class="form subscribe" action="https://www.purdys.com/newsletter/subscriber/new/" method="post" x-data="{ ...initNewsletterForm(), ...hyva.formValidation($el) }" @submit.prevent="submitForm" id="newsletter-validate-detail" aria-label="Subscribe to Newsletter"> 8143 <div class="fieldset"> 8144 <div class="flex justify-center items-center md:justify-start fieldset"> 8145 <div class="field field-reserved w-full"> 8146 <div class="control w-full"> 8147 <input name="email" type="email" required id="newsletter-subscribe" class="form-input inline-flex w-full h-[42px] !rounded-r-none !min-h-0" placeholder="Your email address" autocomplete="email" aria-describedby="footer-newsletter-heading" @change="onChange" data-validate='{"email": true, "required": true}' data-msg-email="Enter a valid email." data-msg-required="This is a required field."> 8148 </div> 8149 <input name="form_key" type="hidden" value="cbEPL7j02Kuo6BSa"> </div> 8150 <button class="btn h-[42px] btn-primary bg-primary-lightest text-black hover:bg-white shrink-0 rounded-l-none"> 8151 Sign Up </button> 8152 </div> 8153 <!-- <button class="btn btn-primary bg-container-darker text-black hover:bg-white mt-8 w-full lg:hidden">--> 8154<!-- --><!-- </button>--> 8155 </div> 8156 </form> 8157 <div class="w-full"> 8158 </div> 8159
8159<script> 8160 function initNewsletterForm() { 8161 return { 8162 errors: 0, 8163 hasCaptchaToken: 0, 8164 submitForm() { 8165 // Do not rename $form, the variable is expected to be declared in the recaptcha output 8166 const $form = document.querySelector('#newsletter-validate-detail'); 8167 8168 this.validate() 8169 .then(() => { 8170 // all fields validated 8171 $form.submit(); 8172 }) 8173 .catch((invalid) => { 8174 if (invalid.length > 0) { 8175 invalid[0].focus(); 8176 } 8177 }); 8178 8179 // if (this.errors === 0) { 8180 // $form.submit(); 8181 // } 8182 } 8183 } 8184 } 8185 </script>
8185 8186</div> 8187 8188</div><div data-content-type="html" data-appearance="default" data-element="main" data-decoded="true"><style> 8189 .klaviyo-newsletter-block { 8190 max-width:600px; 8191 } 8192 .klaviyo-newsletter-block button { 8193 background-color: red; 8194 } 8195 .klaviyo-newsletter-block h4 { 8196 color: #b09fcc; 8197 font-size: 20px; 8198 font-weight: 700; 8199 line-height: 26px; 8200 margin-bottom: 0; 8201 } 8202 .klaviyo-newsletter-block p { 8203 color: #fff; 8204 font-size: 16px; 8205 line-height: 22px; 8206 margin-bottom: 0; 8207 } 8208 </style> 8209 <div class="klaviyo-newsletter-block"> 8210 <div class="klaviyo-form-V5WemK"></div> 8211 <!-- 8212 <p>By submitting this form, you agree to receive email newsletters, also our <a style="color: #B09FCC;" href="https://www.purdys.com/terms-of-use" target="_blank">Terms and Conditions</a> &<a style="color: #B09FCC;" href="https://www.purdys.com/privacy-policy" target="_blank"> Privacy policy</a>. This form is protected by reCAPTCHA. 8213 --> 8214 </div> 8215</div></div></div></div></div><div class="footer-content" data-content-type="row" data-appearance="full-bleed" data-enable-parallax="0" data-parallax-speed="0.5" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-background-lazy-load="false" data-element="main" data-pb-style="OML5YU9"><div class="pagebuilder-column-group" data-background-images="{}" data-content-type="column-group" data-appearance="default" data-grid-size="12" data-background-lazy-load="" data-element="main" data-pb-style="SLEH9ES"><div class="pagebuilder-column-line" data-content-type="column-line" data-element="main" data-pb-style="FQHBO9B"><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="K2IN203"><div data-content-type="scandiweb_accordion" data-appearance="default" block_title="Shop" content='<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Holidays collections" href="/holidays-collections">Holidays collections</a></p> 8216<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Catalogues" href="/catalogues">Catalogues</a></p>' is-opened="0" desktop-disabled="1" title_color="#ffffff" data-element="main" data-pb-style="IX9PFCI"><div data-element="block_directive">
8217<script> 8218 function accordion_6ab6cfd6088a1() { 8219 return { 8220 isOpen: Boolean(0), 8221 } 8222 } 8223</script>
8223 8224 8225<div x-data="accordion_6ab6cfd6088a1" data-content-type="accordion" class="w-full" x-defer="intersect"> 8226 <button class="flex justify-between items-center gap-3 px-0 py-5 w-full body-2 max-md:px-4 max-lg:px-5 !border-0 md:border-0 !bg-transparent lg:pointer-events-none" x-on:click="isOpen = !isOpen" style="color: #ffffff"> 8227 <span class="font-bold">Shop</span> 8228 <div class="lg:hidden"> 8229 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewbox="0 0 20 20" fill="none" class="transition-transform duration-200" aria-hidden="true" :class="{ 'rotate-180': isOpen }"> 8230<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> 8231</svg> 8232 </div> 8233 </button> 8234 <div class="grid transition-grid-row grid-rows-0fr lg:grid-rows-1fr duration-200" :class="{ 'grid-rows-1fr': isOpen, 'grid-rows-0fr': !isOpen }"> 8235 <div class="overflow-hidden"> 8236 <div class="max-lg:pb-5" data-content-type="accordion-content"> 8237 <p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Holidays collections" href="/holidays-collections">Holidays collections</a></p> 8238<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Catalogues" href="/catalogues">Catalogues</a></p> </div> 8239 </div> 8240 </div> 8241</div> 8242</div></div></div><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="FUXQTVN"><div data-content-type="scandiweb_accordion" data-appearance="default" block_title="About Purdys" content='<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="About Us" href="/about-us">About Us</a></p> 8243<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Reviews" href="/reviews">Reviews</a></p> 8244<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Purdys Blog" href="/blog.html">Purdys Blog</a></p> 8245<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Careers" href="/careers">Careers</a></p> 8246<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Shop Locations" href="/shops">Shop Locations</a></p> 8247<p><a style="color: #ffffff;" tabindex="0" title="Donation Requests" href="/donation-requests">Donation Requests</a></p> 8248' is-opened="0" desktop-disabled="1" title_color="#ffffff" data-element="main" data-pb-style="LWQ7T9S"><div data-element="block_directive">
8249<script> 8250 function accordion_6ab6cfd6093f1() { 8251 return { 8252 isOpen: Boolean(0), 8253 } 8254 } 8255</script>
8255 8256 8257<div x-data="accordion_6ab6cfd6093f1" data-content-type="accordion" class="w-full" x-defer="intersect"> 8258 <button class="flex justify-between items-center gap-3 px-0 py-5 w-full body-2 max-md:px-4 max-lg:px-5 !border-0 md:border-0 !bg-transparent lg:pointer-events-none" x-on:click="isOpen = !isOpen" style="color: #ffffff"> 8259 <span class="font-bold">About Purdys</span> 8260 <div class="lg:hidden"> 8261 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewbox="0 0 20 20" fill="none" class="transition-transform duration-200" aria-hidden="true" :class="{ 'rotate-180': isOpen }"> 8262<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> 8263</svg> 8264 </div> 8265 </button> 8266 <div class="grid transition-grid-row grid-rows-0fr lg:grid-rows-1fr duration-200" :class="{ 'grid-rows-1fr': isOpen, 'grid-rows-0fr': !isOpen }"> 8267 <div class="overflow-hidden"> 8268 <div class="max-lg:pb-5" data-content-type="accordion-content"> 8269 <p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="About Us" href="/about-us">About Us</a></p> 8270<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Reviews" href="/reviews">Reviews</a></p> 8271<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Purdys Blog" href="/blog.html">Purdys Blog</a></p> 8272<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Careers" href="/careers">Careers</a></p> 8273<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Shop Locations" href="/shops">Shop Locations</a></p> 8274<p><a style="color: #ffffff;" tabindex="0" title="Donation Requests" href="/donation-requests">Donation Requests</a></p> 8275 </div> 8276 </div> 8277 </div> 8278</div> 8279</div></div></div><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="AOFKXMR"><div data-content-type="scandiweb_accordion" data-appearance="default" block_title="Support" content='<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Shipping" href="/shipping-gift-guarantee">Shipping</a></p> 8280<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="FAQ" href="/faqs">FAQ</a></p> 8281<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="FAQ" href="/accessible-customer-service-policy">Accessible Customer Service Policy</a></p>' is-opened="0" desktop-disabled="1" title_color="#ffffff" data-element="main" data-pb-style="QX4A3JA"><div data-element="block_directive">
8282<script> 8283 function accordion_6ab6cfd609d69() { 8284 return { 8285 isOpen: Boolean(0), 8286 } 8287 } 8288</script>
8288 8289 8290<div x-data="accordion_6ab6cfd609d69" data-content-type="accordion" class="w-full" x-defer="intersect"> 8291 <button class="flex justify-between items-center gap-3 px-0 py-5 w-full body-2 max-md:px-4 max-lg:px-5 !border-0 md:border-0 !bg-transparent lg:pointer-events-none" x-on:click="isOpen = !isOpen" style="color: #ffffff"> 8292 <span class="font-bold">Support</span> 8293 <div class="lg:hidden"> 8294 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewbox="0 0 20 20" fill="none" class="transition-transform duration-200" aria-hidden="true" :class="{ 'rotate-180': isOpen }"> 8295<path d="M18.3329 5.83337L9.99959 14.1667L1.66626 5.83337" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> 8296</svg> 8297 </div> 8298 </button> 8299 <div class="grid transition-grid-row grid-rows-0fr lg:grid-rows-1fr duration-200" :class="{ 'grid-rows-1fr': isOpen, 'grid-rows-0fr': !isOpen }"> 8300 <div class="overflow-hidden"> 8301 <div class="max-lg:pb-5" data-content-type="accordion-content"> 8302 <p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="Shipping" href="/shipping-gift-guarantee">Shipping</a></p> 8303<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="FAQ" href="/faqs">FAQ</a></p> 8304<p><a style="color: #ffffff; margin-bottom: 12px;" tabindex="0" title="FAQ" href="/accessible-customer-service-policy">Accessible Customer Service Policy</a></p> </div> 8305 </div> 8306 </div> 8307</div> 8308</div></div></div><div class="pagebuilder-column footer-contact" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="false" data-element="main" data-pb-style="C6W2PBQ"><div data-content-type="html" data-appearance="default" data-element="main" data-decoded="true"><style> 8309 .contact-text { 8310 color: #ffffff; 8311 font-size: 18px; 8312 line-height: 24px; 8313 font-weight: bold; 8314 padding: 16px 0; 8315 } 8316 8317 @media (max-width: 1024px) { 8318 .contact-text { 8319 padding: 20px 0; 8320 } 8321 } 8322 8323 .footer-link { 8324 color: #ffffff; 8325 margin-bottom: 12px; 8326 display: inline-flex;
8327 align-items: center; 8328 gap: 8px; 8329 text-decoration: none; 8330 } 8331 8332 .footer-link span { 8333 text-decoration: underline; 8334 } 8335 8336 .footer-location { 8337 color: #ffffff; 8338 display: flex; 8339 align-items: flex-start; 8340 gap: 8px; 8341 text-decoration: none; 8342 } 8343 8344 .footer-location div { 8345 display: flex; 8346 flex-direction: column; 8347 } 8348 8349 .footer-regular-hours { 8350 color: #ffffff; 8351 font-weight: bold; 8352 margin-top: 20px; 8353 } 8354 8355 .footer-time { 8356 color: #ffffff; 8357 } 8358 </style> 8359 8360 <p class="contact-text">Contact Us</p> 8361 8362 <p> 8363 <a class="footer-link" tabindex="-1" title="1.888.478.7397" href="tel:1.888.478.7397"> 8364 <img src="/media/wysiwyg/footer/mobile.png" alt="tel" width="20" height="20"> 8365 <span>1.888.478.7397</span> 8366 </a> 8367 </p> 8368 8369 <p> 8370 <a class="footer-link" tabindex="-1" title="email" href="mailto:[email protected]"> 8371 <img src="/media/wysiwyg/footer/email.png" alt="email" width="20" height="20"> 8372 <span>[email protected]</span> 8373 </a> 8374 </p> 8375 8376 <div class="footer-location"> 8377 <a tabindex="0" title="location" href="#"> 8378 <img src="/media/wysiwyg/footer/location.png" alt="location" width="24" height="24"> 8379 </a> 8380 <a style=" 8381 display: flex; 8382 flex-direction: column; 8383 gap: 0; 8384 color: white; 8385 text-decoration: underline;" 8386 tabindex="0" title="location" href="#"> 8387 <span>8330 Chester Street</span> 8388 <span>Vancouver, British Columbia</span> 8389 <span>Canada V5X 3Y7</span> 8390 </a> 8391 </a> 8392 </div> 8393 8394 <p class="footer-regular-hours">Regular hours:</p> 8395 <p class="footer-time">Mon - Fri, 9:00am to 4:00pm (PT)</p> 8396</div></div></div></div></div><div class="footer-icons" data-content-type="row" data-appearance="full-bleed" data-enable-parallax="0" data-parallax-speed="0.5" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-background-lazy-load="false" data-element="main" data-pb-style="BKE0357"><div class="pagebuilder-column-group" data-background-images="{}" data-content-type="column-group" data-appearance="default" data-grid-size="12" data-background-lazy-load="" data-element="main" data-pb-style="LYHHH0M"><div class="pagebuilder-column-line" data-content-type="column-line" data-element="main" data-pb-style="XN0IGTB"><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="G112RCT"><div class="footer-icons-social" data-content-type="text" data-appearance="default" data-element="main"><p><a tabindex="0" title="Instagram" href="https://www.instagram.com/purdyschocolatier/"><img id="KJFHO9A" style="width: 20px; height: 20px;" src="/media/wysiwyg/footer/instagram.png" alt="instagram" width="20" height="20"></a></p> 8397 <p><a style="box-shadow: none; outline: 0px;" tabindex="0" title="Facebook" href="https://www.facebook.com/PurdysChocolatier"><img id="SPY4HUC" style="width: 20px; height: 20px;" src="/media/wysiwyg/footer/facebook.png" alt="facebook" width="20" height="20"></a></p> 8398 <p><a style="box-shadow: none; outline: 0px;" tabindex="0" title="Youtube" href="https://www.youtube.com/user/PurdysChocolates1"><img style="width: 20px; height: 20px;" src="/media/wysiwyg/footer/youtube.png" alt="youtube" width="20" height="20"></a></p> 8399 <p><a style="text-decoration-line: underline; box-shadow: none; outline: 0px;" tabindex="0" title="X" href="https://x.com/PurdysChocolate"><img style="width: 20px; height: 20px;" src="/media/wysiwyg/footer/x.png" alt="X" width="20" height="20"></a></p> 8400 <p><a tabindex="0" title="Tiktok" href="https://www.tiktok.com/@purdys.chocolatier"><img style="width: 20px; height: 20px;" src="/media/wysiwyg/footer/ticktok.png" alt="ticktok" width="20" height="20"></a></p> 8401 <p> </p></div></div><div class="pagebuilder-column footer-language-switcher-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="UNQSC74"><div data-content-type="html" data-appearance="default" data-element="main" data-decoded="true"> 8402<div class="footer-language-switcher-wrapper"> 8403 <div class="footer-language-switcher" x-data="{ open: false }" @click.outside="open = false"> 8404 <p class="footer-language-switcher__label">Choose language:</p> 8405 <div class="footer-language-switcher__content"> 8406 <img src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/images/canad
8406a-flag.svg" alt="Canada flag" class="footer-language-switcher__flag" width="29" height="16"> 8407 <button type="button" class="footer-language-switcher__toggle" @click="open = !open" :aria-expanded="open.toString()" aria-haspopup="listbox"> 8408 <span>Canada â EN</span> 8409 <img src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/images/language-switcher-chevron.svg" alt="" class="footer-language-switcher__chevron" :class="{ 'is-open': open }" aria-hidden="true" width="12" height="7"> 8410 </button> 8411 <div class="footer-language-switcher__menu" x-show="open" x-cloak> 8412 <a href="https://www.purdys.com/stores/store/redirect/___store/default/uenc/aHR0cHM6Ly93d3cucHVyZHlzLmNvbS9jdXN0b21lci9hY2NvdW50L2xvZ2luLw~~/___from_store/default/" class="footer-language-switcher__item footer-language-switcher__item--active" role="option" aria-selected="true"> 8413 Canada â EN </a> 8414 <a href="https://www.purdys.com/stores/store/redirect/___store/fr/uenc/aHR0cHM6Ly93d3cucHVyZHlzLmNvbS9mci9jdXN0b21lci9hY2NvdW50L2xvZ2luLw~~/___from_store/default/" class="footer-language-switcher__item" role="option" aria-selected="false"> 8415 Canada â FR </a> 8416 </div> 8417 </div> 8418 </div> 8419</div> 8420</div></div><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="V56ANT6"><div class="footer-icons-payments" data-content-type="text" data-appearance="default" data-element="main"><p><img id="JUMKUDR" style="width: 34px; height: 24px;" src="/media/wysiwyg/footer/visa.png" alt="visa" width="40" height="28"></p> 8421<p><img style="width: 34px; height: 24px;" src="/media/wysiwyg/footer/mastercard.png" alt="mastercard" width="40" height="28"></p> 8422<p><img style="width: 35px; height: 24px;" src="/media/wysiwyg/Payment_Method.png" alt="" width="35" height="24"></p> 8423<p><img style="width: 34px; height: 24px;" src="/media/wysiwyg/footer/paypal.png" alt="paypal" width="40" height="28"></p></div></div></div></div></div><div class="footer-misc" data-content-type="row" data-appearance="full-bleed" data-enable-parallax="0" data-parallax-speed="0.5" data-background-images="{}" data-background-type="image" data-video-loop="true" data-video-play-only-visible="true" data-video-lazy-load="true" data-video-fallback-src="" data-background-lazy-load="false" data-element="main" data-pb-style="J3XVEBX"><div class="pagebuilder-column-group" data-background-images="{}" data-content-type="column-group" data-appearance="default" data-grid-size="12" data-background-lazy-load="" data-element="main" data-pb-style="HDQ4Y37"><div class="pagebuilder-column-line" data-content-type="column-line" data-element="main" data-pb-style="WOWMXKM"><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="VVP4G3W"><div data-content-type="text" data-appearance="default" data-element="main"><p><span style="color: rgb(255, 255, 255);"><strong><span id="WP1B73J" style="font-size: 14px;">© 2026 Purdys Chocolatier. All rights reserved.</span></strong></span></p></div></div><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="JLA0JXQ"></div><div class="pagebuilder-column" data-content-type="column" data-appearance="full-height" data-background-images="{}" data-background-lazy-load="" data-element="main" data-pb-style="PA43XLI"><div class="footer-misc-terms" data-content-type="text" data-appearance="default" data-element="main"><p><span style="color: rgb(255, 255, 255);"><strong><span style="font-size: 14px;"><a style="color: rgb(255, 255, 255);" tabindex="0" href="/terms-of-use">TERMS OF USE</a></span></strong></span></p> 8424 <p><span style="color: rgb(255, 255, 255);"><strong><span style="font-size: 14px;"><a style="color: rgb(255, 255, 255);" tabindex="0" href="/privacy-policy">PRIVACY POLICY</a></span></strong></span></p></div></div></div></div></div></div>
8424<script> 8425 'use strict'; 8426 8427 function amBrandsPopup() { 8428 let cached = false; 8429 8430 const displayError = (error) => { 8431 console.error(error); 8432 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 8433 [{ 8434 type: "error", 8435 text: "Something\u0020went\u0020wrong.\u0020Please\u0020try\u0020again." 8436 }], 10000 8437 ); 8438 }; 8439 8440 let letters = [ 8441 "A","B","C","D", 8442 "E","F","G","H","I","J","K","L","M", 8443 "N","O","P","Q","R","S","T","U","V","W","X","Y","Z","#" 8444 ]; 8445 8446 return { 8447 hoverPanelActiveId: null, 8448 selectedLetter: null, 8449 items: [], 8450 letters: [], 8451 showCount: null, 8452 showFilter: null, 8453 showImages: true, 8454 showSearch: null, 8455 loading: false, 8456 label: 'Brands', 8457 isPopupEnabled: false, 8458 isTopMenuItemEnabled: false, 8459 cache: false, 8460 isMobile: false, 8461 8462 initErrorMessages(errors) { 8463 const messages = []; 8464 for (let error in Object.keys(errors)) { 8465 messages.push({type: 'error', text: errors[error].message}); 8466 } 8467 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages(messages, 10000); 8468 }, 8469 8470 chooseLetter(letter) { 8471 this.selectedLetter = this.selectedLetter === letter ? null : letter; 8472 }, 8473 8474 getQuery() { 8475 return "query BrandsPopupQuery { ambrandlist(\n showCount: true\n displayZero: true\n ) {\n all_letters,\n items {\n alt\n brandId\n cnt\n image\n img\n label\n letter\n url\n }\n }\n storeConfig {\n amshopby_brand_general_brands_brands_popup,\n amshopby_brand_general_menu_item_label,\n amshopby_brand_general_brands_page,\n amshopby_brand_general_topmenu_enabled,\n amshopby_brand_general_brands_popup_config {\n show_images,\n display_zero,\n filter_display_all,\n image_width,\n image_height,\n show_filter,\n show_count\n }\n } }" }, 8476 8477 /** 8478 * Retrieve media url for given path 8479 * 8480 * @param path 8481 * @returns {string} 8482 */ 8483 getMediaUrl(path) { 8484 let stringToRemove = 'index.php/'; 8485 let baseUrl = BASE_URL.replace(stringToRemove, ''); 8486 return path.includes(baseUrl) ? path : baseUrl + path; 8487 }, 8488 8489 checkIsMobileResolution() { 8490 this.isMobile = window.matchMedia('(max-width: 1023px)').matches; 8491 }, 8492 8493 getBrandUrl(url){ 8494 return BASE_URL + url; 8495 }, 8496 8497 init() { 8498 this.checkIsMobileResolution(); 8499 8500 this.$watch('hoverPanelActiveId', (value) => { 8501 if (value === 'am-brand') { 8502 this.sendRequest(); 8503 } 8504 }); 8505 }, 8506 8507 /** 8508 * Get brands by letter 8509 * 8510 * @param result 8511 * @returns {{imageWidth: *, isTopMenuItemEnabled: *, showCount: *, displayAll: *, showFilter: *, showImages: *, isPopupEnabled: *, label: *, imageHeight: *, filterDisplayAll: *, brandsPage: *}} 8512 */ 8513 settingsMapper(result) { 8514 const config = result.data.storeConfig?.amshopby_brand_general_brands_popup_config; 8515 return { 8516 showImages: config.show_images, 8517 showCount: config.show_count, 8518 showFilter: config.show_filter, 8519 imageWidth: config.image_width, 8520 imageHeight: config.image_height, 8521 filterDisplayAll: config.filter_display_all, 8522 displayAll: config.display_zero, 8523 isPopupEnabled: result.data.storeConfig?.amshopby_brand_general_brands_brands_popup, 8524 label: result.data.storeConfig?.amshopby_brand_general_menu_item_label, 8525 isTopMenuItemEnabled: result.data.storeConfig?.amshopby_brand_general_topmenu_enabled, 8526 brandsPage: result.data.storeConfig?.amshopby_brand_general_brands_page, 8527 } 8528 }, 8529 8530 /** 8531 * Send request to graphql 8532 * 8533 * @returns {Promise<any>} 8534 */ 8535 async sendRequest() { 8536 if (this.isMobile) { 8537 return false;
8538 } 8539 8540 this.loading = true; 8541 8542 try { 8543 if (cached) { 8544 this.loading = false; 8545 return false; 8546 } 8547 8548 const response = await fetch('https://www.purdys.com/graphql', { 8549 method: 'POST', 8550 headers: { 8551 'Content-Type': 'application/json', 8552 'Store': 'default' 8553 }, 8554 credentials: 'include', 8555 body: JSON.stringify({query: this.getQuery(), variables: {}}) 8556 }); 8557 const result = await response.json(); 8558 8559 if (result?.errors) { 8560 this.initErrorMessages(result.errors); 8561 return; 8562 } 8563 8564 const data = result.data?.ambrandlist; 8565 const mapper = this.settingsMapper(result); 8566 8567 // Update component settings 8568 Object.keys(mapper).forEach(config => { 8569 this[config] = mapper[config]; 8570 }); 8571 8572 if (!this.filterDisplayAll) { 8573 letters = data.all_letters.split(','); 8574 } 8575 8576 const items = data.items.map(brand => { 8577 brand.image = brand.img 8578 ? this.getMediaUrl(brand.img) : (brand.image ? this.getMediaUrl(brand.image) : null); 8579 return brand; 8580 }); 8581 8582 this.letters = letters.map(letter => ({ 8583 letter, 8584 brands: this.getLetterItems(items, letter), 8585 })); 8586 8587 this.items = this.letters.filter(row => row.brands?.length > 0); 8588 8589 cached = true; 8590 } catch (error) { 8591 displayError(error); 8592 } finally { 8593 this.loading = false; 8594 } 8595 }, 8596 8597 /** 8598 * Get brands by letter 8599 * 8600 * @param items 8601 * @param letter 8602 * @returns {*} 8603 */ 8604 getLetterItems(items, letter) { 8605 return items.filter(row => row.letter === letter); 8606 }, 8607 8608 convertHtml(text) { 8609 let textField = document.createElement("textarea"); 8610 textField.innerHTML = text; 8611 return textField.value; 8612 } 8613 } 8614 } 8615</script>
8615 8616</div></footer>
8617<script> 8618 function dataServicesShoppingCartContextData() { 8619 return { 8620 initContext() { 8621 const cartData = this.$event.detail.data.cart; 8622 let contextCartItems = []; 8623 8624 if (cartData && cartData.items) { 8625 cartData.items.forEach(function (item) { 8626 contextCartItems.push({ 8627 id: item.item_id, 8628 formattedPrice: parseFloat(item.product_price_value ? item.product_price_value : 0), 8629 quantity: item.qty, 8630 product: { 8631 productType: item.product_type, 8632 productId: item.product_id, 8633 name: item.product_name, 8634 sku: item.product_sku, 8635 mainImageUrl: item.product_image.src 8636 }, 8637 prices: { 8638 price: { 8639 value: parseFloat(item.product_price_value ? item.product_price_value : 0) 8640 } 8641 } 8642 }); 8643 }); 8644 8645 magentoStorefrontEvents.context.setShoppingCart({ 8646 id: cartData.dsCartId, 8647 totalQuantity: cartData.summary_count, 8648 prices: { 8649 subtotalExcludingTax: { 8650 value: parseFloat(cartData.subtotalAmountExclTax ? cartData.subtotalAmountExclTax : 0) 8651 }, 8652 subtotalIncludingTax: { 8653 value: parseFloat(cartData.subtotalAmount ? cartData.subtotalAmount : 0) 8654 } 8655 }, 8656 items: contextCartItems, 8657 possibleOnepageCheckout: cartData.possible_onepage_checkout, 8658 giftMessageSelected : false, 8659 giftWrappingSelected : false 8660 }); 8661 } 8662 8663 window.dispatchEvent(new Event("shoppingCartContextSet")); 8664 } 8665 } 8666 } 8667 8668 window.addEventListener('alpine:init', () => Alpine.data('dataServicesShoppingCartContextData', dataServicesShoppingCartContextData), {once: true}); 8669</script>
8669 8670 8671<div x-data="dataServicesShoppingCartContextData" 8672 @private-content-loaded.window="initContext"></div> 8673
8674<script> 8675 function amBannerInjectorResolver () { 8676 const injectors = Object.values({"products.among.injector":{"position":14,"name":"amBannerAmongCategoryInjector"},"products.belowcart.injector":{"position":15,"name":"amBannerBelowCartInjector"}}); 8677 8678 return { 8679 amBannerAmongCategoryInjector: function (sectionId, injectorParams) { 8680 const container = document.querySelector('[data-role="amasty-banner-container"][data-position="' + sectionId + '"]'); 8681 8682 Object.keys(injectorParams.banners).map((id) => { 8683 const params = injectorParams.banners[id]; 8684 const element = container.querySelector('[data-banner-id="' + id + '"]'); 8685 insertBannerAmongCategoryByConfig({ 8686 containerSelector: injectorParams.containerSelector, 8687 itemSelector: injectorParams.itemSelector, 8688 afterProductRow: params.afterProductRow, 8689 afterProductNum: params.afterProductNum, 8690 span: params.width 8691 }, element); 8692 }); 8693 8694 function insertBannerAmongCategoryByConfig (injectorConfig, element) { 8695 const options = { 8696 injectorConfig: {}, 8697 container: null, 8698 after: null, 8699 wrapper: null, 8700 initialized: false, 8701 gridSizes: ['sm', 'xl'], 8702 bannerWidthClass: 'col-span', 8703 ...injectorConfig 8704 } 8705 8706 insertBanner(element); 8707 8708 function insertBanner(element) { 8709 const initialized = setOptions(); 8710 8711 if (!initialized) { 8712 return; 8713 } 8714 8715 element.classList.add('ambanners-injected-banner'); 8716 const wrapper = options.wrapper.cloneNode(true); 8717 8718 wrapper.classList.remove('card', 'card-interactive'); 8719 addStylesForDifferentSizes(wrapper); 8720 8721 insertWrapper(wrapper); 8722 wrapper.append(element); 8723 } 8724 8725 function setOptions () { 8726 const initialized = document.querySelector(options.containerSelector) !== null; 8727 8728 if (!initialized) { 8729 console.warn( 8730 'Failed to initialize banner: Can\'t find an element with selector "' + options.containerSelector + '"' 8731 ); 8732 8733 return; 8734 } 8735 8736 options.container = document.querySelector(options.containerSelector); 8737 8738 setLineToInject(); 8739 8740 if (!options.after) { 8741 return; 8742 } 8743 8744 setWrapper(); 8745 8746 return true; 8747 } 8748 8749 function setLineToInject() { 8750 if (options.afterProductNum === -1) { 8751 setFirstLineToInject(); 8752 } else { 8753 setLineByPositionToInject(); 8754 } 8755 } 8756 8757 function setWrapper() { 8758 if (options.after) { 8759 options.wrapper = options.after.cloneNode(); 8760 } 8761 } 8762 8763 function setFirstLineToInject() { 8764 options.after = document.querySelector(options.itemSelector); 8765 } 8766 8767 function setLineByPositionToInject() { 8768 // calc banner insert offset 8769 const offset = calculateOffsetBasedOnPreviousBanners(); 8770 8771 options.after = document.querySelectorAll(options.itemSelector)[options.afterProductNum - offset]; 8772 } 8773 8774 function calculateOffsetBasedOnPreviousBanners () { 8775 return [...document.querySelectorAll(`${options.itemSelector}`)] 8776 ?.reduce((accumulator, amInjectedBannerWrapper) => { 8777 const widthAttributeValue = Number(amInjectedBannerWrapper?.getAttribute('am-banner-span')) 8778 8779 if (widthAttributeValue > 1) { 8780 accumulator += (widthAttributeValue - 1); 8781 } 8782 8783 return accumulator; 8784 }, 0); 8785 } 8786 8787 function insertWrapper(insert) { 8788 if (options.after) { 8789 insertWrapperAfterFoundProduct(insert); 8790 } else if (options.container) { 8791 insertWrapperInProductListContainer(insert); 8792 } 8793 } 8794 8795 function insertWrapperAfterFoundProduct(insert) { 8796 if (options.afterProductNum === -1) { 8797 options.after.parentNode.insertBefore(insert, options.after); 8798 } else { 8799 options.after.parentNode.insertBefore(insert, options.after.nextSibling); 8800 } 8801 } 8802 8803 function insertWrapperInProductListContainer(insert) { 8804 if (parseInt(options.afterProductRow) > 1) {
8805 options.container.innerHTML += insert.innerHTML; 8806 } else { 8807 options.container.innerHTML = insert.innerHTML + options.container.innerHTML; 8808 } 8809 } 8810 8811 function addStylesForDifferentSizes (wrapper) { 8812 options.gridSizes?.forEach((size, index) => { 8813 const widthValue = options.span > index + 1 ? index + 2 : options.span; 8814 wrapper?.classList.add(`${size}:${options.bannerWidthClass}-${widthValue}`); 8815 wrapper?.setAttribute('am-banner-span', widthValue); 8816 }); 8817 } 8818 } 8819 }, 8820 8821 amBannerBelowCartInjector: function (sectionId) { 8822 const belowProductAddToCartBanner = document.querySelector('.ambanners.ambanner-' + sectionId); 8823 belowProductAddToCartBanner.style.display = 'block'; 8824 document.querySelectorAll('.product-item')?.forEach(product => { 8825 product?.append(belowProductAddToCartBanner.cloneNode(true)); 8826 }); 8827 belowProductAddToCartBanner.style.display = 'none'; 8828 }, 8829 8830 resolve: function (position, injectorParams) { 8831 const injector = injectors.find((injector) => { 8832 return injector.position === Number(position); 8833 }); 8834 8835 return this[injector?.name]?.(position, injectorParams); 8836 } 8837 } 8838 } 8839</script>
8839 8840 8841
8842<script> 8843 (function () { 8844 'use strict'; 8845 8846 /** 8847 * Config to determine which element is responsible for the form submission 8848 * 8849 * @type {[{elementSelector: undefined|string, submitAttribute: string}]} 8850 */ 8851 const formWrapperConfig = [{"elementSelector":null,"submitAttribute":"submit"},{"elementSelector":"[type=\"submit\"]","submitAttribute":"click"}]; 8852 8853 /** 8854 * Replace protected forms submit with amRecaptchaModel.execute($event) method. 8855 * Call original submit on amrecaptcha-callback event after successful captcha validation. 8856 * 8857 * @return {void} 8858 */ 8859 const wrapFormSubmit = function () { 8860 document.addEventListener("DOMContentLoaded", function () { 8861 const formsSelector = window.amRecaptchaModel?.formsToProtect || ''; 8862 if (formsSelector.length === 0) { 8863 return; 8864 } 8865 8866 document.querySelectorAll(formsSelector)?.forEach(formElement => { 8867 if (!formElement) { 8868 return; 8869 } 8870 8871 const { elementToWrap, attributeNameToWrap } = resolveElementWrapping(formElement); 8872 if (!elementToWrap || !attributeNameToWrap) { 8873 return; 8874 } 8875 8876 setElementAttributes(formElement, elementToWrap, attributeNameToWrap); 8877 }); 8878 }); 8879 }; 8880 8881 /** 8882 * Find submit event and DOM element that has this event using formWrapperConfig. 8883 * 8884 * @param {HTMLElement} formElement 8885 * @return {{elementToWrap: undefined|string, attributeNameToWrap: undefined|string}} 8886 */ 8887 const resolveElementWrapping = function (formElement) { 8888 let result = { 8889 elementToWrap: undefined, 8890 attributeNameToWrap: undefined 8891 }; 8892 8893 formWrapperConfig.forEach(({ elementSelector, submitAttribute }) => { 8894 const elementToWrap = !!elementSelector ? formElement.querySelector(elementSelector) : formElement; 8895 if (!elementToWrap) { 8896 return; 8897 } 8898 8899 const attributeNameToWrap = elementToWrap.getAttributeNames().find( 8900 (elementAttribute) => { 8901 /** @type {string[]} */ 8902 const submitAttributeVariations = ['@', 'x-on:'].map(variation => variation + submitAttribute); 8903 8904 return submitAttributeVariations.some( 8905 (submitAttributeVariation) => elementAttribute.startsWith(submitAttributeVariation) 8906 ) 8907 } 8908 ); 8909 if (!attributeNameToWrap) { 8910 return; 8911 } 8912 8913 result.elementToWrap = elementToWrap; 8914 result.attributeNameToWrap = attributeNameToWrap; 8915 }); 8916 8917 return result; 8918 } 8919 8920 /** 8921 * Set attribute to wrap original submit, add eventListeners for captcha callbacks 8922 * 8923 * @param {HTMLElement} formElement 8924 * @param {HTMLElement} elementToWrap 8925 * @param {string} attributeNameToWrap 8926 */ 8927 const setElementAttributes = function (formElement, elementToWrap, attributeNameToWrap) { 8928 const originalSubmit = elementToWrap.getAttribute(attributeNameToWrap); 8929 elementToWrap.removeAttribute(attributeNameToWrap); 8930 8931 /** 8932 * To properly set submit attribute - we need to replace '@' with 'x-on:' 8933 * and add preventDefault behaviour 8934 */ 8935 elementToWrap.setAttribute( 8936 attributeNameToWrap.replace(/^@/, 'x-on:').replace(/.prevent$/, '') + '.prevent', 8937 'amRecaptchaModelExecute' 8938 ); 8939 elementToWrap.setAttribute( 8940 'x-on:amrecaptcha-executable-callback', 8941 originalSubmit 8942 ); 8943 } 8944 8945 const setAmRecaptchaModelExecute = function () { 8946 const formsSelector = window.amRecaptchaModel?.formsToProtect || ''; 8947 8948 if (formsSelector.length === 0) { 8949 return; 8950 } 8951 8952 document.querySelectorAll(formsSelector).forEach(formElement => { 8953 const { elementToWrap, attributeNameToWrap } = resolveElementWrapping(formElement); 8954 if (!elementToWrap || !attributeNameToWrap) { 8955 return; 8956 } 8957 8958 const formComponent = getFormComponent(elementToWrap), 8959 origFormComponent = window[formComponent]; 8960 8961 window[formComponent] = function () { 8962 const instance = origFormComponent.apply(this, arguments), 8963 reviewComponent = document.querySelector( 8964 '[x-data="initReviewForm()"], [x-data="initReviewForm"]' 8965 ); 8966 8967 if (reviewComponent?.getAttribute('x-data') === formComponent) { 8968 instance.init = function () { 8969 origFormComponent.apply(this, arguments).init; 8970 this.$watch( 8971 'errorMessages', 8972 errorMessages => { 8973 errorMessages?.length > 0 8974 && window.dispatchEvent(new CustomEvent("reload-customer-section-data")); 8975 } 8976 ) 8977 } 8978 } 8979 8980 instance.amRecaptchaModelExecute = function ($event) { 8981 window.amRecaptchaModel.execute($event); 8982 } 8983 8984 instance.amRecaptchaModelRecaptchaCallback = function ($event, $el) { 8985 $el ??= $event.detail.form; 8986 window.amRecaptchaModel.recaptchaCallback($event, $el); 8987 } 8988 8989 instance.amRecaptchaModelExpiredCallback = function ($event, $el) { 8990 $el ??= $event.detail.form; 8991 window.amRecaptchaModel.expiredCallback($event, $el); 8992 } 8993 8994 return instance; 8995 } 8996 }); 8997 } 8998 8999 /** 9000 * @param {HTMLElement} elementToWrap 9001 * @return {string} 9002 */ 9003 const getFormComponent = function (elementToWrap) { 9004 let formComponent = elementToWrap.hasAttribute('x-data') 9005 ? elementToWrap.getAttribute('x-data') 9006 : elementToWrap.closest('[x-data]').getAttribute('x-data'); 9007 const match = formComponent.match(/\b([a-zA-Z0-9_$]+)\s*\(\s*\)/); 9008 9009 if (match && match[1]) { 9010 return match[1]; 9011 } 9012 9013 return formComponent.replace(/\(\)$/g, ''); 9014 } 9015 9016 setAmRecaptchaModelExecute(); 9017 wrapFormSubmit(); 9018 })(window.amRecaptchaModel = window.amRecaptchaModel || {}); 9019</script>
9019 9020<input type="hidden" name="wp_ga4_server_side_view_item_list" id="wp_ga4_server_side_view_item_list" value="2be88ca4242c76e8253ac62474851065032d6833"> 9021
9021<script> 9022 window.addEventListener('load', function() { 9023 window.wpGA4ServerSide.pushViewItemList('2be88ca4242c76e8253ac62474851065032d6833'); 9024 }); 9025 </script>
vendor: 1 bytes, line 9025
9025
9026<script> 9027 9028/** 9029 * Restructure slide links to only apply to buttons 9030 */ 9031function restructureSlideLinks() { 9032 9033 // Find all slide wrappers that are inside links, but only within home-3image-cta-block 9034 const slideWrappers = document.querySelectorAll('.home-3image-cta-block .pagebuilder-slide-wrapper'); 9035 9036 slideWrappers.forEach(function(slideWrapper) { 9037 const parentLink = slideWrapper.parentElement; 9038 9039 if (parentLink && parentLink.tagName === 'A' && parentLink.getAttribute('data-element') === 'link') { 9040 9041 // Get link attributes 9042 const href = parentLink.getAttribute('href'); 9043 const target = parentLink.getAttribute('target') || ''; 9044 const linkType = parentLink.getAttribute('data-link-type') || ''; 9045 const title = parentLink.getAttribute('title') || ''; 9046 9047 // Find the button inside the slide 9048 const button = slideWrapper.querySelector('.pagebuilder-slide-button'); 9049 9050 if (button && href) { 9051 9052 // Create new link element for the button 9053 const newLink = document.createElement('a'); 9054 newLink.setAttribute('href', href); 9055 if (target) newLink.setAttribute('target', target); 9056 if (linkType) newLink.setAttribute('data-link-type', linkType); 9057 if (title) newLink.setAttribute('title', title); 9058 newLink.setAttribute('data-element', 'link'); 9059 9060 // Insert slide wrapper after parent link 9061 parentLink.parentNode.insertBefore(slideWrapper, parentLink.nextSibling); 9062 9063 // Remove the now-empty parent link 9064 parentLink.remove(); 9065 9066 // Wrap the button in the new link 9067 button.parentNode.insertBefore(newLink, button); 9068 newLink.appendChild(button); 9069 9070 } 9071 } 9072 }); 9073} 9074 9075/** 9076 * Initialize on page load and when content is updated 9077 */ 9078function init() { 9079 // Run immediately 9080 restructureSlideLinks(); 9081 9082 // Run on DOM ready 9083 if (document.readyState === 'loading') { 9084 document.addEventListener('DOMContentLoaded', function() { 9085 setTimeout(restructureSlideLinks, 100); 9086 }); 9087 } else { 9088 setTimeout(restructureSlideLinks, 100); 9089 } 9090 9091 // Run on window load 9092 window.addEventListener('load', function() { 9093 setTimeout(restructureSlideLinks, 500); 9094 }); 9095 9096 // Run when content is updated (for dynamic content) 9097 document.addEventListener('contentUpdated', function() { 9098 setTimeout(restructureSlideLinks, 100); 9099 }); 9100} 9101 9102init(); 9103</script>
9103 9104
9105<script src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Purdys_CustomWeltPixelEvents/js/custom-weltpixel-utils.js"></script>
9105 9106
9107<script> 9108 (() => { 9109 function src_default(Alpine) { 9110 Alpine.directive("intersect", Alpine.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => { 9111 let evaluate = evaluateLater(expression); 9112 let options = { 9113 rootMargin: getRootMargin(modifiers), 9114 threshold: getThreshold(modifiers) 9115 }; 9116 let observer = new IntersectionObserver((entries) => { 9117 entries.forEach((entry) => { 9118 if (entry.isIntersecting === (value === "leave")) 9119 return; 9120 evaluate(); 9121 modifiers.includes("once") && observer.disconnect(); 9122 }); 9123 }, options); 9124 observer.observe(el); 9125 cleanup(() => { 9126 observer.disconnect(); 9127 }); 9128 })); 9129 } 9130 function getThreshold(modifiers) { 9131 if (modifiers.includes("full")) 9132 return 0.99; 9133 if (modifiers.includes("half")) 9134 return 0.5; 9135 if (!modifiers.includes("threshold")) 9136 return 0; 9137 let threshold = modifiers[modifiers.indexOf("threshold") + 1]; 9138 if (threshold === "100") 9139 return 1; 9140 if (threshold === "0") 9141 return 0; 9142 return Number(`.${threshold}`); 9143 } 9144 function getLengthValue(rawValue) { 9145 let match = rawValue.match(/^(-?[0-9]+)(px|%)?$/); 9146 return match ? match[1] + (match[2] || "px") : void 0; 9147 } 9148 function getRootMargin(modifiers) { 9149 const key = "margin"; 9150 const fallback = "0px 0px 0px 0px"; 9151 const index = modifiers.indexOf(key); 9152 if (index === -1) 9153 return fallback; 9154 let values = []; 9155 for (let i = 1; i < 5; i++) { 9156 values.push(getLengthValue(modifiers[index + i] || "")); 9157 } 9158 values = values.filter((v) => v !== void 0); 9159 return values.length ? values.join(" ").trim() : fallback; 9160 } 9161 9162 document.addEventListener("alpine:init", () => { 9163 window.Alpine.plugin(src_default); 9164 }); 9165 })(); 9166</script>
vendor: 1 bytes, line 9166
9166
vendor: 1,455 bytes, lines 9167-9169
9167<script> 9168(()=>{function g(n){n.directive("collapse",e),e.inline=(t,{modifiers:i})=>{i.includes("min")&&(t._x_doShow=()=>{},t._x_doHide=()=>{})};function e(t,{modifiers:i}){let r=l(i,"duration",250)/1e3,u=l(i,"min",0),h=!i.includes("min");t._x_isShown||(t.style.height=`${u}px`),!t._x_isShown&&h&&(t.hidden=!0),t._x_isShown||(t.style.overflow="hidden");let c=(d,s)=>{let o=n.setStyles(d,s);return s.height?()=>{}:o},f={transitionProperty:"height",transitionDuration:`${r}s`,transitionTimingFunction:"cubic-bezier(0.4, 0.0, 0.2, 1)"};t._x_transition={in(d=()=>{},s=()=>{}){h&&(t.hidden=!1),h&&(t.style.display=null);let o=t.getBoundingClientRect().height;t.style.height="auto";let a=t.getBoundingClientRect().height;o===a&&(o=u),n.transition(t,n.setStyles,{during:f,start:{height:o+"px"},end:{height:a+"px"}},()=>t._x_isShown=!0,()=>{t.getBoundingClientRect().height==a&&(t.style.overflow=null)})},out(d=()=>{},s=()=>{}){let o=t.getBoundingClientRect().height;n.transition(t,c,{during:f,start:{height:o+"px"},end:{height:u+"px"}},()=>t.style.overflow="hidden",()=>{t._x_isShown=!1,t.style.height==`${u}px`&&h&&(t.style.display="none",t.hidden=!0)})}}}}function l(n,e,t){if(n.indexOf(e)===-1)return t;let i=n[n.indexOf(e)+1];if(!i)return t;if(e==="duration"){let r=i.match(/([0-9]+)ms/);if(r)return r[1]}if(e==="min"){let r=i.match(/([0-9]+)px/);if(r)return r[1]}return i}document.addEventListener("alpine:init",()=>{window.Alpine.plugin(g)});})(); 9169</script>
vendor: 1 bytes, line 9169
9169
9170<script> 9171 for (const [selector, deferUntil] of Object.entries({".product-slider section[x-data]":"intersect",".product-info [x-data]":"intersect","#filters-content [x-data]":"intersect","#review_form":"intersect","section[x-data^=initRecentlyViewedProductsComponent]":"intersect","div[x-data^=initBundleOptions]":"intersect","#product_addtocart_form [x-data]":"intersect","#notice-cookie-block":"idle"})) { 9172 document.querySelectorAll(selector).forEach(el => el.setAttribute('x-defer', `${deferUntil}`)); 9173 } 9174</script>
vendor: 1 bytes, line 9174
9174
9175<script> 9176 (function () { 9177 "use strict"; 9178 9179 const hasAlpine = new Promise(resolve => { 9180 window.addEventListener('alpine:initialized', resolve, {once: true, passive: true}); 9181 }); 9182 9183 const hasInteract = new Promise(resolve => { 9184 (events => { 9185 const onInteract = () => { 9186 resolve(); 9187 events.forEach(type => window.removeEventListener(type, onInteract)); 9188 } 9189 events.forEach(type => window.addEventListener(type, onInteract, {once: true, passive: true})) 9190 })(['touchstart', 'mouseover', 'wheel', 'scroll', 'keydown']) 9191 }); 9192 9193 const onIntersect = (el) => { 9194 return new Promise(resolve => { 9195 const observer = new IntersectionObserver(entries => { 9196 for (const entry of entries) { 9197 if (entry.isIntersecting) { 9198 observer.disconnect() 9199 resolve(); 9200 } 9201 } 9202 }, {}); 9203 observer.observe(el); 9204 }); 9205 } 9206 9207 function runComponent(el) { 9208 hasAlpine.then(() => { 9209 el.removeAttribute('x-ignore'); 9210 queueMicrotask(() => Alpine.initTree(el)); 9211 }); 9212 } 9213 9214 function initDeferredComponents() { 9215 document.querySelectorAll('[x-data][x-defer]').forEach(el => { 9216 el.setAttribute('x-ignore', ''); 9217 const deferUntil = (el.getAttribute('x-defer') || '').trim(); 9218 switch (deferUntil) { 9219 case 'interact': 9220 hasInteract.then(() => runComponent(el)); 9221 break; 9222 case 'intersect': 9223 onIntersect(el).then(() => runComponent(el)) 9224 break; 9225 case 'idle': 9226 window.requestIdleCallback 9227 ? window.requestIdleCallback(() => runComponent(el), {timeout: 4000}) 9228 : setTimeout(() => runComponent(el), 4000); 9229 break; 9230 case 'eager': 9231 runComponent(el); 9232 break; 9233 default: 9234 if (deferUntil.startsWith('event:') && deferUntil.length > 6) { 9235 window.addEventListener(deferUntil.substring(6), () => runComponent(el), {once: true, passive: true}); 9236 } 9237 } 9238 }); 9239 } 9240 9241 window.addEventListener('alpine:init', initDeferredComponents, {once: true, passive: true}); 9242 })() 9243</script>
vendor: 1 bytes, line 9243
9243
9244<script> 9245 (() => { 9246 // src/dialog.js 9247 function dialog_default(Alpine) { 9248 Alpine.directive("htmldialog", fylgjaAlpineDialog); 9249 function fylgjaAlpineDialog(el, { expression, modifiers }, { evaluateLater, cleanup }) { 9250 const evaluate = expression.length ? evaluateLater(expression) : () => { 9251 }; 9252 const lockPageScroll = modifiers.includes("noscroll"); 9253 el.style.display = null; 9254 el.hidden = false; 9255 el.style.length === 0 && el.removeAttribute("style"); 9256 function scrollLock(use = true) { 9257 document.body.style.overflow = use ? "hidden" : ""; 9258 } 9259 function escapeDialog(event) { 9260 if (event.key !== "Escape") 9261 return; 9262 event.preventDefault(); 9263 evaluate(); 9264 } 9265 function backdropDialog(event) { 9266 const rect = el.getBoundingClientRect(); 9267 const isInDialog = rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width; 9268 if (isInDialog) 9269 return; 9270 evaluate(); 9271 } 9272 el._x_doShow = () => { 9273 if (el.hasAttribute("open")) 9274 return; 9275 el.showModal(); 9276 document.addEventListener("keydown", escapeDialog); 9277 el.addEventListener("click", backdropDialog); 9278 scrollLock(lockPageScroll); 9279 }; 9280 el._x_doHide = () => { 9281 if (!el.hasAttribute("open")) 9282 return; 9283 el.close(); 9284 document.removeEventListener("keydown", escapeDialog); 9285 el.removeEventListener("click", backdropDialog); 9286 scrollLock(false); 9287 }; 9288 cleanup(() => { 9289 document.removeEventListener("keydown", escapeDialog); 9290 el.removeEventListener("click", backdropDialog); 9291 scrollLock(false); 9292 }); 9293 } 9294 } 9295 9296 // src/cdn.js 9297 document.addEventListener("alpine:init", () => { 9298 window.Alpine.plugin(dialog_default); 9299 }); 9300 })(); 9301</script>
9301 9302
9303<script type="module" 9304 src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/Hyva_Theme/js/alpine3.min.js" 9305 defer 9306 crossorigin 9307></script>
vendor: 1 bytes, line 9307
9307
9308<script> 9309 'use strict'; 9310 function dispatchMessages(messages, hideAfter) { 9311 const messagesEvent = new CustomEvent("messages-loaded", { 9312 detail: { 9313 messages: messages, 9314 hideAfter: hideAfter 9315 } 9316 }); 9317 window.dispatchEvent(messagesEvent); 9318 } 9319 9320 if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) { 9321 console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script"); 9322 } 9323 9324 (function( hyva, undefined ) { 9325 9326 hyva.initFormKey = () => { 9327 const inputSelector = 'input[name="form_key"]', 9328 formKey = hyva.getFormKey(); 9329 9330 Array.from(document.querySelectorAll(inputSelector)).map(function (input) { 9331 input.value = formKey 9332 }); 9333 } 9334 9335 hyva.initMessages = () => { 9336 try { 9337 const messages = hyva.getCookie('mage-messages'); 9338 window.mageMessages = messages ? JSON.parse(decodeURIComponent(messages).replace(/\+/g, ' ')) : []; 9339 9340 dispatchMessages(window.mageMessages); 9341 9342 // empty `mage-messages` cookie 9343 const skipSetDomain = true; 9344 hyva.setCookie('mage-messages','', -1, skipSetDomain); 9345 9346 } catch (error) { 9347 console.warn('Error parsing Cookie Messages:', error); 9348 } 9349 } 9350 9351 window.addEventListener('DOMContentLoaded', hyva.initFormKey); 9352 hyva.alpineInitialized(hyva.initMessages) 9353 9354 }( window.hyva = window.hyva || {} )); 9355</script>
vendor: 1 bytes, line 9355
9355
9356<script> 9357 'use strict'; 9358 { 9359 const private_content_key = 'mage-cache-storage'; 9360 const private_content_expire_key = 'mage-cache-timeout'; 9361 const private_content_version_key = 'private_content_version'; 9362 const section_data_ids_key = 'section_data_ids'; 9363 const mage_cache_session_id_key = 'mage-cache-sessid'; 9364 const last_visited_store_key = 'last_visited_store'; 9365 9366 const ttl = 604800; 9367 9368 if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) { 9369 console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script"); 9370 } 9371 9372 function loadSectionData () { 9373 const browserStorage = hyva.getBrowserStorage(); 9374 if (!browserStorage) { 9375 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 9376 [{ 9377 type: "warning", 9378 text: "Please enable LocalStorage in your browser." 9379 }] 9380 ); 9381 return; 9382 } 9383 try { 9384 let isInvalid = false; 9385 9386 if (hyva.getCookie(last_visited_store_key) !== CURRENT_STORE_CODE) { 9387 isInvalid = true; 9388 } 9389 hyva.setCookie(last_visited_store_key, CURRENT_STORE_CODE, false, false); 9390 9391 if (!hyva.getCookie(mage_cache_session_id_key)) { 9392 isInvalid = true; 9393 browserStorage.removeItem(private_content_key); 9394 const skipSetDomain = true; 9395 const days = false; 9396 hyva.setCookie(mage_cache_session_id_key, true, days, skipSetDomain) 9397 } 9398 9399 const cookieVersion = hyva.getCookie(private_content_version_key); 9400 const storageVersion = browserStorage.getItem(private_content_version_key); 9401 9402 if (cookieVersion && !storageVersion || cookieVersion !== storageVersion) { 9403 isInvalid = true; 9404 } 9405 9406 const privateContentExpires = browserStorage.getItem(private_content_expire_key); 9407 if (privateContentExpires && new Date(privateContentExpires) < new Date()) { 9408 browserStorage.removeItem(private_content_key); 9409 } 9410 9411 if (isInvalid && cookieVersion) { 9412 fetchPrivateContent([]); 9413 } else if (cookieVersion && storageVersion && cookieVersion === storageVersion) { 9414 const privateContent = JSON.parse(browserStorage.getItem(private_content_key)); 9415 if ( 9416 privateContent && 9417 privateContentExpires && 9418 privateContent.cart && 9419 privateContent.customer 9420 ) { 9421 dispatchPrivateContent(privateContent); 9422 } else { 9423 fetchPrivateContent([]); 9424 } 9425 } else { 9426 if (document.getElementById('default-section-data')) { 9427 const privateContent = JSON.parse(document.getElementById('default-section-data').innerText.trim()); 9428 dispatchPrivateContent(privateContent); 9429 } else { 9430 dispatchPrivateContent({}); 9431 } 9432 } 9433 9434 } catch (error) { 9435 console.warn('Error retrieving Private Content:', error); 9436 } 9437 } 9438 9439 hyva.alpineInitialized(loadSectionData) 9440 window.addEventListener('reload-customer-section-data', loadSectionData); 9441 9442 window.processSectionDataBeforeDispatch = window.processSectionDataBeforeDispatch || (data => data); 9443 9444 function dispatchPrivateContent(data) { 9445 9446 const privateContentEvent = new CustomEvent("private-content-loaded", { 9447 detail: { 9448 data: window.processSectionDataBeforeDispatch(data) 9449 } 9450 }); 9451 window.dispatchEvent(privateContentEvent); 9452 } 9453 9454 function fetchPrivateContent(sections) { 9455 fetch(`${BASE_URL}customer/section/load/?sections=${encodeURIComponent(sections.join(','))}`, { 9456 method: 'GET', 9457 headers: { 9458 'Content-Type': 'application/json', 9459 'X-Requested-With': 'XMLHttpRequest' 9460 } 9461 }) 9462 .then(response => response.ok && response.json()) 9463 .then( 9464 data => { 9465 if (data) { 9466 try { 9467 const browserStorage = hyva.getBrowserStorage(); 9468 9469 // merge new data preserving non-invalidated sections 9470 const oldSectionData = JSON.parse(browserStorage.getItem(private_content_key) || '{}') || {}; 9471 9472 if ((! data.cart || ! data.cart.cartId) && oldSectionData['checkout-data']) { 9473 delete oldSectionData['checkout-data']; 9474 } 9475 const newSectionData = Object.assign(oldSectionData, data); 9476 9477 dispatchPrivateContent(newSectionData); 9478 9479 // don't persist messages, they've been dispatched already 9480 if (newSectionData.messages && newSectionData.messages.messages ) { 9481 newSectionData.messages.messages = []; 9482 } 9483 9484 browserStorage.setItem(private_content_key, JSON.stringify(newSectionData)); 9485 9486 const expiresAt = new Date(Date.now() + (ttl * 1000)).toISOString(); 9487 browserStorage.setItem(private_content_expire_key, expiresAt); 9488 9489 const newCookieVersion = hyva.getCookie(private_content_version_key); 9490 browserStorage.setItem(private_content_version_key, newCookieVersion); 9491 9492 // We don't need the section_data_ids in Hyvä, but we store them for compatibility 9493 // with Luma Fallback. Otherwise, not all sections are loaded in Luma Checkout 9494 hyva.setCookie( 9495 section_data_ids_key, 9496 JSON.stringify( 9497 Object.keys(data).reduce((sectionDataIds, sectionKey) => { 9498 sectionDataIds[sectionKey] = data[sectionKey]['data_id']; 9499 return sectionDataIds; 9500 }, {}) 9501 ), 9502 false, 9503 true 9504 ); 9505 } catch (error) { 9506 console.warn("Couldn't store privateContent", error); 9507 } 9508 } 9509 } 9510 ); 9511 } 9512 } 9513</script>
vendor: 1 bytes, line 9513
9513
9514<script> 9515 (() => { 9516 document.addEventListener('submit', event => event.target.action = event.target.action.replace('%25uenc%25', hyva.getUenc())); 9517 })() 9518</script>
vendor: 1 bytes, line 9518
9518
9519<script> 9520 (events => { 9521 const dispatchUserInteractionEvent = () => { 9522 events.forEach(type => window.removeEventListener(type, dispatchUserInteractionEvent)) 9523 window.dispatchEvent(new Event('init-external-scripts')) 9524 }; 9525 events.forEach(type => window.addEventListener(type, dispatchUserInteractionEvent, {once: true, passive: true})) 9526 })(['touchstart', 'mousemove', 'wheel', 'scroll', 'keydown']) 9527</script>
vendor: 1 bytes, line 9527
9527
9528<script> 9529 'use strict'; 9530 9531 (() => { 9532 const initCarousels = (elements) => { 9533 const initSlider = (slider) => { 9534 slider.innerHTML = `<div data-role="swiper-wrapper" class="swiper-wrapper">${slider.innerHTML}</div>`; 9535 slider.parentElement.classList.add('relative'); 9536 slider.insertAdjacentHTML( 9537 'beforeend', 9538 '\u000A\u003Cdiv\u000A\u0020\u0020\u0020\u0020class\u003D\u0022\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020swiper\u002Darrows\u002Dcontainer\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020max\u002Dlg\u0026\u0023x3A\u003Bmb\u002D6\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0022\u000A\u0020\u0020\u0020\u0020\u003E\u000A\u0020\u0020\u0020\u0020\u003Cdiv\u0020class\u003D\u0022swiper\u002Darrows\u002Dcontainer\u002Dwrapper\u0020\u0022\u003E\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003Cdiv\u0020class\u003D\u0022swiper\u002Dbutton\u002Dprev\u0020\u0022\u003E\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003Csvg\u0020xmlns\u003D\u0022http\u003A\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\u0022\u0020fill\u003D\u0022none\u0022\u0020viewBox\u003D\u00220\u00200\u002024\u002024\u0022\u0020stroke\u002Dwidth\u003D\u00222\u0022\u0020stroke\u003D\u0022currentColor\u0022\u0020class\u003D\u0022rtl\u003Arotate\u002D180\u0022\u0020width\u003D\u002220\u0022\u0020height\u003D\u002220\u0022\u0020aria\u002Dhidden\u003D\u0022true\u0022\u003E\u000A\u0020\u0020\u003Cpath\u0020stroke\u002Dlinecap\u003D\u0022round\u0022\u0020stroke\u002Dlinejoin\u003D\u0022round\u0022\u0020d\u003D\u0022M15\u002019l\u002D7\u002D7\u00207\u002D7\u0022\u002F\u003E\u000A\u003C\u002Fsvg\u003E\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003C\u002Fdiv\u003E\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003Cdiv\u0020class\u003D\u0022swiper\u002Dbutton\u002Dnext\u0020\u0022\u003E\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003Csvg\u0020xmlns\u003D\u0022http\u003A\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\u0022\u0020fill\u003D\u0022none\u0022\u0020viewBox\u003D\u00220\u00200\u002024\u002024\u0022\u0020stroke\u002Dwidth\u003D\u00222\u0022\u0020stroke\u003D\u0022currentColor\u0022\u0020class\u003D\u0022rtl\u003Arotate\u002D180\u0022\u0020width\u003D\u002220\u0022\u0020height\u003D\u002220\u0022\u0020aria\u002Dhidden\u003D\u0022true\u0022\u003E\u000A\u0020\u0020\u003Cpath\u0020stroke\u002Dlinecap\u003D\u0022round\u0022\u0020stroke\u002Dlinejoin\u003D\u0022round\u0022\u0020d\u003D\u0022M9\u00205l7\u00207\u002D7\u00207\u0022\u002F\u003E\u000A\u003C\u002Fsvg\u003E\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u003C\u002Fdiv\u003E\u000A\u0020\u0020\u0020\u0020\u003C\u002Fdiv\u003E\u000A\u003C\u002Fdiv\u003E\u000A\u0020\u0020\u0020\u0020\u003Cdiv\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020class\u003D\u0022swiper\u002Dpagination\u000A\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020my\u002D6\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0020\u0022\u000A\u0020\u0020\u0020\u0020\u003E\u003C\u002Fdiv\u003E\u000A'); 9539 slider.classList.add('swiper'); 9540 slider.classList.add('swiper-slider-content'); 9541 9542 const slides = slider.querySelectorAll('[data-content-type="slide"]'); 9543
9544 slides.forEach(slide => { 9545 slide.classList.add('swiper-slide'); 9546 }); 9547 9548 const isAutoplay = slider.dataset.autoplay === 'true'; 9549 const autoplaySpeed = slider.dataset.autoplaySpeed; 9550 const isLoop = slider.dataset.infiniteLoop === 'true'; 9551 9552 const swiperOptions = { 9553 spaceBetween: 0, 9554 loop: isLoop, 9555 speed: 800, 9556 threshold: 20, 9557 autoHeight: true, 9558 navigation: { 9559 nextEl: '.swiper-button-next', 9560 prevEl: '.swiper-button-prev', 9561 }, 9562 pagination: { 9563 el: '.swiper-pagination', 9564 type: 'bullets', 9565 dynamicBullets: true, 9566 dynamicMainBullets: 4, 9567 clickable: true, 9568 }, 9569 mousewheel: { 9570 forceToAxis: true, 9571 thresholdDelta: 10, 9572 }, 9573 autoplay: isAutoplay && autoplaySpeed ? { 9574 delay: autoplaySpeed 9575 } : undefined, 9576 }; 9577 9578 initSwiperSlider(slider, swiperOptions); 9579 } 9580 9581 elements.forEach(element => { 9582 if (element.dataset.contentType === 'slider') { 9583 initSlider(element); 9584 } 9585 }); 9586 }; 9587 9588 9589 document.addEventListener('alpine:init', () => { 9590 const carouselElements = document.querySelectorAll( 9591 `[data-content-type="products"][data-appearance="carousel"], 9592 [data-content-type="slider"],[data-content-type="products"][data-appearance="insider_carousel"]` 9593 ); 9594 9595 if (carouselElements.length > 0) { 9596 initCarousels(carouselElements); 9597 } 9598 }); 9599 })(); 9600</script>
9600<script> 9601 'use strict'; 9602 9603 (() => { 9604 const applyShowOnHover = (element) => { 9605 const showOverlayOnHover = (element) => { 9606 const overlay = element.querySelector('.pagebuilder-overlay'); 9607 const color = overlay.dataset.overlayColor; 9608 9609 element.addEventListener('mouseover', () => { 9610 overlay.style.backgroundColor = color; 9611 }); 9612 element.addEventListener('mouseout', () => { 9613 overlay.style.backgroundColor = 'transparent'; 9614 }); 9615 }; 9616 9617 const showButtonOnHover = (element) => { 9618 const button = element.querySelector('.pagebuilder-banner-button'); 9619 9620 element.addEventListener('mouseover', () => { 9621 button.style.opacity = '1'; 9622 button.style.visibility = 'visible'; 9623 }); 9624 element.addEventListener('mouseout', () => { 9625 button.style.opacity = '0'; 9626 button.style.visibility = 'hidden'; 9627 }); 9628 }; 9629 9630 if (element.dataset.showOverlay === 'hover') { 9631 showOverlayOnHover(element); 9632 } 9633 if (element.dataset.showButton === 'hover') { 9634 showButtonOnHover(element); 9635 } 9636 }; 9637 9638 window.addEventListener('DOMContentLoaded', () => { 9639 document.querySelectorAll('[data-content-type="banner"],[data-content-type="slider"]').forEach(element => { 9640 applyShowOnHover(element); 9641 }); 9642 }); 9643 })(); 9644</script>
vendor: 1 bytes, line 9644
9644
9645<script> 9646 'use strict'; 9647 9648 (function () { 9649 const modals = []; 9650 const excludedFromFocusTrapping = new Set(); 9651 9652 function trapFocusInNextModalWithOverlay() { 9653 for (let idx = modals.length -1; idx >= 0; idx--) { 9654 const nextOnStack = modals[idx]; 9655 const nextDialogElement = nextOnStack.instance.$refs[nextOnStack.name]; 9656 if (! isOverlayDisabled(nextDialogElement)) { 9657 hyva.trapFocus(nextDialogElement); 9658 break; 9659 } 9660 } 9661 } 9662 9663 function focusables(dialogElement) { 9664 const selector = 'button, [href], input, textarea, select, details, [tabindex]:not([tabindex="-1"])'; 9665 return Array.from(dialogElement.querySelectorAll(selector)) 9666 .filter(el => !el.hasAttribute('disabled')); 9667 } 9668 9669 function firstVisible(elements) { 9670 const a = Array.from(elements); 9671 for (let i = 0; i < a.length; i++) { 9672 if (a[i].offsetWidth || a[i].offsetHeight || a[i].getClientRects().length) return a[i]; 9673 } 9674 return null; 9675 } 9676 9677 function isInViewport(element) { 9678 const rect = element && element.getBoundingClientRect(); 9679 return rect && 9680 rect.top >= 0 && 9681 rect.left >= 0 && 9682 rect.right <= window.innerWidth && 9683 rect.bottom <= window.innerHeight; 9684 } 9685 9686 function setFocusAfterTransition(dialogElement, duration) { 9687 const nested = Array.from(dialogElement.querySelectorAll('[role="dialog"]')); 9688 const candidates = Array.from(dialogElement.querySelectorAll('[x-focus-first]')); 9689 next: for (let candidate of candidates) { 9690 for (let child of nested) { 9691 if (child.contains(candidate)) continue next; 9692 } 9693 setTimeout(() => candidate.focus(), 50); 9694 break; 9695 } 9696 window.setTimeout(() => { 9697 const focusElement = firstVisible(dialogElement.querySelectorAll('[x-focus-first]')) || 9698 focusables(dialogElement)[0] || 9699 null; 9700 focusElement && isInViewport(focusElement) && focusElement.focus(); 9701 }, Math.max(1, duration)); 9702 } 9703 9704 function determineTrigger($refs, dialog, trigger) { 9705 if (typeof trigger === 'undefined' && typeof dialog === 'object' && dialog.target instanceof HTMLElement) { 9706 return dialog.target; 9707 } 9708 if (typeof dialog === 'string' && typeof trigger === 'object' && trigger.target instanceof HTMLElement) { 9709 return trigger.target; 9710 } 9711 if (typeof trigger === 'string') { 9712 try { 9713 return $refs[trigger] || document.querySelector(trigger) 9714 } catch (e) {} 9715 } 9716 if (trigger instanceof Element) { 9717 return trigger; 9718 } 9719 9720 return null; 9721 } 9722 9723 function isOverlayDisabled(dialog) { 9724 return dialog && dialog.hasAttribute('x-no-overlay') 9725 } 9726 9727 function areRemainingModalsWithoutOverlay(modals) 9728 { 9729 const overflowDisabled = modals.map(modal => modal.instance.$refs[modal.name]).filter(isOverlayDisabled); 9730 9731 return overflowDisabled.length === modals.length; 9732 } 9733 9734 window.hyva.modal = function(options = {}) { 9735 if (this.$el && this.$el.dataset && this.$el.dataset.options) { 9736 try { 9737 options = Object.assign(options, JSON.parse(this.$el.dataset.options || '{}')); 9738 } catch (e) { 9739 throw new Error('Cannot read the dialog options from the data-options attribute: not valid JSON'); 9740 } 9741 } 9742 9743 const config = Object.assign({ 9744 dialog: 'dialog', duration: 300, transitionEnter: 'transition ease-out duration-300', 9745 transitionEnterStart: 'opacity-0', 9746 transitionEnterEnd: 'opacity-100', 9747 transitionLeave: 'transition ease-in duration-300', 9748 transitionLeaveStart: 'opacity-100', 9749 transitionLeaveEnd: 'opacity-0', 9750 }, options); 9751 let lastHide = 0; 9752 9753 return { 9754 opened: {}, 9755 show(dialog, trigger) { 9756 const focusTargetAfterHide = determineTrigger(this.$refs, dialog, trigger); 9757 const name = typeof dialog === 'string' ? dialog : config.dialog; 9758 const dialogElement = this.$refs[name]; 9759 if (! dialogElement) { 9760 return; 9761 } 9762 const useOverlay = ! dialogElement.hasAttribute('x-no-overlay'); 9763 9764 dialogElement.scrollTop = 0; 9765 9766 if (this.opened[name]) { 9767 return; 9768 } 9769 9770 if (focusTargetAfterHide) { 9771 focusTargetAfterHide.setAttribute('aria-expanded', 'true'); 9772 } 9773 9774 this.opened[name] = true; 9775 useOverlay && this.$nextTick(() => hyva.trapFocus(dialogElement)); 9776 setFocusAfterTransition(dialogElement, config.duration); 9777 9778 const frame = {name, instance: this, focusTarget: focu
9778sTargetAfterHide, time: Date.now()}; 9779 9780 modals.push(frame); 9781 if (useOverlay) { 9782 document.body.classList.add('overflow-hidden'); 9783 } 9784 return new Promise(resolve => frame.resolve = resolve); 9785 }, 9786 cancel() { 9787 this.hide(false); 9788 }, 9789 ok() { 9790 this.hide(true); 9791 }, 9792 hide(value) { 9793 if (Date.now() - lastHide < config.duration) { 9794 return; 9795 } 9796 lastHide = Date.now(); 9797 9798 const modal = modals.pop() || {}; 9799 if (! modal.instance) { 9800 return; 9801 } 9802 9803 const name = modal.name; 9804 this.opened[name] = false; 9805 hyva.releaseFocus(modal.instance.$refs[modal.name]) 9806 trapFocusInNextModalWithOverlay(); 9807 9808 const nextFocusAfterHide = modal.focusTarget; 9809 nextFocusAfterHide && setTimeout(() => { 9810 nextFocusAfterHide.setAttribute('aria-expanded', 'false'); 9811 nextFocusAfterHide.focus() 9812 }, config.duration); 9813 9814 if (modals.length === 0 || areRemainingModalsWithoutOverlay(modals)) { 9815 document.body.classList.remove('overflow-hidden'); 9816 } 9817 9818 modal.resolve(value); 9819 }, 9820 overlay(dialog) { 9821 dialog = this.$el.dataset.dialogRefName || dialog; 9822 const name = typeof dialog === 'string' ? dialog : config.dialog; 9823 return { 9824 ['x-show']() { 9825 return this.opened[name] 9826 }, 9827 ['x-transition:enter']: config.transitionEnter, 9828 ['x-transition:enter-start']: config.transitionEnterStart, 9829 ['x-transition:enter-end']: config.transitionEnterEnd, 9830 ['x-transition:leave']: config.transitionLeave, 9831 ['x-transition:leave-start']: config.transitionLeaveStart, 9832 ['x-transition:leave-end']: config.transitionLeaveEnd, 9833 ['@hyva-modal-show.window'](event) { 9834 event.detail && event.detail.dialog === name && this.show(name, event.detail.focusAfterHide) 9835 } 9836 }; 9837 }, 9838 showDialogOnInit() { 9839 const parent = this.$el.closest('[data-dialog-ref-name]'); 9840 if (parent && parent.dataset.dialogRefName) { 9841 this.$dispatch('hyva-modal-show', {dialog: parent.dataset.dialogRefName}) 9842 } 9843 } 9844 }; 9845 } 9846 9847 window.hyva.modal.peek = () => modals.length > 0 && modals[modals.length -1] 9848 9849 window.hyva.modal.pop = function () { 9850 if (modals.length > 0) { 9851 const modal = modals[modals.length -1]; 9852 modal.instance.hide(); 9853 } 9854 } 9855 9856 window.hyva.modal.excludeSelectorsFromFocusTrap = function (selectors) { 9857 typeof selectors === 'string' || selectors instanceof String 9858 ? excludedFromFocusTrapping.add(selectors) 9859 : selectors.map(selector => excludedFromFocusTrapping.add(selector)); 9860 } 9861 9862 window.hyva.modal.eventListeners = { 9863 keydown: event => { 9864 if (event.key === 'Escape') { 9865 window.hyva.modal.pop(); 9866 } 9867 }, 9868 click: event => { 9869 if (modals.length > 0) { 9870 const modal = modals[modals.length -1]; 9871 const dialog = modal.instance.$refs[modal.name]; 9872 if (modal.time + 50 < Date.now() && // if last click processing is more than 50ms ago 9873 ! isOverlayDisabled(dialog) && // if dialog has overlay 9874 ! dialog.contains(event.target)) { // if click is outside of dialog 9875 modal.instance.hide(); 9876 } 9877 } 9878 } 9879 }; 9880 9881 document.addEventListener('keydown', window.hyva.modal.eventListeners.keydown); 9882 9883 document.addEventListener('click', window.hyva.modal.eventListeners.click); 9884 })(); 9885 window.addEventListener('alpine:init', () => { 9886 Alpine.data('hyvaModal', window.hyva.modal); 9887 }, {once: true}); 9888</script>
vendor: 1 bytes, line 9888
9888
9889<script> 9890 function initConfigurableOptions(productId, optionConfig) { 9891 9892 function findPathParam(key) { 9893 const baseUrl = (BASE_URL.substring(0, 2) === '//' ? 'http:' : '') + BASE_URL; 9894 const baseUrlParts = (new URL(baseUrl)).pathname.replace(/\/$/, '').split('/'); 9895 const pathParts = window.location.pathname.split('/').slice(baseUrlParts.length + 3); 9896 for (let i = 0; i < pathParts.length; i += 2) { 9897 if (pathParts[i] === key && pathParts.length > i) { 9898 return pathParts[i + 1]; 9899 } 9900 } 9901 } 9902 9903 return { 9904 optionConfig, 9905 productId, 9906 itemId: (new URLSearchParams(window.location.search)).get('id') || findPathParam('id'), 9907 allowedAttributeOptions: [], 9908 selectedValues: [], 9909 init() { 9910 this.findAllowedAttributeOptions(); 9911 this.$nextTick(() => { 9912 if (typeof this.optionConfig.defaultValues === 'object') { 9913 for (const [attributeId, value] of Object.entries(this.optionConfig.defaultValues)) { 9914 this.changeOption(attributeId, value + ''); 9915 } 9916 } 9917 this.preselectQuerystringItems(); 9918 this.preselectLocationHashItems(); 9919 }); 9920 }, 9921 productIndex: 0, 9922 productIndexForPrice: 0, 9923 optionIsActive(attributeId, optionId) { 9924 return !!this.getAllowedAttributeOptions(attributeId).find( 9925 option => option.id === optionId 9926 ) 9927 }, 9928 optionIsEnabled(attributeId, optionId) { 9929 for (const productId in this.optionConfig.index) { 9930 if (this.optionConfig.index[productId][attributeId] === optionId) { 9931 return true; 9932 } 9933 } 9934 return false; 9935 }, 9936 findSimpleIndex() { 9937 this.productIndex = this.calculateSimpleIndexForPartialSelection(this.selectedValues); 9938 this.productIndexForPrice = this.findCheapestProductForPartialSelection(this.selectedValues); 9939 }, 9940 calculateSimpleIndexForPartialSelection(selectedValues) { 9941 if (selectedValues.length === 0) return 0; 9942 return this.findProductIdsForPartialSelection(selectedValues)[0]; 9943 }, 9944 calculateSimpleIndexForFullSelection(selectedValues) { 9945 if (! this.isFullSelection(selectedValues)) return false; 9946 9947 const productIndexes = this.optionConfig.index; 9948 return Object.keys(productIndexes).find(productIndex => { 9949 const productCandidateOptions = productIndexes[productIndex]; 9950 9951 for (const productOption in productCandidateOptions) { 9952 if ( 9953 !selectedValues[productOption] || 9954 selectedValues[productOption] !== productCandidateOptions[productOption] 9955 ) { 9956 return false; 9957 } 9958 } 9959 return productIndex; 9960 }); 9961 }, 9962 findAllowedAttributeOptions() { 9963 this.allowedAttributeOptions = this.calculateAllowedAttributeOptions(this.selectedValues); 9964 }, 9965 calculateAllowedAttributeOptions(selectedValues) { 9966 const allAttributes = this.optionConfig.attributes; 9967 const allAttributesSorted = Object.values(allAttributes).sort((a,b) => { 9968 return a.position - b.position 9969 }); 9970 9971 const newAllowedAttributeOptions = []; 9972 9973 allAttributesSorted.forEach(attribute => { 9974 const selectionWithoutAttr = Object.assign({}, this.removeAttrFromSelection(selectedValues, attribute.id)); 9975 const availableIndexes = this.calculateAvailableProductIndexes(selectionWithoutAttr); 9976 newAllowedAttributeOptions[attribute.id] = allAttributes[attribute.id].options.filter(option => { 9977 return !!option.products.find(product => { 9978 return availableIndexes.includes(product); 9979 }) 9980 }); 9981 }); 9982 return newAllowedAttributeOptions; 9983 }, 9984 calculateAvailableProductIndexes(selectedOptions) { 9985 if (Object.keys(selectedOptions).length === 0) { 9986 if (Object.values(this.optionConfig.salable || {}).length) { 9987 return [].concat.apply([], [].concat.apply([], Object.values(this.optionConfig.salable).map(Object.values))).filter((x, i, a) => a.indexOf(x) === i) 9988 } 9989 return Object.keys(this.optionConfig.index); 9990 } 9991 9992 const selectedIds = Object.keys(selectedOptions); 9993 if (Object.values(this.optionConfig.salable || {}).length) { 9994 const selectedOptionIndexes = selectedIds.map(attrId => { 9995 const optionValue = selectedOptions[attrId]; 9996 return this.optionConfig.salable[attrId] && this.optionConfig.salable[attrId][optionValue] || [] 9997 }) 9998 return selectedOptionIndexes.reduce((acc, optionIndexes) => { 9999 return acc.filter(index => optionIndexes.includes(index)); 10000 }); 10001 } else { 10002 const productIndexes = this.optionConfig.index; 10003 return Object.keys(productIndexes).filter(index => { 10004 for (const attrId of selectedIds) { 10005 if (productIndexes[index][attrId] !== `${selectedOptions[attrId]}`) return false 10006 } 10007 return true 10008 }); 10009 } 10010 }, 10011 findAttributeByOptionId(optionId) { 10012 for (const attributeId in this.optionConfig.attributes) { 10013 const attributeOptions = this.optionConfig.attributes[attributeId].options || []; 10014 if (attributeOptions.find(option => option.id === optionId)) { 10015 return attributeId; 10016 } 10017 } 10018 }, 10019 getAllowedAttributeOptions(attributeId) { 10020 return this.allowedAttributeOptions[attributeId] || [] 10021 }, 10022 getAllAttributeOptions(attributeId) { 10023 return ( 10024 this.optionConfig.attributes[attributeId] && 10025 this.optionConfig.attributes[attributeId].options 10026 ) || [] 10027 }, 10028 getProductIdsForOption(option) { 10029 const attributeId = this.findAttributeByOptionId(option.id); 10030 const allOptions = this.optionConfig.attributes[attributeId]; 10031 const opt = (allOptions && allOptions.options || []).find(o => o.id === option.id); 10032 return opt && opt.products 10033 ? opt.products 10034 : []; 10035 }, 10036 findProductIdsForPartialSelection(optionSelection) { 10037 const candidateProducts = Object.values(optionSelection).reduce((candidates, optionId) => { 10038 const newCandidates = this.getProductIdsForOption({id: optionId}); 10039 return candidates === null 10040 ? newCandidates 10041 : candidates.filter(productId => newCandidates.includes(productId)); 10042 }, null); 10043 return candidateProducts || []; 10044 },
10045 findCheapestProductForPartialSelection(optionSelection) { 10046 const candidateProducts = this.findProductIdsForPartialSelection(optionSelection); 10047 return candidateProducts.reduce((cheapest, simpleIdx) => { 10048 // in the first iteration we start with simpleIdx as the currently cheapest product 10049 if (! this.optionConfig.optionPrices[cheapest]) return simpleIdx; 10050 const knownCheapestPrice = this.optionConfig.optionPrices[cheapest].finalPrice.amount; 10051 return knownCheapestPrice > this.optionConfig.optionPrices[simpleIdx].finalPrice.amount 10052 ? simpleIdx 10053 : cheapest; 10054 }, 0) 10055 }, 10056 findProductIdToUseForOptionPrice(option) { 10057 // try to find a product for a complete selection 10058 const attributeId = this.findAttributeByOptionId(option.id); 10059 const optionSelection = Object.assign({}, this.selectedValues, {[attributeId]: option.id}); 10060 const matchingSimpleIndex = this.calculateSimpleIndexForFullSelection(optionSelection); 10061 // if there is no complete selection, use the cheapest product for the option 10062 return matchingSimpleIndex || this.findCheapestProductForPartialSelection(optionSelection); 10063 }, 10064 getAttributeOptionLabel(option) { 10065 const optionProduct = this.findProductIdToUseForOptionPrice(option); 10066 10067 if ((! optionProduct) || (optionProduct === this.productIndexForPrice)) { 10068 return option.label; 10069 } 10070 10071 const currentPrice = this.getOptionPriceAdjustmentBasePrice(); 10072 10073 if (this.optionConfig.optionPrices[optionProduct]) { 10074 const optionPrice = this.optionConfig.optionPrices[optionProduct].finalPrice.amount; 10075 if (optionPrice !== currentPrice){ 10076 return option.label + ' ' + hyva.formatPrice(optionPrice - currentPrice, true); 10077 } 10078 } 10079 return option.label; 10080 }, 10081 getOptionPriceAdjustmentBasePrice() { 10082 if (this.optionConfig.optionPrices[this.productIndexForPrice]) { 10083 return this.optionConfig.optionPrices[this.productIndexForPrice].finalPrice.amount 10084 } 10085 return this.optionConfig.prices.finalPrice.amount; // default price if no option selection 10086 }, 10087 clearOptionIfActive(optionId, value) { 10088 if (this.selectedValues[optionId] === value) { 10089 this.blurLabel() 10090 this.changeOption(optionId, '') 10091 } 10092 }, 10093 removeAttrFromSelection(selectedValues, attributeId) { 10094 attributeId = parseInt(attributeId); 10095 return selectedValues.reduce((newSelection, val, attr) => { 10096 if (attr !== attributeId) { 10097 newSelection[attr] = val; 10098 } 10099 return newSelection; 10100 }, []); 10101 }, 10102 changeOption(attributeId, value) { 10103 if (value === '') { 10104 this.selectedValues = this.removeAttrFromSelection(this.selectedValues, attributeId) 10105 } else if (value && this.getAllowedAttributeOptions(attributeId).find(option => option.id === value)) { 10106 this.selectedValues[attributeId] = value; 10107 } 10108 this.findSimpleIndex(); 10109 this.findAllowedAttributeOptions(); 10110 this.updatePrices(); 10111 this.updateGallery(); 10112 10113 const candidates = this.findProductIdsForPartialSelection(this.selectedValues); 10114 10115 window.dispatchEvent( 10116 new CustomEvent( 10117 'configurable-selection-changed', 10118 { 10119 detail: { 10120 productId: this.productId, 10121 optionId: attributeId, 10122 value: value, 10123 productIndex: this.productIndex, 10124 selectedValues: this.selectedValues, 10125 candidates: candidates, 10126 skuCandidates: Object.values(candidates).map(id =>
10126 this.optionConfig.sku?.[id] ?? null).filter(Boolean), 10127 } 10128 } 10129 ) 10130 ); 10131 }, 10132 calculateIsMinimalPrice() { 10133 return ! this.isFullSelection(this.selectedValues); 10134 }, 10135 isFullSelection(selectedValues) { 10136 return Object.values(selectedValues).length === Object.keys(this.optionConfig.attributes).length; 10137 }, 10138 updatePrices() { 10139 const value = this.optionConfig.optionPrices[this.productIndexForPrice] || this.optionConfig.prices; 10140 window.dispatchEvent( 10141 new CustomEvent( 10142 "update-prices-" + this.productId, 10143 { 10144 detail: Object.assign( 10145 value, 10146 { isMinimalPrice: this.calculateIsMinimalPrice() } 10147 ) 10148 } 10149 ) 10150 ); 10151 }, 10152 updateGallery () { 10153 if (this.productIndex) { 10154 const images = this.optionConfig.images[this.productIndex]; 10155 images && window.dispatchEvent(new CustomEvent( 10156 "update-gallery", 10157 { detail: this.sortImagesByPosition(images) } 10158 )); 10159 } else { 10160 window.dispatchEvent(new Event("reset-gallery")); 10161 } 10162 }, 10163 sortImagesByPosition(images) { 10164 return images.sort((x, y) => { 10165 return x.position === y.position ? 0 : (parseInt(x.position) > parseInt(y.position) ? 1 : -1) 10166 }); 10167 }, 10168 onGetCartData(data) { 10169 }, 10170 preselectCartItems(data) { 10171 // pre-select options based on cart data for current (quote) itemId 10172 const cart = data && data.cart; 10173 if (cart && cart.items) { 10174 const cartItem = cart.items.find((item) => { 10175 return ( 10176 item.item_id === this.itemId 10177 && item.product_id === this.productId 10178 ) 10179 }); 10180 if (cartItem && cartItem.options && cartItem.options.length) { 10181 cartItem.options.map(option => { 10182 this.changeOption(option.option_id, option.option_value); 10183 }) 10184 } 10185 } 10186 }, 10187 preselectQuerystringItems() { 10188 // pre-select option like ?size=167 10189 const urlQueryParams = new URLSearchParams(window.location.search.replace('?','')); 10190 this.preselectItemsBasedOnLocation(attribute => urlQueryParams.get(attribute.code)); 10191 }, 10192 preselectLocationHashItems() { 10193 // pre-select option like #144=167 10194 const urlHashParams = new URLSearchParams(window.location.hash.replace('#','')); 10195 this.preselectItemsBasedOnLocation(attribute => urlHashParams.get(attribute.id)); 10196 }, 10197 preselectItemsBasedOnLocation(getLocationValue) { 10198 Object.values(this.optionConfig.attributes).map(attribute => { 10199 const v = getLocationValue(attribute); 10200 v && this.changeOption(attribute.id, v) 10201 }); 10202 } 10203 } 10204 } 10205</script>
vendor: 1 bytes, line 10205
10205
10206<script> 10207 function initSwatchOptions(swatchConfig) { 10208 return { 10209 swatchConfig, 10210 getAttributeSwatchData(attributeId) { 10211 const swatchConfig = Object.assign({}, this.swatchConfig[attributeId]); 10212 swatchConfig['details'] = JSON.parse(swatchConfig['additional_data']); 10213 10214 return swatchConfig; 10215 }, 10216 mapSwatchTypeNumberToTypeCode(typeNumber) { 10217 switch ("" + typeNumber) { 10218 case "1": 10219 return "color" 10220 case "2": 10221 return "image" 10222 case "3": 10223 return "empty" 10224 case "0": 10225 default: 10226 return "text" 10227 } 10228 }, 10229 getTypeOfFirstOption(attributeId) { 10230 for (const optionId in this.swatchConfig[attributeId]) { 10231 const option = this.swatchConfig[attributeId][optionId]; 10232 if (typeof option.type !== 'undefined') { 10233 return this.mapSwatchTypeNumberToTypeCode(option.type); 10234 } 10235 } 10236 }, 10237 getVisualSwatchType(attributeId, targetOptionId) { 10238 // If a type configuration is present for the given option id, use it 10239 const config = this.swatchConfig[attributeId]; 10240 if (config[targetOptionId] && typeof config[targetOptionId].type !== 'undefined') { 10241 return this.mapSwatchTypeNumberToTypeCode(config[targetOptionId].type); 10242 } 10243 10244 // Otherwise - if no config is present for the target option - use the type of the first option 10245 // with a type property from the attribute, thus assuming its the same type as the target option. 10246 // (This edge case condition can occur on single swatch products if some options are not salable) 10247 return this.getTypeOfFirstOption(attributeId); 10248 }, 10249 getSwatchType(attributeId, optionId) { 10250 // Deserialize the attribute details the first time they are used 10251 if (this.swatchConfig[attributeId] && ! this.swatchConfig[attributeId].details) { 10252 this.swatchConfig[attributeId] = this.getAttributeSwatchData(attributeId); 10253 } 10254 const type = this.swatchConfig[attributeId] && 10255 this.swatchConfig[attributeId].details && 10256 this.swatchConfig[attributeId].details.swatch_input_type || 10257 "empty"; 10258 return type === 'visual' ? this.getVisualSwatchType(attributeId, optionId) : type; 10259 }, 10260 isTextSwatch(attributeId, optionId) { 10261 return this.getSwatchType(attributeId, optionId) === 'text'; 10262 }, 10263 isVisualSwatch(attributeId, optionId) { 10264 const type = this.getSwatchType(attributeId, optionId); 10265 10266 return ['image', 'color'].includes(type); 10267 }, 10268 getSwatchBackgroundStyle(attributeId, optionId) { 10269 const config = this.getSwatchConfig(attributeId, optionId); 10270 const type = this.getSwatchType(attributeId, optionId); 10271 10272 if (type === "color") { 10273 return 'background-color:' + config.value; 10274 } else if (type === "image") { 10275 return "background: #fff url('" + config.value + "') no-repeat center"; 10276 } else { 10277 return ''; 10278 } 10279 }, 10280 getSwatchText(attributeId, optionId) { 10281 const config = this.getSwatchConfig(attributeId, optionId); 10282 return config.label || config.value || this.getOptionLabelFromOptionConfig(attributeId, optionId); 10283 }, 10284 getOptionLabelFromOptionConfig(attributeId, optionId) { 10285 // Fallback if no value is present in swatchConfig data 10286 // Reference issue https://gitlab.hyva.io/hyva-themes/magento2-default-theme/-/issues/190 10287 const option = this.getAllAttributeOptions(attributeId).filter(option => option.id === optionId); 10288 return option && option[0] && option[0].label ||''; 10289 }, 10290 getSwatchConfig(attributeId, optionId) { 10291 return this.swatchConfig[attributeId] && this.swatchConfig[attributeId][optionId] 10292 ? this.swatchConfig[attributeId][optionId] 10293 : false;
10294 }, 10295 activeTooltipItem: false, 10296 tooltipPositionElement: false, 10297 isTooltipVisible() { 10298 return this.activeTooltipItem && 10299 this.getSwatchConfig( 10300 this.activeTooltipItem.attribute, 10301 this.activeTooltipItem.item 10302 ); 10303 }, 10304 isFirstItemCol() { 10305 return this.activeTooltipItem.index === 0; 10306 }, 10307 getTooltipImageStyle(attributeId, optionId) { 10308 const config = this.getSwatchConfig(attributeId, optionId); 10309 const type = this.getSwatchType(attributeId, optionId); 10310 10311 if (type === "color") { 10312 return 'background-color:' + config.value + '; width: 110px; height: 90px;'; 10313 } else if (type === "image") { 10314 return "background: #ffffff url('" + config.thumb + 10315 "') center center no-repeat; width: 110px; height: 90px;"; 10316 } else { 10317 return 'display:none'; 10318 } 10319 }, 10320 getTooltipPosition() { 10321 return this.tooltipPositionElement ? 10322 `top: ${this.tooltipPositionElement.offsetTop}px;` + 10323 `left: ${ 10324 this.tooltipPositionElement.offsetLeft - ( 10325 this.tooltipPositionElement.closest('.snap') && 10326 this.tooltipPositionElement.closest('.snap').scrollLeft || 10327 0 10328 ) 10329 }px;` : '' 10330 }, 10331 getTooltipLabel() { 10332 return this.getSwatchConfig(this.activeTooltipItem.attribute, this.activeTooltipItem.item).label 10333 }, 10334 focusedLabel: false, 10335 focusLabel(optionId) { 10336 this.focusedLabel = optionId; 10337 }, 10338 blurLabel() { 10339 this.focusedLabel = false; 10340 }, 10341 showSwatches: false, 10342 initShowSwatchesIntersect() { 10343 if ('IntersectionObserver' in window && !window.scrollY) { 10344 let io = new IntersectionObserver( 10345 entries => { 10346 entries.map(entry => { 10347 if (entry.isIntersecting) { 10348 this.showSwatches = true; 10349 io.unobserve(this.$root); 10350 } 10351 }) 10352 } 10353 ); 10354 io.observe(this.$root); 10355 } else { 10356 this.showSwatches = true 10357 } 10358 } 10359 } 10360 } 10361</script>
vendor: 1 bytes, line 10361
10361
10362<script> 10363 'use strict'; 10364 10365 (function () { 10366 const EVENT_NAME = 'amhideprice-swatch-change'; 10367 const selectors = { 10368 swatchOptions: 'div[x-data^=initConfigurable][x-data*=Options]', 10369 parentSwatchContainers: '.product_addtocart_form', 10370 qty: '[x-data^=initQtyField]', 10371 wishlist: '[data-addto="wishlist"]', 10372 compare: '[data-addto="compare"]', 10373 addToCart: '.product-info button.btn.btn-primary, [x-data^=initQtyField] ~ button.btn.btn-primary', 10374 replaceAddToCart: '.amasty-hide-price-button', 10375 replacePriceButton: '[data-amhide="AmastyHidePricePopup"]', 10376 priceBox: '.price-box.price-final_price' 10377 }; 10378 10379 const injectSwatchObserver = function () { 10380 const swatches = document.querySelectorAll(selectors.swatchOptions); 10381 10382 swatches.forEach(swatch => { 10383 const injectedDispatch = `$dispatch('${EVENT_NAME}', ` 10384 + `{ productIndex: productIndex, element: $el, optionConfig: optionConfig })`; 10385 10386 let mutatedInitAttribute = swatch.getAttribute('x-init'); 10387 mutatedInitAttribute += `; $watch('productIndex', (productIndex) => { ${injectedDispatch} });`; 10388 swatch.setAttribute('x-init', mutatedInitAttribute); 10389 }); 10390 }; 10391 10392 const hideProductElements = function ({ detail: eventData }) { 10393 const { element, optionConfig, productIndex } = eventData; 10394 10395 const isVisible = optionConfig?.optionPrices?.[productIndex]?.finalPrice?.amount !== 0; 10396 setProductElementsVisibility(element, isVisible, optionConfig, productIndex); 10397 } 10398 10399 const setProductElementsVisibility = function (element, isVisible, optionConfig, productIndex) { 10400 const parentNode = getParentNode(element, optionConfig); 10401 if (!parentNode) { 10402 return; 10403 } 10404 10405 const affectedNodes = getAffectedNodes(parentNode, optionConfig, productIndex); 10406 affectedNodes.forEach((node) => { 10407 isVisible ? node.classList.remove('hidden') : node.classList.add('hidden'); 10408 }); 10409 10410 amHidePrice.removeHidePriceButtons(parentNode); 10411 !isVisible && insertHidePriceElements(parentNode, optionConfig, productIndex); 10412 } 10413 10414 const getParentNode = function (element, optionConfig) { 10415 const parentContainers = selectors.parentSwatchContainers + ', ' + optionConfig?.amHidePrice.selectors.parent ?? ''; 10416 10417 return element.closest(parentContainers) ?? element.parentNode?.parentNode; 10418 } 10419 10420 const insertHidePriceElements = function (parentNode, optionConfig, productIndex) { 10421 amHidePrice.insertAfterDomElement( 10422 parentNode, 10423 selectors.priceBox, 10424 optionConfig?.amHidePrice.children[productIndex] 10425 ); 10426 amHidePrice.insertAfterDomElement( 10427 parentNode, 10428 selectors.addToCart, 10429 optionConfig?.amHidePrice.addToCartHtml 10430 ); 10431 } 10432 10433 const getAffectedNodes = function (parentNode, optionConfig, productIndex) { 10434 if (parentNode === null) { 10435 return []; 10436 } 10437 10438 const allAffectedNodes = [selectors.qty]; 10439 10440 if (optionConfig?.amHidePrice.hide_compare) { 10441 allAffectedNodes.push(selectors.compare); 10442 } 10443 10444 if (optionConfig?.amHidePrice.hide_wishlist) { 10445 allAffectedNodes.push(selectors.wishlist); 10446 } 10447 10448 if (optionConfig?.amHidePrice.hide_price || !!optionConfig?.amHidePrice.children[productIndex]) { 10449 allAffectedNodes.push(selectors.priceBox); 10450 } 10451 10452 if (optionConfig?.amHidePrice.hide_addtocart) { 10453 allAffectedNodes.push(selectors.addToCart); 10454 allAffectedNodes.push(optionConfig?.amHidePrice.selectors.add_to_cart); 10455 } 10456 10457 return parentNode.querySelectorAll(allAffectedNodes.join(',')) ?? []; 10458 } 10459 10460 injectSwatchObserver(); 10461 window.addEventListener(EVENT_NAME, hideProductElements); 10462 })(); 10463</script>
vendor: 1 bytes, line 10463
10463
10464<script> 10465 'use strict'; 10466 10467 (function (amHidePrice) { 10468 amHidePrice.activateScripts = function (contentNode) { 10469 // Extract all the script tags from the content. 10470 const tmpScripts = contentNode.getElementsByTagName('script'); 10471 10472 if (tmpScripts.length > 0) { 10473 const scripts = []; 10474 for (let i = 0; i < tmpScripts.length; i++) { 10475 scripts.push(tmpScripts[i]); 10476 } 10477 10478 for (let i = 0; i < scripts.length; i++) { 10479 let script = document.createElement('script'); 10480 script.innerHTML = scripts[i].innerHTML; 10481 document.head.appendChild(script); 10482 scripts[i].parentNode.removeChild(scripts[i]); 10483 } 10484 } 10485 10486 return contentNode; 10487 }; 10488 10489 amHidePrice.removeScripts = function (contentNode) { 10490 let scripts = contentNode.getElementsByTagName('script'); 10491 for (let i = 0; i < scripts.length; i++) { 10492 scripts[i].parentNode.removeChild(scripts[i]); 10493 } 10494 10495 return contentNode; 10496 }; 10497 10498 amHidePrice.insertAfterDomElement = function (parentNode, targetSelector, content) { 10499 const node = parentNode.querySelector(targetSelector); 10500 if (!node || !content) { 10501 return; 10502 } 10503 10504 const contentNode = document.createElement('div'); 10505 contentNode.setAttribute("data-id","amHidePriceButton"); 10506 contentNode.insertAdjacentHTML('afterbegin', content); 10507 10508 node.after(contentNode); 10509 amHidePrice.activateScripts(contentNode); 10510 }; 10511 10512 amHidePrice.removeHidePriceButtons = function (parentNode) { 10513 Object.values(parentNode.querySelectorAll(this.amHidePriceButtonSelector)).forEach( 10514 (amHidePriceButton) => { 10515 amHidePriceButton.remove(); 10516 }); 10517 } 10518 10519 amHidePrice.amHidePriceButtonSelector = '[data-id="amHidePriceButton"]'; 10520 } (window.amHidePrice = window.amHidePrice || {})); 10521</script>
10521 10522 10523 <section x-cloak x-data="amRecaptchaLoading" 10524 @amcaptcha-loading-start.window="loadingStart" @amcaptcha-loading-stop.window="loadingStop"> 10525 <div class="flex flex-row start-0 top-0 justify-center items-center w-full h-full absolute select-none z-10" 10526 style="background: rgba(255,255,255,0.5);" 10527 x-show="isLoading" 10528 x-cloak 10529 x-transition:enter="ease-out duration-200" 10530 x-transition:enter-start="opacity-0" 10531 x-transition:enter-end="opacity-100" 10532 x-transition:leave="ease-in duration-200" 10533 x-transition:leave-start="opacity-100" 10534 x-transition:leave-end="opacity-0"> 10535 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 56 56" fill="none" class="animate-[spin_2s_linear_infinite] text-primary" role="img"> 10536 <path d="M52.5 28C52.5 31.2174 51.8663 34.4033 50.6351 37.3757C49.4038 40.3482 47.5992 43.0491 45.3241 45.3241C43.0491 47.5992 40.3482 49.4038 37.3757 50.635C34.4033 51.8663 31.2174 52.5 28 52.5C24.7826 52.5 21.5967 51.8663 18.6243 50.635C15.6518 49.4038 12.9509 47.5991 10.6759 45.3241C8.40085 43.0491 6.59619 40.3482 5.36495 37.3757C4.13371 34.4033 3.5 31.2174 3.5 28C3.5 24.7826 4.13371 21.5967 5.36496 18.6242C6.5962 15.6518 8.40086 12.9509 10.6759 10.6759C12.9509 8.40084 15.6518 6.59619 18.6243 5.36495C21.5967 4.13371 24.7826 3.5 28 3.5C31.2174 3.5 34.4033 4.13371 37.3758 5.36496C40.3482 6.5962 43.0491 8.40086 45.3241 10.6759C47.5992
1053612.9509 49.4038 15.6518 50.6351 18.6243C51.8663 21.5967 52.5 24.7826 52.5 28L52.5 28Z" stroke="#EEEAF4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> 10537 <path d="M52.5 28C52.5 24.7826 51.8663 21.5967 50.6351 18.6243C49.4038 15.6518 47.5992 12.9509 45.3241 10.6759C43.0491 8.40085 40.3482 6.59619 37.3757 5.36495C34.4033 4.13371 31.2174 3.5 28 3.5" stroke="#532E91" stroke-width="6" stroke-linecap="square"/> 10538<title>loader</title></svg> 10539</div> 10540 </section> 10541
10541<script> 10542 function amRecaptchaLoading() { 10543 return { 10544 isLoading: false, 10545 10546 loadingStart() { 10547 this.isLoading = true; 10548 }, 10549 10550 loadingStop() { 10551 this.isLoading = false; 10552 } 10553 }; 10554 } 10555 (() => { 10556 const initFn = () => Alpine.data('amRecaptchaLoading', amRecaptchaLoading); 10557 window.Alpine ? initFn() : window.addEventListener('alpine:init', initFn, {once: true}) 10558 })() 10559 </script>
10559 10560
10560<script> 10561 'use strict'; 10562 10563 function initParentContainerProcessor() { 10564 const selectors = { 10565 labelContainer: '.amasty-label-container', 10566 galleryImg: '#gallery img:not(.amasty-label-image)' 10567 } 10568 10569 return { 10570 /** 10571 * @param {AmlabelComponent} label 10572 * @return {void} 10573 */ 10574 process: function (label) { 10575 this.moveLabelToParent(label); 10576 this.setParentStyles(label); 10577 }, 10578 10579 /** 10580 * @param {AmlabelComponent} label 10581 * @return {void} 10582 */ 10583 moveLabelToParent: function (label) { 10584 const newParent = this.getNewParent(label); 10585 10586 if (!newParent) { 10587 return; 10588 } 10589 10590 const wrapperElement = newParent.querySelector(label.wrapperClass); 10591 label.parent = newParent; 10592 10593 // if same wrapper already exists - move label there 10594 if (wrapperElement && !wrapperElement.isSameNode(label.element)) { 10595 this.insertLabelByPriority(label, wrapperElement); 10596 } else { 10597 newParent.prepend(label.element); 10598 } 10599 }, 10600 10601 /** 10602 * @param {AmlabelComponent} label 10603 * @return {HTMLElement|null} 10604 */ 10605 getNewParent: function (label) { 10606 // move label to container from settings 10607 let newParent = label.element.closest(label.config.path) 10608 || this.findPreviousNodeExcludingScriptsAndDifferentLabels(label.element)?.querySelector(label.config.path) 10609 || label.parent.querySelector(label.config.path); 10610 10611 // Try to find default gallery container on product page if none of the config selectors found 10612 if (!newParent && label.config.move !== 1 && label.config.mode === 'prod') { 10613 newParent = document.querySelector(selectors.galleryImg)?.parentElement; 10614 } 10615 10616 return newParent; 10617 }, 10618 10619 /** 10620 * @param {HTMLElement} element 10621 * @return {HTMLElement|null} 10622 */ 10623 findPreviousNodeExcludingScriptsAndDifferentLabels: function (element) { 10624 const previousElement = element?.previousElementSibling; 10625 if (!previousElement) { 10626 return null; 10627 } 10628 10629 if (previousElement?.tagName !== 'SCRIPT' 10630 && !previousElement?.className?.includes?.('amlabel-position-wrapper') 10631 ) { 10632 return previousElement; 10633 } 10634 10635 return this.findPreviousNodeExcludingScriptsAndDifferentLabels(previousElement); 10636 }, 10637 10638 /** 10639 * @param {AmlabelComponent} label 10640 * @param {HTMLElement} wrapperElement 10641 * @return {void} 10642 */ 10643 insertLabelByPriority: function (label, wrapperElement) { 10644 const labelContainers = wrapperElement.querySelectorAll(selectors.labelContainer); 10645 const currentLabelOrder = label.labelContainer.dataset.amlabelOrder; 10646 let insertAfterContainer = null; 10647 10648 // find the label behind which we want to insert current one by priority 10649 labelContainers.forEach(labelContainer => { 10650 if (labelContainer.dataset.amlabelOrder < currentLabelOrder) { 10651 insertAfterContainer = labelContainer; 10652 } 10653 }); 10654 10655 insertAfterContainer === null 10656 ? wrapperElement.prepend(label.labelContainer) 10657 : insertAfterContainer.after(label.labelContainer); 10658 10659 label.element.remove(); 10660 label.element = wrapperElement; 10661 }, 10662 10663 /** 10664 * @param {AmlabelComponent} label 10665 * @return {void} 10666 */ 10667 setParentStyles: function (label) { 10668 /* required for child position absolute */ 10669 !(label.parent.style.position === 'absolute') && (label.parent.style.position = 'relative'); 10670 !label.parent.style.width && label.parent.classList.add('w-fit-content'); 10671 !label.parent.style.marginLeft && label.parent.classList.add('ml-auto'); 10672 !label.parent.style.marginRight && label.parent.classList.add('mr-auto'); 10673 }, 10674 } 10675 } 10676</script>
vendor: 1 bytes, line 10676
10676
10677<script> 10678 'use strict'; 10679 10680 function initLabelItemProcessor() { 10681 /** 10682 * @typedef {Object} AmlabelComponent 10683 * 10684 * @property {HTMLElement} element Label wrapper element 10685 * @property {HTMLElement} parent Element where label should be placed 10686 * @property {boolean} initialized Is label initialized 10687 * @property {string} wrapperClass Dynamic class for label wrapper to place multiple labels properly by position 10688 * @property {HTMLElement} labelContainer Container for label text image and tooltip 10689 * @property {Config} config Label Configuration 10690 * @property {Object} parentContainerProcessor 10691 * @property {Object} labelItemProcessor 10692 */ 10693 10694 /** 10695 * @typedef {Object} AmLabelConfig 10696 * 10697 * @property {0|1} alignment Labels Alignment config value (0 - Horizontal| 1 - Vertical) 10698 * @property {number} label Label Id 10699 * @property {number} margin Margin between labels config value 10700 * @property {('cat'|'prod')} mode Label mode 10701 * @property {number} move 10702 * @property {number} order Label order 10703 * @property {string} path The css selector to which label should place 10704 * @property {string} position Label position 10705 * @property {string} product Product Id 10706 * @property {string} redirect_url 10707 * @property {string} size Label size 10708 * @property {{ 10709 * backgroundColor: string, 10710 * color: string, 10711 * content: string, 10712 * status: 1|2|3 10713 * }} tooltip 10714 */ 10715 10716 return { 10717 /** 10718 * @param {AmlabelComponent} label 10719 * @return {void} 10720 */ 10721 process: function(label) { 10722 this.setLabelSize(label); 10723 this.setLinkProps(label); 10724 }, 10725 10726 /** 10727 * @param {AmlabelComponent} label 10728 * @return {void} 10729 */ 10730 setLabelSize: function (label) { 10731 if (!label.config.size) { 10732 return; 10733 } 10734 10735 label.labelContainer.style.width = label.parent.offsetWidth * label.config.size / 100 + 'px'; 10736 }, 10737 10738 /** 10739 * 10740 * @param {Object} $event 10741 * @param {HTMLElement} tooltipNode 10742 */ 10743 placeTooltip: function($event, tooltipNode) { 10744 const labelNode = $event.target; 10745 const labelNodeRect = labelNode.getBoundingClientRect(); 10746 10747 tooltipNode.style.bottom = (window.innerHeight - labelNodeRect.top) + 'px'; 10748 tooltipNode.style.left = (labelNodeRect.left + labelNode.offsetWidth / 2 ) + 'px'; 10749 }, 10750 10751 /** 10752 * @param {AmlabelComponent} label 10753 * @return {void} 10754 */ 10755 setLinkProps: function (label) { 10756 !!label.config.redirect_url && label.labelContainer.classList.add('-link', 'cursor-pointer'); 10757 }, 10758 10759 /** 10760 * @param {Object} $event Click event 10761 * @param {string} redirectUrl 10762 */ 10763 openLink: function($event, redirectUrl) { 10764 if (redirectUrl.length === 0) { 10765 return; 10766 } 10767 10768 $event.preventDefault(); 10769 window.open(redirectUrl, '_blank'); 10770 } 10771 } 10772 } 10773</script>
10773 10774
10775<script> 10776 (function () { 10777 'use strict'; 10778 10779 const observedEvent = 'amlabel-swatch-product-changed'; 10780 const selectors = { 10781 swatchOptions: 'div[x-data^=initConfigurable][x-data*=Options]', 10782 addToCartFormOnListing: 'form.product_addtocart_form', 10783 galleryImage: '#gallery img:not(.amasty-label-image)', 10784 labelWrapper: '.amlabel-position-wrapper' 10785 } 10786 10787 /** 10788 * @return {void} 10789 */ 10790 const injectLabelObserver = function () { 10791 const swatches = document.querySelectorAll(selectors.swatchOptions); 10792 10793 swatches.forEach(swatch => { 10794 const isListing = !!swatch.closest(selectors.addToCartFormOnListing); 10795 const injectedDispatch = `$dispatch('${observedEvent}', ` 10796 + `{productIndex: productIndex, element: $el, optionConfig: optionConfig, isListing: ${isListing ? 1 : 0}})`; 10797 10798 let mutatedInitAttribute = swatch.getAttribute('x-init'); 10799 mutatedInitAttribute += `; $watch('productIndex', (productIndex) => { ${injectedDispatch} });`; 10800 swatch.setAttribute('x-init', mutatedInitAttribute); 10801 }); 10802 } 10803 10804 /** 10805 * @param {{ 10806 * productIndex: number, 10807 * element: HTMLElement, 10808 * optionConfig: Object, 10809 * isListing: 0|1 10810 * }} payload 10811 * @return {void} 10812 */ 10813 const updateLabel = function ({detail: payload}) { 10814 const {productIndex, element, optionConfig, isListing} = payload; 10815 10816 if (!productIndex) { 10817 return; 10818 } 10819 10820 const reloadUrl = optionConfig.label_reload; 10821 const headers = { 10822 "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 10823 }; 10824 const body = new URLSearchParams({ 10825 product_ids: productIndex, 10826 in_product_list: isListing 10827 }); 10828 const labelContainerSelector = isListing ? optionConfig.label_category : optionConfig.label_product; 10829 10830 fetch(`${reloadUrl}?${body.toString()}`, {headers: headers, method: 'GET'}).then((response) => { 10831 if (response.ok) { 10832 return response.json(); 10833 } 10834 }).then((result) => { 10835 const labelHtml = result.labels && result.labels[productIndex]; 10836 let labelContainer = findClosest(element, labelContainerSelector); 10837 10838 // Try to find default gallery container on product page if none of the config selectors found 10839 if (labelContainer === null && isListing === 0) { 10840 labelContainer = document.querySelector(selectors.galleryImage)?.parentElement; 10841 } 10842 10843 if (!!labelContainer) { 10844 !!labelHtml ? replaceLabels(labelContainer, labelHtml) : clearLabelWrapper(labelContainer); 10845 } 10846 }).catch((error) => { 10847 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 10848 [{ 10849 type: 'error', 10850 text: error 10851 }], 10852 5000 10853 ); 10854 }); 10855 } 10856 10857 /** 10858 * We use approach from hyva.replaceDomElement method to replace labels and init components 10859 * 10860 * @param {HTMLElement} labelContainer 10861 * @param {string} labelHtml 10862 * @return {void} 10863 */ 10864 function replaceLabels(labelContainer, labelHtml) { 10865 const parser = new DOMParser(); 10866 const doc = parser.parseFromString(labelHtml, 'text/html'); 10867 const nodes = doc.querySelectorAll(selectors.labelWrapper); 10868 10869 if (!nodes) { 10870 return; 10871 } 10872 10873 clearLabelWrapper(labelContainer); 10874 10875 // Extract all the script tags from the new content. 10876 // Script tags won't execute when inserted into a dom-element directly, 10877 // therefore we need to inject them to the head of the document. 10878 const tmpScripts = doc.getElementsByTagName('script'); 10879 10880 if (tmpScripts.length > 0) { 10881 // Push all script tags into an array 10882 // (to prevent dom manipulation while iterating over dom nodes) 10883 const scripts = [...tmpScripts]; 10884 10885 // Iterate over all script tags and duplicate+inject each into the head
10886 scripts.forEach(script => { 10887 let scriptNode = document.createElement('script'); 10888 scriptNode.innerHTML = script.innerHTML; 10889 document.head.appendChild(scriptNode); 10890 10891 // Remove the original (non-executing) node from the page 10892 script.parentNode.removeChild(script); 10893 }); 10894 } 10895 10896 labelContainer.append(...nodes); 10897 } 10898 10899 /** 10900 * @param {HTMLElement} labelContainer 10901 * @return {void} 10902 */ 10903 function clearLabelWrapper(labelContainer) { 10904 labelContainer.querySelectorAll(selectors.labelWrapper).forEach(wrapper => wrapper.remove()); 10905 } 10906 10907 /** 10908 * @param {HTMLElement} currentElement 10909 * @param {string} selector 10910 * @return {HTMLElement|null} 10911 */ 10912 function findClosest(currentElement, selector) { 10913 // We need to limit parent searches to prevent labels from being associated with the wrong product. 10914 const maxDepth = 3; 10915 let counter = 0; 10916 let closest = null; 10917 10918 while (currentElement.parentNode && closest === null && counter < maxDepth) { 10919 ++counter; 10920 currentElement = currentElement.parentNode; 10921 closest = currentElement.querySelector(selector); 10922 } 10923 10924 return closest; 10925 } 10926 10927 injectLabelObserver(); 10928 window.addEventListener(observedEvent, updateLabel); 10929 })(); 10930</script>
vendor: 1 bytes, line 10930
10930
10931<script> 10932 'use strict'; 10933 10934 (function () { 10935 window.amLabels = { 10936 productLabelsUrl: 'amasty_label/ajax/label', 10937 /** 10938 * @param {Array} productIds 10939 * @return {Promise<any>} 10940 */ 10941 getLabelsHtml: function (productIds) { 10942 const headers = { 10943 "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 10944 }; 10945 const body = new URLSearchParams({ 10946 product_ids: productIds, 10947 in_product_list: true 10948 }); 10949 10950 return fetch(BASE_URL + this.productLabelsUrl + '?' + body.toString(), { 10951 headers: headers, 10952 method: 'GET' 10953 }).then((response) => { 10954 if (response.ok) { 10955 return response.json(); 10956 } 10957 }).then((result) => { 10958 return Object.values(result.labels); 10959 }).catch((error) => { 10960 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 10961 [{ 10962 type: 'error', 10963 text: error 10964 }], 10965 5000 10966 ); 10967 }); 10968 } 10969 }; 10970 })(); 10971</script>
10971<script> 10972 'use strict'; 10973 10974 (function () { 10975 window.initAmRecentlyViewedLabels = window.initAmRecentlyViewedLabels || { 10976 selectors: { 10977 recentlyViewedContainer: '[x-data^=initRecentlyViewedProductsComponent]', 10978 productPhoto: '.product-item-photo' 10979 }, 10980 recentlyViewedProductsKey: 'recently_viewed_products', 10981 10982 /** 10983 * @return number[] 10984 */ 10985 getProductIds: function () { 10986 const startIndex = '' === '' ? 0 : 1; 10987 10988 return JSON.parse(hyva.getBrowserStorage().getItem(this.recentlyViewedProductsKey) ?? '[]') 10989 .slice(startIndex) 10990 .map((item) => item.id); 10991 }, 10992 10993 /** 10994 * @return {void} 10995 */ 10996 mutationObserver: function () { 10997 const recentlyViewedContainers = document.querySelectorAll(this.selectors.recentlyViewedContainer); 10998 10999 recentlyViewedContainers.forEach(recentlyViewedContainer => { 11000 const mutationObserver = new MutationObserver((mutationsList, observer) => { 11001 for(let mutation of mutationsList) { 11002 if (mutation.type === 'childList') { 11003 this.addLabels(recentlyViewedContainer, this.getProductIds()); 11004 observer.disconnect(); 11005 } 11006 } 11007 }); 11008 11009 mutationObserver.observe(recentlyViewedContainer, {childList: true}); 11010 }) 11011 }, 11012 11013 /** 11014 * @param {HTMLElement} element 11015 * @param {Array} productIds 11016 * @return {void} 11017 */ 11018 addLabels: function (element, productIds) { 11019 if (!productIds.length) { 11020 return; 11021 } 11022 11023 const productPhotos = element.querySelectorAll(this.selectors.productPhoto); 11024 11025 window.amLabels.getLabelsHtml(productIds).then( 11026 (result) => { 11027 result.length && productPhotos?.forEach((productPhoto, index) => { 11028 !!result[index] && this.appendLabel({ 11029 productPhoto: productPhoto, 11030 labelHtml: result[index], 11031 productId: productIds[index] 11032 }); 11033 }) 11034 } 11035 ); 11036 }, 11037 11038 /** 11039 * @param {{ 11040 * productContainer: Element, 11041 * labelHtml: string, 11042 * productId: number, 11043 * }} labelConfig 11044 * @return {void} 11045 */ 11046 appendLabel: function (labelConfig) { 11047 const {productPhoto, labelHtml, productId} = labelConfig; 11048 const labelClass = 'am-label-product-id-' + productId; 11049 11050 productPhoto.innerHTML += labelHtml; 11051 productPhoto.classList.add(labelClass); 11052 this.overrideParentLabelStyle(productPhoto); 11053 hyva.replaceDomElement( 11054 '.' + labelClass, 11055 productPhoto.outerHTML 11056 ); 11057 }, 11058 11059 /** 11060 * @param {HTMLElement} productPhoto 11061 * @return {void} 11062 */ 11063 overrideParentLabelStyle: function (productPhoto) { 11064 productPhoto.classList.add('!w-auto'); 11065 } 11066 }; 11067 11068 window.initAmRecentlyViewedLabels.mutationObserver(); 11069 })(); 11070</script>
vendor: 1 bytes, line 11070
11070
11071<script> 11072 (function () { 11073 'use strict'; 11074 11075 const selectors = { 11076 body: '.catalog-product-view', 11077 productItem: 'product-item', 11078 productGalleryImage: '#gallery' 11079 } 11080 11081 window.addEventListener('alpine:init', () => { 11082 Alpine.directive('amintersect', (el, { expression }, { evaluate, cleanup }) => { 11083 const observer = new IntersectionObserver(entries => { 11084 entries.forEach(entry => { 11085 if (entry.isIntersecting) { 11086 evaluate(expression); 11087 observer.disconnect(); 11088 } 11089 }) 11090 }, { threshold: 0.5 }); 11091 11092 observer.observe(getElement()); 11093 11094 cleanup(() => { 11095 observer.disconnect() 11096 }); 11097 11098 function getElement() { 11099 return !!document.querySelector(selectors.body) ? getImageProductBlock(el) : getCategoryProductBlock(el); 11100 } 11101 11102 function getCategoryProductBlock(el) { 11103 if (el.previousElementSibling === null) { 11104 return el; 11105 } 11106 11107 if (!el.previousElementSibling.classList.contains(selectors.productItem)) { 11108 return getCategoryProductBlock(el.previousElementSibling); 11109 } 11110 11111 return el.previousElementSibling; 11112 } 11113 11114 function getImageProductBlock(el) { 11115 return el.parentNode.querySelector(selectors.productGalleryImage)?.parentNode ?? el.parentNode; 11116 } 11117 }); 11118 }); 11119 })(); 11120</script>
11120 11121
11122<script> 11123 'use strict'; 11124 11125 (() => { 11126 const browserStorage = hyva.getBrowserStorage(); 11127 const storageKey = 'mage-banners-cache-storage'; 11128 const cacheTimeoutKey = 'mage-banners-cache-timeout'; 11129 const storeIdKey = 'mage-banners-storeId'; 11130 const cartIdKey = 'mage-banners-cartDataId'; 11131 const options = { 11132 cacheTtl: 30000, 11133 sectionLoadUrl: 'https://www.purdys.com/banner/ajax/load/' 11134 }; 11135 11136 if (browserStorage && browserStorage.getItem(storageKey) === null) { 11137 browserStorage.setItem(storageKey, "{}"); 11138 } 11139 11140 async function initBanners(elements) { 11141 invalidateCacheBySessionTimeOut(); 11142 11143 const storageData = JSON.parse(browserStorage.getItem(storageKey)); 11144 let items = []; 11145 11146 if (storageData.data) { 11147 items = getFromStorage(); 11148 } else { 11149 items = await getFromServer(); 11150 } 11151 11152 populateDynamicBlocks(elements, items); 11153 } 11154 11155 // Cache invalidation. Banner cache ttl is 30 sec by default.
11156 function invalidateCacheBySessionTimeOut() { 11157 const cacheEol = new Date(browserStorage.getItem(cacheTimeoutKey)) 11158 const dateTo = new Date(Date.now() + options.cacheTtl); 11159 const globalStoreId = hyva.getCookie('store') || 'default'; 11160 const mageCacheStorage = JSON.parse(browserStorage.getItem('mage-cache-storage')); 11161 let cartDataId = null; 11162 11163 if (browserStorage.getItem(storeIdKey) === null) { 11164 browserStorage.setItem(storeIdKey, globalStoreId); 11165 } 11166 11167 if (mageCacheStorage !== null && 11168 mageCacheStorage.hasOwnProperty('cart')) { 11169 cartDataId = mageCacheStorage.cart['data_id']; 11170 } 11171 11172 if (cacheEol < new Date() || 11173 browserStorage.getItem(storeIdKey) !== globalStoreId || 11174 Number(browserStorage.getItem(cartIdKey)) !== cartDataId) 11175 { 11176 browserStorage.setItem(storageKey, "{}"); 11177 browserStorage.setItem(cacheTimeoutKey, dateTo); 11178 browserStorage.setItem(storeIdKey, globalStoreId); 11179 11180 if (cartDataId) { 11181 browserStorage.setItem(cartIdKey, cartDataId); 11182 } 11183 } 11184 } 11185 11186 async function getFromServer() { 11187 try { 11188 const url = new URL(options.sectionLoadUrl); 11189 url.searchParams.append('requesting_page_url', url.origin); 11190 url.searchParams.append('_', Date.now()); 11191 11192 const response = await fetch(url.href, { 11193 headers: { 11194 'Content-Type': 'application/json', 11195 'X-Requested-With': 'XMLHttpRequest' 11196 } 11197 }); 11198 const storageData = await response.json(); 11199 11200 browserStorage.setItem(storageKey, JSON.stringify(storageData)); 11201 11202 if (storageData.data && storageData.data.items) { 11203 return storageData.data.items.fixed ? storageData.data.items.fixed : []; 11204 } 11205 } catch (error) { 11206 console.warn(error); 11207 11208 return []; 11209 } 11210 } 11211 11212 function getFromStorage() { 11213 const storageData = JSON.parse(browserStorage.getItem(storageKey)); 11214 if (storageData.data && storageData.data.items) { 11215 return storageData.data.items.fixed || []; 11216 } 11217 } 11218 11219 function populateDynamicBlocks(elements, items) { 11220 if (!items || items.length === 0) { 11221 console.log(`No\u0020banners\u0020found`); 11222 11223 return; 11224 } 11225 11226 for (const element of elements) { 11227 // Filter IDs by those allowed for current customer segment 11228 const ids = element.getAttribute('data-ids').split(',').filter((id) => items.hasOwnProperty(id)); 11229 const config = { 11230 type: element.getAttribute('data-rotate'), 11231 dynamicBlockId: element.getAttribute('data-banner-id'), 11232 }; 11233 11234 getByType(config, element, items, ids); 11235 } 11236 } 11237 11238 function getByType(config = null, element, items, ids) { 11239 if (!config) return; 11240 11241 if (config.type === 'random') { 11242 ids = [ids[Math.floor(Math.random() * ids.length)]]; 11243 } 11244 11245 if (config.type === 'series' || config.type === 'shuffle') { 11246 ids = [getNextInOrder(config, ids)]; 11247 } 11248 11249 for (const id of ids) { 11250 if (items[id]) { 11251 element.insertAdjacentHTML('beforeend', items[id].content); 11252 } 11253 } 11254 } 11255 11256 function getNextInOrder(config, ids) { 11257 const dynamicBlockId = config.dynamicBlockId; 11258 const localStorageKey = 'dynamic-block-banner-rotator'; 11259 let localStorageData = JSON.parse(localStorage.getItem(localStorageKey)) || {}; 11260 let nextId; 11261 11262 if (localStorageData[dynamicBlockId] && localStorageData[dynamicBlockId].length > 0) { 11263 nextId = localStorageData[dynamicBlockId].shift(0); 11264 } else { 11265 if (config.type === 'shuffle') { 11266 ids.sort(() => Math.random() - 0.5); 11267 } 11268 nextId = ids.shift(0); 11269 localStorageData[dynamicBlockId] = ids; 11270 } 11271 11272 localStorage.setItem( 11273 localStorageKey, 11274 JSON.stringify(localStorageData) 11275 ); 11276 11277 return nextId; 11278 } 11279 11280 const elements = document.querySelectorAll('[data-banner-id][data-ids]'); 11281 if (elements.length > 0) { 11282 initBanners(elements); 11283 } 11284 })(); 11285</script>
vendor: 1 bytes, line 11285
11285
11286<script> 11287 window.setAjaxCart = (recursive = true, parentSelector = null) => { 11288 const parent = parentSelector ? document.querySelector(parentSelector) : document; 11289 11290 if (!parent) { 11291 console.warn('No parent element found for Ajax Cart'); 11292 return; 11293 } 11294 11295 const forms = parent.querySelectorAll('.product_addtocart_form, #product_addtocart_form, [data-role="tocart-form"]:not(.product_addtocart_form)'); 11296 11297 forms.forEach(form => { 11298 form.addEventListener('submit', async (e) => { 11299 e.preventDefault(); 11300 11301 const formData = new URLSearchParams(new FormData(form)); 11302 11303 // Fix FPC form key issue: replace cached form key with fresh one 11304 if (typeof hyva !== 'undefined' && hyva.getFormKey) { 11305 formData.set('form_key', hyva.getFormKey()); 11306 } else if (window.checkoutConfig && window.checkoutConfig.formKey) { 11307 formData.set('form_key', window.checkoutConfig.formKey); 11308 } 11309 11310 const isButtonInsideForm = !!form.querySelector('button'); 11311 const button = isButtonInsideForm ? 11312 form.querySelector('button[type="submit"], button') : 11313 document.getElementById('product-addtocart-button'); 11314 const loader = document.createElement('div'); 11315 11316 loader.className = 'absolute inset-0 flex justify-center items-center loader'; 11317 loader.innerHTML = ` 11318<div x-data="{ isLoading: true }" class="w-full h-full absolute top-0 left-0"> 11319 <div class="flex flex-row start-0 top-0 justify-center items-center w-full h-full absolute select-none z-10" 11320 style="background: rgba(255,255,255,0.5);" 11321 x-show="isLoading" 11322 x-cloak 11323 x-transition:enter="ease-out duration-200" 11324 x-transition:enter-start="opacity-0" 11325 x-transition:enter-end="opacity-100" 11326 x-transition:leave="ease-in duration-200" 11327 x-transition:leave-start="opacity-100" 11328 x-transition:leave-end="opacity-0"> 11329 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 56 56" fill="none" class="animate-[spin_2s_linear_infinite] text-primary" role="img"> 11330 <path d="M52.5 28C52.5 31.2174 51.8663 34.4033 50.6351 37.3757C49.4038 40.3482 47.5992 43.0491 45.3241 45.3241C43.0491 47.5992 40.3482 49.4038 37.3757 50.635C34.4033 51.8663 31.2174 52.5 28 52.5C24.7826 52.5 21.5967 51.8663 18.6243 50.635C15.6518 49.4038 12.9509 47.5991 10.6759 45.3241C8.40085 43.0491 6.59619 40.3482 5.36495 37.3757C4.13371 34.4033 3.5 31.2174 3.5 28C3.5 24.7826 4.13371 21.5967 5.36496 18.6242C6.5962 15.6518 8.40086 12.9509 10.6759 10.6759C12.9509 8.40084 15.6518 6.59619 18.6243 5.36495C21.5967 4.13371 24.7826 3.5 28 3.5C31.2174 3.5 34.4033 4.13371 37.3758 5.36496C40.3482 6.5962 43.0491 8.40086 45.3241 10.6759C47.5992
1133012.9509 49.4038 15.6518 50.6351 18.6243C51.8663 21.5967 52.5 24.7826 52.5 28L52.5 28Z" stroke="#EEEAF4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> 11331 <path d="M52.5 28C52.5 24.7826 51.8663 21.5967 50.6351 18.6243C49.4038 15.6518 47.5992 12.9509 45.3241 10.6759C43.0491 8.40085 40.3482 6.59619 37.3757 5.36495C34.4033 4.13371 31.2174 3.5 28 3.5" stroke="#532E91" stroke-width="6" stroke-linecap="square"/> 11332<title>loader</title></svg> 11333</div> 11334</div>`; 11335 11336 if (isButtonInsideForm) { 11337 form.prepend(loader); 11338 form.classList.add('relative'); 11339 } else { 11340 button.prepend(loader); 11341 button.classList.add('relative') 11342 } 11343 11344 button.disabled = true; 11345 11346 try { 11347 const response = await fetch(e.currentTarget.action, { 11348 method: 'POST', 11349 body: formData, 11350 headers: { 11351 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 11352 'X-Requested-With': 'XMLHttpRequest' 11353 } 11354 }); 11355 11356 if (!response.ok) { 11357 return form.submit(); 11358 } 11359 11360 if (response.redirected) { 11361 return window.location.href = response.url; 11362 } 11363 11364 const data = await response.json(); 11365 11366 if (data.backUrl) { 11367 return window.location.href = data.backUrl; 11368 } 11369 11370 if (data?.message && data?.success) { 11371 setMessage({ 11372 text: data?.message, 11373 type: data?.success ? 'success' : 'error' 11374 }); 11375 } else if (data?.error && !data?.success) { 11376 setMessage({ 11377 text: data?.error, 11378 type: 'error' 11379 }); 11380 } 11381 11382 if (recursive) { 11383 window.dispatchEvent(new CustomEvent('product-addtocart-success')); 11384 } 11385 11386 window.dispatchEvent(new CustomEvent('reload-customer-section-data')); 11387 if (data.checkoutConfig && data.checkoutConfig.totalsData) { 11388 window.dispatchEvent(new CustomEvent('update-totals', { 11389 detail: { data: data.checkoutConfig.totalsData } 11390 })); 11391 } 11392 } catch (err) { 11393 console.warn(err); 11394 window.dispatchEvent(new CustomEvent('product-addtocart-error')); 11395 11396 setMessage({ 11397 text: 'There\u0020was\u0020a\u0020problem\u0020adding\u0020your\u0020item\u0020to\u0020the\u0020cart.', 11398 type: 'error' 11399 }); 11400 } finally { 11401 try { 11402 var messages = hyva.getCookie('mage-messages'); 11403 messages = messages ? JSON.parse(decodeURIComponent(messages).replace(/\+/g, ' ')) : []; 11404 document.cookie = 'mage-messages=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; 11405 11406 } catch (error) { 11407 console.warn('Error parsing Cookie Messages:', error); 11408 return; 11409 } 11410 11411 setTimeout(() => { 11412 loader.remove(); 11413 button.disabled = false; 11414 }, 0); 11415 } 11416 }); 11417 }); 11418 } 11419 11420 /** 11421 * Set Message 11422 */ 11423 function setMessage(message) { 11424 typeof window.dispatchMessages !== "undefined" && 11425 window.dispatchMessages([message], 5000); 11426 } 11427 11428 /** Init on pageload */ 11429 window.setAjaxCart(); 11430</script>
11430 11431
11432<script> 11433 'use strict'; 11434 (() => { 11435 function _calculateStrength({elementID, valid}) { 11436 const password = document.getElementById(elementID), 11437 emailElm = document.getElementById('email_address'); 11438 let displayScore; 11439 // Display score is based on combination of whether password is empty, valid, and zxcvbn strength 11440 if (!password || !password.value) { 11441 displayScore = 0; 11442 } else { 11443 // We should only perform this check in case there is an email field on screen 11444 if (emailElm && password.value && emailElm.value && 11445 password.value.toLowerCase() === emailElm.value.toLowerCase()) { 11446 displayScore = 1; 11447 } else { 11448 const zxcvbnScore = zxcvbn(password.value).score; 11449 displayScore = valid && zxcvbnScore > 0 ? zxcvbnScore : 1; 11450 } 11451 } 11452 11453 _displayStrength(displayScore); 11454 } 11455 11456 function _displayStrength(displayScore) { 11457 let strengthLabel = '', 11458 className; 11459 11460 switch (displayScore) { 11461 case 0: 11462 strengthLabel = "No\u0020Password"; 11463 className = 'password-none'; 11464 break; 11465 11466 case 1: 11467 strengthLabel = "Weak"; 11468 className = 'password-weak'; 11469 break; 11470 11471 case 2: 11472 strengthLabel = "Medium"; 11473 className = 'password-medium'; 11474 break; 11475 11476 case 3: 11477 strengthLabel = "Strong"; 11478 className = 'password-strong'; 11479 break; 11480 11481 case 4: 11482 strengthLabel = "Very\u0020Strong"; 11483 className = 'password-very-strong'; 11484 break; 11485 } 11486 const meterElm = document.getElementById('password-strength-meter-container'), 11487 meterLabelElm = document.getElementById('password-strength-meter-label'); 11488 meterElm.className = ''; 11489 meterElm.classList.add(className); 11490 meterLabelElm.textContent = strengthLabel; 11491 } 11492 11493 let loading = false; 11494 const callbacks = []; 11495 11496 function loadZxcvbn(cb) { 11497 callbacks.push(cb); 11498 if (loading) return; 11499 loading = true; 11500 11501 const script = document.createElement('script'); 11502 script.type = 'text/javascript'; 11503 script.src = 'https\u003A\u002F\u002Fwww.purdys.com\u002Fstatic\u002Fversion1790083580\u002Ffrontend\u002FPurdys2\u002Fhyva\u002Fen_US\u002Fjs\u002Fzxcvbn.js'; 11504 script.async = true; 11505 script.onload = () => callbacks.forEach(cb => cb()); 11506 document.head.appendChild(script); 11507 } 11508 11509 window.addEventListener('password-validate', function (evt) { 11510 const meterElm = document.getElementById('password-strength-meter-container'), 11511 meterLabelElm = document.getElementById('password-strength-meter-label'); 11512 if (meterElm && meterLabelElm) { 11513 if (evt.detail && evt.detail.elementID) { 11514 const cb = () => _calculateStrength(evt.detail); 11515 window.zxcvbn ? cb() : loadZxcvbn(cb) 11516 } 11517 } 11518 }); 11519 11520 11521 document.addEventListener('DOMContentLoaded', () => { 11522 hyva && hyva.formValidation && hyva.formValidation.addRule('password-strength', function(value, options, field, context) { 11523 let counter = 0; 11524 const minCharacterSets = options.minCharacterSets !== undefined ? options.minCharacterSets : 1; 11525 if (value.match(/\d+/)) { 11526 counter++; 11527 } 11528 11529 if (value.match(/[a-z]+/)) { 11530 counter++; 11531 } 11532 11533 if (value.match(/[A-Z]+/)) { 11534 counter++; 11535 } 11536 11537 if (value.match(/[^a-zA-Z0-9]+/)) { 11538 counter++; 11539 } 11540 11541 queueMicrotask(() => { 11542 window.dispatchEvent(new CustomEvent('password-validate', { detail: {
11543 elementID: field.element.id || 'password', 11544 valid: field.state.valid 11545 }})); 11546 }); 11547 11548 if (counter < minCharacterSets) { 11549 const missing = minCharacterSets - counter; 11550 return hyva.str('The\u0020password\u0020must\u0020include\u0020at\u0020least\u0020\u00251\u0020or\u0020more\u0020of\u003A\u0020uppercase\u0020\u0028A\u002DZ\u0029,\u0020lowercase\u0020\u0028a\u002Dz\u0029,\u0020number\u0020and\u0020symbol.', missing); 11551 } 11552 return true; 11553 }); 11554 }) 11555 })(); 11556 11557</script>
vendor: 1 bytes, line 11557
11557
11558<script type="text/javascript"> 11559/** 11560 * Mobile Video Switcher for PageBuilder Banner Videos 11561 * Properly integrates with Jarallax video system 11562 */ 11563(function() { 11564 'use strict'; 11565 11566 // Store original video sources to prevent data loss 11567 const originalVideoSources = new Map(); 11568 11569 /** 11570 * Switch video source based on screen size 11571 */ 11572 function switchVideoSource() { 11573 const videoElements = document.querySelectorAll('[data-background-type="video"][data-video-src-mobile]'); 11574 11575 videoElements.forEach(function(element) { 11576 const mobileVideoSrc = element.getAttribute('data-video-src-mobile'); 11577 const currentVideoSrc = element.getAttribute('data-video-src'); 11578 11579 // Store original video source if not already stored 11580 if (!originalVideoSources.has(element)) { 11581 originalVideoSources.set(element, currentVideoSrc); 11582 } 11583 11584 const originalVideoSrc = originalVideoSources.get(element); 11585 11586 // Validate video sources 11587 const targetVideoSrc = window.innerWidth <= 768 ? mobileVideoSrc : originalVideoSrc; 11588 if (!targetVideoSrc) { 11589 return; 11590 } 11591 11592 if (window.innerWidth <= 768) { 11593 // Mobile: use mobile video source 11594 if (currentVideoSrc !== mobileVideoSrc) { 11595 // Update the data-video-src attribute 11596 element.setAttribute('data-video-src', mobileVideoSrc); 11597 11598 // Reinitialize Jarallax 11599 reinitializeJarallax(element, mobileVideoSrc); 11600 } 11601 } else { 11602 // Desktop: use original video source 11603 if (currentVideoSrc !== originalVideoSrc) { 11604 // Update the data-video-src attribute 11605 element.setAttribute('data-video-src', originalVideoSrc); 11606 11607 // Reinitialize Jarallax 11608 reinitializeJarallax(element, originalVideoSrc); 11609 } 11610 } 11611 }); 11612 } 11613 11614 /** 11615 * Properly reinitialize Jarallax with new video source 11616 */ 11617 function reinitializeJarallax(element, videoSrc) { 11618 // Destroy existing Jarallax instance if it exists 11619 if (element.jarallax && typeof element.jarallax.destroy === 'function') { 11620 element.jarallax.destroy(); 11621 } 11622 11623 // Remove any existing jarallax-container elements 11624 const existingContainers = element.querySelectorAll('[id^="jarallax-container-"]'); 11625 existingContainers.forEach(container => { 11626 container.remove(); 11627 }); 11628 11629 // Wait a bit for cleanup to complete 11630 setTimeout(() => { 11631 if (typeof window.jarallax !== 'undefined') { 11632 // Get all the original attributes for proper reinitialization 11633 const videoLoop = element.getAttribute('data-video-loop') === 'true'; 11634 const videoPlayOnlyVisible = element.getAttribute('data-video-play-only-visible') === 'true'; 11635 const videoLazyLoad = element.getAttribute('data-video-lazy-load') === 'true'; 11636 const videoFallbackSrc = element.getAttribute('data-video-fallback-src') || ''; 11637 11638 // Ensure the element has the proper data attributes for video 11639 element.setAttribute('data-background-type', 'video'); 11640 element.setAttribute('data-video-src', videoSrc); 11641 11642 // Reinitialize Jarallax with the new video source 11643 window.jarallax(element, { 11644 videoSrc: videoSrc, 11645 videoLoop: videoLoop, 11646 videoPlayOnlyVisible: videoPlayOnlyVisible, 11647 videoLazyLoading: videoLazyLoad, 11648 imgSrc: videoFallbackSrc 11649 }); 11650 11651 // Check if video was created successfully 11652 setTimeout(() => { 11653 const videoContainer = element.querySelector('[id^="jarallax-container-"]'); 11654 const videoElement = videoContainer ? videoContainer.querySelector('video') : null; 11655 11656 if (videoContainer && !videoElement) { 11657 createManualVideoElement(videoContainer, videoSrc); 11658 } else if (videoElement) { 11659 fixVideoPositioning(videoElement); 11660 ensureVideoPlays(videoElement); 11661 } 11662 }, 500); 11663 } 11664 }, 200); 11665 } 11666 11667 /** 11668 * Create a manual video element if Jarallax fails to create one 11669 */ 11670 function createManualVideoElement(container, videoSrc) { 11671 const video = document.createElement('video'); 11672 video.setAttribute('loop', ''); 11673 video.setAttribute('playsinline', ''); 11674 video.setAttribute('webkit-playsinline', ''); 11675 video.setAttribute('autoplay', ''); 11676 video.setAttribute('muted', ''); 11677 video.setAttribute('id', 'VideoWorker-' + Date.now()); 11678 11679 // Use absolute positioning to stay in place relative to container 11680 video.style.cssText = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; max-width: none; max-height: none; pointer-events: none; transform-style: preserve-3d; backface-visibility: hidden; will-change: transform, opacity; z-index: -1; object-fit: cover;'; 11681 11682 const source = document.createElement('source'); 11683 source.setAttribute('src', videoSrc); 11684 source.setAttribute('type', 'video/mp4'); 11685 video.appendChild(source); 11686 11687 container.appendChild(video); 11688 11689 // Ensure the video plays 11690 ensureVideoPlays(video); 11691 } 11692 11693 /** 11694 * Fix video positioning to prevent scroll movement 11695 */ 11696 function fixVideoPositioning(videoElement) { 11697 // Check current position 11698 const currentPosition = videoElement.style.position; 11699 11700 // If position is fixed, change to absolute 11701 if (currentPosition === 'fixed') { 11702 videoElement.style.position = 'absolute'; 11703 videoElement.style.top = '0'; 11704 videoElement.style.left = '0'; 11705 videoElement.style.right = 'auto'; 11706 videoElement.style.bottom = 'auto'; 11707 } 11708 11709 // Ensure proper sizing and object-fit 11710 videoElement.style.width = '100%'; 11711 videoElement.style.height = '100%'; 11712 videoElement.style.objectFit = 'cover'; 11713 videoElement.style.objectPosition = 'center'; 11714 } 11715 11716 /** 11717 * Ensure video plays properly 11718 */ 11719 function ensureVideoPlays(videoElement) { 11720 // Set video properties 11721 videoElement.muted = true; 11722 videoElement.loop = true;
11723 videoElement.playsInline = true; 11724 videoElement.autoplay = true; 11725 11726 // Try to play the video 11727 const playPromise = videoElement.play(); 11728 11729 if (playPromise !== undefined) { 11730 playPromise.catch(() => { 11731 // Try again after a short delay 11732 setTimeout(() => { 11733 videoElement.play().catch(() => {}); 11734 }, 1000); 11735 }); 11736 } 11737 } 11738 11739 /** 11740 * Wait for Jarallax to be ready and then switch videos 11741 */ 11742 function waitForJarallaxAndSwitch() { 11743 if (typeof window.jarallax !== 'undefined') { 11744 switchVideoSource(); 11745 } else { 11746 setTimeout(waitForJarallaxAndSwitch, 500); 11747 } 11748 } 11749 11750 // Initialize on DOM ready 11751 if (document.readyState === 'loading') { 11752 document.addEventListener('DOMContentLoaded', function() { 11753 setTimeout(waitForJarallaxAndSwitch, 1000); 11754 }); 11755 } else { 11756 // DOM is already ready 11757 setTimeout(waitForJarallaxAndSwitch, 1000); 11758 } 11759 11760 // Handle window resize 11761 let resizeTimeout; 11762 window.addEventListener('resize', function() { 11763 clearTimeout(resizeTimeout); 11764 resizeTimeout = setTimeout(function() { 11765 waitForJarallaxAndSwitch(); 11766 }, 250); 11767 }); 11768})(); 11769</script>
11769 11770
11771<script> 11772 window.addEventListener('load', function () { 11773 magentoStorefrontEvents.context.setPage({ 11774 pageType: "MyAccount" 11775 }); 11776 magentoStorefrontEvents.context.setContext("pageExtended", { 11777 action: "login" 11778 }); 11779 magentoStorefrontEvents.context.setShoppingCart({ 11780 id: 0 }); 11781 11782 const pageAction = magentoStorefrontEvents.context.getContext("pageExtended").action; 11783 11784 if (pageAction === "logout") { 11785 handleLogoutActions(); 11786 } else if (pageAction === "login") { 11787 handleLoginActions(); 11788 } 11789 11790 function handleLoginActions() { 11791 document.getElementById("customer-login-form").addEventListener("submit", function () { 11792 const email = document.getElementById("email"); 11793 if (email && email.value) { 11794 magentoStorefrontEvents.context.setAccount({ emailAddress: email.value }); 11795 magentoStorefrontEvents.publish.signIn(); 11796 } 11797 }); 11798 } 11799 11800 function handleLogoutActions() { 11801 let accountContext = magentoStorefrontEvents.context.getAccount() || {}; 11802 11803 if (Object.entries(accountContext).length === 0) { 11804 magentoStorefrontEvents.publish.signOut(); 11805 magentoStorefrontEvents.context.setShopper({ shopperId: "guest" }); 11806 } 11807 } 11808 }); 11809</script>
11809 11810<div x-cloak x-data="{...initAmBanners()}" x-init="initBanners()"> 11811 <div class="flex flex-row justify-center items-center w-full h-full fixed select-none z-50" 11812 style="left: 50%;top: 50%;transform: translateX(-50%) translateY(-50%);background: rgba(255,255,255,0.7);" 11813 x-show="isLoading" 11814 x-cloak 11815 x-transition:enter="ease-out duration-200" 11816 x-transition:enter-start="opacity-0" 11817 x-transition:enter-end="opacity-100" 11818 x-transition:leave="ease-in duration-200" 11819 x-transition:leave-start="opacity-100" 11820 x-transition:leave-end="opacity-0"> 11821 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 56 56" fill="none" class="animate-[spin_2s_linear_infinite] text-primary" role="img"> 11822 <path d="M52.5 28C52.5 31.2174 51.8663 34.4033 50.6351 37.3757C49.4038 40.3482 47.5992 43.0491 45.3241 45.3241C43.0491 47.5992 40.3482 49.4038 37.3757 50.635C34.4033 51.8663 31.2174 52.5 28 52.5C24.7826 52.5 21.5967 51.8663 18.6243 50.635C15.6518 49.4038 12.9509 47.5991 10.6759 45.3241C8.40085 43.0491 6.59619 40.3482 5.36495 37.3757C4.13371 34.4033 3.5 31.2174 3.5 28C3.5 24.7826 4.13371 21.5967 5.36496 18.6242C6.5962 15.6518 8.40086 12.9509 10.6759 10.6759C12.9509 8.40084 15.6518 6.59619 18.6243 5.36495C21.5967 4.13371 24.7826 3.5 28 3.5C31.2174 3.5 34.4033 4.13371 37.3758 5.36496C40.3482 6.5962 43.0491 8.40086 45.3241 10.6759C47.5992
1182212.9509 49.4038 15.6518 50.6351 18.6243C51.8663 21.5967 52.5 24.7826 52.5 28L52.5 28Z" stroke="#EEEAF4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> 11823 <path d="M52.5 28C52.5 24.7826 51.8663 21.5967 50.6351 18.6243C49.4038 15.6518 47.5992 12.9509 45.3241 10.6759C43.0491 8.40085 40.3482 6.59619 37.3757 5.36495C34.4033 4.13371 31.2174 3.5 28 3.5" stroke="#532E91" stroke-width="6" stroke-linecap="square"/> 11824<title>loader</title></svg> 11825</div></div>
11826<script> 11827 'use strict'; 11828 11829 function initAmBanners() { 11830 return { 11831 options: { 11832 sections: [], 11833 content: [], 11834 banners: [], 11835 injectorSectionId: null, 11836 injectorParams: {} 11837 }, 11838 isLoading: true, 11839 injectorResolver: {}, 11840 11841 initBanners: function () { 11842 this.options = {"sections":{"14":[190,193]},"content":{"190":"\n<div data-banner-id=\"190\">\n <a href='https:\/\/www.purdys.com\/halloween-gift-ideas'><img src='https:\/\/www.purdys.com\/media\/amasty\/ampromobanners\/banner_190_Halloween-Coffin-Gift-Box-Category-Promo.jpg' alt='' \/><\/a> <\/div>\n","193":"\n<div data-banner-id=\"193\">\n <a href='https:\/\/www.purdys.com\/gifts\/diwali'><img src='https:\/\/www.purdys.com\/media\/amasty\/ampromobanners\/banner_193_Diwali-Category-Promo.jpg' alt='' \/><\/a> <\/div>\n"},"injectorParams":{"containerSelector":".products-grid .product-items:not(.amasty-banner-items)","itemSelector":".products-grid .product-items:not(.amasty-banner-items) .product-item","banners":{"190":{"afterProductRow":1,"positionInProductRow":0,"afterProductNum":4,"width":1,"priority":0,"id":190},"193":{"afterProductRow":1,"positionInProductRow":0,"afterProductNum":4,"width":1,"priority":0,"id":193}}},"banners":[],"injectorSectionId":14}; 11843 this.injectorResolver = amBannerInjectorResolver?.(); 11844 11845 this.insertBanners(); 11846 this.isLoading = false; 11847 }, 11848 11849 insertBanners: function () { 11850 document.querySelectorAll('[data-role="amasty-banner-container"]').forEach((container) => { 11851 const sectionId = container.getAttribute('data-position'); 11852 11853 if (sectionId) { 11854 this.loadBannerBySection(sectionId, container); 11855 } else { 11856 this.loadBannerByBannerId(container); 11857 } 11858 }); 11859 }, 11860 11861 getBanners: function (bannerId) { 11862 if (this.options.banners.indexOf(bannerId) === -1) { 11863 return []; 11864 } 11865 const insertedBanner = this.banners.find((element) => { 11866 return element === bannerId; 11867 }); 11868 11869 return content[insertedBanner]; 11870 }, 11871 11872 getSectionBanners: function (section) { 11873 if (!(section in this.options.sections)) { 11874 return []; 11875 } 11876 11877 return this.options.sections[section].map((id) => { 11878 return this.options.content[id]; 11879 }); 11880 }, 11881 11882 loadBannerBySection: function (sectionId, container) { 11883 container.innerHTML = this.getSectionBanners(sectionId).join(''); 11884 this.injectorResolver?.resolve(sectionId, this.options.injectorParams); 11885 this.updateSection(`[data-role="amasty-banner-container"][data-position="${sectionId}"]`, container); 11886 }, 11887 11888 loadBannerByBannerId: function (container) { 11889 const bannerId = container.getAttribute('data-bannerid'); 11890 container.innerHTML = (this.getBanners(bannerId)); 11891 this.updateSection(`[data-role="amasty-banner-container"][data-bannerid="${bannerId}"]`, container); 11892 }, 11893 11894 updateSection: function(containerSelector, container) { 11895 hyva.replaceDomElement( 11896 containerSelector, 11897 container.innerHTML 11898 ); 11899 } 11900 } 11901 } 11902</script>
11902 11903
11904<script data-source="Purdys_CustomWeltPixelEvents / products-click-tracking.phtml"> 11905 (() => { 11906 // [1] Get the utilities 11907 const utils = window.customWeltPixelEventsUtils; 11908 11909 if (!utils) { 11910 console.error('Purdys_CustomWeltPixelEvents / products-click-tracking.phtml: customWeltPixelEventsUtils is not defined'); 11911 return; 11912 } 11913 11914 // [2] Wait for DOM to be fully loaded before setting up tracking 11915 utils.dom.onDomContentLoaded(() => { 11916 utils.dom.addMutationObserver(trackProductClick); 11917 }) 11918 11919 /** 11920 * Track the product click event 11921 * 11922 * @returns {void} 11923 */ 11924 function trackProductClick() { 11925 // Find all product links - try multiple selectors to catch different product link patterns 11926 const productLinks = document.querySelectorAll('a.product, .product-item a, .product-item-info a, [data-product-id]'); 11927 11928 if (!productLinks.length) { 11929 // console.warn('Purdys_CustomWeltPixelEvents / products-click-tracking.phtml: No product links found to track select_item event'); 11930 return; 11931 } 11932 11933 // Add click event listeners to each product link 11934 productLinks.forEach((link, index) => { 11935 // Skip if already has click listener to prevent duplicates 11936 if (link.hasAttribute('data-weltpixel-tracked')) { 11937 return; 11938 } 11939 11940 link.addEventListener('click', () => { 11941 // Fire the select_item event 11942 fireSelectItemEvent(link, index + 1); 11943 }); 11944 11945 // Mark as tracked to prevent duplicate listeners 11946 link.setAttribute('data-weltpixel-tracked', 'true'); 11947 }); 11948 } 11949 11950 /** 11951 * Get the product data and fire the select_item event 11952 * 11953 * @param {Element} productLink - The clicked product link element 11954 * @param {number} index - The position of the product in the list 11955 */ 11956 async function fireSelectItemEvent(productLink, index) { 11957 // Get the product ID from the form 11958 const itemId = productLink?.closest('form')?.querySelector('input[name="product"]')?.value; 11959 11960 // If no product ID found, return 11961 if (!itemId) { 11962 // console.warn('Purdys_CustomWeltPixelEvents / products-click-tracking.phtml: No product ID found to track select_item event'); 11963 return; 11964 } 11965 11966 // Get the product data 11967 const productData = await getProductData(Number(itemId)); 11968 11969 // Enhance the product data 11970 const enhancedProductData = enchanceData(productData, productLink); 11971 11972 // Fire the select_item event 11973 window.dataLayer.push({ 11974 event: 'select_item', 11975 ecommerce: { 11976 items: [enhancedProductData] 11977 } 11978 }); 11979 } 11980 11981 /** 11982 * Get the product data via AJAX call to PHP helper 11983 * 11984 * @param {string} productId - Product ID/SKU 11985 * @returns {Promise<Object>} Product data 11986 */ 11987 async function getProductData(productId) { 11988 // SECURITY: Use GET request with query parameters (matches CSRF protection) 11989 // Use absolute URL to prevent issues on PLPs where relative URLs get resolved incorrectly 11990 const url = `/customweltpixelevents/data/products?productsIds=${productId}`; 11991 const response = await fetch(url, { 11992 method: 'GET', 11993 headers: { 11994 'X-Requested-With': 'XMLHttpRequest' 11995 } 11996 }); 11997 11998 if (!response.ok) { 11999 console.error('Failed to fetch product data:', response.status, response.statusText); 12000 return {}; 12001 } 12002 12003 const data = await response.json(); 12004 12005 return data.products[productId] || {}; 12006 } 12007 12008 /** 12009 * Enhance the product data 12010 * 12011 * @param {Object} productData - Product data 12012 * @returns {Object} Enhanced product data 12013 */ 12014 function enchanceData(productData, productLink) { 12015 // Enhance the index 12016 productData.index = getIndex(productLink); 12017 12018 // Item list name 12019 productData.item_list_name = productData.item_list_name ? productData.item_list_name : getItemListName(productLink, productData); 12020 12021 // Item list id 12022 const itemListIds = { 12023 'Seasonal Collections': 'home_page_product_sliders', 12024 'Shop Bestsellers': 'home_page_product_sliders', 12025 'Recommended Products': 'related_products', 12026 'Search Result': 'search_result' 12027 }; 12028 12029 if (productData.item_list_id) { 12030 productData.item_list_id = productData.item_list_id; 12031 } else if (productData.item_list_name && itemListIds[productData.item_list_name]) { 12032 productData.item_list_id = itemListIds[productData.item_list_name]; 12033 } else if (productData.item_list_name) { 12034 productData.item_list_id = productData.item_list_name.toLowerCase().replace(/ /g, '_').replace(/,/g, ''); 12035 } else { 12036 productData.item_list_id = 'N/A'; 12037 } 12038 12039 return productData; 12040 } 12041 12042 /** 12043 * Get the index of the product 12044 * 12045 * @param {Element} productLink - The clicked product link element 12046 * @returns {number|null} The index of the product 12047 */ 12048 function getIndex(productLink) { 12049 // [1] Get the index from the aria-label (Home Page & PDPs Product Sliders) 12050 const indexFromAriaLabel = productLink.closest('.swiper-slide')?.getAttribute('aria-label')?.split('/')[0]; 12051 12052 if (indexFromAriaLabel) { 12053 return parseInt(indexFromAriaLabel); 12054 } 12055 12056 // [2] Get the index from the closest form (PLPs, Category Pages, Search Results) 12057 const closestForm = productLink.closest('form'); 12058 if (closestForm) { 12059 const productForms = [...closestForm.parentElement.querySelectorAll('form')]; 12060 const formIndex = productForms.indexOf(closestForm); 12061 if (formIndex !== -1) { 12062 return formIndex + 1; 12063 } 12064 } 12065 12066 return "N/A"; 12067 } 12068 12069 function getItemListName(productLink, productData) { 12070 // [1] Get the item list name from the header (Ecommerce Home Page & PDPs Product Sliders) 12071 const productListContainer = productLink.closest('[x-data*="initFeaturedProducts"], [x-data*="initProductSlider"]'); 12072 const itemListName = productListContainer?.querySelector('h1, h2, h3')?.innerText?.trim(); 12073 12074 const itemListNames = { 12075 'Seasonal Collections': `Home Page ${itemListName}`, 12076 'Shop Bestsellers': `Home Page ${itemListName}`, 12077 'Recommended Products': getCurrentProductName() ? `Related Products From ${getCurrentProductName()}` : 'Related Products', 12078 }; 12079 12080 if (itemListName) { 12081 return itemListNames[itemListName] || itemListName; 12082 } 12083 12084 // [2] Get the item list name from the header (GnF Home Page) 12085 const gnfProductListContainer = productLink.closest('.selection-container'); 12086 const gnfItemListName = gnfProductListContainer?.querySelector('h1, h2, h3')?.innerText?.trim(); 12087 12088 if (gnfItemListName) { 12089 return gnfItemListName; 12090 } 12091 12092 // [2] Get the item list name based on the page type (PLPs, Category Pages, Search Results) 12093 const pageTitle = document.querySelector('h1.page-title')?.innerText?.trim(); 12094 12095 if (typeof pageTitle === 'string' && pageTitle.includes('Search results')) { 12096 return 'Search Result'; 12097 } 12098 12099 return pageTitle || null; 12100 } 12101 12102 12103 // Get current product name for PDP context 12104 function getCurrentProductName() { 12105 // Try to get product name from sticky cart (most reliable on PDP) 12106 const stickyProductName = document.querySelector('.sticky-cart .font-bold.body-2')?.innerText?.trim(); 12107 if (stickyProductName) { 12108 return stickyProductName; 12109 } 12110 12111 // Fallback: try to get from page title or other product name elements 12112 const pageTitle = document.querySelector('h1.page-title')?.innerText?.trim(); 12113 if (pageTitle && !pageTitle.includes('Search results')) { 12114 return pageTitle; 12115 } 12116 12117 // Fallback: try to get from product info section 12118 const productInfoName = document.querySelector('#custom-product-info h1, #custom-product-info .product-name')?.innerText?.trim(); 12119 if (productInfoName) { 12120 return productInfoName; 12121 } 12122 12123 return null; 12124 }; 12125 })(); 12126</script>
vendor: 1 bytes, line 12126
12126
12127<script type="text/json" id="default-section-data"> 12128 {"messages":[],"customer":[],"compare-products":[],"last-ordered-items":[],"requisition":[],"cart":[],"directory-data":{"AU":{"name":"Australia","regions":{"605":{"code":"ACT","name":"Australian Capital Territory"},"606":{"code":"NSW","name":"New South Wales"},"612":{"code":"NT","name":"Northern Territory"},"608":{"code":"QLD","name":"Queensland"},"609":{"code":"SA","name":"South Australia"},"610":{"code":"TAS","name":"Tasmania"},"607":{"code":"VIC","name":"Victoria"},"611":{"code":"WA","name":"Western Australia"}}},"CA":{"name":"Canada","regions":{"66":{"code":"AB","name":"Alberta"},"67":{"code":"BC","name":"British Columbia"},"68":{"code":"MB","name":"Manitoba"},"70":{"code":"NB","name":"New Brunswick"},"69":{"code":"NL","name":"Newfoundland and Labrador"},"72":{"code":"NT","name":"Northwest Territories"},"71":{"code":"NS","name":"Nova Scotia"},"73":{"code":"NU","name":"Nunavut"},"74":{"code":"ON","name":"Ontario"},"75":{"code":"PE","name":"Prince Edward Island"},"76":{"code":"QC","name":"Quebec"},"77":{"code":"SK","name":"Saskatchewan"},"78":{"code":"YT","name":"Yukon Territory"}}},"DE":{"name":"Germany","regions":{"80":{"code":"BAW","name":"Baden-W\u00fcrttemberg"},"81":{"code":"BAY","name":"Bayern"},"82":{"code":"BER","name":"Berlin"},"83":{"code":"BRG","name":"Brandenburg"},"84":{"code":"BRE","name":"Bremen"},"85":{"code":"HAM","name":"Hamburg"},"86":{"code":"HES","name":"Hessen"},"87":{"code":"MEC","name":"Mecklenburg-Vorpommern"},"79":{"code":"NDS","name":"Niedersachsen"},"88":{"code":"NRW","name":"Nordrhein-Westfalen"},"89":{"code":"RHE","name":"Rheinland-Pfalz"},"90":{"code":"SAR","name":"Saarland"},"91":{"code":"SAS","name":"Sachsen"},"92":{"code":"SAC","name":"Sachsen-Anhalt"},"93":{"code":"SCN","name":"Schleswig-Holstein"},"94":{"code":"THE","name":"Th\u00fcringen"}}},"GB":{"name":"United Kingdom"},"JP":{"name":"Japan"},"NL":{"name":"Netherlands"},"US":{"name":"United States","regions":{"1":{"code":"AL","name":"Alabama"},"2":{"code":"AK","name":"Alaska"},"3":{"code":"AS","name":"American Samoa"},"4":{"code":"AZ","name":"Arizona"},"5":{"code":"AR","name":"Arkansas"},"6":{"code":"AE","name":"Armed Forces Africa"},"7":{"code":"AA","name":"Armed Forces Americas"},"8":{"code":"AE","name":"Armed Forces Canada"},"9":{"code":"AE","name":"Armed Forces Europe"},"10":{"code":"AE","name":"Armed Forces Middle East"},"11":{"code":"AP","name":"Armed Forces Pacific"},"12":{"code":"CA","name":"California"},"13":{"code":"CO","name":"Colorado"},"14":{"code":"CT","name":"Connecticut"},"15":{"code":"DE","name":"Delaware"},"16":{"code":"DC","name":"District of Columbia"},"17":{"code":"FM","name":"Federated States Of Micronesia"},"18":{"code":"FL","name":"Florida"},"19":{"code":"GA","name":"Georgia"},"20":{"code":"GU","name":"Guam"},"21":{"code":"HI","name":"Hawaii"},"22":{"code":"ID","name":"Idaho"},"23":{"code":"IL","name":"Illinois"},"24":{"code":"IN","name":"Indiana"},"25":{"code":"IA","name":"Iowa"},"26":{"code":"KS","name":"Kansas"},"27":{"code":"KY","name":"Kentucky"},"28":{"code":"LA","name":"Louisiana"},"29":{"code":"ME","name":"Maine"},"30":{"code":"MH","name":"Marshall Islands"},"31":{"code":"MD","name":"Maryland"},"32":{"code":"MA","name":"Massachusetts"},"33":{"code":"MI","name":"Michigan"},"34":{"code":"MN","name":"Minnesota"},"35":{"code":"MS","name":"Mississippi"},"36":{"code":"MO","name":"Missouri"},"37":{"code":"MT","name":"Montana"},"38":{"code":"NE","name":"Nebraska"},"39":{"code":"NV","name":"Nevada"},"40":{"code":"NH","name":"New Hampshire"},"41":{"code":"NJ","name":"New Jersey"},"42":{"code":"NM","name":"New Mexico"},"43":{"code":"NY","name":"New York"},"44":{"code":"NC","name":"North Carolina"},"45":{"code":"ND","name":"North Dakota"},"46":{"code":"MP","name":"Northern Mariana Islands"},"47":{"code":"OH","name":"Ohio"},"48":{"code":"OK","name":"Oklahoma"},"49":{"code":"OR","name":"Oregon"},"50":{"code":"PW","name":"Palau"},"51":{"code":"PA","name":"Pennsylvania"},"52":{"code":"PR","name":"Puerto Rico"},"53":{"code":"RI","name":"Rhode Island"},"54":{"code":"SC","name":"South Carolina"},"55":{"code":"SD","name":"South Dakota"},"56":{"code":"TN","name":"Tennessee"},"57":{"code":"TX","name":"Texas"}
12128,"58":{"code":"UT","name":"Utah"},"59":{"code":"VT","name":"Vermont"},"60":{"code":"VI","name":"Virgin Islands"},"61":{"code":"VA","name":"Virginia"},"62":{"code":"WA","name":"Washington"},"63":{"code":"WV","name":"West Virginia"},"64":{"code":"WI","name":"Wisconsin"},"65":{"code":"WY","name":"Wyoming"}}},"data_id":1790365654},"captcha":[],"wishlist":{"items":[]},"company":[],"company_authorization":[],"customer-companies":[],"negotiable_quote":[],"purchase_order":[],"instant-purchase":[],"loggedAsCustomer":[],"multiplewishlist":[],"persistent":[],"review":[],"faq":[],"ammessages":[],"cart-data":[],"hyva_enterprise":[],"capi-event-ids":[],"wp_ga4":[],"yotposms-customer-behaviour":[],"recently_viewed_product":[],"recently_compared_product":[],"product_data_storage":[],"paypal-billing-agreement":[]}</script>
12128 12129<div x-data="dataAmMessage()" x-bind="eventListeners" x-spread="eventListeners"></div>
12130<script> 12131 function dataAmMessage() { 12132 return { 12133 eventListeners: { 12134 ['@private-content-loaded.window'](event) { 12135 const { ammessages } = event.detail.data; 12136 12137 // Check if we're not on the 'checkout-cart-index' page and if messages exist 12138 if (!document.body.classList.contains('checkout-cart-index') && ammessages?.messages) { 12139 this.processMessages(ammessages.messages); 12140 } 12141 12142 const messages = JSON.parse(localStorage.getItem('amFreeGiftCartMessages')); 12143 if (messages && messages.length > 0) { 12144 messages.forEach(message => { 12145 dispatchMessages([message], 5000); 12146 }); 12147 localStorage.removeItem('amFreeGiftCartMessages'); 12148 } 12149 } 12150 }, 12151 processMessages(messages) { 12152 if (messages.notice) { 12153 this.handleNoticeMessage(messages.notice); 12154 } 12155 }, 12156 handleNoticeMessage(notice) { 12157 if (!notice) return; 12158 const { type, text } = notice; 12159 dispatchMessages([{ type, text }]); 12160 } 12161 } 12162 } 12163</script>
12163 12164</div>
12164<script> 12165 function initAuthModal() { 12166 return { 12167 ...hyva.modal(), 12168 redirect: null, 12169 reload: false, 12170 showOrganizationModal: false, 12171 isInitializing: false, 12172 forceVisible: false, 12173 hide(value) { 12174 if (this.isInitializing) { 12175 return; 12176 } 12177 12178 this.forceVisible = false; 12179 window.localStorage.removeItem('login_campaign_cart'); 12180 this.$dispatch('set-landing', null); 12181 hyva.modal().hide.call(this, value); 12182 12183 setTimeout(() => { 12184 this.$dispatch('set-login-title', null); 12185 this.$dispatch('set-register-title', null); 12186 }, 800); 12187 12188 if (this.reload) { 12189 window.location.reload(); 12190 } 12191 }, 12192 show(dialog, trigger) { 12193 this.isInitializing = true; 12194 this.forceVisible = true; 12195 12196 const result = hyva.modal().show.call(this, dialog, trigger); 12197 setTimeout(() => { 12198 this.isInitializing = false; 12199 this.forceVisible = false; 12200 }, 500); 12201 12202 return result; 12203 }, 12204 handleOpenAuthModal(event) { 12205 if (event.detail) { 12206 this.showOrganizationModal = event.detail.showOrganizationModal; 12207 12208 // Handle tab parameter 12209 if (event.detail.tab) { 12210 // Dispatch event to set initial tab 12211 this.$dispatch('set-initial-tab', { tab: event.detail.tab }); 12212 } 12213 } 12214 } 12215 }; 12216 } 12217</script>
12217 12218<div 12219 x-data="initAuthModal" 12220 @set-redirect.window="redirect = $event.detail" 12221 @open-auth-modal.window="show('auth-modal', this.$event || $event); handleOpenAuthModal($event)" 12222 @close-auth-modal.window="hide('auth-modal')" 12223> 12224 <div x-cloak data-dialog-ref-name="auth-modal" 12225 x-bind="overlay" 12226 class="fixed inset-0 bg-black bg-opacity-50 z-50"> 12227 <div class="fixed flex justify-center items-center text-left z-40 items-center inset-0"> 12228 <div x-ref="auth-modal" role="dialog" aria-modal="true" 12229 class="inline-block bg-white shadow-xl p-6 md:p-[48px] max-h-[100dvh] w-full md:w-auto overflow-auto overscroll-y-contain rounded-none md:rounded h-fit lg:max-h-[90dvh] !rounded p-12 max-md:p-6 max-md:!w-full max-md:!rounded-none [&_.label]:mb-4 [&_.field.choice]:flex"> 12230 12231<div 12232 id="auth-modal-content" 12233 class="auth-modal-content modal-content mx-auto relative max-w-[400px] m-auto" 12234 x-data="authModalData()" 12235 :data-status="state" 12236 :class="{'max-w-none': state === 'register-success'}" 12237 @set-initial-tab.window="setInitialTab($event.detail.tab)"> 12238 <button class="modal-close absolute right-0 top-2 z-20 md:top-3 text-primary hover:bg-transparent !border-transparent !bg-transparent" 12239 @click="hide" 12240 x-show="state !== 'register-success' && state !== 'beta-redirect'"> 12241 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 26 24" fill="none" 12242 class="close-button-icon" role="img"> 12243 <path d="M3 2L23 21.5234" stroke="currentColor" stroke-width="4" stroke-linecap="round"></path> 12244 <path d="M3 22L23 2.47665" stroke="currentColor" stroke-width="4" stroke-linecap="round"></path> 12245 <title>x</title></svg> 12246 </button> 12247 <div x-show="state === 'login'"> 12248 <div class="login-modal-content text-black"> 12249 <div aria-labelledby="block-customer-login-heading"> 12250 <form class="form form-login" 12251 action="https://www.purdys.com/purdys-customer/AccountModal/login/" method="post" 12252 x-data="initCustomerLoginForm_6ab6cfd678bd1()" 12253 @submit.prevent="amRecaptchaModelExecute" 12254 x-on:amrecaptcha-executable-callback="submitForm" 12255 x-on:amrecaptcha-callback.window="if ($el.offsetParent !== null) amRecaptchaModelRecaptchaCallback($event)" 12256 x-on:amrecaptcha-expired-callback.window="if ($el.offsetParent !== null) amRecaptchaModelExpiredCallback($event)" 12257 id="customer-login-form-modal" 12258 data-role="customer-login-form"> 12259 <div class="flex flex-row start-0 top-0 justify-center items-center w-full h-full absolute select-none z-10" 12260 style="background: rgba(255,255,255,0.5);" 12261 x-show="isLoading" 12262 x-cloak 12263 x-transition:enter="ease-out duration-200" 12264 x-transition:enter-start="opacity-0" 12265 x-transition:enter-end="opacity-100" 12266 x-transition:leave="ease-in duration-200" 12267 x-transition:leave-start="opacity-100" 12268 x-transition:leave-end="opacity-0"> 12269 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 56 56" fill="none" class="animate-[spin_2s_linear_infinite] text-primary" role="img"> 12270 <path d="M52.5 28C52.5 31.2174 51.8663 34.4033 50.6351 37.3757C49.4038 40.3482 47.5992 43.0491 45.3241 45.3241C43.0491 47.5992 40.3482 49.4038 37.3757 50.635C34.4033 51.8663 31.2174 52.5 28 52.5C24.7826 52.5 21.5967 51.8663 18.6243 50.635C15.6518 49.4038 12.9509 47.5991 10.6759 45.3241C8.40085 43.0491 6.59619 40.3482 5.36495 37.3757C4.13371 34.4033 3.5 31.2174 3.5 28C3.5 24.7826 4.13371 21.5967 5.36496 18.6242C6.5962 15.6518 8.40086 12.9509 10.6759 10.6759C12.9509 8.40084 15.6518 6.59619 18.6243 5.36495C21.5967 4.13371 24.7826 3.5 28 3.5C31.2174 3.5 34.4033 4.13371 37.3758 5.36496C40.3482 6.5962 43.0491 8.40086 45.3241 10.6759C47.5992
1227012.9509 49.4038 15.6518 50.6351 18.6243C51.8663 21.5967 52.5 24.7826 52.5 28L52.5 28Z" stroke="#EEEAF4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> 12271 <path d="M52.5 28C52.5 24.7826 51.8663 21.5967 50.6351 18.6243C49.4038 15.6518 47.5992 12.9509 45.3241 10.6759C43.0491 8.40085 40.3482 6.59619 37.3757 5.36495C34.4033 4.13371 31.2174 3.5 28 3.5" stroke="#532E91" stroke-width="6" stroke-linecap="square"/> 12272<title>loader</title></svg> 12273</div> 12274 <input type="hidden" name="form_key" :value="hyva.getFormKey()" /> 12275 <input type="text" name="login[redirect]" x-model="redirect" hidden> 12276 <fieldset class="fieldset login"> 12277 <legend class="mb-8"> 12278 <h2 class="text-2xl md:text-h2-lg !font-bold text-black max-w-[310px] font-richmond" 12279 @set-login-title.window="customTitle = $event.detail" x-text="customTitle ?? defaultTitle"> 12280 </h2> 12281 </legend> 12282 <div class="text-black mb-8"> 12283 If you already have an account with us, sign in with your email address. </div> 12284 <div class="field email required" :class="{'field-error': wrongCredentials}"> 12285 <label class="label !font-normal after:!m-0" for="email"> 12286 <span>Email Address</span> 12287 </label> 12288 <div class="control"> 12289 <input name="email" class="form-input w-full" required x-model="email" 12290 autocomplete="off" id="email" 12291 type="email" placeholder="Enter your email address" 12292 title="Email Address" /> 12293 </div> 12294 </div> 12295 <div class="field password required mb-3" :class="{'field-error': wrongCredentials}"> 12296 <label for="pass after:!m-0 !font-normal" class="label"> 12297 <span>Password</span> 12298 </label> 12299 <div class="control flex items-center relative"> 12300 <div class="sr-only" aria-live="polite"> 12301 <template x-if="!showPassword"> 12302 <span> 12303 Password hidden </span> 12304 </template> 12305 <template x-if="showPassword"> 12306 <span> 12307 Password shown </span> 12308 </template> 12309 </div> 12310 <input name="password" class="form-input w-full" required 12311 :type="showPassword ? 'text' : 'password'" x-model="password" 12312 placeholder="Enter your password" 12313 autocomplete="off" 12314 id="pass" title="Password" /> 12315 <button type="button" @click.stop="showPassword = !showPassword" 12316 :aria-pressed="showPassword ? 'true' : 'false'" class="px-4 py-3 absolute right-0 !border-transparent h-full !bg-transparent" 12317 :aria-label="showPassword ? 'Hide\u0020Password' : 'Show\u0020Password'"> 12318 <template x-if="!showPassword"> 12319 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" width="24" height="24" role="img"> 12320 <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/> 12321 <path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/> 12322<title>eye</title></svg> 12323 </template> 12324 <template x-if="showPassword"> 12325 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" width="24" height="24" role="img"> 12326 <path fill-rule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clip-rule="evenodd"/> 12327 <path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z"/> 12328<title>
12328eye-off</title></svg> 12329 </template> 12330 </button> 12331 </div> 12332 </div> 12333 12334 <span class="text-[14px] leading-[18px] text-red contents" x-show="wrongCredentials"> 12335 Incorrect email or password. Try again or <a href="#" class="underline mb-4 !text-red" 12336 @click.prevent="setState('forgotPassword', { email: email })"> 12337 <span class="font-bold">Forgot your password?</span> 12338 </a> 12339 </span> 12340 12341 <a href="#" x-show="!wrongCredentials" class="underline block mb-4" 12342 @click.prevent="setState('forgotPassword', { email: email })"> 12343 <span class="text-base font-bold">Forgot your password?</span> 12344 </a> 12345 12346 <div class="pt-4 pb-2"> 12347 <button type="submit" class="btn btn-primary disabled:opacity-75 w-full h-11" name="send" 12348 :disabled="!email || !password || isLoading" > 12349 <span class="body-2 leading-[22px]">Log In</span></button> 12350 </div> 12351 <div> 12352 <template x-if="displayErrorMessage"> 12353 <p class="text-red flex items-center"> 12354 <span class="inline-block w-8 h-8 mr-3"> 12355 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="24" height="24" role="img"> 12356 <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> 12357<title>exclamation-circle</title></svg> 12358 </span> 12359 <template x-for="errorMessage in errorMessages"> 12360 <span x-html="errorMessage"></span> 12361 </template> 12362 </p> 12363 </template> 12364 </div> 12365 </fieldset> 12366 </form> 12367 </div> 12368 <div class="w-full"> 12369 </div> 12370
12370<script> 12371 // Stub function to prevent amRecaptchaModelExecute error 12372 if (typeof window.amRecaptchaModelExecute === 'undefined') { 12373 window.amRecaptchaModelExecute = function() { 12374 // Stub function - does nothing 12375 return true; 12376 }; 12377 } 12378 12379 function initCustomerLoginForm_6ab6cfd678bd1() { 12380 return { 12381 defaultTitle: 'Sign in or create an account to continue', 12382 customTitle: null, 12383 errors: 0, 12384 hasCaptchaToken: 0, 12385 showPassword: false, 12386 displayErrorMessage: false, 12387 errorMessages: [], 12388 email: '', 12389 password: '', 12390 isLoading: false, 12391 wrongCredentials: false, 12392 isSubmitting: false, 12393 submitTimeout: null, 12394 captchaTimeout: null, 12395 setErrorMessages(messages) { 12396 this.errorMessages = [messages] 12397 this.displayErrorMessage = this.errorMessages.length 12398 }, 12399 submitForm() { 12400 console.log('LOGIN submitForm called, isLoading:', this.isLoading, 'isSubmitting:', this.isSubmitting, 'formId: customer-login-form-modal'); 12401 12402 // Bouncer: prevent multiple rapid submissions 12403 if (this.isSubmitting) { 12404 console.log('Form already submitting, returning. isLoading:', this.isLoading, 'isSubmitting:', this.isSubmitting); 12405 return; 12406 } 12407 12408 // Clear any existing timeouts 12409 if (this.submitTimeout) { 12410 clearTimeout(this.submitTimeout); 12411 } 12412 if (this.captchaTimeout) { 12413 clearTimeout(this.captchaTimeout); 12414 this.captchaTimeout = null; 12415 } 12416 12417 // Set submitting state and timeout 12418 this.isSubmitting = true; 12419 this.submitTimeout = setTimeout(() => { 12420 console.log('Submit timeout reached, resetting isSubmitting'); 12421 this.isSubmitting = false; 12422 // Also reset loading state as a safety measure 12423 if (this.isLoading) { 12424 console.log('Also resetting stuck isLoading state'); 12425 this.isLoading = false; 12426 } 12427 }, 15000); // 15 second timeout (longer to allow for network delays) 12428 12429 this.wrongCredentials = false; 12430 const $form = document.querySelector('#customer-login-form-modal'); 12431 12432 if (this.errors === 0) { 12433 this.isLoading = true; 12434 fetch('https://www.purdys.com/purdys-customer/AccountModal/login/', { 12435 method: 'POST', 12436 body: new FormData($form) 12437 }) 12438 .then(response => response.json()) 12439 .then(data => { 12440 if (data.success) { 12441 localStorage.setItem('showLoginSuccess', 'true'); 12442 if (data.customer) { 12443 localStorage.setItem('customerData', JSON.stringify(data.customer)); 12444 } 12445 12446 const cartData = window.localStorage.getItem('login_campaign_cart'); 12447 12448 this.$dispatch('reload-customer-section-data'); 12449 12450 if (cartData) { 12451 window.localStorage.removeItem('login_campaign_cart'); 12452 const { 12453 product, 12454 campaign, 12455 qty = 1 12456 } = JSON.parse(cartData); 12457 this.hide('auth-modal'); 12458 this.$dispatch('campaign-add-to-cart', { 12459 product, 12460 qty, 12461 campaign, 12462 referral: true 12463 }); 12464 return; 12465 } 12466 12467 if (this.redirect) { 12468 window.location.href = this.redirect; 12469 return; 12470 } 12471 12472 if (this.showOrganizationModal) { 12473 this.setState('organization'); 12474 this.reload = true; 12475 return; 12476 } 12477 12478 if (data.redirectToDashboard && window.location.pathname.includes('/fr')) { 12479 window.location.href = 'https://www.purdys.com/customer/account/'; 12480 return; 12481 } 12482 12483 if (data.redirectToDashboard) { 12484 window.location.href = 'https://www.purdys.com/customer/campaigns/'; 12485 return; 12486 } 12487 12488 window.location.reload(); 12489 return; 12490 } 12491 12492 if (data.error && data.type === 'auth') { 12493 this.wrongCredentials = true; 12494 return; 12495 } 12496 12497 window.dispatchMessages([{ 12498 type: 'error', 12499 text: data.error 12500 }], 10000); 12501 }) 12502 .catch(error => { 12503 12504 }) 12505 .finally(() => { 12506 this.isLoading = false;
12507 this.isSubmitting = false; 12508 }); 12509 } 12510 }, 12511 // Initialize Amasty captcha integration 12512 init() { 12513 console.log('Login modal form initialized'); 12514 12515 // Add our form to Amasty's forms list after it's initialized 12516 const addFormToAmasty = () => { 12517 if (window.amRecaptchaModel && window.amRecaptchaModel.formsToProtect !== undefined) { 12518 const currentForms = window.amRecaptchaModel.formsToProtect || ''; 12519 const ourFormSelector = '#customer-login-form-modal'; 12520 12521 if (!currentForms.includes(ourFormSelector)) { 12522 window.amRecaptchaModel.formsToProtect = currentForms ? 12523 currentForms + ',' + ourFormSelector : ourFormSelector; 12524 console.log('Added our form to Amasty protection:', window.amRecaptchaModel.formsToProtect); 12525 } 12526 12527 return true; 12528 } 12529 return false; 12530 }; 12531 12532 // Try immediately and then keep trying 12533 if (!addFormToAmasty()) { 12534 const interval = setInterval(() => { 12535 if (addFormToAmasty()) { 12536 clearInterval(interval); 12537 } 12538 }, 100); 12539 12540 // Stop trying after 5 seconds 12541 setTimeout(() => clearInterval(interval), 5000); 12542 } 12543 12544 // Check what forms Amasty is configured to protect 12545 setTimeout(() => { 12546 if (window.amRecaptchaModel) { 12547 console.log('Final Amasty forms to protect:', window.amRecaptchaModel.formsToProtect); 12548 console.log('Our form element:', this.$el.id, this.$el.getAttribute('data-role')); 12549 } 12550 }, 2000); 12551 }, 12552 12553 // Amasty captcha methods - these will be overridden by the form submit handler 12554 amRecaptchaModelExecute($event) { 12555 // Prevent multiple rapid executions 12556 if (this.isLoading) { 12557 console.log('Already processing captcha, ignoring duplicate call'); 12558 return; 12559 } 12560 12561 console.log('amRecaptchaModelExecute called'); 12562 this.isLoading = true; 12563 12564 // Clear any existing timeout 12565 if (this.captchaTimeout) { 12566 clearTimeout(this.captchaTimeout); 12567 } 12568 12569 // Set a timeout to prevent infinite loading if captcha fails 12570 this.captchaTimeout = setTimeout(() => { 12571 if (this.isLoading) { 12572 console.log('Captcha timeout reached, submitting form anyway'); 12573 this.isLoading = false; 12574 this.isSubmitting = false; // Reset submitting state 12575 this.submitForm(); 12576 } 12577 }, 10000); // 10 second timeout 12578 12579 // Check if Amasty captcha is properly initialized 12580 if (window.amRecaptchaModel && typeof window.amRecaptchaModel.execute === 'function') { 12581 console.log('Calling Amasty execute method'); 12582 try { 12583 window.amRecaptchaModel.execute($event); 12584 } catch (error) { 12585 console.error('Captcha execution failed:', error); 12586 this.isLoading = false; 12587 this.isSubmitting = false; // Reset submitting state 12588 this.submitForm(); 12589 } 12590 } else { 12591 console.log('Amasty not properly initialized, using fallback'); 12592 this.submitForm(); 12593 } 12594 }, 12595 12596 amRecaptchaModelRecaptchaCallback($event, $el) { 12597 console.log('LOGIN amRecaptchaModelRecaptchaCallback called - should submit form now', { 12598 formId: 'customer-login-form-modal', 12599 visible: $el ? $el.offsetParent !== null : 'unknown', 12600 event: $event 12601 }); 12602 12603 // Clear the timeout since captcha succeeded 12604 if (this.captchaTimeout) { 12605 clearTimeout(this.captchaTimeout); 12606 this.captchaTimeout = null; 12607 } 12608 12609 // This callback means captcha validation passed, so submit the form
12610 this.submitForm(); 12611 }, 12612 12613 amRecaptchaModelExpiredCallback($event, $el) { 12614 console.log('amRecaptchaModelExpiredCallback called - captcha expired'); 12615 12616 // Clear the timeout and reset loading state 12617 if (this.captchaTimeout) { 12618 clearTimeout(this.captchaTimeout); 12619 this.captchaTimeout = null; 12620 } 12621 12622 // Reset loading state and show error 12623 this.isLoading = false; 12624 this.isSubmitting = false; 12625 12626 // Show user-friendly error message 12627 window.dispatchMessages([{ 12628 type: 'error', 12629 text: 'Security\u0020verification\u0020expired.\u0020Please\u0020try\u0020again.' 12630 }], 5000); 12631 12632 // Reset the form to allow retry 12633 this.wrongCredentials = false; 12634 }, 12635 12636 // Cleanup method to clear timeouts 12637 destroy() { 12638 if (this.captchaTimeout) { 12639 clearTimeout(this.captchaTimeout); 12640 this.captchaTimeout = null; 12641 } 12642 if (this.submitTimeout) { 12643 clearTimeout(this.submitTimeout); 12644 this.submitTimeout = null; 12645 } 12646 } 12647 } 12648 } 12649 12650 document.addEventListener('DOMContentLoaded', function() { 12651 if (localStorage.getItem('showLoginSuccess') === 'true') { 12652 const customerData = JSON.parse(localStorage.getItem('customerData') || '{}'); 12653 const firstName = customerData.firstname || ''; 12654 12655 let welcomeMessage = 'You\u0020have\u0020successfully\u0020logged\u0020in.'; 12656 if (firstName) { 12657 welcomeMessage = `You\u0020have\u0020successfully\u0020logged\u0020in,\u0020\u00251\u0021` 12658 .replace('%1', firstName); 12659 } 12660 12661 window.dispatchMessages([{ 12662 type: 'success', 12663 text: welcomeMessage 12664 }], 5000); 12665 12666 localStorage.removeItem('showLoginSuccess'); 12667 localStorage.removeItem('customerData'); 12668 } 12669 }); 12670 </script>
12670 12671</div> 12672 <div class="text-center mt-6"> 12673 <p>Don't have an account?</p> 12674 <a href="#" class="underline" @click.prevent="setState('register')"> 12675 <span class="text-base font-bold">Create one now</span> 12676 </a> 12677 </div> 12678 </div> 12679 <div x-show="state === 'forgotPassword'"> 12680 <div class="password-modal-content"> 12681 <div aria-labelledby="block-customer-password-heading"> 12682 <form class="form form-password" 12683 action="https://www.purdys.com/scandiweb-customer/AccountModal/forgotPassword/" 12684 method="post" 12685 x-data="initForgotPasswordForm()" 12686 @submit.prevent="amRecaptchaModelExecute" 12687 x-on:amrecaptcha-executable-callback="submitForm" 12688 x-on:amrecaptcha-callback.window="if ($el.offsetParent !== null) amRecaptchaModelRecaptchaCallback($event)" 12689 x-on:amrecaptcha-expired-callback.window="if ($el.offsetParent !== null) amRecaptchaModelExpiredCallback($event)" 12690 12691 id="user_forgotpassword"> 12692 <input type="hidden" name="form_key" :value="hyva.getFormKey()" /> 12693 <input type="hidden" name="formId" value="user_forgotpassword"/> 12694 <fieldset class="fieldset password"> 12695 <legend class="mb-8"> 12696 <h2 class="text-2xl md:text-h2-lg !font-bold text-black pr-16"> 12697 Forgot your password? </h2> 12698 </legend> 12699 <div class=" mb-8"> 12700 Please enter your email address below to receive a password reset link. </div> 12701 12702 <!-- Error Message Display --> 12703 <div class="mb-4" x-show="displayErrorMessage"> 12704 <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert"> 12705 <span class="block sm:inline" x-text="errorMessages[0] || 'An\u0020error\u0020occurred.\u0020Please\u0020try\u0020again.'"></span> 12706 </div> 12707 </div> 12708 12709 <div class="field email required"> 12710 <label for="email_address" class="label after:!m-0"> 12711 <span>Email</span> 12712 </label> 12713 <div class="control"> 12714 <input type="email" 12715 name="email" 12716 alt="email" 12717 id="forgot_password_email_address" 12718 class="form-input w-full" 12719 autocomplete="off" 12720 required 12721 value=""> 12722 </div> 12723 </div> 12724 <div class="pt-4 pb-2"> 12725 <button type="submit" class="btn btn-primary disabled:opacity-75 w-full h-11" 12726 :disabled="isLoading" 12727 > 12728 <span class="body-2 leading-[22px]">RESET MY PASSWORD</span> 12729 </button> 12730 </div> 12731 </fieldset> 12732 </form> 12733 </div> 12734 <div class="w-full"> 12735 </div> 12736
12736<script> 12737 function initForgotPasswordForm() { 12738 return { 12739 errors: 0, 12740 hasCaptchaToken: 0, 12741 displayErrorMessage: false, 12742 errorMessages: [], 12743 isSubmitting: false, 12744 submitTimeout: null, 12745 captchaTimeout: null, 12746 setErrorMessages(messages) { 12747 this.errorMessages = [messages] 12748 this.displayErrorMessage = this.errorMessages.length 12749 }, 12750 // Initialize Amasty captcha integration 12751 init() { 12752 console.log('Forgot password modal form initialized'); 12753 12754 // Add our form to Amasty's forms list after it's initialized 12755 const addFormToAmasty = () => { 12756 if (window.amRecaptchaModel && window.amRecaptchaModel.formsToProtect !== undefined) { 12757 const currentForms = window.amRecaptchaModel.formsToProtect || ''; 12758 const ourFormSelector = '#user_forgotpassword'; 12759 12760 if (!currentForms.includes(ourFormSelector)) { 12761 window.amRecaptchaModel.formsToProtect = currentForms ? 12762 currentForms + ',' + ourFormSelector : ourFormSelector; 12763 console.log('Added forgot password form to Amasty protection:', window.amRecaptchaModel.formsToProtect); 12764 } 12765 12766 return true; 12767 } 12768 return false; 12769 }; 12770 12771 // Try immediately and then keep trying 12772 if (!addFormToAmasty()) { 12773 const interval = setInterval(() => { 12774 if (addFormToAmasty()) { 12775 clearInterval(interval); 12776 } 12777 }, 100); 12778 12779 // Stop trying after 5 seconds 12780 setTimeout(() => clearInterval(interval), 5000); 12781 } 12782 12783 // Check what forms Amasty is configured to protect 12784 setTimeout(() => { 12785 if (window.amRecaptchaModel) { 12786 console.log('Final Amasty forms to protect:', window.amRecaptchaModel.formsToProtect); 12787 console.log('Our forgot password form element:', this.$el.id); 12788 } 12789 }, 2000); 12790 }, 12791 12792 // Amasty captcha methods 12793 amRecaptchaModelExecute($event) { 12794 // Prevent multiple rapid executions 12795 if (this.isLoading) { 12796 console.log('Already processing captcha, ignoring duplicate call'); 12797 return; 12798 } 12799 12800 console.log('Forgot password amRecaptchaModelExecute called'); 12801 this.isLoading = true; 12802 12803 // Clear any existing timeout 12804 if (this.captchaTimeout) { 12805 clearTimeout(this.captchaTimeout); 12806 } 12807 12808 // Set a timeout to prevent infinite loading if captcha fails 12809 this.captchaTimeout = setTimeout(() => { 12810 if (this.isLoading) { 12811 console.log('Forgot password captcha timeout reached, submitting form anyway'); 12812 this.isLoading = false; 12813 this.isSubmitting = false; // Reset submitting state 12814 this.submitForm(); 12815 } 12816 }, 10000); // 10 second timeout 12817 12818 // Check if Amasty captcha is properly initialized 12819 if (window.amRecaptchaModel && typeof window.amRecaptchaModel.execute === 'function') { 12820 console.log('Calling Amasty execute method for forgot password'); 12821 try { 12822 window.amRecaptchaModel.execute($event); 12823 } catch (error) { 12824 console.error('Forgot password captcha execution failed:', error); 12825 this.isLoading = false;
12826 this.isSubmitting = false; // Reset submitting state 12827 this.submitForm(); 12828 } 12829 } else { 12830 console.log('Amasty not properly initialized, using fallback for forgot password'); 12831 this.submitForm(); 12832 } 12833 }, 12834 12835 amRecaptchaModelRecaptchaCallback($event, $el) { 12836 console.log('FORGOT PASSWORD amRecaptchaModelRecaptchaCallback called - should submit form now', { 12837 formId: 'user_forgotpassword', 12838 visible: $el ? $el.offsetParent !== null : 'unknown', 12839 event: $event 12840 }); 12841 12842 // Clear the timeout since captcha succeeded 12843 if (this.captchaTimeout) { 12844 clearTimeout(this.captchaTimeout); 12845 this.captchaTimeout = null; 12846 } 12847 12848 // This callback means captcha validation passed, so submit the form 12849 this.submitForm(); 12850 }, 12851 12852 amRecaptchaModelExpiredCallback($event, $el) { 12853 console.log('Forgot password amRecaptchaModelExpiredCallback called - captcha expired'); 12854 12855 // Clear the timeout and reset loading state 12856 if (this.captchaTimeout) { 12857 clearTimeout(this.captchaTimeout); 12858 this.captchaTimeout = null; 12859 } 12860 12861 // Reset loading state and show error 12862 this.isLoading = false; 12863 this.isSubmitting = false; 12864 12865 // Show user-friendly error message 12866 window.dispatchMessages([{ 12867 type: 'error', 12868 text: 'Security\u0020verification\u0020expired.\u0020Please\u0020try\u0020again.' 12869 }], 5000); 12870 }, 12871 submitForm() { 12872 console.log('FORGOT PASSWORD submitForm called, formId:', 'user_forgotpassword'); 12873 12874 // Bouncer: prevent multiple rapid submissions 12875 if (this.isSubmitting) { 12876 console.log('Forgot password form already submitting, returning. isLoading:', this.isLoading, 'isSubmitting:', this.isSubmitting); 12877 return; 12878 } 12879 12880 // Clear any existing timeouts 12881 if (this.submitTimeout) { 12882 clearTimeout(this.submitTimeout); 12883 } 12884 if (this.captchaTimeout) { 12885 clearTimeout(this.captchaTimeout); 12886 this.captchaTimeout = null; 12887 } 12888 12889 // Clear error state when submitting 12890 this.displayErrorMessage = false; 12891 this.errorMessages = []; 12892 12893 // Set submitting state and timeout 12894 this.isSubmitting = true; 12895 this.submitTimeout = setTimeout(() => { 12896 console.log('Forgot password submit timeout reached, resetting isSubmitting'); 12897 this.isSubmitting = false; 12898 // Also reset loading state as a safety measure 12899 if (this.isLoading) { 12900 console.log('Also resetting stuck isLoading state for forgot password'); 12901 this.isLoading = false; 12902 } 12903 }, 15000); // 15 second timeout (longer to allow for network delays) 12904 12905 const $form = document.querySelector('#user_forgotpassword'); 12906 12907 if (this.errors === 0) { 12908 this.isLoading = true; 12909 12910 // Use fetch to submit the form and handle JSON response 12911 const formData = new FormData($form); 12912 12913 fetch($form.action, { 12914 method: 'POST', 12915 body: formData, 12916 headers: { 12917 'X-Requested-With': 'XMLHttpRequest' 12918 } 12919 }) 12920 .then(response => response.json()) 12921 .then(data => { 12922 this.isLoading = false;
12923 this.isSubmitting = false; 12924 12925 if (data.success) { 12926 // Clear error state on success 12927 this.displayErrorMessage = false; 12928 this.errorMessages = []; 12929 12930 // Close the modal using the event dispatch approach 12931 const modalContent = document.querySelector('#auth-modal-content'); 12932 12933 if (modalContent && modalContent.__x) { 12934 // First set state to login 12935 modalContent.__x.$data.setState('login'); 12936 } 12937 12938 // Dispatch the close event that the modal is listening for 12939 window.dispatchEvent(new CustomEvent('close-auth-modal')); 12940 12941 // Show success message using multiple methods to ensure it appears 12942 if (typeof window.dispatchMessages !== "undefined") { 12943 window.dispatchMessages([{ 12944 type: 'success', 12945 text: data.message 12946 }], 5000); 12947 } else if (window.hyva && window.hyva.messages) { 12948 window.hyva.messages.addMessage('success', data.message); 12949 } else { 12950 // Fallback: dispatch custom event 12951 window.dispatchEvent(new CustomEvent('messages-loaded', { 12952 detail: { 12953 messages: [{ 12954 type: 'success', 12955 text: data.message 12956 }], 12957 hideAfter: 5000 12958 } 12959 })); 12960 } 12961 12962 // Clear the form 12963 $form.reset(); 12964 } else { 12965 // Show error message in the form 12966 this.setErrorMessages(data.error || 'An\u0020error\u0020occurred.\u0020Please\u0020try\u0020again.'); 12967 12968 // Show error message using multiple methods to ensure it appears 12969 if (typeof window.dispatchMessages !== "undefined") { 12970 window.dispatchMessages([{ 12971 type: 'error', 12972 text: data.error 12973 }], 8000); 12974 } else if (window.hyva && window.hyva.messages) { 12975 window.hyva.messages.addMessage('error', data.error); 12976 } else { 12977 // Fallback: dispatch custom event 12978 window.dispatchEvent(new CustomEvent('messages-loaded', { 12979 detail: { 12980 messages: [{ 12981 type: 'error', 12982 text: data.error 12983 }], 12984 hideAfter: 8000 12985 } 12986 })); 12987 } 12988 } 12989 }) 12990 .catch(error => { 12991 this.isLoading = false;
12992 if (typeof window.dispatchMessages !== "undefined") { 12993 window.dispatchMessages([{ 12994 type: 'error', 12995 text: 'An\u0020error\u0020occurred.\u0020Please\u0020try\u0020again.' 12996 }], 8000); 12997 } else if (window.hyva && window.hyva.messages) { 12998 window.hyva.messages.addMessage('error', 'An\u0020error\u0020occurred.\u0020Please\u0020try\u0020again.'); 12999 } else { 13000 // Fallback: dispatch custom event 13001 window.dispatchEvent(new CustomEvent('messages-loaded', { 13002 detail: { 13003 messages: [{ 13004 type: 'error', 13005 text: 'An\u0020error\u0020occurred.\u0020Please\u0020try\u0020again.' 13006 }], 13007 hideAfter: 8000 13008 } 13009 })); 13010 } 13011 console.error('Error:', error); 13012 }); 13013 } else { 13014 this.isLoading = false; 13015 } 13016 }, 13017 13018 // Cleanup method to clear timeouts 13019 destroy() { 13020 if (this.captchaTimeout) { 13021 clearTimeout(this.captchaTimeout); 13022 this.captchaTimeout = null; 13023 } 13024 if (this.submitTimeout) { 13025 clearTimeout(this.submitTimeout); 13026 this.submitTimeout = null; 13027 } 13028 } 13029 } 13030 } 13031 </script>
13031 13032</div> 13033 <div class="text-center mt-6"> 13034 <a href="#" class="underline uppercase" @click.prevent="setState('login')"> 13035 <span class="text-base font-bold">Back</span> 13036 </a> 13037 </div> 13038 <div class="text-center mt-6"> 13039 <p>Don't have an account?</p> 13040 <a href="#" class="underline" @click.prevent="setState('register')"> 13041 <span class="text-base font-bold">Then create one now.</span> 13042 </a> 13043 </div> 13044 </div> 13045 <div x-show="state === 'register'"> 13046 <div class="mb-4"> 13047 <form class="form create account form-create-account text-black !min-w-0 !w-full" 13048 action="https://www.purdys.com/purdys-customer/AccountModal/Create/" 13049 x-data="Object.assign(hyva.formValidation($el), initRegisterModalForm6ab6cfd67accc())" 13050 id="accountcreate-modal6ab6cfd67accc" 13051 @submit.prevent="amRecaptchaModelExecute" 13052 x-on:amrecaptcha-executable-callback="submitForm" 13053 x-on:amrecaptcha-callback.window="if ($el.offsetParent !== null) amRecaptchaModelRecaptchaCallback($event)" 13054 x-on:amrecaptcha-expired-callback.window="if ($el.offsetParent !== null) amRecaptchaModelExpiredCallback($event)" 13055 method="post" id="form-validate" enctype="multipart/form-data" autocomplete="off" 13056 > 13057 <div class="flex flex-row start-0 top-0 justify-center items-center w-full h-full absolute select-none z-10" 13058 style="background: rgba(255,255,255,0.5);" 13059 x-show="isLoading" 13060 x-cloak 13061 x-transition:enter="ease-out duration-200" 13062 x-transition:enter-start="opacity-0" 13063 x-transition:enter-end="opacity-100" 13064 x-transition:leave="ease-in duration-200" 13065 x-transition:leave-start="opacity-100" 13066 x-transition:leave-end="opacity-0"> 13067 <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 56 56" fill="none" class="animate-[spin_2s_linear_infinite] text-primary" role="img"> 13068 <path d="M52.5 28C52.5 31.2174 51.8663 34.4033 50.6351 37.3757C49.4038 40.3482 47.5992 43.0491 45.3241 45.3241C43.0491 47.5992 40.3482 49.4038 37.3757 50.635C34.4033 51.8663 31.2174 52.5 28 52.5C24.7826 52.5 21.5967 51.8663 18.6243 50.635C15.6518 49.4038 12.9509 47.5991 10.6759 45.3241C8.40085 43.0491 6.59619 40.3482 5.36495 37.3757C4.13371 34.4033 3.5 31.2174 3.5 28C3.5 24.7826 4.13371 21.5967 5.36496 18.6242C6.5962 15.6518 8.40086 12.9509 10.6759 10.6759C12.9509 8.40084 15.6518 6.59619 18.6243 5.36495C21.5967 4.13371 24.7826 3.5 28 3.5C31.2174 3.5 34.4033 4.13371 37.3758 5.36496C40.3482 6.5962 43.0491 8.40086 45.3241 10.6759C47.5992
1306812.9509 49.4038 15.6518 50.6351 18.6243C51.8663 21.5967 52.5 24.7826 52.5 28L52.5 28Z" stroke="#EEEAF4" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/> 13069 <path d="M52.5 28C52.5 24.7826 51.8663 21.5967 50.6351 18.6243C49.4038 15.6518 47.5992 12.9509 45.3241 10.6759C43.0491 8.40085 40.3482 6.59619 37.3757 5.36495C34.4033 4.13371 31.2174 3.5 28 3.5" stroke="#532E91" stroke-width="6" stroke-linecap="square"/> 13070<title>loader</title></svg> 13071</div> 13072 <input type="hidden" name="form_key" :value="hyva.getFormKey()"/> 13073 <input type="text" name="redirect" x-model="redirect" hidden> 13074 <div class="w-full"> 13075 <fieldset class="fieldset register !m-0"> 13076 <legend class="mb-3"> 13077 <h2 class="text-2xl md:text-h2-lg !font-bold text-black max-w-[235px]" 13078 @set-register-title.window="customTitle = $event.detail" 13079 x-text="getTitle()" 13080 x-init="updateContentBasedOnContext()" 13081 ></h2> 13082 </legend> 13083 13084 <!-- Dynamic description based on context --> 13085 <div class="mb-6 text-center" x-show="showDescription" x-text="getDescription()"></div> 13086 13087 <!-- Error Message Display --> 13088 <div class="mb-4" x-show="registrationError"> 13089 <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert"> 13090 <span class="block sm:inline" x-text="errorMessages[0] || 'Registration\u0020failed.\u0020Please\u0020try\u0020again.'"></span> 13091 </div> 13092 </div> 13093 13094 <input type="hidden" name="success_url" value=""> 13095 <input type="hidden" name="error_url" value=""> 13096 <div class="field field-reserved required"> 13097 <label for="firstname" class="label !mb-3 !font-normal"> 13098 <span>First Name</span> 13099 </label> 13100 <div class="control !m-0"> 13101 <input 13102 type="text" 13103 name="firstname" 13104 id="firstname" 13105 required 13106 value="" 13107 title="First Name" 13108 placeholder="First Name" 13109 class="form-input" 13110 @input.debounce="onChange" 13111 /> 13112 <ul class="messages !m-0" aria-live="polite"></ul> 13113 </div> 13114 </div> 13115 <div class="field field-reserved required"> 13116 <label for="lastname" class="label !mb-3 !font-normal after:!m-0"> 13117 <span>Last Name</span> 13118 </label> 13119 <div class="control !m-0"> 13120 <input 13121 type="text" 13122 name="lastname" 13123 id="lastname" 13124 required 13125 value="" 13126 title="Last Name" 13127 placeholder="Last Name" 13128 class="form-input" 13129 @input.debounce="onChange" 13130 /> 13131 </div> 13132 </div> 13133 <div class="field field-reserved required"> 13134 <label for="email_address" class="label !mb-3 !font-normal after:!m-0"> 13135 <span>Email</span> 13136 </label> 13137 <div class="control !m-0"> 13138 <input 13139 type="email" 13140 name="email" 13141 autocomplete="email" 13142 id="register_email_address" 13143 required 13144 value="" 13145 title="Email" 13146 class="form-input" 13147 placeholder="Enter your email address" 13148 @input.debounce="onChange" 13149 /> 13150 </div> 13151 </div> 13152 <div class="field field-reserved required"> 13153 <label for="password" class="label !mb-3 !font-normal after:!m-0">
13154 <span>Password</span> 13155 </label> 13156 <div class="flex flex-wrap gap-4"> 13157 <div class="control !m-0 flex items-center w-full relative"> 13158 <div class="sr-only" aria-live="polite"> 13159 <template x-if="!showPassword"> 13160 <span> 13161 Password hidden </span> 13162 </template> 13163 <template x-if="showPassword"> 13164 <span> 13165 Password shown </span> 13166 </template> 13167 </div> 13168 <input 13169 :type="showPassword ? 'text' : 'password'" 13170 type="password" 13171 id="password" 13172 name="password" 13173 title="Password" 13174 minlength="8" 13175 class="form-input placeholder:tracking-normal" 13176 required 13177 placeholder="Enter your password" 13178 data-validate='{"password-strength": {"minCharacterSets": 3}}' 13179 @input.debounce="onChange" 13180 autocomplete="off" 13181 > 13182 <button 13183 type="button" 13184 :aria-pressed="showPassword ? true : false" 13185 @click.stop="showPassword = !showPassword" 13186 class="px-4 py-3 absolute right-0 h-full !bg-transparent !border-transparent" 13187 :aria-label="showPassword ? 'Hide\u0020Password' : 'Show\u0020Password'" 13188 > 13189 <template x-if="!showPassword"> 13190 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" width="24" height="24" role="img"> 13191 <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/> 13192 <path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/> 13193<title>eye</title></svg> 13194 </template> 13195 <template x-if="showPassword"> 13196 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" width="24" height="24" role="img"> 13197 <path fill-rule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clip-rule="evenodd"/> 13198 <path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z"/> 13199<title>eye-off</title></svg> 13200 </template> 13201 </button> 13202 </div> 13203 </div> 13204 </div> 13205 <div class="field field-reserved required"> 13206 <label for="password-confirmation" class="label !mb-3 !font-normal after:!m-0"> 13207 <span>Confirm Password</span> 13208 </label> 13209 <div class="control !m-0 flex items-center relative"> 13210 <div class="sr-only" aria-live="polite"> 13211 <template x-if="!showPasswordConfirm"> 13212 <span> 13213 Confirm password hidden </span> 13214 </template> 13215 <template x-if="showPasswordConfirm">
13216 <span> 13217 confirm password shown </span> 13218 </template> 13219 </div> 13220 <input 13221 :type="showPasswordConfirm ? 'text' : 'password'" 13222 type="password" 13223 name="password_confirmation" 13224 title="Confirm Password" 13225 id="password-confirmation" 13226 data-validate='{"equalTo": "password"}' 13227 @input.debounce="onChange" 13228 required 13229 placeholder="Re-enter your password" 13230 class="form-input placeholder:tracking-normal" 13231 autocomplete="off" 13232 > 13233 <button 13234 type="button" 13235 @click.stop="showPasswordConfirm = !showPasswordConfirm" 13236 :aria-pressed="showPasswordConfirm ? true : false" 13237 class="px-4 py-3 absolute right-0 h-full !bg-transparent !border-transparent" 13238 :aria-label=" 13239 showPasswordConfirm ? 'Hide\u0020Password' : 'Show\u0020Password' 13240 " 13241 > 13242 <template x-if="!showPasswordConfirm"> 13243 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" width="24" height="24" role="img"> 13244 <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/> 13245 <path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/> 13246<title>eye</title></svg> 13247 </template> 13248 <template x-if="showPasswordConfirm"> 13249 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5" width="24" height="24" role="img"> 13250 <path fill-rule="evenodd" d="M3.707 2.293a1 1 0 00-1.414 1.414l14 14a1 1 0 001.414-1.414l-1.473-1.473A10.014 10.014 0 0019.542 10C18.268 5.943 14.478 3 10 3a9.958 9.958 0 00-4.512 1.074l-1.78-1.781zm4.261 4.26l1.514 1.515a2.003 2.003 0 012.45 2.45l1.514 1.514a4 4 0 00-5.478-5.478z" clip-rule="evenodd"/> 13251 <path d="M12.454 16.697L9.75 13.992a4 4 0 01-3.742-3.741L2.335 6.578A9.98 9.98 0 00.458 10c1.274 4.057 5.065 7 9.542 7 .847 0 1.669-.105 2.454-.303z"/> 13252<title>eye-off</title></svg> 13253 </template> 13254 </button> 13255 </div> 13256 </div> 13257 <!-- Promotional Email Checkbox --> 13258 <div class="field choice newsletter !m-0 !mt-0 !gap-6"> 13259 <input type="checkbox" 13260 name="is_subscribed" 13261 title="Sign me up to receive promotional emails from Purdys Chocolatier." 13262 value="1" 13263 id="is_subscribed_modal_6ab6cfd67accc" 13264 class="checked:!bg-primary focus:checked:!bg-primary !m-0 !mt-[5px]"> 13265 <label for="is_subscribed_modal_6ab6cfd67accc" class="label !mb-0"> 13266 <span>Sign me up to receive promotional emails from Purdys Chocolatier.</span> 13267 </label> 13268 </div> 13269 13270 <!-- Text Messages Checkbox --> 13271 <div class="field choice newsletter !m-0 !mt-0 !gap-6"> 13272 <input type="checkbox" 13273 name="text_messages_subscription" 13274 title="Sign me up to receive text messages from Purdys Chocolatier." 13275 value="1" 13276 id="text_messages_subscription_modal_6ab6cfd67accc" 13277 class="checked:!bg-primary focus:checked:!bg-primary !m-0 !mt-[3px]"> 13278 <label for="text_messages_subscription_modal_6ab6cfd67accc" 13279 class="label !font-normal after:!m-0">
13280 <span>Sign me up to receive text messages from Purdys Chocolatier.</span> 13281 </label> 13282 </div> 13283 13284 <!-- Terms and Conditions Disclaimer --> 13285 <div class="field choice !mb-2.5 !ms-10 term-cond"> 13286 <p>*By selecting, you agree to receive emails and newsletters, also to our <a href="https://www.purdys.com/terms-of-use/" target="_blank" 13287 class="text-primary underline">Terms & Conditions</a> 13288 and <a href="https://www.purdys.com/privacy-policy/" target="_blank" 13289 class="text-primary underline">Privacy Policy</a>. 13290 This is protected by reCAPTCHA</p> 13291 </div> 13292 13293 </fieldset> 13294 </div> 13295 <div class="pt-4 pb-2"> 13296 <button type="submit" class="action submit !primary !btn !btn-primary disabled:opacity-75 w-full h-11" 13297 title="Create Account" 13298 :disabled="isCreateAccountButtonDisabled || isLoading"> 13299 <span class="body-2 leading-[22px]">Create Account</span> 13300 </button> 13301 </div> 13302 </form> 13303
13303<script> 13304 function initRegisterModalForm6ab6cfd67accc() { 13305 return { 13306 defaultTitle: 'Create\u0020your\u0020account.', 13307 customTitle: null, 13308 showDescription: false, 13309 isInMainContent: false, 13310 errors: 0, 13311 hasCaptchaToken: 0, 13312 showPassword: false, 13313 showPasswordConfirm: false, 13314 isCreateAccountButtonDisabled: true, 13315 isLoading: false, 13316 isSubmitting: false, 13317 submitTimeout: null, 13318 captchaTimeout: null, 13319 displayErrorMessage: false, 13320 errorMessages: [], 13321 registrationError: false, 13322 updateContentBasedOnContext() { 13323 // Check if form is inside #maincontent 13324 const form = document.querySelector('#accountcreate\u002Dmodal6ab6cfd67accc'); 13325 this.isInMainContent = form && form.closest('#maincontent') !== null; 13326 this.showDescription = this.isInMainContent; 13327 }, 13328 getTitle() { 13329 if (this.customTitle) { 13330 return this.customTitle; 13331 } 13332 return this.isInMainContent ? 'Create\u0020New\u0020Customer\u0020Account' : this.defaultTitle; 13333 }, 13334 getDescription() { 13335 return this.isInMainContent ? 'If\u0020you\u0020have\u0020an\u0020account,\u0020sign\u0020in\u0020with\u0020your\u0020email\u0020address.' : ''; 13336 }, 13337 // Initialize Amasty captcha integration 13338 init() { 13339 console.log('Register modal form initialized'); 13340 13341 // Add our form to Amasty's forms list after it's initialized 13342 const addFormToAmasty = () => { 13343 if (window.amRecaptchaModel && window.amRecaptchaModel.formsToProtect !== undefined) { 13344 const currentForms = window.amRecaptchaModel.formsToProtect || ''; 13345 const ourFormSelector = '#accountcreate\u002Dmodal6ab6cfd67accc'; 13346 13347 if (!currentForms.includes(ourFormSelector)) { 13348 window.amRecaptchaModel.formsToProtect = currentForms ? 13349 currentForms + ',' + ourFormSelector : ourFormSelector; 13350 console.log('Added register form to Amasty protection:', window.amRecaptchaModel.formsToProtect); 13351 } 13352 13353 return true; 13354 } 13355 return false; 13356 }; 13357 13358 // Try immediately and then keep trying 13359 if (!addFormToAmasty()) { 13360 const interval = setInterval(() => { 13361 if (addFormToAmasty()) { 13362 clearInterval(interval); 13363 } 13364 }, 100); 13365 13366 // Stop trying after 5 seconds 13367 setTimeout(() => clearInterval(interval), 5000); 13368 } 13369 13370 // Check what forms Amasty is configured to protect 13371 setTimeout(() => { 13372 if (window.amRecaptchaModel) { 13373 console.log('Final Amasty forms to protect:', window.amRecaptchaModel.formsToProtect); 13374 console.log('Our register form element:', this.$el.id); 13375 } 13376 }, 2000); 13377 }, 13378 13379 // Amasty captcha methods 13380 amRecaptchaModelExecute($event) { 13381 // Prevent multiple rapid executions 13382 if (this.isLoading) { 13383 console.log('Already processing captcha, ignoring duplicate call'); 13384 return; 13385 } 13386 13387 console.log('Register amRecaptchaModelExecute called'); 13388 this.isLoading = true; 13389 13390 // Clear any existing timeout 13391 if (this.captchaTimeout) { 13392 clearTimeout(this.captchaTimeout); 13393 } 13394 13395 // Set a timeout to prevent infinite loading if captcha fails 13396 this.captchaTimeout = setTimeout(() => { 13397 if (this.isLoading) { 13398 console.log('Register captcha timeout reached, submitting form anyway'); 13399 this.isLoading = false;
13400 this.isSubmitting = false; // Reset submitting state 13401 this.submitForm(); 13402 } 13403 }, 10000); // 10 second timeout 13404 13405 // Check if Amasty captcha is properly initialized 13406 if (window.amRecaptchaModel && typeof window.amRecaptchaModel.execute === 'function') { 13407 console.log('Calling Amasty execute method for register'); 13408 try { 13409 window.amRecaptchaModel.execute($event); 13410 } catch (error) { 13411 console.error('Register captcha execution failed:', error); 13412 this.isLoading = false; 13413 this.isSubmitting = false; // Reset submitting state 13414 this.submitForm(); 13415 } 13416 } else { 13417 console.log('Amasty not properly initialized, using fallback for register'); 13418 this.submitForm(); 13419 } 13420 }, 13421 13422 amRecaptchaModelRecaptchaCallback($event, $el) { 13423 console.log('REGISTER amRecaptchaModelRecaptchaCallback called - should submit form now', { 13424 formId: 'accountcreate\u002Dmodal6ab6cfd67accc', 13425 visible: $el ? $el.offsetParent !== null : 'unknown', 13426 event: $event 13427 }); 13428 13429 // Clear the timeout since captcha succeeded 13430 if (this.captchaTimeout) { 13431 clearTimeout(this.captchaTimeout); 13432 this.captchaTimeout = null; 13433 } 13434 13435 // This callback means captcha validation passed, so submit the form 13436 this.submitForm(); 13437 }, 13438 13439 amRecaptchaModelExpiredCallback($event, $el) { 13440 console.log('Register amRecaptchaModelExpiredCallback called - captcha expired'); 13441 13442 // Clear the timeout and reset loading state 13443 if (this.captchaTimeout) { 13444 clearTimeout(this.captchaTimeout); 13445 this.captchaTimeout = null; 13446 } 13447 13448 // Reset loading state and show error 13449 this.isLoading = false; 13450 this.isSubmitting = false; 13451 13452 // Show user-friendly error message 13453 window.dispatchMessages([{ 13454 type: 'error', 13455 text: 'Security\u0020verification\u0020expired.\u0020Please\u0020try\u0020again.' 13456 }], 5000); 13457 13458 // Reset the form to allow retry 13459 this.registrationError = false; 13460 this.errorMessages = []; 13461 }, 13462 submitForm() { 13463 console.log('REGISTER submitForm called, formId:', 'accountcreate\u002Dmodal6ab6cfd67accc'); 13464 13465 // Bouncer: prevent multiple rapid submissions 13466 if (this.isSubmitting) { 13467 console.log('Register form already submitting, returning. isLoading:', this.isLoading, 'isSubmitting:', this.isSubmitting); 13468 return; 13469 } 13470 13471 // Clear any existing timeouts 13472 if (this.submitTimeout) { 13473 clearTimeout(this.submitTimeout); 13474 } 13475 if (this.captchaTimeout) { 13476 clearTimeout(this.captchaTimeout); 13477 this.captchaTimeout = null; 13478 } 13479 13480 // Clear error state when submitting 13481 this.registrationError = false; 13482 this.errorMessages = []; 13483 13484 // Set submitting state and timeout 13485 this.isSubmitting = true; 13486 this.submitTimeout = setTimeout(() => { 13487 console.log('Register submit timeout reached, resetting isSubmitting'); 13488 this.isSubmitting = false;
13489 // Also reset loading state as a safety measure 13490 if (this.isLoading) { 13491 console.log('Also resetting stuck isLoading state for register'); 13492 this.isLoading = false; 13493 } 13494 }, 15000); // 15 second timeout (longer to allow for network delays) 13495 13496 const $form = document.querySelector('#accountcreate\u002Dmodal6ab6cfd67accc'); 13497 13498 if (this.errors === 0) { 13499 this.isLoading = true; 13500 13501 fetch('https://www.purdys.com/purdys-customer/AccountModal/Create/', { 13502 method: 'POST', 13503 body: new FormData($form) 13504 }) 13505 .then(response => { 13506 console.log('Response received:', response); 13507 return response.json(); 13508 }) 13509 .then(data => { 13510 console.log('Data received:', data); 13511 if (data.success) { 13512 // Clear any error state on success 13513 this.registrationError = false; 13514 this.errorMessages = []; 13515 13516 const localStorage = window.localStorage; 13517 const cartData = localStorage.getItem('login_campaign_cart'); 13518 13519 this.$dispatch('reload-customer-section-data'); 13520 hyva.setCookie('isNewlyCreatedAccount', 1); 13521 13522 // Handle cart data if exists 13523 if (cartData) { 13524 window.localStorage.removeItem('login_campaign_cart'); 13525 13526 const {product, campaign, qty = 1} = JSON.parse(cartData); 13527 13528 this.$dispatch('campaign-add-to-cart', { 13529 product, 13530 qty, 13531 campaign, 13532 }); 13533 } 13534 13535 // Wait for session synchronization before redirect 13536 setTimeout(() => { 13537 // Force reload customer data sections to ensure session sync 13538 if (window.customerData) { 13539 window.customerData.reload(['customer', 'cart'], false); 13540 } 13541 13542 // Small additional delay to ensure session is written 13543 setTimeout(() => { 13544 // Use location.replace to avoid back button issues and clear any form state 13545 window.location.replace('https://www.purdys.com/customer/account/?registered=1'); 13546 }, 100); 13547 }, 300); 13548 } else { 13549 console.log('Registration failed:', data.error); 13550 13551 // Show the error message in the form 13552 this.registrationError = true; 13553 this.errorMessages = [data.error || 'Registration\u0020failed.\u0020Please\u0020try\u0020again.']; 13554 13555 // Also show global message 13556 window.dispatchMessages([ 13557 { 13558 type: 'error', 13559 text: data.error || 'Registration\u0020failed.\u0020Please\u0020try\u0020again.' 13560 } 13561 ], 10000); 13562 } 13563 }) 13564 .catch(error => { 13565 console.error('Error submitting form:', error); 13566 window.dispatchMessages([ 13567 { 13568 type: 'error', 13569 text: 'Oops\u0020something\u0020went\u0020wrong,\u0020Please\u0020try\u0020again.' 13570 } 13571 ], 10000); 13572 }) 13573 .finally(() => { 13574 this.isLoading = false;
13575 this.isSubmitting = false; 13576 }); 13577 } else { 13578 console.log('Form validation failed with', this.errors, 'errors'); 13579 this.isLoading = false; 13580 } 13581 }, 13582 validate() { 13583 return new Promise((resolve, reject) => { 13584 this.errors = 0; 13585 const formFields = document.querySelectorAll('#accountcreate\u002Dmodal6ab6cfd67accc input, #accountcreate\u002Dmodal6ab6cfd67accc select, #accountcreate\u002Dmodal6ab6cfd67accc textarea'); 13586 13587 formFields.forEach(field => { 13588 // Clear previous custom validity 13589 field.setCustomValidity(''); 13590 13591 // Manually trigger Hyva validation for fields with data-validate 13592 const validateAttr = field.getAttribute('data-validate'); 13593 if (validateAttr && window.hyva && window.hyva.formValidation && window.hyva.formValidation.rules) { 13594 try { 13595 const rules = JSON.parse(validateAttr); 13596 for (const [ruleName, options] of Object.entries(rules)) { 13597 if (window.hyva.formValidation.rules[ruleName]) { 13598 const result = window.hyva.formValidation.rules[ruleName](field.value, options, {element: field}, {}); 13599 if (result !== true) { 13600 field.setCustomValidity(result); 13601 break; 13602 } 13603 } 13604 } 13605 } catch (e) { 13606 console.warn('Error parsing validation rules for field:', field.name, e); 13607 } 13608 } 13609 13610 if (!field.checkValidity()) { 13611 this.errors++; 13612 } 13613 }); 13614 13615 this.isCreateAccountButtonDisabled = this.errors > 0; 13616 if (this.errors === 0) { 13617 resolve(); 13618 } else { 13619 reject(formFields); 13620 } 13621 }); 13622 }, 13623 init() { 13624 const formFields = document.querySelectorAll('#accountcreate\u002Dmodal6ab6cfd67accc input, #accountcreate\u002Dmodal6ab6cfd67accc select, #accountcreate\u002Dmodal6ab6cfd67accc textarea'); 13625 formFields.forEach(field => { 13626 field.addEventListener('input', this.validate.bind(this)); 13627 }); 13628 }, 13629 13630 // Cleanup method to clear timeouts 13631 destroy() { 13632 if (this.captchaTimeout) { 13633 clearTimeout(this.captchaTimeout); 13634 this.captchaTimeout = null; 13635 } 13636 if (this.submitTimeout) { 13637 clearTimeout(this.submitTimeout); 13638 this.submitTimeout = null; 13639 } 13640 } 13641 } 13642 } 13643 13644 document.addEventListener('DOMContentLoaded', function () { 13645 const form = initRegisterModalForm6ab6cfd67accc(); 13646 form.init(); 13647 }); 13648 </script>
13648 13649
13650<script> 13651 'use strict'; 13652 (() => { 13653 function _calculateStrength({elementID, valid}) { 13654 const password = document.getElementById(elementID), 13655 emailElm = document.getElementById('email_address'); 13656 let displayScore; 13657 // Display score is based on combination of whether password is empty, valid, and zxcvbn strength 13658 if (!password || !password.value) { 13659 displayScore = 0; 13660 } else { 13661 // We should only perform this check in case there is an email field on screen 13662 if (emailElm && password.value && emailElm.value && 13663 password.value.toLowerCase() === emailElm.value.toLowerCase()) { 13664 displayScore = 1; 13665 } else { 13666 const zxcvbnScore = zxcvbn(password.value).score; 13667 displayScore = valid && zxcvbnScore > 0 ? zxcvbnScore : 1; 13668 } 13669 } 13670 13671 _displayStrength(displayScore); 13672 } 13673 13674 function _displayStrength(displayScore) { 13675 let strengthLabel = '', 13676 className; 13677 13678 switch (displayScore) { 13679 case 0: 13680 strengthLabel = "No\u0020Password"; 13681 className = 'password-none'; 13682 break; 13683 13684 case 1: 13685 strengthLabel = "Weak"; 13686 className = 'password-weak'; 13687 break; 13688 13689 case 2: 13690 strengthLabel = "Medium"; 13691 className = 'password-medium'; 13692 break; 13693 13694 case 3: 13695 strengthLabel = "Strong"; 13696 className = 'password-strong'; 13697 break; 13698 13699 case 4: 13700 strengthLabel = "Very\u0020Strong"; 13701 className = 'password-very-strong'; 13702 break; 13703 } 13704 const meterElm = document.getElementById('password-strength-meter-container'), 13705 meterLabelElm = document.getElementById('password-strength-meter-label'); 13706 meterElm.className = ''; 13707 meterElm.classList.add(className); 13708 meterLabelElm.textContent = strengthLabel; 13709 } 13710 13711 let loading = false; 13712 const callbacks = []; 13713 13714 function loadZxcvbn(cb) { 13715 callbacks.push(cb); 13716 if (loading) return; 13717 loading = true; 13718 13719 const script = document.createElement('script'); 13720 script.type = 'text/javascript'; 13721 script.src = 'https\u003A\u002F\u002Fwww.purdys.com\u002Fstatic\u002Fversion1790083580\u002Ffrontend\u002FPurdys2\u002Fhyva\u002Fen_US\u002Fjs\u002Fzxcvbn.js'; 13722 script.async = true; 13723 script.onload = () => callbacks.forEach(cb => cb()); 13724 document.head.appendChild(script); 13725 } 13726 13727 window.addEventListener('password-validate', function (evt) { 13728 const meterElm = document.getElementById('password-strength-meter-container'), 13729 meterLabelElm = document.getElementById('password-strength-meter-label'); 13730 if (meterElm && meterLabelElm) { 13731 if (evt.detail && evt.detail.elementID) { 13732 const cb = () => _calculateStrength(evt.detail); 13733 window.zxcvbn ? cb() : loadZxcvbn(cb) 13734 } 13735 } 13736 }); 13737 13738 13739 document.addEventListener('DOMContentLoaded', () => { 13740 hyva && hyva.formValidation && hyva.formValidation.addRule('password-strength', function(value, options, field, context) { 13741 let counter = 0; 13742 const minCharacterSets = options.minCharacterSets !== undefined ? options.minCharacterSets : 1; 13743 if (value.match(/\d+/)) { 13744 counter++; 13745 } 13746 13747 if (value.match(/[a-z]+/)) { 13748 counter++; 13749 } 13750 13751 if (value.match(/[A-Z]+/)) { 13752 counter++; 13753 } 13754 13755 if (value.match(/[^a-zA-Z0-9]+/)) { 13756 counter++; 13757 } 13758 13759 queueMicrotask(() => { 13760 window.dispatchEvent(new CustomEvent('password-validate', { detail: {
13761 elementID: field.element.id || 'password', 13762 valid: field.state.valid 13763 }})); 13764 }); 13765 13766 if (counter < minCharacterSets) { 13767 const missing = minCharacterSets - counter; 13768 return hyva.str('The\u0020password\u0020must\u0020include\u0020at\u0020least\u0020\u00251\u0020or\u0020more\u0020of\u003A\u0020uppercase\u0020\u0028A\u002DZ\u0029,\u0020lowercase\u0020\u0028a\u002Dz\u0029,\u0020number\u0020and\u0020symbol.', missing); 13769 } 13770 return true; 13771 }); 13772 }) 13773 })(); 13774 13775</script>
13775 13776</div> 13777 <div class="text-center mt-4"> 13778 <p>Already have an account?</p> 13779 <a href="#" class="underline" @click.prevent="setState('login')"> 13780 <span class="text-base font-bold">Login now</span> 13781 </a> 13782 </div> 13783 </div> 13784 <div x-show="state === 'register-success'" 13785 @open-register-success-modal.window="setState('register-success', event.detail)"> 13786 13787<div class="flex gap-x-10"> 13788 <img src="https://www.purdys.com/static/version1790083580/frontend/Purdys2/hyva/en_US/images/register-success-img.png" 13789 class="hidden md:block h-[250px] w-[250px] lg:h-[390px] lg:w-[390px] rounded-lg" 13790 loading="lazy" 13791 > 13792 <div class="md:max-w-[250px] lg:max-w-[390px] flex flex-col gap-y-6 md:gap-y-10 items-center"> 13793 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 98 98" fill="none" class="w-24 h-24" role="img"> 13794 <path d="M31.75 50.9418L43.452 63L66.75 36" stroke="#B09FCC" stroke-width="8" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/> 13795 <path d="M49.0761 1C75.5483 1 97 22.6038 97 49.0761C97 75.5483 75.3962 97 48.9239 97C22.4517 97 1.15214 75.5483 1 49.0761C1 22.6038 22.4517 1 48.9239 1H49.0761Z" stroke="#532E91" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round"/> 13796<title>rounded-checkmark</title></svg> 13797 <legend> 13798 <h2 class="font-bold text-center !max-w-full">Your Purdys account has been created!</h2> 13799 <p class="font-bold text-center" x-show="requireEmailConfirmation">Please check your email</p> 13800 </legend> 13801 <button 13802 @click="showOrganizationModal && !requireEmailConfirmation ? setState('organization') : hide();" 13803 class="w-full btn btn-primary px-12 max-h-[44px] uppercase" 13804 x-html="requireEmailConfirmation ? 'close' : 'get started'" 13805 ></button> 13806 </div> 13807</div> 13808 </div> 13809 <div x-show="state === 'beta-redirect'" 13810 x-intersect.once="setTimeout(() => window.location.href = 'https://www.purdys.com/', 3500)" 13811 class="p-6 text-center"> 13812 <h3>Our Winter Season will start soon! Come back on August 18th to start your fundraiser.</h3> 13813 <p class="m-4">In the meantime you will be redirected to shop at purdys.com</p> 13814 </div> 13815 <div x-show="state === 'organization'" class="min-h-[512px]"> 13816 13817<!-- Organization Modal Placeholder --> 13818<div class="organization-modal-content"> 13819 <h2>Organization Setup</h2> 13820 <p>Organization modal content will be implemented here.</p> 13821</div> 13822 <div class="text-center mt-6"> 13823 <a href="https://www.purdys.com/customer/account/" class="underline"> 13824 <span class="text-base font-bold">Go to my account</span> 13825 </a> 13826 </div> 13827 </div> 13828</div> 13829
13830<script> 13831 function authModalData() { 13832 return { 13833 isLoading: false, 13834 state: 'login', 13835 requireEmailConfirmation: false, 13836 initialTab: null, 13837 modalInitialized: false, 13838 async init() { 13839 try { 13840 const newState = await this.initState(); 13841 this.state = newState; 13842 this.modalInitialized = true; 13843 } catch (error) { 13844 console.error('Error initializing auth modal:', error); 13845 this.state = 'login'; 13846 this.modalInitialized = true; 13847 } 13848 }, 13849 setInitialTab(tab) { 13850 this.initialTab = tab; 13851 if (tab && this.modalInitialized) { 13852 this.setState(tab); 13853 } else if (tab) { 13854 this.state = tab; 13855 } 13856 }, 13857 async checkIfUserHasAccess() { 13858 try { 13859 const response = await fetch(BASE_URL + 'purdys_gnfbeta/beta/checkIfUserHasAccess', { 13860 headers: {accept: 'application/json'} 13861 }); 13862 13863 const data = await response.json(); 13864 const {hasAccess} = data; 13865 13866 return hasAccess ? 'organization' : 'beta-redirect'; 13867 } catch (error) { 13868 console.error('Error checking user access:', error); 13869 return 'organization'; 13870 } 13871 }, 13872 async initState() { 13873 const loggedIn = false; 13874 13875 // If initial tab is set, use it for non-logged-in users 13876 if (!loggedIn && this.initialTab) { 13877 return this.initialTab; 13878 } 13879 13880 if (loggedIn) { 13881 if (localStorage.getItem('isBetaEnabled')) { 13882 return await this.checkIfUserHasAccess(); 13883 } else { 13884 return 'organization'; 13885 } 13886 } else { 13887 return 'login'; 13888 } 13889 }, 13890 updateOrganizationFormKey() { 13891 const formKeyInput = document.querySelector('.organization-modal-content form input[name="form_key"]'); 13892 13893 if (formKeyInput) { 13894 formKeyInput.value = hyva.getFormKey(); 13895 } 13896 }, 13897 setIsLoading(isLoading) { 13898 this.isLoading = isLoading; 13899 }, 13900 async setState(state, data = {}) { 13901 this.state = state; 13902 13903 if ((state === 'register' || state === 'forgotPassword') && data.email) { 13904 const emailInputId = state.replace('P', '_p') + '_email_address'; 13905 const emailInput = document.getElementById(emailInputId); 13906 if (emailInput) { 13907 emailInput.value = data.email; 13908 } 13909 } 13910 13911 if (state === 'organization') { 13912 //to make sure that customer is in beta list 13913 if (localStorage.isBetaEnabled) { 13914 const newState = await this.checkIfUserHasAccess(); 13915 if (newState !== state) { 13916 this.state = newState; 13917 return; 13918 } 13919 } 13920 13921 this.updateOrganizationFormKey(); 13922 } 13923 13924 if (data.requireEmailConfirmation) { 13925 this.requireEmailConfirmation = data.requireEmailConfirmation; 13926 } 13927 13928 if (data.showOrganizationModal) { 13929 this.showOrganizationModal = data.showOrganizationModal; 13930 } 13931 } 13932 } 13933 } 13934</script>
13934 13935 </div> 13936 </div> 13937 </div> 13938 13939 <form action="recpatcha-holder"></form> 13940</div> 13941
13941<script> 13942 const wpGA4ServerSide = { 13943 pushClick: function(productId, listId, listName, index, elm) { 13944 if (elm && elm.hasAttribute('onclick')) { 13945 var requestUrl = 'https://www.purdys.com/weltpixel_ga4/track/productclick/'; 13946 if (productId && listId && listName) { 13947 fetch(requestUrl, { 13948 method: 'POST', 13949 headers: { 13950 'Content-Type': 'application/x-www-form-urlencoded', 13951 'X-Requested-With': 'XMLHttpRequest' 13952 }, 13953 body: 'product_id=' + productId + '&list_id=' + listId +'&list_name=' + listName + '&index=' + index 13954 }) 13955 .then(res => res.json()) 13956 .then(data => { 13957 }); 13958 } 13959 } 13960 }, 13961 13962 pushViewItemList: function(hashId) { 13963 if (hashId && document.getElementById('wp_ga4_server_side_view_item_list')) { 13964 var requestUrl = 'https://www.purdys.com/weltpixel_ga4/track/viewitemlist/'; 13965 fetch(requestUrl, { 13966 method: 'POST', 13967 headers: { 13968 'Content-Type': 'application/x-www-form-urlencoded', 13969 'X-Requested-With': 'XMLHttpRequest' 13970 }, 13971 13972 body: 'hash_id='+hashId 13973 }) 13974 .then(res => res.json()) 13975 .then(data => { 13976 }); 13977 } 13978 } 13979 }; 13980 13981 window.wpGA4ServerSide = wpGA4ServerSide; 13982 13983 let allowGa4Services = window.ga4AllowServices ?? true; 13984 13985 if (allowGa4Services) { 13986 let selectedShippingMethod = ''; 13987 let selectedPaymentMethod = ''; 13988 13989 const {fetch: originalFetch} = window; 13990 window.fetch = async (...args) => { 13991 let [resource, config] = args; 13992 let response = await originalFetch(resource, config); 13993 let ajaxContentBody = false; 13994 13995 if ((typeof resource == 'string') && 13996 ((resource.search('post/livewire/message/checkout.shipping.methods') > 0) || (resource.search('post/livewire/message/checkout.payment.methods') > 0))) { 13997 var jsonBody = false; 13998 if (config.body) { 13999 try { 14000 jsonBody = JSON.parse(config.body); 14001 } catch (e) {} 14002 } 14003 14004 if (jsonBody && jsonBody.updates && jsonBody.updates[0] && jsonBody.updates[0].payload && jsonBody.updates[0].payload.value) { 14005 if (resource.search('checkout.shipping.methods') > 0) { 14006 if (selectedShippingMethod != jsonBody.updates[0].payload.value) { 14007 selectedShippingMethod = jsonBody.updates[0].payload.value; 14008 ajaxContentBody = 'step=1&option=' + selectedShippingMethod; 14009 } 14010 } 14011 if (resource.search('checkout.payment.methods') > 0) { 14012 if (selectedPaymentMethod != jsonBody.updates[0].payload.value) { 14013 selectedPaymentMethod = jsonBody.updates[0].payload.value; 14014 ajaxContentBody = 'step=2&option=' + selectedPaymentMethod; 14015 } 14016 } 14017 14018 if (ajaxContentBody) { 14019 fetch('https://www.purdys.com/weltpixel_ga4/track/hyva/', { 14020 method: 'POST', 14021 headers: { 14022 'Content-Type': 'application/x-www-form-urlencoded', 14023 'X-Requested-With': 'XMLHttpRequest' 14024 }, 14025 body: ajaxContentBody 14026 }) 14027 .then(res => res.json()) 14028 .then(data => { 14029 if (data) { 14030 window.dataLayer.push({ecommerce: null}); 14031 window.dataLayer.push(data); 14032 } 14033 }); 14034 } 14035 } 14036 } 14037 14038 if ((typeof resource == 'string') && resource.search('/customer\/section\/load/') > 0) { 14039 response.clone().json().then(data => { 14040 if (data?.wp_ga4?.datalayer) { 14041 const dataLayerData = JSON.parse(data.wp_ga4.datalayer); 14042 dataLayerData.forEach((dataLayerItem) => { 14043 window.dataLayer.push({ecommerce: null}); 14044 window.dataLayer.push(dataLayerItem); 14045 }); 14046 } 14047 }); 14048 } 14049 return response; 14050 }; 14051 } 14052 14053 </script>
vendor: 1 bytes, line 14053
14053
14054<script type="text/javascript">window.NREUM||(NREUM={});NREUM.info={"beacon":"bam.nr-data.net","licenseKey":"NRJS-8c25a1a7b460fa8b963","applicationID":"1118134761","transactionName":"ZwcBNxNUDRIEAkRRDl5NIgAVXAwPSgJFSxVfDwYRTlQAAgoUXkxOXA0ECg8=","queueTime":0,"applicationTime":833,"atts":"S0ACQVtOHhw=","errorBeacon":"bam.nr-data.net","agent":""}</script>
14054</body> 14055</html>
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.