/**
 * @license
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const t$2=globalThis,e$5=t$2.ShadowRoot&&(void 0===t$2.ShadyCSS||t$2.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$2=Symbol(),o$3=new WeakMap;let n$2 = class n{constructor(t,e,o){if(this._$cssResult$=!0,o!==s$2)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$5&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$3.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$3.set(s,t));}return t}toString(){return this.cssText}};const r$3=t=>new n$2("string"==typeof t?t:t+"",void 0,s$2),i$3=(t,...e)=>{const o=1===t.length?t[0]:e.reduce(((e,s,o)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[o+1]),t[0]);return new n$2(o,t,s$2)},S$1=(s,o)=>{if(e$5)s.adoptedStyleSheets=o.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet));else for(const e of o){const o=document.createElement("style"),n=t$2.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$2=e$5?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$3(e)})(t):t;

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */const{is:i$2,defineProperty:e$4,getOwnPropertyDescriptor:r$2,getOwnPropertyNames:h$1,getOwnPropertySymbols:o$2,getPrototypeOf:n$1}=Object,a$1=globalThis,c$1=a$1.trustedTypes,l$1=c$1?c$1.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t);}catch(t){i=null;}}return i}},f$1=(t,s)=>!i$2(t,s),y$1={attribute:!0,type:String,converter:u$1,reflect:!1,hasChanged:f$1};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;class b extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=y$1){if(s.state&&(s.attribute=!1),this._$Ei(),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),r=this.getPropertyDescriptor(t,i,s);void 0!==r&&e$4(this.prototype,t,r);}}static getPropertyDescriptor(t,s,i){const{get:e,set:h}=r$2(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get(){return e?.call(this)},set(s){const r=e?.call(this);h.call(this,s),this.requestUpdate(t,r,i);},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??y$1}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$1(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...h$1(t),...o$2(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$2(s));}else void 0!==s&&i.push(c$2(s));return i}static _$Eu(t,s){const i=s.attribute;return !1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev();}_$Ev(){this._$Eg=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$ES(),this.requestUpdate(),this.constructor.l?.forEach((t=>t(this)));}addController(t){(this._$E_??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.();}removeController(t){this._$E_?.delete(t);}_$ES(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$E_?.forEach((t=>t.hostConnected?.()));}enableUpdating(t){}disconnectedCallback(){this._$E_?.forEach((t=>t.hostDisconnected?.()));}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$EO(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&!0===i.reflect){const r=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==r?this.removeAttribute(e):this.setAttribute(e,r),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),r="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e,this[e]=r.fromAttribute(s,t.type),this._$Em=null;}}requestUpdate(t,s,i,e=!1,r){if(void 0!==t){if(i??=this.constructor.getPropertyOptions(t),!(i.hasChanged??f$1)(e?r:this[t],s))return;this.C(t,s,i);}!1===this.isUpdatePending&&(this._$Eg=this._$EP());}C(t,s,i){this._$AL.has(t)||this._$AL.set(t,s),!0===i.reflect&&this._$Em!==t&&(this._$Ej??=new Set).add(t);}async _$EP(){this.isUpdatePending=!0;try{await this._$Eg;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t)!0!==i.wrapped||this._$AL.has(s)||void 0===this[s]||this.C(s,this[s],i);}let t=!1;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$E_?.forEach((t=>t.hostUpdate?.())),this.update(s)):this._$ET();}catch(s){throw t=!1,this._$ET(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$E_?.forEach((t=>t.hostUpdated?.())),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t);}_$ET(){this._$AL=new Map,this.isUpdatePending=!1;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$Eg}shouldUpdate(t){return !0}update(t){this._$Ej&&=this._$Ej.forEach((t=>this._$EO(t,this[t]))),this._$ET();}updated(t){}firstUpdated(t){}}b.elementStyles=[],b.shadowRootOptions={mode:"open"},b[d$1("elementProperties")]=new Map,b[d$1("finalized")]=new Map,p$1?.({ReactiveElement:b}),(a$1.reactiveElementVersions??=[]).push("2.0.2");

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const t$1=globalThis,i$1=t$1.trustedTypes,s$1=i$1?i$1.createPolicy("lit-html",{createHTML:t=>t}):void 0,e$3="$lit$",h=`lit$${(Math.random()+"").slice(9)}$`,o$1="?"+h,n=`<${o$1}>`,r$1=document,l=()=>r$1.createComment(""),c=t=>null===t||"object"!=typeof t&&"function"!=typeof t,a=Array.isArray,u=t=>a(t)||"function"==typeof t?.[Symbol.iterator],d="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,v=/-->/g,_=/>/g,m=RegExp(`>|${d}(?:([^\\s"'>=/]+)(${d}*=${d}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),p=/'/g,g=/"/g,$=/^(?:script|style|textarea|title)$/i,y=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),x=y(1),w=Symbol.for("lit-noChange"),T=Symbol.for("lit-nothing"),A=new WeakMap,E=r$1.createTreeWalker(r$1,129);function C(t,i){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==s$1?s$1.createHTML(i):i}const P=(t,i)=>{const s=t.length-1,o=[];let r,l=2===i?"<svg>":"",c=f;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,y=0;for(;y<s.length&&(c.lastIndex=y,u=c.exec(s),null!==u);)y=c.lastIndex,c===f?"!--"===u[1]?c=v:void 0!==u[1]?c=_:void 0!==u[2]?($.test(u[2])&&(r=RegExp("</"+u[2],"g")),c=m):void 0!==u[3]&&(c=m):c===m?">"===u[0]?(c=r??f,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?m:'"'===u[3]?g:p):c===g||c===p?c=m:c===v||c===_?c=f:(c=m,r=void 0);const x=c===m&&t[i+1].startsWith("/>")?" ":"";l+=c===f?s+n:d>=0?(o.push(a),s.slice(0,d)+e$3+s.slice(d)+h+x):s+h+(-2===d?i:x);}return [C(t,l+(t[s]||"<?>")+(2===i?"</svg>":"")),o]};class V{constructor({strings:t,_$litType$:s},n){let r;this.parts=[];let c=0,a=0;const u=t.length-1,d=this.parts,[f,v]=P(t,s);if(this.el=V.createElement(f,n),E.currentNode=this.el.content,2===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=E.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(e$3)){const i=v[a++],s=r.getAttribute(t).split(h),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:c,name:e[2],strings:s,ctor:"."===e[1]?k:"?"===e[1]?H:"@"===e[1]?I:R}),r.removeAttribute(t);}else t.startsWith(h)&&(d.push({type:6,index:c}),r.removeAttribute(t));if($.test(r.tagName)){const t=r.textContent.split(h),s=t.length-1;if(s>0){r.textContent=i$1?i$1.emptyScript:"";for(let i=0;i<s;i++)r.append(t[i],l()),E.nextNode(),d.push({type:2,index:++c});r.append(t[s],l());}}}else if(8===r.nodeType)if(r.data===o$1)d.push({type:2,index:c});else {let t=-1;for(;-1!==(t=r.data.indexOf(h,t+1));)d.push({type:7,index:c}),t+=h.length-1;}c++;}}static createElement(t,i){const s=r$1.createElement("template");return s.innerHTML=t,s}}function N(t,i,s=t,e){if(i===w)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=c(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(!1),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=N(t,h._$AS(t,i.values),h,e)),i}class S{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??r$1).importNode(i,!0);E.currentNode=e;let h=E.nextNode(),o=0,n=0,l=s[0];for(;void 0!==l;){if(o===l.index){let i;2===l.type?i=new M(h,h.nextSibling,this,t):1===l.type?i=new l.ctor(h,l.name,l.strings,this,t):6===l.type&&(i=new L(h,this,t)),this._$AV.push(i),l=s[++n];}o!==l?.index&&(h=E.nextNode(),o++);}return E.currentNode=r$1,e}p(t){let i=0;for(const s of this._$AV)void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class M{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=T,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??!0;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=N(this,t,i),c(t)?t===T||null==t||""===t?(this._$AH!==T&&this._$AR(),this._$AH=T):t!==this._$AH&&t!==w&&this._(t):void 0!==t._$litType$?this.g(t):void 0!==t.nodeType?this.$(t):u(t)?this.T(t):this._(t);}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t));}_(t){this._$AH!==T&&c(this._$AH)?this._$AA.nextSibling.data=t:this.$(r$1.createTextNode(t)),this._$AH=t;}g(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=V.createElement(C(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new S(e,this),s=t.u(this.options);t.p(i),this.$(s),this._$AH=t;}}_$AC(t){let i=A.get(t.strings);return void 0===i&&A.set(t.strings,i=new V(t)),i}T(t){a(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new M(this.k(l()),this.k(l()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,i){for(this._$AP?.(!1,!0,i);t&&t!==this._$AB;){const i=t.nextSibling;t.remove(),t=i;}}setConnected(t){void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class R{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=T,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=T;}_$AI(t,i=this,s,e){const h=this.strings;let o=!1;if(void 0===h)t=N(this,t,i,0),o=!c(t)||t!==this._$AH&&t!==w,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=N(this,e[s+n],i,n),r===w&&(r=this._$AH[n]),o||=!c(r)||r!==this._$AH[n],r===T?t=T:t!==T&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.O(t);}O(t){t===T?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class k extends R{constructor(){super(...arguments),this.type=3;}O(t){this.element[this.name]=t===T?void 0:t;}}class H extends R{constructor(){super(...arguments),this.type=4;}O(t){this.element.toggleAttribute(this.name,!!t&&t!==T);}}class I extends R{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=N(this,t,i,0)??T)===w)return;const s=this._$AH,e=t===T&&s!==T||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==T&&(s===T||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t);}}class L{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){N(this,t);}}const Z=t$1.litHtmlPolyfillSupport;Z?.(V,M),(t$1.litHtmlVersions??=[]).push("3.1.0");const j=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new M(i.insertBefore(l(),t),t,void 0,s??{});}return h._$AI(t),h};

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */class s extends b{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=j(i,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1);}render(){return w}}s._$litElement$=!0,s[("finalized")]=!0,globalThis.litElementHydrateSupport?.({LitElement:s});const r=globalThis.litElementPolyfillSupport;r?.({LitElement:s});(globalThis.litElementVersions??=[]).push("4.0.2");

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const t={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6},e$2=t=>(...e)=>({_$litDirective$:t,values:e});class i{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i;}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}}

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */let e$1 = class e extends i{constructor(i){if(super(i),this.et=T,i.type!==t.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===T||null==r)return this.vt=void 0,this.et=r;if(r===w)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.et)return this.vt;this.et=r;const s=[r];return s.raw=s,this.vt={_$litType$:this.constructor.resultType,strings:s,values:[]}}};e$1.directiveName="unsafeHTML",e$1.resultType=1;const o=e$2(e$1);

class ElineSearchAutocomplete extends s {
    static styles = [
        i$3`[class*=" icon-"]:before,[class^=icon-]:before{font-family:glicons!important;font-style:normal!important;font-weight:400!important;font-variant:normal!important;text-transform:none!important;speak:none;vertical-align:middle;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-hierarchy-2:before{content:"\\73"}.icon-search-1:before{content:"\\70"}.icon-arrow-left:before{content:"\\74"}.icon-close-1:before{content:"\\42"}.autocomplete-suggestion:hover{background:#f7f7f7}*,:after,:before{box-sizing:border-box}.full-width{width:100%}fieldset{margin-bottom:1.5em;padding:0;border-width:0}legend{padding:0;border-width:0}form li{margin:1em auto}label{font-weight:400;cursor:pointer;display:block;margin-bottom:.4em;position:relative;top:0;left:0}label em{font-weight:300}label.inline{display:inline;margin:0}label span.right{position:absolute;bottom:0;right:1em;text-align:right}label.disabled{color:#ccc}.required label{font-weight:600}input[type=checkbox],input[type=radio]{box-sizing:border-box}input[type=search]{box-sizing:content-box}input[type=search]:focus{outline:0!important}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input[type=email],input[type=password],input[type=tel],input[type=text],textarea{-moz-appearance:none;-webkit-appearance:none;margin:0;transition:border .3s ease-in-out;border:1px solid #bebebb;font-size:1.1em;padding:.4em .5em;background:#fff;width:100%}input::-webkit-textfield-decoration-container{visibility:hidden}textarea{height:150px;resize:none}input[type=email]:focus,input[type=password]:focus,input[type=tel]:focus,input[type=text]:focus,textarea:focus{box-shadow:0 0 0 #07affc;border:1px solid #868686;background:#fff;outline:0}.placeholder,input:-moz-placeholder,input::-webkit-input-placeholder{color:#b6b6b6}input::-moz-focus-inner{border:0}input.disabled,input[disabled=disabled]{color:#999;background:#f5f5f5;-moz-box-shadow:inset 0 0 2px #ddd;-webkit-box-shadow:inset 0 1px 2px #ddd;box-shadow:inset 0 1px 2px #ddd}input.invalid,select.invalid,textarea.invalid{border:1px solid red!important;background-color:snow!important}label.invalid,select.invalid,select.invalid option{color:red!important}.sort-bar{position:relative}.sort-bar .select-wrap{top:.3em}.sort-bar .cust-btn-wrap{margin-left:0}.select-wrap{background:#fff;overflow:hidden;position:relative;visibility:visible;padding:0;width:100%}.select-wrap:before{position:absolute;font-family:glicons;top:0;right:0;content:"\\"";
            font-size: .8em;
            padding: 2px;
          }

          select {
            border-radius: 2px;
            display: inline-block;
            font-size: .8em;
            margin: 0;
            width: 100%;
            outline: 0 none;
            padding: 0 1.5em 0 0;
            position: relative;
            border: 0;
            padding: .49em;
            background: #fff;
            cursor: pointer;
          }

          /* hide input */

          input.radio:empty {
            margin-left: -999px;
          }

          /* style label */

          input.radio:empty ~ label {
            position: relative;
            float: left;
            line-height: 2.5em;
            text-indent: 3.25em;
            cursor: pointer;
            -webkit-user-select: none;
            user-select: none;
          }

          input.radio:empty ~ label:before {
            position: absolute;
            display: block;
            top: 0;
            bottom: 0;
            left: 0;
            content: '';
            width: 2.5em;
            background: #d1d3d4;
            border-radius: 3px 0 0 3px;
          }

          /* toggle hover */

          input.radio:hover:not(:checked) ~ label:before {
            content: '\\2714';
            text-indent: .9em;
            color: #c2c2c2;
          }

          input.radio:hover:not(:checked) ~ label {
            color: #888;
          }

          /* toggle on */

          input.radio:checked ~ label:before {
            content: '\\2714';
            text-indent: .9em;
            color: #fff;
            background-color: #4dcb6d;
          }

          input.radio:checked ~ label {
            color: #777;
          }

          /* radio focus */

          input.radio:focus ~ label:before {
            box-shadow: 0 0 0 2px #999;
          }

          label > input[type="checkbox"], label > input[type="radio"], label > input[type="radio"] > select {
            display: inline-block;
            margin: 0 .3em 0 0;
            vertical-align: middle;
            width: auto;
          }

          .spinner {
            width: 100%;
            height: 100vh;
            background-color: rgb(0, 0, 0);
            background-color: rgba(0, 0, 0, .8);
            color: white;
            display: none;
            z-index: 1000;
            font-size: 5em;
            position: absolute;
          }

          .spinner div {
            margin: auto;
            position: absolute;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            height: 1em;
            width: 1em;
            line-height: 1em;
          }

          .check-box {
            display: flex;
            margin: .5em 0;
          }

          .check-box label {
            display: inline-block;
            margin-bottom: 0;
          }


          .form-inline input[type="button"], .form-inline input[type="submit"] {
            border: 1px solid #c2c2c2;
            background: transparent;
            background-color: #fff;
            padding: .4em;
            cursor: pointer;
            border-radius: 0;
            display: block;
            font-size: 1.4em;
            border-left: none;
            padding-right: 0;
            color: #555;
            position: absolute;
            top: 0;
            left: 100%;
            font-family: "glicons" !important;
            cursor: pointer;
            box-shadow: none;
            padding: 0 8px;
            width: 2.5rem;
            height: 2.3rem;
            -webkit-appearance: none;
          }

          .form-inline input[type="search"], .form-inline input[type="text"], .form-inline input[type="email"] {
            border-radius: 0;
            box-sizing: border-box;
            border: 1px solid #c2c2c2;
            border-right: none;
            display: block;
            background-color: white;
            color: #666;
            font: inherit;
            line-height: normal;
            border-right: none;
            padding: 8px 10px;
            outline: none;
            box-sizing: border-box;
            text-shadow: none;
            text-align: left;
            -webkit-appearance: none;
            -moz-appearance: none;
            height: 2.3rem;
            font-size: 1rem !important;
          }

          .form-inline {
            margin: 0 0 0 0;
            display: flex;
            align-items: center;
            justify-items: center;
            position: relative;
            width: 100%;
          }

          .form-inline-wrapper {
            position: relative;
            margin-right: 2em;
          }

          .form-inline-field {
            width: 100%;
          }

          /* Toggle switch - based on https://www.cssscript.com/realistic-ios-switch-pure-css/ */

          .form-switch {
            display: inline-block;
            cursor: pointer;
            -webkit-tap-highlight-color: transparent;
          }

          label.form-switch {
            font-size: .8rem;
            margin-left: 1rem
          }

          .form-switch i {
            position: relative;
            display: block;
            margin-right: .5rem;
            width: 46px;
            height: 26px;
            background-color: #e6e6e6;
            border-radius: 23px;
            vertical-align: text-bottom;
            transition: all .3s linear;
          }

          .form-switch.form-switch-sm i {
            width: 36px;
            height: 16px;
          }

          .form-switch i::before {
            content: "";
            position: absolute;
            left: 0;
            width: 42px;
            height: 22px;
            background-color: #fff;
            border-radius: 11px;
            transform: translate3d(2px, 2px, 0) scale3d(1, 1, 1);
            transition: all .25s linear;
          }

          .form-switch.form-switch-sm i::before {
            width: 32px;
            height: 12px;
          }

          .form-switch i::after {
            content: "";
            position: absolute;
            left: 0;
            width: 22px;
            height: 22px;
            background-color: #fff;
            border-radius: 11px;
            box-shadow: 0 2px 2px rgba(0, 0, 0, .24);
            transform: translate3d(2px, 2px, 0);
            transition: all .2s ease-in-out;
          }

          .form-switch.form-switch-sm i::after {
            width: 12px;
            height: 12px;
          }

          .form-switch:active i::after {
            width: 28px;
            transform: translate3d(2px, 2px, 0);
          }

          .form-switch:active input:checked + i::after {
            transform: translate3d(16px, 2px, 0);
          }

          .form-switch input[type="checkbox"] {
            opacity: 0;
            height: 1px;
            display: block;
          }

          .form-switch input:checked + i {
            background-color: var(--secondaryLightColor);
          }

          .form-switch input:checked + i::before {
            transform: translate3d(18px, 2px, 0) scale3d(0, 0, 0);
          }

          .form-switch input:checked + i::after {
            transform: translate3d(22px, 2px, 0);
          }

          /*--Clears chrome and IE styles on  search fields--*/

          input[type=text]::-ms-clear {
            display: none;
            width: 0;
            height: 0;
          }

          input[type=text]::-ms-reveal {
            display: none;
            width: 0;
            height: 0;
          }

          input[type="search"]::-webkit-search-decoration,
          input[type="search"]::-webkit-search-cancel-button,
          input[type="search"]::-webkit-search-results-button,
          input[type="search"]::-webkit-search-results-decoration {
            display: none;
          }

          input[type=text]::-ms-clear {
            display: none;
            width: 0;
            height: 0;
          }

          input[type=text]::-ms-reveal {
            display: none;
            width: 0;
            height: 0;
          }


          input[type="checkbox"] {
            position: relative;
            width: 1.5rem !important;
            height: 1.5rem;
            min-width: 1.5rem;
            color: #363839;
            border: 1px solid #bdc1c6;
            border-radius: 4px;
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            outline: 0;
            cursor: pointer;
            background: #fafafa;
            transition: background 175ms cubic-bezier(.1, .1, .25, 1);
            margin-right: .5rem;
            margin-bottom: 0;
          }

          input[type="checkbox"]::before {
            position: absolute;
            content: "";
            display: block;
            top: 2px;
            left: 7px;
            width: 8px;
            height: 14px;
            border-style: solid;
            border-color: #fff;
            border-width: 0 2px 2px 0;
            transform: rotate(45deg);
            opacity: 0;
          }

          input[type="checkbox"]:checked {
            color: #fff;
            border-color: #222;
            background: #222;
          }

          input[type="checkbox"]:checked::before {
            opacity: 1;
          }


          /*------------------------------------------------------------
          8.1 - Search
          ------------------------------------------------------------*/

          ::-webkit-input-placeholder {
            color: #b6b6b6;
          }

          input:-moz-placeholder {
            color: #b6b6b6;
          }          

          .search-box {
              padding: .5rem;
            background: transparent;
            border-radius: 6px;
            position: relative;
            overflow: visible;
            z-index: 354;
            max-width: 100%;
            display: flex;
            align-items: center;
            width: 100%;
            transition: width .5s;
          }

          .search-box .select-wrap {
            background: #f0f0f0;
            position: absolute;
            overflow: hidden;
            top: 0;
            left: auto;
            right: 100%;
            visibility: visible;
            width: 5.6em;
            border-radius: 0;
            border: 1px solid #c2c2c2;
            border-right: 0;
            height: 2.3rem;
          }


          .search-box .select-wrap:before {
            position: absolute;
            font-family: "glicons";
            top: .6em;
            right: .09em;
            content: "\\"";font-size:.8em}.search-box select{background:0 0;cursor:pointer;display:inline-block;font-size:.72em;height:39px;margin:0;width:100%;outline:0 none;padding:0 1em 0 .2em;position:relative;border:0;-webkit-appearance:none;-moz-appearance:none}.search-box .form-inline input[type=button],.search-box .form-inline input[type=submit]{height:2.8rem;color:#111;position:initial;border:0;border-left:none;background:0 0;margin-right:.3rem}.search-box .form-inline input[type=search]{height:2.8rem;border:0;border-right:none;background:0 0}.search-box.form-active .form-inline input[type=search]{height:2.8rem;border:0;color:#111;background:0 0}.search-box.form-active .form-inline input[type=button],.search-box.form-active .form-inline input[type=submit]{height:2.8rem;color:#111;position:initial;border:0;background:0 0}.search-box .form-inline-wrapper{border-radius:2rem;border:1px solid #999;overflow:hidden;background:#f5f5f5;margin-left:0;display:flex;margin-right:0;width:100%;align-items:center}.search-box.form-active .form-inline-wrapper{border-radius:2rem;border:2px solid var(--primaryColor);overflow:hidden;background:#fff}.search-box-form .icon-arrow-left,.search-box-form .icon-arrow-left-alt1{display:none}.search-box.form-active{padding:.5rem .5rem;background:#fff;box-shadow:2px 6px 5px rgb(68 68 68 / 50%);z-index:901;position:relative;width:100%;min-width:280px;margin:0}.search-box.form-active .search-box-form .icon-arrow-left,.search-box.form-active .search-box-form .icon-arrow-left-alt1{display:block;padding-right:.6rem;font-size:1.7em;color:#111;cursor:pointer}.clear-button{background:0 0;font-size:.85rem;box-shadow:none;height:32px;width:32px;border-radius:6px;padding:0;margin-right:.2rem;border:0}#clear-search-history{color:#111;border-bottom:1px solid #111}#clear-search-history:hover{color:var(--primaryColor);border-bottom:1px solid var(--primaryColor)}.autocomplete-suggestions{background:#fff;position:absolute;overflow-x:hidden;overflow-y:auto;padding:1rem 0;box-shadow:2px 6px 5px rgb(68 68 68 / 50%);color:#383838;z-index:9999;border:0;top:58px;left:0;width:100%;display:none;text-align:left;max-height:calc(100vh - 1.5rem)}.autocomplete-selected{background:#f5f5f5;border-radius:6px}.autocomplete-suggestion{padding:1.2rem .5rem;font-size:1rem;margin:.5rem .5rem;display:flex;align-items:center;cursor:pointer;border-bottom:0 solid #f4f4f4;position:relative;overflow:hidden}.autocomplete-suggestion img{margin-right:.4em;max-width:46px}.autocomplete-suggestion .icon{font-size:1.1rem;margin-right:.9rem;color:#6d6d6d}.auto-sug-search-header{border-top:1px solid #b0b0b0;width:100%}.autocomplete-group a{cursor:pointer}.auto-sug-cat,.auto-sug-search{padding-left:1rem}.autocomplete-suggestion-header{background:#333;color:#fff;font-weight:700;padding:.5rem 1rem;font-size:.9rem}.autocomplete-suggestion-divider{margin:0 1.4rem;border-top:1px solid #999}.autocomplete-suggestion:last-child{border-bottom:0}.sm-cat-txt{font-size:.85rem;font-style:italic}.autocomplete-group{padding-left:1rem}.search-box-screen{height:100vh;position:fixed;opacity:.5;background:#000;top:0;right:0;bottom:0;left:0;z-index:900}@media screen and (max-width:48rem){.search-box.form-active{padding:1.4rem .5rem;background:#fff;z-index:901;position:fixed;width:100%;min-width:280px;margin:0;top:0;left:0;border-radius:0}.autocomplete-suggestions{top:80px}.search-box.form-active .search-box-form .icon-arrow-left,.search-box.form-active .search-box-form .icon-arrow-left-alt1{padding-right:.8rem;font-size:2.3em}}`
    ];

    static properties = {
        minLength: {type: Number},
        inputDelay: {type: Number},
        shouldQuery: {type: Function},
        fetchResults: {type: Function},
        selectResult: {type: Function},
        formatOption: {type: Function},
        clearParentState: {type: Function},
        input: {type: Object},
        options: {type: Array, state:true,},
        select: {type: Object},
        searchPlaceHolder: {type: String},
        queryForInput: {type: String},
        searchTypesJson: { type: String,},
        searchTypes: { type: Array,},
        isFocus: {type: Boolean, state:true,},
        observer: {type: Object, state:true},
        initializedVisible: {type: Boolean, state:true},
    };

    constructor() {
        super();

        this.minLength = 0;
        this.inputDelay = 200;
        this.shouldQuery = () => true;
        this.fetchResults = this.autocompleteFetchResults();
        this.selectResult = () => true;
        this.formatOption = (option) => option;
        this.clearParentState = () => true;
        this.input = null;
        this.options = [];
        this.timeoutHandle = -1;
        this.searchPlaceHolder = '';
        this.queryForInput = '';
        this.searchTypesJson = '{}';
        this.searchTypes = {};
        this.isFocus = false;
        this.observer = null;
        this.initializedVisible = true;

        this.handleInput = this.handleInput.bind(this);
        this.handleSelect = this.handleSelect.bind(this);
        this.handleClearInput = this.handleClearInput.bind(this);
        this.handleSearchToggle = this.handleSearchToggle.bind(this);
        this.handleClearHistory = this.handleClearHistory.bind(this);
        this.handleShowing = this.handleShowing.bind(this);
    }

    dlog(msg, ...args) {
        console.log('* dlog * ' + msg, ...args);
    }

    delay = t => new Promise(resolve => setTimeout(resolve, t));

    connectedCallback() {
        super.connectedCallback();
        this.searchTypes = JSON.parse(this.searchTypesJson);

        if (this.closest('.search-expand') != null) {
            this.initializedVisible = false;
        }

        if (!this.initializedVisible) {
            document.querySelectorAll('.menu-search-toggle').forEach((searchToggle) => {
                searchToggle.addEventListener('click', this.handleSearchToggle);
            });

            this.observer = new IntersectionObserver(this.handleShowing, {
                threshold: 1.0,
            });
        }
    }

    disconnectedCallback() {
        super.disconnectedCallback();

        if (!this.initializedVisible) {
            document.querySelectorAll('.menu-search-toggle').forEach((searchToggle) => {
                searchToggle.removeEventListener('click', this.handleSearchToggle);
            });

            this.observer.disconnect();
            this.observer = null;
        }
    }

    firstUpdated(_changedProperties) {
        super.firstUpdated(_changedProperties);
        this.input = this.shadowRoot.querySelector(".search-box-input");
        if (!this.initializedVisible) {
            this.observer.observe(this.input);
        }
    }

    handleInput(e) {
        this.clearTimeout();
        /* we do history when input is empty */
        // if (this.input) {
        //     if (this.input.value) {
        //         if (this.input.value.length > this.minLength) {
        if (this.shouldQuery() || true) {
            this.checkInputChanging(this.input.value.length)
                .then(() => this.fetchResults(this.input.value ?? ''))
                .then((results) => {
                    this.options = results;
                })
                .catch((error) => this.dlog(error));
        } else {
            this.dlog('shouldQuery false');
        }
        // } else {
        //     this.dlog('too short');
        // }
        // } else {
        //     this.dlog('no value');
        // }
        // } else {
        //     this.dlog('no input');
        // }
    }

    checkInputChanging(originalLength) {
        return this.delay(this.inputDelay)
            .then(() => {
                if (this.input.value.length === originalLength) {
                    return Promise.resolve(true);
                } else {
                    return Promise.reject('Still changing.');
                }
            });
    }

    handleSelect(e) {
        e.preventDefault();
        const div = e.target.closest('.autocomplete-suggestion');
        const suggestion = JSON.parse(div.dataset.option);
        if (suggestion.data.url) {
            window.location.href = suggestion.data.url;
        } else {
            const form = this.shadowRoot.querySelector('form');
            this.shadowRoot.querySelector('.searchTypeSelect').value = suggestion.data.searchType;
            this.input.value = suggestion.value;
            form.submit();
        }
    }

    handleClearInput(e) {
        e.preventDefault();
        this.input.value = '';
    }

    handleSearchToggle(e) {
        e.preventDefault();
        if (this.initializedVisible) {
            this.toggleFocus(false);
        } else {
            document.querySelector('.search-expand').classList.toggle('open');
        }
    }

    handleClearHistory(e) {
        e.preventDefault();
        const st = this.shadowRoot.querySelector('input[name="st"]');
        if (typeof st !== null && typeof st.value !== 'undefined' && st.value !== null) {
            fetch(`/public/handler/clear_search_history.jsp?${new URLSearchParams({type: st.value})}`, {
                headers: {
                    "Accept": "application/json",
                },
                method: "GET",
                signal: AbortSignal.timeout(20000),
            }).then((response) => {
                if (response.ok) {
                    return response.json();
                }
                return Promise.reject(response.statusText);
            }).then((data) => {
                this.options = [];
            }).catch((error) => {
                this.options = [];
                console.error(error);
            });
        }
    }

    handleShowing(entries) {
        entries.forEach((entry) => {
            this.toggleFocus(entry.isIntersecting);
        });
    }

    clearTimeout() {
        if (this.timeoutHandle !== -1) {
            clearTimeout(this.timeoutHandle);
            this.timeoutHandle = -1;
        }
    }

    render() {
        return x`<div class="search-box ${this.isFocus ? 'form-active' : ''}"><form class="search-box-form form-inline" action="/search" method="GET"><i class="icon icon-arrow-left" @click="${this.handleSearchToggle}"></i><div class="form-inline-wrapper">${this.renderSearchTypes()} <input @focus="${() => this.toggleFocus(true)}" autocomplete="off" class="search-box-input form-inline-field" @input="${this.handleInput}" type="search" placeholder="${this.searchPlaceHolder}" name="q" value="${this.queryForInput}" aria-label="search"> <button class="clear-button" @click="${this.handleClearInput}" aria-label="Clear Search" title="clear search" type="button" style="${this.input?.value?.length > 0 ? 'display: block;' : 'display: none;'}"><i class="icon icon-close-1"></i></button> <input class="search-box-submit form-inline-submit" type="submit" value="p"></div></form><div class="autocomplete-suggestions" style="${this.options.length > 0 && this.isFocus ? 'display: block;' : 'display: none;'}">${this.renderResults()}</div></div><div class="search-box-screen" style="${this.isFocus ? 'display: block;' : 'display: none;'}" @click="${this.handleSearchToggle}"></div>`;
    }

    renderSearchTypes() {
        return this.searchTypes.length === 1
            ? x`<input class="searchTypeSelect" type="hidden" name="st" value="${this.searchTypes[0].mType}">`
            : x`<select class="searchTypeSelect" name="st" aria-label="Choose type of search">${this.searchTypes.map(st => x`<option value="${st.mType}">${st.mLabel}</option>`)}</select>`;
    }

    renderResults() {
        const that = this;
        let category;

        return x`${this.options.map((suggestion, i) => {
                const currentCategory = suggestion.data.group;
                if (category !== currentCategory) {
                    category = currentCategory;
                    return x`${that.formatGroup(suggestion, category)}<div @click="${this.handleSelect}" class="autocomplete-suggestion" data-index="${i}" data-option="${JSON.stringify(suggestion)}">${that.formatResult(suggestion, that.input.value, i)}</div>`;
                } else {
                    return x`<div @click="${this.handleSelect}" class="autocomplete-suggestion" data-index="${i}" data-option="${JSON.stringify(suggestion)}">${that.formatResult(suggestion, that.input.value, i)}</div>`;
                }
            })}`;
    }

    formatGroup(suggestion, category) {
        if (category === 'history') {
            return x`<div class="autocomplete-group">Your search history <a id="clear-search-history" data-st="${suggestion.data.searchType}" @click="${this.handleClearHistory}">clear</a></div>`;
        } else if (category === 'category') {
            return x``;
        } else if (category === 'searchterms') {
            const hasCategory = this.options?.some(s => s.data.group === 'category') ?? false;
            return hasCategory ? x`<div class="autocomplete-suggestion-divider"></div>` : x``;
        } else if (category === 'Products') {
            return x`<div class="autocomplete-suggestion-header">Products</div>`;
        } else {
            return x``;
        }
    }

    formatResult(suggestion, value, i) {
        const { image, group, parentCategory } = suggestion.data;
        const currentValue = this.input.value;

        if (group && group.length > 0) {
            if (group === 'history' || group === 'searchterms') {
                return x`<div class="auto-sug-search"><i class="icon icon-search-1"></i> <span class="search-sug-txt">${this._formatResult(suggestion, currentValue)}</span></div>`;
            } else if (group === 'category' && parentCategory) {
                return x`<div class="auto-sug-cat"><i class="icon icon-hierarchy-2"></i> <span class="search-sug-txt">${this._formatResult(suggestion, currentValue)} <span class="sm-cat-txt">in ${parentCategory}</span></span></div>`;
            }
        }

        if (image && image.length > 0) {
            if (image === 'none') {
                return this._formatResult(suggestion, currentValue);
            }
            return x`<img src="${image}" alt=""> <span class="search-sug-txt">${this._formatResult(suggestion, currentValue)}</span>`;
        }

        return x`<i class="icon icon-photo"></i> <span class="search-sug-txt">${this._formatResult(suggestion, currentValue)}</span>`;
    }

    _formatResult(suggestion, currentValue) {
        if (!currentValue) {
            return suggestion.value;
        }

        const pattern = new RegExp('(' + this._escapeRegExChars(currentValue) + ')', 'gi');
        const result = suggestion.value
            .replace(pattern, '<strong>$1</strong>')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/&lt;(\/?strong)&gt;/g, '<$1>');
        return x`${o(result)}`;
    }

    _escapeRegExChars(value) {
        return value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&");
    }

    toggleFocus(isFocus) {
        this.isFocus = isFocus;
        const searchAutocompleteScreen = document.getElementById('search-autocomplete-screen');
        if (searchAutocompleteScreen) {
            searchAutocompleteScreen.style.display = isFocus ? 'block' : 'none';
        }
        if (isFocus) {
            if (this.input) {
                this.input.focus();
            }

            if (this.input?.value !== this.queryForInput || this.options.length === 0) {
                this.handleInput();
            }
        }
    }

    autocompleteFetchResults() {
        return (input) => {
            this.queryForInput = input ?? '';
            let abortController = new AbortController();
            const fetchOptions = {
                method: 'GET',
                headers: {
                    "Accept": "application/json",
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                signal: abortController.signal
            };
            const params = {
                minChars: 2,
                type: () => this.shadowRoot.querySelector('.searchTypeSelect')?.value,
                q: input
            };
            for (const key in params) {
                if (typeof params[key] === 'function') {
                    params[key] = params[key]();
                }
            }

            const urlWithParams = '/public/handler/autocomplete.jsp' + '?' + new URLSearchParams(params);

            return new Promise((resolve, reject) => {
                fetch(urlWithParams, fetchOptions)
                    .then(function (response) {
                        if (!response.ok) {
                            reject(new Error('Network response was not ok'));
                        }
                        return response.json();
                    }).then(function (data) {
                    resolve(data.suggestions);
                }).catch(function (error) {
                    reject([error]);
                });
            });
        };
    }
}

customElements.define('eline-search-autocomplete', ElineSearchAutocomplete);

/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */const e=e$2(class extends i{constructor(t$1){if(super(t$1),t$1.type!==t.ATTRIBUTE||"class"!==t$1.name||t$1.strings?.length>2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.")}render(t){return " "+Object.keys(t).filter((s=>t[s])).join(" ")+" "}update(s,[i]){if(void 0===this.it){this.it=new Set,void 0!==s.strings&&(this.st=new Set(s.strings.join(" ").split(/\s/).filter((t=>""!==t))));for(const t in i)i[t]&&!this.st?.has(t)&&this.it.add(t);return this.render(i)}const r=s.element.classList;for(const t of this.it)t in i||(r.remove(t),this.it.delete(t));for(const t in i){const s=!!i[t];s===this.it.has(t)||this.st?.has(t)||(s?(r.add(t),this.it.add(t)):(r.remove(t),this.it.delete(t)));}return w}});

const popupStyles = i$3`.m-bg{top:0;left:0;width:100%;height:100%;z-index:9999998;overflow:hidden;position:fixed;background:#0b0b0b;opacity:.6;display:none}.m-bg.obscure-page{opacity:.95}.m-bg .m-bg-dark{background-color:#000}.m-wrap{top:0;left:0;width:100%;height:100%;z-index:9999999;position:fixed;outline:0!important;-webkit-backface-visibility:hidden;overflow:hidden auto;display:none}.m-bg.show,.m-wrap.show{display:block}.m-container{text-align:center;position:absolute;width:100%;height:100%;left:0;top:0;padding:0 8px;box-sizing:border-box;display:block}.m-container:before{content:'';display:inline-block;height:100%;vertical-align:middle}.m-content{width:100%;position:relative;vertical-align:middle;margin:0 auto;text-align:left;z-index:1045!important;display:inline-block}.popup{max-width:1000px;position:relative;background:#fff;padding:20px;width:auto;margin:20px auto;border-radius:10px;overflow:hidden;display:block}.popup .close-icon,.popup span{cursor:pointer;position:absolute;right:10px;top:-4px}.popup .close-icon{font-size:1.8em}.xs-popup{max-width:400px}.sm-popup{max-width:550px}.md-popup{max-width:850px}.lg-popup{max-width:1150px}.xl-popup{max-width:1400px}`;

/*
    these definitions and widths are rough approximations of MUI dialog maxWidth values. Not exact, because
    they construct things slightly differently.
 */
const POPUP_SIZE_CLASSES = {
    xsmall: 'xs-popup',
    small: "sm-popup",
    medium: "md-popup",
    large: "lg-popup",
    xlarge: "xl-popup",
};

const EVENT_NAME_POPUP_CLOSED = "popupClosed";

/*
    Abstract base class for popups.
    The basic approach here is to create a single instance of each style of popup (subclass of this), prepended
    to the beginning of the body. Contents of the popup are rendered inside of a slot, and rendered or replaced via
    render() calls in APIs defined in the subclass modules. Being at top of document works around issues
    with content showing above the popups due to different z-index stacking contexts, and this approach also DRYs up
    a lot of duplicated markup rendering and open/closed state management.

    For web component based work this means that the popup contents need to
    be broken out into their own components, and receive any necessary properties from
    the opening component. It also means that the popup contents are not actually DOM children
    of the parent component, so things like event handling need to be handled cleverly.
    See the giftlist components for usage examples.
 */
class BasePopup extends s {
    constructor() {
        super();

        this.open = false;
        this.size = '';
        this.modal = false;
        this.obscureBg = false;

        this.closeModal = this.closeModal.bind(this);
        this.stopEvent = this.stopEvent.bind(this);
        this.restoreDefaults = this.restoreDefaults.bind(this);
    }

    static properties = {
        open: {type: Boolean, reflect: true,},
        size: {type: String,},
        modal: {type: Boolean, state: true,}, /* meaning - user must interact with contents. no close box or backdrop dismiss */
        obscureBg: {type:Boolean, state: true, },
    };

    restoreDefaults() {
        this.modal = false;
        this.obscureBg = false;
    }

    triggerCloseEvent() {
        if (this.shadowRoot) {
            let slot = this.shadowRoot.querySelector('slot');
            if (slot) {
                slot.assignedElements().forEach((el) => {
                    //console.log('dispatching popup closed event');
                    el.dispatchEvent(new CustomEvent(EVENT_NAME_POPUP_CLOSED));
                });
            }
        }
    }

    openModal() {
        this.open = true;
    }

    closeModal(e) {
        if (e) {
            e.stopPropagation();
        }
        //console.log('BasePopup.closeModal');
        this.triggerCloseEvent();
        this.open = false;
    }

    /*
    The click listener on m-wrap is leading to problems where clicking inside the popup content -
    even to manipulate inputs - is bubbling up and closing the modal. At least that's my theory,
    so I'm attempting to bury them.
    I think the user should have to click the backdrop or the close box to trigger a close.
    */
    stopEvent(e) {
        if(e) {
            e.stopPropagation();
        }
    }

    /* for use with classMap */
    createSizeClasses() {
        let sizeClasses = {};
        for (let key in POPUP_SIZE_CLASSES) {
            sizeClasses[POPUP_SIZE_CLASSES[key]] = (key === this.size);
        }
        return sizeClasses;
    }
}

const ID = 'light-popup';

class LightPopup extends BasePopup {
    constructor() {
        super();
    }

    static styles = [
        popupStyles,
    ];

    render() {
        const wrapClasses = {'show': this.open},
            bgClasses = {'show': this.open, 'obscure-page': this.obscureBg},
            sizeClasses = this.createSizeClasses();
        return x`<div class="m-bg ${e(bgClasses)}"></div><div class="m-wrap ${e(wrapClasses)}" @click="${this.modal ? () => {} : this.closeModal}"><div class="m-container"><div class="m-content"><div class="popup ${e(sizeClasses)}" @click="${this.stopEvent}">${this.modal ? T : x`<span class="close-icon" @click="${this.closeModal}">×</span>`}<slot></slot></div></div></div></div>`;
    }
}

customElements.define('light-popup', LightPopup);

(function() {
    let singleton = document.getElementById(ID);
    if (!singleton) {
        singleton = document.createElement('light-popup');
        singleton.setAttribute("id", ID);
        document.body.prepend(singleton);
    }
})();

/* content should ideally be a TemplateResult created with html``.
    see https://lit.dev/docs/api/templates/#render
    https://lit.dev/docs/templates/expressions/#child-expressions
    TODO - move size into options - too much work to do now
 */
function openLightPopup(content, size, options) {
    let singleton = document.getElementById(ID);
    if (singleton) {
        j(content, singleton);
        if (size) {
            singleton.setAttribute("size", size);
        }
        if (options) {
            if (options.modal) {
                singleton.modal = true;
            }
            if (options.obscureBg) {
                singleton.obscureBg = true;
            }
        }
        // open and close should always happen through the API
        // so events get fired properly
        singleton.openModal();
    }
}

function resizeLightPopup(size) {
    let singleton = document.getElementById(ID);
    if (singleton) {
        singleton.setAttribute("size", size);
    }
}

function closeLightPopup() {
    //console.log('closeLightPopup()');
    let singleton = document.getElementById(ID);
    if (singleton) {
        // open and close should always happen through the API
        // so events get fired properly
        singleton.closeModal();
        singleton.removeAttribute("size");
        singleton.restoreDefaults();
    }
}

const giftlistStyles = i$3`.button,button,input[type=button],input[type=reset],input[type=submit]{border:1px solid #888;background:#fff;color:#3a3a3a;padding:.8em 1em;font-size:1rem;cursor:pointer;box-shadow:1px 1px 2px rgb(68 68 68 / 15%);display:block;border-radius:4px;-webkit-appearance:none}.button:hover,button:hover,input[type=button]:hover,input[type=reset]:hover,input[type=submit]:hover{background:#f0f0f0;color:var(--secondaryColor)}button:focus{outline:0}button[disabled]{border:1px solid #cecece;background:#fbfbfb;color:#cbcbcb;box-shadow:none;cursor:default}button[disabled]:hover{border:1px solid #cecece;background:#fbfbfb;color:#cbcbcb;box-shadow:none;cursor:default}.btn-icon{border:0;box-shadow:none;display:flex;gap:.25em;align-items:center;padding:.4em 1em}.btn-icon[disabled],.btn-icon[disabled]:hover{border:0;background:0 0}.btn-icon .icon{font-size:1.8rem}.btn-main-cta,.btn-main-cta:active,.btn-main-cta:visited{background:var(--btn-main-cta-bg-color);color:var(--btn-main-cta-font-color);padding:.8em 1em;font-size:1.1rem;cursor:pointer;box-shadow:1px 1px 2px rgba(68,68,68,.3);line-height:1.1;font-weight:var(--btn-main-cta-font-weight);border:0}.btn-main-cta:hover{background:var(--btn-main-cta-hover-bg-color);color:var(--btn-main-cta-hover-font-color);text-decoration:none}.btn-main-cta.disabled,.btn-main-cta:active.disabled,.btn-main-cta:hover.disabled,.btn-main-cta:visited.disabled{background:0;color:var(--secondaryDarkColor)}.form-section{margin-top:1rem;padding:0 2rem 2rem}.f-field{padding:.5rem}label{font-weight:400;cursor:pointer;display:block;margin-bottom:.4em;position:relative;top:0;left:0;font-size:1.1rem}label em{font-weight:300}label.inline{display:inline;margin:0}label span.right{position:absolute;bottom:0;right:1em;text-align:right}label.disabled{color:#ccc}.required label{font-weight:600}label a{font-size:.9rem}label .sub{font-weight:400;font-size:.9rem}input[type=checkbox],input[type=radio]{box-sizing:border-box}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input[type=email],input[type=password],input[type=tel],input[type=text],textarea{-moz-appearance:none;-webkit-appearance:none;margin:0;transition:border .3s ease-in-out;border:1px solid #bebebb;font-size:1.1em;padding:.6rem .8rem;background:#fff;width:100%;border-radius:2px}input::-webkit-textfield-decoration-container{visibility:hidden}input[type=email]:focus,input[type=password]:focus,input[type=tel]:focus,input[type=text]:focus,textarea:focus{box-shadow:0 0 0 #07affc;border:1px solid #868686;background:#fff;outline:0}::-webkit-input-placeholder{color:#b6b6b6}input:-moz-placeholder{color:#b6b6b6}input::placeholder{color:#b6b6b6}input::-moz-focus-inner{border:0}input.disabled,input[disabled=disabled]{color:#999;background:#f5f5f5;-moz-box-shadow:inset 0 0 2px #ddd;-webkit-box-shadow:inset 0 1px 2px #ddd;box-shadow:inset 0 1px 2px #ddd}select{border-radius:8px;display:inline-block;margin:0;width:100%;height:38px;outline:0 none;position:relative;padding:.6rem 1.2rem;cursor:pointer;border:1px solid #bebebb;font-size:.9rem;font-weight:700}label>input[type=checkbox],label>input[type=radio],label>input[type=radio]>select{display:inline-block;margin:0 .3em 0 0;vertical-align:middle;width:auto}.check-box{display:flex;margin:.5em 0}.check-box label{display:inline-block;margin-bottom:0;padding-bottom:0;line-height:1.6;margin-left:.4rem}input[type=text]::-ms-clear{display:none;width:0;height:0}input[type=text]::-ms-reveal{display:none;width:0;height:0}input[type=text]::-ms-clear{display:none;width:0;height:0}input[type=text]::-ms-reveal{display:none;width:0;height:0}input[type=checkbox]{position:relative;width:1.5rem!important;height:1.5rem;min-width:1.5rem;color:#363839;border:1px solid #bdc1c6;border-radius:4px;-webkit-appearance:none;-moz-appearance:none;appearance:none;outline:0;cursor:pointer;background:#fafafa;transition:background 175ms cubic-bezier(.1,.1,.25,1)}input[type=checkbox]::before{position:absolute;content:"";display:block;top:0;left:7px;width:8px;height:14px;border-style:solid;border-color:#fff;border-width:0 2px 2px 0;transform:rotate(45deg);opacity:0}input[type=checkbox]:checked{color:#fff;border-color:#222;background:#222}input[type=checkbox]:checked::before{opacity:1}input[type=radio]{position:relative;width:1.5rem!important;height:1.5rem;min-width:1.5rem;color:#363839;border:1px solid #bdc1c6;border-radius:1.5rem;outline:0;cursor:pointer;background:#fafafa;transition:background 175ms cubic-bezier(.1,.1,.25,1)}input[type=radio]:checked{accent-color:#00a82c}.button-row,.check-row,.icon-row,.row{display:flex;justify-content:center;white-space:nowrap;gap:.8em;align-items:center}.icon-row{gap:.3em}.button-row{width:100%;margin:1rem auto}.check-row{gap:2em}.row-icon{font-size:1.8em}.button-row button,.button-row button:active,.button-row button:visited,.button-row checkout-validated-button{max-width:10em;width:100%}.error,.warning{color:red}.row-check{color:#00a82c}a.product-name,a.sku-descriptor{display:block;text-decoration:none;color:#111;font-size:.9rem}a.sku-descriptor{font-size:.8rem}.cart-name{text-transform:capitalize}.page-heading{display:flex;justify-content:center;align-items:baseline;padding:.8em .2em;max-width:100%!important}.page-heading h1{margin:0;font-size:1.9em;width:auto;letter-spacing:.03em;font-weight:700;padding:.08em 0}.page-content{display:flex;flex-direction:column;align-items:center}.popup-content{display:flex;flex-direction:column;align-items:center;gap:.5em}.popup-header{font-weight:700;font-size:1.5em}.badge{position:absolute;z-index:10;background:var(--badge-bg-color);color:var(--badge-txt-color);line-height:1;display:flex;align-items:center;left:5px;top:0;justify-content:center}.badge-inner{display:flex;flex-wrap:wrap;font-weight:700}.badge.sticker{border-radius:30px;height:30px;width:30px;font-size:var(--dsk-sticker);padding:.6rem}.badge.ribbon{border-radius:6px;height:24px;width:66px;font-size:var(--dsk-ribbon);padding:0 .6rem 0 .6rem}.badge.sticker .badge-inner{justify-content:center;text-align:center}@media screen and (max-width:30em){.badge.sticker{border-radius:30px;height:30px;width:30px;font-size:.7rem;font-size:var(--mbl-sticker)}.badge.ribbon{border-radius:6px;height:20px;width:56px;font-size:var(--mbl-ribbon)}}[class*=" icon-"]:before,[class^=icon-]:before{font-family:glicons!important;font-style:normal!important;font-weight:400!important;font-variant:normal!important;text-transform:none!important;speak:none;vertical-align:middle;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[class*=" icon-heart-"],[class^=icon-heart-]{color:var(--lists-heart-color)}.icon-heart-filled:before{content:"\\2d"}.icon-heart-open:before{content:"\\2e"}.icon-check:before{content:"\\55"}.icon-close-circled:before{content:"\\5a"}.icon-ios-trash-outline:before{content:"\\e04d"}.icon-ios-gear-outline:before{content:"\\e04b"}.icon-ios-upload-outline:before{content:"\\e04a"}.icon-plus:before{content:"\\49"}.icon-minus:before{content:"\\4a"}`;

!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o});},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0});},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0);}([function(e,t,n){e.exports=n(1);},function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o);}}var o=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.options={overlayBackgroundColor:"#666666",overlayOpacity:.6,spinnerIcon:"ball-circus",spinnerColor:"#000",spinnerSize:"3x",overlayIDName:"overlay",spinnerIDName:"spinner",offsetY:0,offsetX:0,lockScroll:!1,containerID:null,spinnerZIndex:99999,overlayZIndex:99998},this.stylesheetBaseURL="https://cdn.jsdelivr.net/npm/load-awesome@1.1.0/css/",this.spinner=null,this.spinnerStylesheetURL=null,this.numberOfEmptyDivForSpinner={"ball-8bits":16,"ball-atom":4,"ball-beat":3,"ball-circus":5,"ball-climbing-dot":1,"ball-clip-rotate":1,"ball-clip-rotate-multiple":2,"ball-clip-rotate-pulse":2,"ball-elastic-dots":5,"ball-fall":3,"ball-fussion":4,"ball-grid-beat":9,"ball-grid-pulse":9,"ball-newton-cradle":4,"ball-pulse":3,"ball-pulse-rise":5,"ball-pulse-sync":3,"ball-rotate":1,"ball-running-dots":5,"ball-scale":1,"ball-scale-multiple":3,"ball-scale-pulse":2,"ball-scale-ripple":1,"ball-scale-ripple-multiple":3,"ball-spin":8,"ball-spin-clockwise":8,"ball-spin-clockwise-fade":8,"ball-spin-clockwise-fade-rotating":8,"ball-spin-fade":8,"ball-spin-fade-rotating":8,"ball-spin-rotate":2,"ball-square-clockwise-spin":8,"ball-square-spin":8,"ball-triangle-path":3,"ball-zig-zag":2,"ball-zig-zag-deflect":2,cog:1,"cube-transition":2,fire:3,"line-scale":5,"line-scale-party":5,"line-scale-pulse-out":5,"line-scale-pulse-out-rapid":5,"line-spin-clockwise-fade":8,"line-spin-clockwise-fade-rotating":8,"line-spin-fade":8,"line-spin-fade-rotating":8,pacman:6,"square-jelly-box":2,"square-loader":1,"square-spin":1,timer:1,"triangle-skew-spin":1};}var t,o;return t=e,(o=[{key:"show",value:function(e){this.setOptions(e),this.addSpinnerStylesheet(),this.generateSpinnerElement(),this.options.lockScroll&&(document.body.style.overflow="hidden",document.documentElement.style.overflow="hidden"),this.generateAndAddOverlayElement();}},{key:"hide",value:function(){this.options.lockScroll&&(document.body.style.overflow="",document.documentElement.style.overflow="");var e=document.getElementById("loading-overlay-stylesheet");e&&(e.disabled=!0,e.parentNode.removeChild(e),document.getElementById(this.options.overlayIDName).remove(),document.getElementById(this.options.spinnerIDName).remove());}},{key:"setOptions",value:function(e){if(void 0!==e)for(var t in e)this.options[t]=e[t];}},{key:"generateAndAddOverlayElement",value:function(){var e="50%";0!==this.options.offsetX&&(e="calc(50% + "+this.options.offsetX+")");var t="50%";if(0!==this.options.offsetY&&(t="calc(50% + "+this.options.offsetY+")"),this.options.containerID&&document.body.contains(document.getElementById(this.options.containerID))){var n='<div id="'.concat(this.options.overlayIDName,'" style="display: block !important; position: absolute; top: 0; left: 0; overflow: auto; opacity: ').concat(this.options.overlayOpacity,"; background: ").concat(this.options.overlayBackgroundColor,'; z-index: 50; width: 100%; height: 100%;"></div><div id="').concat(this.options.spinnerIDName,'" style="display: block !important; position: absolute; top: ').concat(t,"; left: ").concat(e,'; -webkit-transform: translate(-50%); -ms-transform: translate(-50%); transform: translate(-50%); z-index: 9999;">').concat(this.spinner,"</div>"),o=document.getElementById(this.options.containerID);return o.style.position="relative",void o.insertAdjacentHTML("beforeend",n)}var i='<div id="'.concat(this.options.overlayIDName,'" style="display: block !important; position: fixed; top: 0; left: 0; overflow: auto; opacity: ').concat(this.options.overlayOpacity,"; background: ").concat(this.options.overlayBackgroundColor,"; z-index: ").concat(this.options.overlayZIndex,'; width: 100%; height: 100%;"></div><div id="').concat(this.options.spinnerIDName,'" style="display: block !important; position: fixed; top: ').concat(t,"; left: ").concat(e,"; -webkit-transform: translate(-50%); -ms-transform: translate(-50%); transform: translate(-50%); z-index: ").concat(this.options.spinnerZIndex,';">').concat(this.spinner,"</div>");document.body.insertAdjacentHTML("beforeend",i);}},{key:"generateSpinnerElement",value:function(){var e=this,t=Object.keys(this.numberOfEmptyDivForSpinner).find((function(t){return t===e.options.spinnerIcon})),n=this.generateEmptyDivElement(this.numberOfEmptyDivForSpinner[t]);this.spinner='<div style="color: '.concat(this.options.spinnerColor,'" class="la-').concat(this.options.spinnerIcon," la-").concat(this.options.spinnerSize,'">').concat(n,"</div>");}},{key:"addSpinnerStylesheet",value:function(){this.setSpinnerStylesheetURL();var e=document.createElement("link");e.setAttribute("id","loading-overlay-stylesheet"),e.setAttribute("rel","stylesheet"),e.setAttribute("type","text/css"),e.setAttribute("href",this.spinnerStylesheetURL),document.getElementsByTagName("head")[0].appendChild(e);}},{key:"setSpinnerStylesheetURL",value:function(){this.spinnerStylesheetURL=this.stylesheetBaseURL+this.options.spinnerIcon+".min.css";}},{key:"generateEmptyDivElement",value:function(e){for(var t="",n=1;n<=e;n++)t+="<div></div>";return t}}])&&n(t.prototype,o),e}();window.JsLoadingOverlay=new o,e.exports=JsLoadingOverlay;}]);

const DOM_PARSER$1 = new DOMParser();

let submitInFlight= false, submitTracker= 0;

function ajaxSubmit(options, nested = false) {
    if (!submitInFlight || nested) {
        submitInFlight = true;
        submitTracker++;

        if (!nested) {
            showDefaultSpinner();
        }

        async function innerSubmit() {
            let retry = 2;

            let fetchOptions = {
                headers: {
                    "Accept": "application/json",
                    "Content-Type": "application/x-www-form-urlencoded",
                },
                method: "POST",
                signal: AbortSignal.timeout(20000),
            };

            if (options.data) {
                fetchOptions.body = cleanURLSearchParams(options.data);
            }

            async function doFetch() {
                const response = await fetch(options.url, fetchOptions);
                if (response.ok || response.status === 403) {
                    const result = await response.json();

                    if (response.status === 403) {
                        if (result.redirect && result.redirectURL) {
                            window.location.href = response.redirectURL;
                        } else {
                            throw new Error("Authorization error without redirect.");
                        }
                    } else if (evalBooleanish(result.concurrentUpdateDetected)) {
                        if (retry--) {
                            await doFetch();
                        } else {
                            throw new Error("Retry limit reached.");
                        }
                    } else if (options.success && typeof options.success === 'function') {
                        options.success(result);
                    } else {
                        console.warn("No success handler in options.");
                    }
                } else {
                    throw new Error(response.statusText);
                }
            }

            try {
                await doFetch();
            } catch (error) {
                console.error(error);
                if (options.error && typeof options.error === 'function') {
                    options.error(error);
                }
            } finally {
                submitTracker--;
                if (submitTracker === 0) {
                    hideDefaultSpinner();
                    submitInFlight = false;
                }
            }
        }

        innerSubmit().catch((error) => console.error(error));
    }
}

function cleanURLSearchParams(params) {
    const searchParams = new URLSearchParams(params);
    for (const [key, value] of Object.entries(params)) {
        if (Array.isArray(value)) {
            searchParams.delete(key);
            value.forEach((v) => searchParams.append(key, v));
        }
        if (value === undefined || value === null) {
            searchParams.delete(key);
        }
    }
    return searchParams;
}

function ajaxSubmitGenericError(error) {
    if (error.name === "TimeoutError") {
        magnificPopup.open({
            items:
                [
                    {
                        src: mp.fromMarkup('<div class="basic-popup">The application has encountered a timeout error. Please wait for a while and try again.</div>')
                    }
                ],
            type: 'inline'
        }, 0);
    } else {
        magnificPopup.open({
            items:
                [
                    {
                        src: mp.fromMarkup('<div class="basic-popup">The application has encountered an error. Please try again.</div>')
                    }
                ],
            type: 'inline'
        }, 0);
    }
}

/*
Utility that traverses parent nodes across shadow DOM boundaries in search of
the requested selector.
 */
function closestCrossShadow(node, selector) {
    if (!node) {
        return null;
    }
    if (node instanceof ShadowRoot) {
        return closestCrossShadow(node.host, selector);
    }

    if (node instanceof HTMLElement) {
        if (node.matches(selector)) {
            return node;
        } else {
            return closestCrossShadow(node.parentNode, selector);
        }
    }

    return closestCrossShadow(node.parentNode, selector);
}

// see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event
// in particular for an explanation of why this test involves no race condition
function runWhenDOMContentLoaded(callback) {
    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", callback);
    } else {
        callback();
    }
}

function fetchJsonGet(url, options, jsonHandler) {
    options.method = 'GET';
    fetchJson(url, options, jsonHandler);
}

function fetchJson(url, options, jsonHandler) {
    showDefaultSpinner();
    try {
        fetch(url, options)
            .then((response) => {
                if (!response.ok) {
                    return {result: false, message: 'Network error',};
                }
                return response.json();
            })
            .then((data) => {
                jsonHandler(data);
            });
    } finally {
        hideDefaultSpinner();
    }
}

function showDefaultSpinner() {
    JsLoadingOverlay.show({
        "spinnerIcon": "ball-spin-clockwise-fade",
        "lockScroll": true,
        "overlayZIndex": 100000,
        "spinnerZIndex": 100001
    });
}

function hideDefaultSpinner() {
    JsLoadingOverlay.hide();
}

function encodePostData(postDataObj) {
    let postData = '';
    if (typeof postDataObj !== 'object') {
        console.log('param passed to encodePostData was not an object!');
        return postData;
    }
    for (let key in postDataObj) {
        postData = postData + (postData.length ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(postDataObj[key]);
    }
    return postData;
}

function evalBooleanish(value) {
    if (typeof value === 'boolean') {
        return value;
    }
    if (typeof value === 'string') {
        return ("true" === value);
    }
    return !!value;
}

/*
    General use object extension function. Am retaining the original name for now (comes from sku picker code).
 */
function spExtend(out) {
    out = out || {};

    for (let i = 1; i < arguments.length; i++) {
        if (!arguments[i])
            continue;

        for (let key in arguments[i]) {
            if (arguments[i].hasOwnProperty(key)) {
                out[key] = arguments[i][key];
            }
        }
    }

    return out;
}

function addQueryParam(url, paramName, paramValue) {
    let paramString = paramName + '=' + paramValue;
    if (!url) {
        return paramString;
    }
    let qIndex = url.indexOf('?');
    if (qIndex !== -1) {
        return url + '&' + paramString;
    } else {
        return url + '?' + paramString;
    }
}

function urlContainsParamValue(url, paramName, paramValue) {
    if (!url) {
        return false;
    }
    let qIndex = url.indexOf('?');
    if (qIndex != -1 && url.length > qIndex + 1) {
        let qString = url.substring(qIndex + 1);
        let allParams = qString.split('&');
        for (let i = 0; i < allParams.length; i++) {
            let pair = allParams[i];
            if (pair) {
                let splitPair = pair.split('=');
                if (splitPair && splitPair.length == 2) {
                    if (splitPair[0] === paramName && splitPair[1] === paramValue) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

function getUrlParameter(sParam) {
    let sPageURL = decodeURIComponent(window.location.search.substring(1)),
        sURLVariables = sPageURL.split('&'),
        sParameterName,
        i;

    for (i = 0; i < sURLVariables.length; i++) {
        sParameterName = sURLVariables[i].split('=');

        if (sParameterName[0] === sParam) {
            return sParameterName[1] === undefined ? true : sParameterName[1];
        }
    }
    return null;
}

/*
    utility for performing type conversions on data coming out of dataset.
    migrated from Tomo's weird i8sdataset code...this is used a couple
    of places so easier to keep for now.
 */
function convertDatasetValue(value) {
    if (value === null) {
        return value;
    }
    if (typeof value === 'boolean') {
        return value;
    }
    if (typeof value === 'string') {
        if (!Number.isNaN(Number(value))) {
            return Number(value);
        }
        if (value === 'true' || value === 'false') {
            return value === 'true';
        }
        return value;
    }
    return value;
}

function initializeAnnoyingPopups() {
    if (!window.rGLPopups) {
        let GLUtils = {};
        (function (GLUtils) {
            GLUtils.Popups = (function () {
                function Popups() {
                    this.popups = [];
                    this.debounceTimeoutId = null;
                    this.executed = false;
                }

                Popups.prototype.add = function (pPopup) {
                    if (!this.executed) {
                        this.popups.push(pPopup);

                        clearTimeout(this.debounceTimeoutId);
                        const _this = this;
                        this.debounceTimeoutId = setTimeout(function () {
                            _this.doPopup();
                        }, 100);
                    }
                };

                Popups.prototype.doPopup = function () {
                    if (!this.executed) {
                        let p = null;
                        for (let i = 0; i < this.popups.length; i++) {
                            if (p == null) {
                                p = this.popups[i];
                            } else {
                                if ((p.hasOwnProperty('popupPriority') ? p.popupPriority : -1) <
                                    (this.popups[i].hasOwnProperty('popupPriority') ? this.popups[i].popupPriority : -1)) {
                                    p = this.popups[i];
                                }
                            }
                        }

                        if (p != null) {
                            p.executePopup();
                            this.executed = true;
                        }
                    }
                };

                return Popups;
            })();
        })(GLUtils);
        window.rGLPopups = new GLUtils.Popups();
    }
}

const LOAD_GIFTLIST_OPTIONS_URL = '/public/handler/load-giftlist-options.jsp';

const ADD_ITEM_TO_GIFTLIST_URL = '/public/handler/add_item_to_giftlist.jsp';

const REMOVE_ITEM_FROM_GIFTLIST_URL = '/public/handler/remove_item_from_giftlist.jsp';

const CREATE_NEW_GIFTLIST_URL = '/public/handler/auto/create-new-giftlist.jsp';

const EVENT_NAME_GIFTLISTS_CHANGED = "giftlistsChanged";

const GIFTLIST_TYPE_WISHLIST= "wishlist";
const GIFTLIST_TYPE_GIFTLIST= "giftlist";
const GIFTLIST_TYPE_REGISTRY= "registry";

const FLAG_GIFTLIST_TYPE_REGISTRIES= "registries";

const EVENT_TYPES= {
    anniversary: {value: "anniversary", displayName: "Anniversary"},
    birthday: {value: "birthday", displayName: "Birthday"},
    bridalShower: {value: "bridalShower", displayName: "Bridal Shower"},
    housewarming: {value: "housewarming", displayName: "Housewarming"},
    iJustWantThisStuff: {value: "iJustWantThisStuff", displayName: "I Just Want This Stuff"},
    party: {value: "party", displayName: "Party"},
    valentinesDay: {value: "valentinesDay", displayName: "Valentine's Day"},
    wedding: {value: "wedding", displayName: "Wedding"},
    other: {value: "other", displayName: "Other"},
};

const EVENT_TYPES_ORDERED = [
    EVENT_TYPES.anniversary,
    EVENT_TYPES.birthday,
    EVENT_TYPES.bridalShower,
    EVENT_TYPES.housewarming,
    EVENT_TYPES.iJustWantThisStuff,
    EVENT_TYPES.party,
    EVENT_TYPES.valentinesDay,
    EVENT_TYPES.wedding,
    EVENT_TYPES.other,
];

const VALID_EMAIL = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/i;

const genericCardImageUrl = 'https://res.cloudinary.com/i8s/image/upload/v1698882348/misc/Credit_card_trim_kldnij.svg';
const creditCardTypes = {
    visa: {
        name: 'Visa',
        regex: /^4\d*$/,
        validLengths: [13,16],
        imageUrl: 'https://res.cloudinary.com/i8s/image/upload/misc/k4826xdgbrypn8gi4eav'
    },
    masterCard: {
        name: 'Mastercard',
        regex: /^5\d*$/,
        validLengths: [16],
        imageUrl: 'https://res.cloudinary.com/i8s/image/upload/misc/eeedqv9km3kqtslkueco'
    },
    americanExpress: {
        name: 'American Express',
        regex: /^(34|37)\d*$/,
        validLengths: [15],
        imageUrl: 'https://res.cloudinary.com/i8s/image/upload/misc/pgldricid86pr4wumxcv'
    },
    discover: {
        name: 'Discover',
        regex: /^6\d*$/,
        validLengths: [16],
        imageUrl: 'https://res.cloudinary.com/i8s/image/upload/misc/zdapgu33jhsgg9plzad5'
    },
    jcb: {
        name: 'JCB',
        regex: /^35\d*$/,
        validLengths: [16],
        imageUrl: 'https://res.cloudinary.com/i8s/image/upload/misc/dzxef7cpn6rbw8lwpkpf'
    }
};

const digitsRegex = /^\d+$/;

class CheckoutValidatedInput extends s {
    constructor() {
        super();

        this.key = '';
        this.group = '';
        this.fieldName = '';
        this.defaultForGroup = false;
        this._validation = '';
        this._mode = 'input'; // should be 'input' or 'change'. name of the event to listen for.
        this.verb = 'enter';
        this._displayError = false;
        this.errorMessage = '';
        this.externalError = false;
        this.externalErrorMessage = '';
        this.suppressMessage = false; // allow toggling error treatment without message
        this.eventListener = null;
        this.changeInputEventListener = null; // used in change mode to trigger validation handling on valid input only
        this._valid = false;
        this.input = null;
        this.touched = false;
        this.creditCardType = null;

        this.ccBeforeInputListener = this.ccBeforeInputListener.bind(this);
        this.ccInputListener = this.ccInputListener.bind(this);
    }

    static properties = {
        key: {type: String,},
        group: {type: String,},
        fieldName: {type: String, attribute: 'field-name',},
        defaultForGroup: {type: Boolean, attribute: 'default-for-group',},
        validation: {type: String,},
        mode: {type: String,},
        verb: {type: String,},
        displayError: {type: Boolean, state: true,},
        errorMessage: {type: String, attribute: 'error-message',},
        externalError: {type: Boolean, state: true,},
        externalErrorMessage: {type: String, state: true,},
        suppressMessage: {type: Boolean, attribute: 'suppress-message',},
        eventListener: {type: Object, state: true,},
        changeInputEventListener: {typ: Object, state: true,},
        valid: {type: Boolean, state: true,},
        input: {type: Object, state: true,},
        touched: {type: Boolean, state: true,},
        creditCardType: {type: Object, state: true,},
    };

    willUpdate(_changedProperties) {
        // this generalizes the test that was being done in render and allows for additional side effects in the displayError set
        if (_changedProperties.has('externalError') || _changedProperties.has('valid') ||
            _changedProperties.has('touched')) {
            if (this.externalError) {
                this.displayError = true;
            } else if (!this.valid && this.touched) {
                this.displayError = true;
            } else {
                this.displayError = false;
            }
        }
    }

    set displayError(newVal) {
        let oldVal = this._displayError;
        this._displayError = newVal;

        // look and see if we have a wrapper element that needs the error class added
        if (this.shadowRoot) {
            let slot = this.shadowRoot.querySelector('slot');
            if (slot) {
                let elements = slot.assignedElements();
                for (let i = 0; i < elements.length; i++) {
                    let el = elements[i];
                    let inputWrapper;
                    if (el.classList.contains('sol-input-wrapper')) {
                        inputWrapper = el;
                    } else {
                        inputWrapper = el.querySelector('.sol-input-wrapper');
                    }
                    if (inputWrapper) {
                        if (newVal) {
                            inputWrapper.classList.add('error');
                        } else {
                            inputWrapper.classList.remove('error');
                        }
                    }
                }
            }
        }

        this.requestUpdate('displayError', oldVal);
    }

    get displayError() {
        return this._displayError;
    }

    get validation() {
        return this._validation;
    }

    set validation(newVal) {
        let oldVal = this._validation;
        this._validation = newVal;
        if (newVal === 'none') {
            this.valid = true;
        } else if (newVal === 'credit-card') {
            // don't want to use input events on credit card validation due to multiple valid
            // lengths for Visa. Also just generally is annoying to get an error while still typing.
            this.mode = 'change';
        }
        this.requestUpdate('validation', oldVal);
    }

    get mode() {
        return this._mode;
    }

    set mode(newVal) {
        if (newVal !== 'change' && this.validation === 'credit-card') {
            this.dlog(`invalid mode ${newVal} specified for credit-card validation`);
            return;
        }
        let oldVal = this._mode;
        this._mode = newVal;
        this.updateEventListener();
        this.requestUpdate('mode', oldVal);
    }

    ccBeforeInputListener(e) {
        if (e.data) {
            this.dlog('testing data');
            if (!digitsRegex.test(e.data)) {
                this.dlog('fail, prevent default');
                e.preventDefault();
            } else {
                this.dlog('pass');
            }
        } else {
            this.dlog('no data to test');
        }
    }

    ccInputListener(e) {
        this.dlog('ccInputListener');
        let logoImage = this.findCCLogoImage();
        if (logoImage) {
            let value = e.target.value;
            let currentCardType = this.determineCreditCardType(value, this.creditCardType);
            if (currentCardType) {
                if (logoImage.getAttribute('src') !== currentCardType.imageUrl) {
                    logoImage.setAttribute('src', currentCardType.imageUrl);
                }
            } else {
                logoImage.setAttribute('src', genericCardImageUrl);
            }
            this.creditCardType = currentCardType;
        }
    }

    findCCLogoImage() {
        if (this.shadowRoot) {
            let slot = this.shadowRoot.querySelector('slot');
            if (slot) {
                let elements = slot.assignedElements();
                for (let i = 0; i < elements.length; i++) {
                    let el = elements[i];
                    if (el.classList.contains('cc-wrapper')) {
                        let candidate = el.querySelector('.cc-logo img');
                        if (candidate) {
                            return candidate;
                        }
                    }
                }
            }
        }
        return null;
    }

    determineCreditCardType(value, previous) {
        if (previous && previous.regex.test(value)) {
            return previous;
        }
        for (let key in creditCardTypes) {
            let ccType = creditCardTypes[key];
            if (ccType.regex.test(value)) {
                return ccType;
            }
        }
        return null;
    }

    validateCreditCard(value) {
        if (value) {
            let ccType = this.determineCreditCardType(value);
            if (ccType) {
                // Only perform length check for types we are positively identifying
                this.dlog(`validateCreditCard found ccType ${ccType.name}`);
                // validate length
                let validLength = false;
                for (let i = 0; i < ccType.validLengths.length; i++) {
                    if (value.length === ccType.validLengths[i]) {
                        validLength = true;
                    }
                }
                if (!validLength) {
                    this.dlog('invalid length: return false');
                    return false;
                }
            }

            // Luhn mod 10 check
            let lastChar = value.substring(value.length - 1);
            let lastDigit = parseInt(lastChar);
            if (isNaN(lastDigit)) {
                return false;
            }
            let sum = 0, doubleIt = true;
            for (let i = value.length - 2; i >= 0; i--) {
                let thisChar = value.substring(i, i+1);
                let thisDigit = parseInt(thisChar);
                if (isNaN(thisDigit)) {
                    return false;
                }
                if (doubleIt) {
                    thisDigit *= 2;
                }
                if (thisDigit > 9) {
                    thisDigit -= 9;
                }
                sum += thisDigit;
                doubleIt = !doubleIt;
            }
            let checkDigit = (10 - (sum % 10)) % 10;
            return checkDigit === lastDigit;
        }
        return false;
    }

    /*
        This attempts to work around an issue where we cannot seem to get access to initial values set via the
        value attribute.
     */
    get checkValid() {
        this.dlog('checkValid()');
        if (!this._valid && this.input && this.input.value) {
            this.validate({skipEvent: true});
        }
        return this.valid;
    }

    get valid() {
        return this._valid;
    }

    set valid(newVal) {
        let oldVal = this._valid;
        this._valid = newVal;
        if (newVal) {
            this.externalErrorMessage = '';
        }

        this.dlog('set valid dispatching event');
        const event = new CustomEvent('validationChange', {bubbles: true});
        this.dispatchEvent(event);

        this.dlog('set valid requesting update');
        this.requestUpdate('valid', oldVal);
    }

    // this is for use in handling external (from the server) errors. If the server is doing some specific
    // conditional validation that we can't easily handle on the client side, this triggers the error display
    // and shows the message but does not actually flag as invalid.
    setExternalError(msg) {
        this.externalError = true;
        this.externalErrorMessage = msg;
    }

    clearExternalError() {
        this.externalError = false;
        this.externalErrorMessage = '';
    }

    createDefaultErrorMessage() {
        return x`<div class="message">Please ${this.verb} a valid ${this.fieldName}</div>`;
    }

    renderErrorMessage() {
        if (this.externalError) {
            if (this.externalErrorMessage) {
                return x`<div class="message">${this.externalErrorMessage}</div>`;
            } else {
                return '';
            }
        } else if (this.valid || this.suppressMessage || !this.touched) {
            return '';
        } else if (this.errorMessage) {
            return x`<div class="message">${this.errorMessage}</div>`;
        } else {
            return this.createDefaultErrorMessage();
        }
    }

    /*
        updateForValidOnly - used for use cases (change mode) where we want to update the form
        state once a valid value is entered, but not flag intermediate invalid values
     */
    validate({skipEvent = false, updateForValidOnly = false}) {
        this.dlog('validate()');
        if (this.input) {
            let valid = false;
            let value = '';
            if (this.input instanceof HTMLSelectElement) {
                let selOptions = this.input.selectedOptions;
                if (selOptions && selOptions.length) {
                    value = selOptions[0].value;
                }
            } else {
                value = this.input.value;
            }
            switch (this.validation) {
                case 'email':
                    valid = VALID_EMAIL.test(value);
                    break;
                case 'credit-card':
                    valid = this.validateCreditCard(value);
                    break;
                case 'required':
                    valid = !!(value && (typeof value === 'string') && value.trim());
                    break;
                default:
                    valid = true;
                    break;
            }
            this.dlog(`validated ${value} for result ${valid}`);

            if (valid) {
                this.externalError = false;
                this.externalErrorMessage = '';
            }
            if (!updateForValidOnly || valid) {
                if (skipEvent) {
                    // this is getting a little spaghetti...but basically we want to be able to
                    // trigger validation and not fire the event, e.g. in cases where we are already
                    // handling an event and don't want to recurse infinitely
                    let oldValue = this._valid;
                    if (oldValue != valid) {
                        this._valid = valid;
                        this.requestUpdate('valid', oldValue);
                    }
                } else {
                    this.valid = valid;
                }
            }
        } else {
            this.dlog(`input not found! cannot validate`);
        }
    }

    dlog(msg) {
        //console.log(`${this.key ?? 'no key'} ${msg}`);
    }

    updateEventListener() {
        this.dlog('updateEventListener()');
        if (this.shadowRoot) {
            if (this.eventListener) {
                this.dlog('removing old event listener');
                this.shadowRoot.removeEventListener(this.mode, this.eventListener);
                this.shadowRoot.removeEventListener('focusout', this.eventListener);
                this.shadowRoot.removeEventListener('validate', this.eventListener);
                if (this.validation === 'credit-card') {
                    this.shadowRoot.removeEventListener('beforeinput', this.ccBeforeInputListener);
                    this.shadowRoot.removeEventListener('input', this.ccInputListener);
                }
            }
            if (this.changeInputEventListener) {
                this.dlog('removing old change input event listener');
                this.shadowRoot.removeEventListener('input', this.changeInputEventListener);
                this.changeInputEventListener = null;
            }
            this.eventListener = ((e) => {
                this.touched = true;
                this.dlog('eventListener - value is ' + e.target.value);
                this.validate({});
                if (e.type === 'validate') {
                    e.stopPropagation();
                }
            });
            this.shadowRoot.addEventListener(this.mode, this.eventListener);
            // this detects mousing into a field then leaving without change
            this.shadowRoot.addEventListener('focusout', this.eventListener);
            // this allows external script triggering of validation without impacting focus
            this.shadowRoot.addEventListener('validate', this.eventListener);
            if (this.validation === 'credit-card') {
                this.shadowRoot.addEventListener('beforeinput', this.ccBeforeInputListener);
                this.shadowRoot.addEventListener('input', this.ccInputListener);
            }
            if (this.mode === 'change') {
                this.changeInputEventListener = ((e) => {
                    this.dlog('changeInputEventListener - value is ' + e.target.value);
                    this.validate({updateForValidOnly: true});
                });
                this.shadowRoot.addEventListener('input', this.changeInputEventListener);
            }

            if (this.input) {
                if (this.input.value) {
                    this.dlog('updateEventListener call validate for value' + this.input.value);
                    this.validate({});
                } else {
                    this.dlog(`input but no value! attribute count: ${this.input.attributes ? this.input.attributes.length : 0}`);
                    if (this.input.hasAttributes()) {
                        for (const attr of this.input.attributes) {
                            this.dlog(`attribute ${attr.name} has value ${attr.value}`);
                        }
                    }
                }
            } else {
                this.dlog('no input!');
            }
        }
    }

    handleSlotchange(e) {
        let elements = e.target.assignedElements();
        // obviously this will break hard if you nest more than one input inside one of these.
        // so don't do that.
        for (let i = 0; i < elements.length; i++) {
            let el = elements[i];
            if (el instanceof HTMLInputElement) {
                this.dlog('slotchange assigned HTMLInputElement');
                this.input = el;
                break;
            } else if (el instanceof HTMLSelectElement)  {
                this.dlog('slotchange assigned HTMLSelectElement');
                this.input = el;
                break;
            } else if (el.classList.contains('sol-input-wrapper')) {
                let candidate = el.querySelector('input');
                if (candidate && candidate instanceof HTMLInputElement) {
                    this.input = candidate;
                    break;
                }
                candidate = el.querySelector('select');
                if (candidate && candidate instanceof HTMLSelectElement) {
                    this.input = candidate;
                    break;
                }
            }
        }
        this.updateEventListener();
    }

    connectedCallback() {
        super.connectedCallback();

        this.dlog('connectedCallback');
        this.updateEventListener();

        // try switching to event-based registration with the form
        this.dispatchEvent(new CustomEvent('validatedInputAdded', {bubbles: true, composed: true,}));

    }

    static styles = i$3`:host{margin-bottom:.9em}.errorMessage .message{color:red;margin:.3em 0 0 0}.slotWrap.error ::slotted(*){color:red!important}.slotWrap.error ::slotted(input),.slotWrap.error ::slotted(select){border:2px solid red!important}::slotted(input){margin-bottom:0!important}`;

    render() {
        const classes = {error: this.displayError};
        this.dlog(`render, classes is ${JSON.stringify(classes)}`);
        return x`<div class="slotWrap ${e(classes)}"><slot @slotchange="${this.handleSlotchange}"></slot></div><div class="errorMessage">${this.renderErrorMessage()}</div>`;
    }
}

customElements.define('checkout-validated-input', CheckoutValidatedInput);

class CheckoutValidatedForm extends s {
    constructor() {
        super();

        this.validatedInputs = {};
        this.buttons = [];
        this.groups = {};
        this.defaultKey = '';
        this._disabledGroups = {};
        this.allValid = false;
    }

    static properties = {
        validatedInputs: {type: Object, state: true,},
        buttons: {type: Array, state: true,},
        groups: {type: Object, state: true,},
        disabledGroups: {type: Object, attribute: false,},
        defaultKey: {type: String, attribute: 'default-key',},
        allValid: {type: Boolean, state: true,},
    };

    set disabledGroups(newVal) {
        let oldVal = this._disabledGroups;
        this._disabledGroups = newVal;
        //console.log(`disabledGroups being set to ${JSON.stringify(newVal)}`);
        this.updateValidatedButtons();
        this.requestUpdate('disabledGroups', oldVal);
    }

    get disabledGroups() {
        return this._disabledGroups;
    }

    updateValidatedButtons() {
        if (this.buttons.length) {
            let allValid = true;
            for (let key in this.validatedInputs) {
                //console.log(`checking input ${key} for valid: ${this.validatedInputs[key].valid}`);
                let vInput = this.validatedInputs[key];
                if (vInput.group) {
                    //console.log(`check grouped input ${vInput.key} for disabled: ${vInput.group}`);
                    if (this.disabledGroups[vInput.group]) {
                        //console.log('skipping input for disabled group ' + vInput.group);
                        continue;
                    }
                }
                if (!vInput.checkValid) {
                    //console.log(`allValid false based on invalid: ${key}`);
                    allValid = false;
                    break;
                }
            }
            this.allValid = allValid;
            for (let i = 0; i < this.buttons.length; i++) {
                //console.log(`Updating button ${i} valid to ${allValid}`);
                this.buttons[i].valid = allValid;
            }
        } else {
            console.log('updateValidatedButtons no buttons found!');
        }
    }

    handleValidatedButtonAdded(e) {
        e.stopPropagation();
        //console.log(`validatedButtonAdded, target is ${e.target}`);
        this.initializeInputs();
    }

    handleValidatedInputAdded(e) {
        e.stopPropagation();
        //console.log(`validatedInputAdded, target is ${e.target}`);
        this.initializeInputs();
    }

    initializeInputs() {
        //console.log('initializeInputs');
        if (this.shadowRoot) {
            const slots = this.shadowRoot.querySelectorAll('slot');
            if (slots.length) {
                let slot = slots[0];
                let elements = slot.assignedElements();
                this.buttons = [];
                this.validatedInputs = {};
                this.groups = {};
                for (let i = 0; i < elements.length; i++) {
                    let el = elements[i];
                    if (el instanceof CheckoutValidatedInput) {
                        //console.log(`found validated input with key ${el.key}`);
                        this.validatedInputs[el.key] = el;
                    } else if (el instanceof CheckoutValidatedButton) {
                        //console.log ('found validated button');
                        this.buttons.push(el);
                    } else {
                        el.querySelectorAll('checkout-validated-input').forEach((input) => {
                            this.validatedInputs[input.key] = input;
                        });
                        el.querySelectorAll('checkout-validated-button').forEach((button) => {
                            this.buttons.push(button);
                        });
                    }
                }
                for (let key in this.validatedInputs) {
                    let input = this.validatedInputs[key];
                    if (input.group) {
                        if (!this.groups[input.group]) {
                            this.groups[input.group] = {};
                        }
                        this.groups[input.group][input.key] = input;
                    }
                }

                this.updateValidatedButtons();
            }
        }
    }

    clearExternalErrors() {
        for (let key in this.validatedInputs) {
            let vInput = this.validatedInputs[key];
            vInput.clearExternalError();
        }
    }

    // for format see form-exception-array.jsp
    setExternalErrors(errors) {
        if (errors) {
            //console.log('Evaluating external errors');
            for (let i = 0; i < errors.length; i++) {
                let error = errors[i];
                if (error.group) ;
                if (error.group && this.groups[error.group]) {
                    for (let inputKey in this.groups[error.group]) {
                        let input = this.groups[error.group][inputKey];
                        let msg = input.defaultForGroup ? error.message : '';
                        //console.log('calling setExternalError on input for group');
                        input.setExternalError(msg);
                    }
                } else if (error.key && this.validatedInputs[error.key]) {
                    //console.log('calling setExternalError on input for key');
                    this.validatedInputs[error.key].setExternalError(error.message);
                } else if (this.defaultKey) {
                    //console.log('calling setExternalError on input for default key');
                    this.validatedInputs[this.defaultKey].setExternalError(error.message);
                } else {
                    console.log(`Unhandled error: ${JSON.stringify(error)}`);
                }
            }
        }
    }

    handleValidationChange(e) {
        e.stopPropagation();
        //console.log('caught validationChange event');
        this.updateValidatedButtons();
    }

    handleSlotchange(e) {
        //console.log('CheckoutValidatedForm slotchange');
        this.initializeInputs();
    }

    connectedCallback() {
        super.connectedCallback();

        this.addEventListener('validationChange', this.handleValidationChange);
        this.addEventListener('validatedInputAdded', this.handleValidatedInputAdded);
        this.addEventListener('validatedButtonAdded', this.handleValidatedButtonAdded);

        this.initializeInputs();
    }

    render() {
        return x`<slot @slotchange="${this.handleSlotchange}"></slot>`;
    }
}

customElements.define('checkout-validated-form', CheckoutValidatedForm);

class CheckoutValidatedButton extends s {
    constructor() {
        super();

        this._valid = false;
        this.button = null;
    }

    static properties = {
        valid: {type: Boolean, state: true,},
        button: {type: Object, state: true,},
    };

    get valid() {
        return this._valid;
    }

    set valid(newVal) {
        newVal = !!newVal;
        this._valid = newVal;
        //console.log(`validated button setting valid to ${newVal}`);
        if (this.button) {
            if (newVal) {
                this.button.removeAttribute('disabled');
            } else {
                this.button.setAttribute('disabled', 'disabled');
            }
        }
    }

    handleSlotchange(e) {
        let elements = e.target.assignedElements();
        for (let i = 0; i < elements.length; i++) {
            let el = elements[i];
            if (el instanceof HTMLButtonElement) {
                this.button = el;
            }
        }
        if (this.button) {
            // we are firing an event after processing slotchange so the button state can be updated if the ValidatedForm
            // initialized before we did (which seems to be the case in most browsers)
            const event = new Event('validationChange', {bubbles: true});
            this.dispatchEvent(event);
        }
    }

    connectedCallback() {
        super.connectedCallback();
        this.dispatchEvent(new CustomEvent('validatedButtonAdded', {bubbles: true, composed: true,}));
        /*
        let validatedForm = closestCrossShadow(this, 'checkout-validated-form');
        if (validatedForm) {
            //console.log(`is it a CheckoutValidatedForm? ${validatedForm instanceof CheckoutValidatedForm}`);
            if (validatedForm.addButton) {
                validatedForm.addButton(this);
            } else {
                console.log('did not find addButton!');
            }
        }*/
    }

    render() {
        return x`<slot @slotchange="${this.handleSlotchange}"></slot>`;
    }
}

customElements.define('checkout-validated-button', CheckoutValidatedButton);

/*
    "abstract" base class for popup content components
 */
class BasePopupContent extends s {
    constructor() {
        super();

        this.closeModal = () => {console.log('BasePopupContent.closeModal');};
        this.errorMessage = '';
    }

    static properties = {
        closeModal: {type: Function, state: true,},
        errorMessage: {type: String, state: true,}
    };
}

class CheckoutPostalCountry extends s {
    constructor() {
        super();
        this.formClass = '';
        this.selfLoad = false;
        this._selectedCountry = '';
        this.selectedState = '';
        this.context = '';

        this.eventListener = this.eventListener.bind(this);
    }

    static properties = {
        formClass: {type: String, attribute: false,},
        selfLoad: {type: Boolean, attribute: 'self-load',},
        selectedCountry: {type: String, attribute: false,},
        selectedState: {type: String, attribute: false,},
        context: {type: String,},
    };

    get selectedCountry() {
        return this._selectedCountry;
    }

    set selectedCountry(val) {
        if (val !== this._selectedCountry) {
            this._selectedCountry = val;
            let theForm = closestCrossShadow(this, 'form');
            if (theForm) {
                //console.log('found form');
                this.loadOptions(theForm, this.selectedCountry);
            }
        }
    }

    createOptions(postalOptions, generateDividers, selectedValue, addPlaceholder) {
        let options = [];
        if (addPlaceholder) {
            let newOpt = document.createElement('option');
            newOpt.setAttribute("value", '');
            newOpt.innerText = '- Select -';
            options.push(newOpt);
        }
        for (let i = 0; i < postalOptions.length; i++) {
            let regOpt = postalOptions[i];
            if (!evalBooleanish(regOpt.divider)) {
                let newOpt = document.createElement('option');
                newOpt.setAttribute("value", regOpt.value);
                newOpt.innerText = regOpt.displayName;
                if (selectedValue && selectedValue === regOpt.value) {
                    newOpt.setAttribute('selected', 'true');
                }
                options.push(newOpt);
            } else if (generateDividers) {
                let newOpt = document.createElement('option');
                newOpt.setAttribute("disabled", "true");
                newOpt.innerText = regOpt.displayName;
                options.push(newOpt);
            }
        }
        return options;
    }

    loadOptions(form, selectedCountry, loadCountries = false) {
        let options = {mode: 'same-origin',};
        fetchJsonGet(`/public/handler/dynamic-country-options.jsp?selectedCountry=${selectedCountry}&loadCountries=${loadCountries}&context=${this.context}`,
            options, (data) => {
            if (data.result) {
                let theForm = form;
                if (!theForm) {
                    console.log('did not find form!');
                } else {
                    if (loadCountries) {
                        let countryInput = theForm.querySelector('[name="country"]');
                        if (countryInput && data.countryOptions) {
                            let newOptions = this.createOptions(data.countryOptions, true, this.selectedCountry);
                            countryInput.innerHTML = '';
                            countryInput.append(...newOptions);
                        }
                    }
                    let dataFormClass = data.formClass;
                    if (this.formClass) {
                        theForm.classList.remove(this.formClass);
                    }
                    if (dataFormClass) {
                        theForm.classList.add(dataFormClass);
                        this.formClass = dataFormClass;
                    } else {
                        this.formClass = '';
                    }
                    if (evalBooleanish(data.suppressRegion)) {
                        theForm.classList.add('address-no-state');
                    } else {
                        theForm.classList.remove('address-no-state');
                    }
                    let regWrapper = theForm.querySelector('.f-state');
                    if (regWrapper) {
                        if (evalBooleanish(data.regionOptional)) {
                            regWrapper.classList.remove('required');
                        } else {
                            regWrapper.classList.add('required');
                        }
                    }
                    let postCodeLabel = theForm.querySelector('label[for="postalCode"]');
                    if (postCodeLabel && data.postalCodeLabel) {
                        postCodeLabel.innerText = data.postalCodeLabel;
                    }
                    let regionLabel = theForm.querySelector('label[for="state"]');
                    if (regionLabel && data.regionLabel) {
                        regionLabel.innerText = data.regionLabel;
                    }
                    let regionInput = theForm.querySelector('[name="state"]');
                    if (regionInput) {
                        if (regionInput.tagName.toLowerCase() === 'select') {
                            if (data.regionOptions && data.regionOptions.length) {
                                // create new options and replace on existing select
                                let newOptions = this.createOptions(data.regionOptions, false, this.selectedState, true);
                                regionInput.innerHTML = '';
                                regionInput.append(...newOptions);
                                const event = new CustomEvent('validationChange', {bubbles: true});
                                regionInput.dispatchEvent(event);
                            } else {
                                // create text input and replace child of parent
                                let newTxt = document.createElement('input');
                                newTxt.setAttribute("type", "text");
                                newTxt.setAttribute("id", "state");
                                newTxt.setAttribute("name", "state");
                                newTxt.setAttribute("maxlength", "30");
                                newTxt.setAttribute("autocapitalize", "words");
                                newTxt.setAttribute("autocomplete", "on");
                                let parent = regionInput.parentNode;
                                parent.removeChild(regionInput);
                                parent.appendChild(newTxt);
                                let validation= newTxt.closest('checkout-validated-input');
                                if (validation) {
                                    let regOpt = evalBooleanish(data.regionOptional);
                                    validation.setAttribute("validation", (regOpt ? 'none' : 'required'));
                                }
                            }
                        } else if (regionInput.tagName.toLowerCase() === 'input') {
                            if (data.regionOptions && data.regionOptions.length) {
                                // create select element and replace child of parent
                                let newSelect = document.createElement('select');
                                newSelect.setAttribute("id", "state");
                                newSelect.setAttribute("name", "state");
                                let newOptions = this.createOptions(data.regionOptions, false, this.selectedState, true);
                                newSelect.append(...newOptions);
                                let inputParent = regionInput.parentNode;
                                inputParent.removeChild(regionInput);
                                inputParent.appendChild(newSelect);
                                const event = new CustomEvent('validationChange', {bubbles: true});
                                newSelect.dispatchEvent(event);
                            }
                        }
                    }
                }
            } else {
                console.log(`fetch fail, message is ${data.message}`);
            }
        });
    }

    eventListener(evt) {
        let evtTarget = evt.target;
        let theForm = evtTarget.closest('form');
        this.loadOptions(theForm, evtTarget.value);
    }

    connectedCallback() {
        super.connectedCallback();
        //console.log('CheckoutPostalCountry connectedCallback');

        this.shadowRoot.addEventListener('change', this.eventListener);

        if (this.selfLoad) {
            //console.log('attempt options load');
            let theForm = closestCrossShadow(this, 'form');
            if (theForm) {
                //console.log('found form');
                this.loadOptions(theForm, this.selectedCountry, true);
            }
        }
    }

    render() {
        return x`<slot></slot>`;
    }
}

customElements.define('checkout-postal-country', CheckoutPostalCountry);

class ElineAutocomplete extends s {
    static properties = {
        minLength: {type: Number},
        inputDelay: {type: Number},
        shouldQuery: {type: Function},
        fetchResults: {type: Function},
        selectResult: {type: Function},
        formatOption: {type: Function},
        clearParentState: {type: Function},
        input: {type: Object},
        options: {type: Array, state:true,},
    };

    static styles = i$3`ol{list-style:none;padding:1rem;margin:0;overflow:hidden auto;max-height:300px;border:0 solid #999;box-shadow:2px 6px 5px rgb(68 68 68 / 50%)}li{padding:.5rem .5rem;font-size:1rem;margin:.5rem .5rem;display:flex;align-items:center;cursor:pointer;border-bottom:0 solid #f4f4f4;position:relative;overflow:hidden}li:hover{background:#f7f7f7}`;

    constructor() {
        super();
        this.minLength = 2;
        this.inputDelay = 200;
        this.shouldQuery = () => true;
        this.fetchResults = () => new Promise((resolve) => resolve([]));
        this.selectResult = () => true;
        this.formatOption = (option) => option;
        this.clearParentState = () => true;
        this.input = null;
        this.options = [];
        this.timeoutHandle = -1;
    }

    dlog(msg) {
        /*
        console.log(`${msg}`);
         */
    }

    handleSlotChange(e) {
        let elements = e.target.assignedElements({flatten: true});
        // obviously this will break hard if you nest more than one input inside one of these.
        // so don't do that.
        for (let i = 0; i < elements.length; i++) {
            let el = elements[i];
            if (el instanceof HTMLInputElement) {
                this.input = el;
                break;
            } else {
                let child = el.querySelector('input');
                if (child) {
                    this.input = child;
                    break;
                }
            }
        }
    }

    delay = t => new Promise(resolve => setTimeout(resolve, t));

    checkInputChanging(originalLength) {
        return this.delay(this.inputDelay)
            .then(() => {
                if (this.input.value.length === originalLength) {
                    return Promise.resolve(true);
                } else {
                    return Promise.reject('Still changing.');
                }
            })
    }

    handleInput() {
        this.clearTimeout();
        if (this.input) {
            if (this.input.value) {
                if (this.input.value.length > this.minLength) {
                    if (this.shouldQuery()) {
                        this.checkInputChanging(this.input.value.length)
                            .then(() => this.fetchResults())
                            .then((results) => {
                                this.options = results;
                            })
                            .catch((error) => this.dlog(error));
                    } else {
                        this.dlog('shouldQuery false');
                    }
                } else {
                    this.dlog('too short');
                }
            } else {
                this.dlog('no value');
            }
        } else {
            this.dlog('no input');
        }
    }

    handleSelect(e) {
        this.clearTimeout();
        if(e.target.dataset.option) {
            if (this.selectResult(JSON.parse(e.target.dataset.option))) {
                this.options = [];
                this.clearParentState();
            } else {
                this.handleInput();
            }
        }
    }

    handleFocusOut() {
        this.clearTimeout();
        this.timeoutHandle = setTimeout(() => {
            this.options = [];
            this.timeoutHandle = -1;
            this.clearParentState();
        }, 200);
    }

    clearTimeout() {
        if (this.timeoutHandle !== -1) {
            clearTimeout(this.timeoutHandle);
            this.timeoutHandle = -1;
        }
    }

    render() {
        return x`<div class="slotWrap" @input="${this.handleInput}" @focusout="${this.handleFocusOut}"><slot @slotchange="${this.handleSlotChange}"></slot>${this.options.length > 0 ?
                        x`<ol @click="${this.handleSelect}">${this.options.map((option) =>
                                        x`<li data-option="${JSON.stringify(option)}">${this.formatOption(option)}</li>`
                                )}</ol>`
                        : x``}</div>`;
    }

}
customElements.define('eline-autocomplete', ElineAutocomplete);

const jsDatepickerStyles = i$3`.qs-datepicker-container{font-size:1rem;font-family:sans-serif;color:#000;position:absolute;width:15.625em;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;z-index:9001;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid grey;border-radius:.263921875em;overflow:hidden;background:#fff;-webkit-box-shadow:0 1.25em 1.25em -.9375em rgba(0,0,0,.3);box-shadow:0 1.25em 1.25em -.9375em rgba(0,0,0,.3)}.qs-datepicker-container *{-webkit-box-sizing:border-box;box-sizing:border-box}.qs-centered{position:fixed;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.qs-hidden{display:none}.qs-overlay{position:absolute;top:0;left:0;background:rgba(0,0,0,.75);color:#fff;width:100%;height:100%;padding:.5em;z-index:1;opacity:1;-webkit-transition:opacity .3s;transition:opacity .3s;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.qs-overlay.qs-hidden{opacity:0;z-index:-1}.qs-overlay .qs-overlay-year{background:rgba(0,0,0,0);border:none;border-bottom:1px solid #fff;border-radius:0;color:#fff;font-size:.875em;padding:.25em 0;width:80%;text-align:center;margin:0 auto;display:block}.qs-overlay .qs-overlay-year::-webkit-inner-spin-button{-webkit-appearance:none}.qs-overlay .qs-close{padding:.5em;cursor:pointer;position:absolute;top:0;right:0}.qs-overlay .qs-submit{border:1px solid #fff;border-radius:.263921875em;padding:.5em;margin:0 auto auto;cursor:pointer;background:hsla(0,0%,50.2%,.4)}.qs-overlay .qs-submit.qs-disabled{color:grey;border-color:grey;cursor:not-allowed}.qs-overlay .qs-overlay-month-container{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.qs-overlay .qs-overlay-month{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:calc(100% / 3);cursor:pointer;opacity:.5;-webkit-transition:opacity .15s;transition:opacity .15s}.qs-overlay .qs-overlay-month.active,.qs-overlay .qs-overlay-month:hover{opacity:1}.qs-controls{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-negative:0;flex-shrink:0;background:#d3d3d3;-webkit-filter:blur(0);filter:blur(0);-webkit-transition:-webkit-filter .3s;transition:-webkit-filter .3s;transition:filter .3s;transition:filter .3s,-webkit-filter .3s}.qs-controls.qs-blur{-webkit-filter:blur(5px);filter:blur(5px)}.qs-arrow{height:1.5625em;width:1.5625em;position:relative;cursor:pointer;border-radius:.263921875em;-webkit-transition:background .15s;transition:background .15s}.qs-arrow:hover{background:rgba(0,0,0,.1)}.qs-arrow:hover.qs-left:after{border-right-color:#000}.qs-arrow:hover.qs-right:after{border-left-color:#000}.qs-arrow:after{content:"";border:.390625em solid transparent;position:absolute;top:50%;-webkit-transition:border .2s;transition:border .2s}.qs-arrow.qs-left:after{border-right-color:grey;right:50%;-webkit-transform:translate(25%,-50%);-ms-transform:translate(25%,-50%);transform:translate(25%,-50%)}.qs-arrow.qs-right:after{border-left-color:grey;left:50%;-webkit-transform:translate(-25%,-50%);-ms-transform:translate(-25%,-50%);transform:translate(-25%,-50%)}.qs-month-year{font-weight:700;-webkit-transition:border .2s;transition:border .2s;border-bottom:1px solid transparent}.qs-month-year:not(.qs-disabled-year-overlay){cursor:pointer}.qs-month-year:not(.qs-disabled-year-overlay):hover{border-bottom:1px solid grey}.qs-month-year:active:focus,.qs-month-year:focus{outline:0}.qs-month{padding-right:.5ex}.qs-year{padding-left:.5ex}.qs-squares{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.3125em;-webkit-filter:blur(0);filter:blur(0);-webkit-transition:-webkit-filter .3s;transition:-webkit-filter .3s;transition:filter .3s;transition:filter .3s,-webkit-filter .3s}.qs-squares.qs-blur{-webkit-filter:blur(5px);filter:blur(5px)}.qs-square{width:calc(100% / 7);height:1.5625em;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;cursor:pointer;-webkit-transition:background .1s;transition:background .1s;border-radius:.263921875em}.qs-square:not(.qs-empty):not(.qs-disabled):not(.qs-day):not(.qs-active):hover{background:orange}.qs-current{font-weight:700;text-decoration:underline}.qs-active,.qs-range-end,.qs-range-start{background:#add8e6}.qs-range-start:not(.qs-range-6){border-top-right-radius:0;border-bottom-right-radius:0}.qs-range-middle{background:#d4ebf2}.qs-range-middle:not(.qs-range-0):not(.qs-range-6){border-radius:0}.qs-range-middle.qs-range-0{border-top-right-radius:0;border-bottom-right-radius:0}.qs-range-end:not(.qs-range-0),.qs-range-middle.qs-range-6{border-top-left-radius:0;border-bottom-left-radius:0}.qs-disabled,.qs-outside-current-month{opacity:.2}.qs-disabled{cursor:not-allowed}.qs-day,.qs-empty{cursor:default}.qs-day{font-weight:700;color:grey}.qs-event{position:relative}.qs-event:after{content:"";position:absolute;width:.46875em;height:.46875em;border-radius:50%;background:#07f;bottom:0;right:0}`;

/*
  Importing this scss file so as to declare it's a dependency in the library.
  Webpack will then separate it out into its own css file and include it in the dist folder.
*/


var datepickers = []; // Get's reassigned in `remove()` below.
var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
var months = [
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December'
];
var sides = {
  // `t`, `r`, `b`, and `l` are all positioned relatively to the input the calendar is attached to.
  t: 'top',
  r: 'right',
  b: 'bottom',
  l: 'left',

  // `centered` fixes the calendar smack in the middle of the screen. Useful for mobile devices.
  c: 'centered'
};

/*
  The default callback functions (onSelect, etc.) will be a noop function.
  Using this variable so we can simply reference the same function.
  Also, this allows us to check if the callback is a noop function
  by doing a `=== noop` anywhere we like.
*/
function noop() {}

/*
  Add a single function as the handler for a few events for ALL datepickers.
  Storing events in an array to access later in the `remove` fxn below.
  Using `focusin` because it bubbles, `focus` does not.
*/
var events = ['click', 'focusin', 'keydown', 'input'];


/*
 *  Datepicker! Get a date with JavaScript...
 */
function datepicker(selectorOrElement, options) {
    // Create the datepicker instance!
  var instance = createInstance(selectorOrElement, options);

  // Apply the event listeners to the document only once.
  if (!datepickers.length) applyListeners(document);

  // Apply the event listeners to a particular shadow DOM only once.
  if (instance.shadowDom) {
    var shadowDomAlreadyInUse = datepickers.some(function(picker) { return picker.shadowDom === instance.shadowDom });
    if (!shadowDomAlreadyInUse) applyListeners(instance.shadowDom);
  }

  // Keep track of all our instances in an array.
  datepickers.push(instance);

  /*
    Daterange processing!
    When we encounted the 2nd in a pair, we need run both through `adjustDateranges`
    to handle the min & max settings, and we need to re-render the 1st.
  */
  if (instance.second) {
    var first = instance.sibling;

    // Adjust both dateranges.
    adjustDateranges({ instance: instance, deselect: !instance.dateSelected });
    adjustDateranges({ instance: first, deselect: !first.dateSelected });

    // Re-render the first daterange instance - the 2nd will be rendered below.
    renderCalendar(first);
  }

  renderCalendar(instance, instance.startDate || instance.dateSelected);
  if (instance.alwaysShow) calculatePosition(instance);

  return instance
}

/*
 *  Applies the event listeners.
 *  This will be called the first time datepicker is run.
 *  It will also be called on the first run *after* having removed
 *  all previous instances from the DOM. In other words, it only
 *  runs the first time for each "batch" of datepicker instances.
 *
 *  The goal is to ever only have one set of listeners regardless
 *  of how many datepicker instances have been initialized.
 */
function applyListeners(documentOrShadowDom) {
  /*
    Using document instead of window because #iphone :/
    Safari won't handle the click event properly if it's on the window.
  */
  events.forEach(function(event) {
    documentOrShadowDom.addEventListener(event, documentOrShadowDom === document ? oneHandler : shadowDomHandler);
  });
}

/*
 *  Creates a datepicker instance after sanitizing the options.
 *  Calls `setCalendarInputValue` and conditionally `showCal`.
 */
function createInstance(selectorOrElement, opts) {
  var options = sanitizeOptions(opts || defaults());

  /*
    This will get assigned the shadow DOM if the calendar is in one.
    We use this property to trigger an extra event listener on the shadow DOM
    as well as tell the <body> listener to ignore events from the shadow DOM.
  */
  var shadowDom;

  /*
    This will get assigned the <custom-element> containing the shadow DOM.
    This can potentially eventually become `positionedEl` (stored on the instance object).
    It is used for positioning purposes. See the explanation below where `positionedEl` is defined.

    PLEASE NOTE - custom elements have a default display of `inline` which, for whatever reason,
    can have negative effects on the calendar. This is only an issue if the calendar is attached
    directly to the shadow DOM and not nested within some other element in the shadow DOM.
    If this is your case and you notice weirdness (such as the calendar disappearing immediately after showing),
    try adding an explicit display property to the custom element. This is also mentioned in the
    "best practices" article by Google here - https://bit.ly/33F7TkJ.
  */
  var customElement;

  /*
    In the case that the selector is an id beginning with a number (e.x. #123),
    querySelector will fail. That's why we need to check and conditionally use `getElementById`.
    Also, datepicker doesn't support string selectors when using a shadow DOM, hence why we use `document`.
  */
  var el = selectorOrElement;

  if (typeof el === 'string') {
    el = el[0] === '#' ? document.getElementById(el.slice(1)) : document.querySelector(el);

  // Maybe this will be supported one day once I understand the use-case.
  } else if (type$1(el) === '[object ShadowRoot]') {
    throw new Error('Using a shadow DOM as your selector is not supported.')

  /*
    If the selector is not a string, we may have been given an element within a shadow DOM (or a shadow DOM itself).
    Iterate up the chain to see what the root node is, throwing an error if shadow DOM is found and not supported.
    IE doesn't support custom elements at all, neither does it support the `Node.getRootNode()` method,
    which would have avoided having to use a while loop with all this logic.
  */
  } else {
    var rootFound;
    var currentParent = el.parentNode;

    while (!rootFound) {
      var parentType = type$1(currentParent);

      // We've reached the document, which means there's no shadow DOM in use.
      if (parentType === '[object HTMLDocument]') {
        rootFound = true;

      // We're using a shadow DOM.
      } else if (parentType === '[object ShadowRoot]') {
        rootFound = true;
        shadowDom = currentParent;
        customElement = currentParent.host;

      // Focus up the chain to the next parent and keep iterating.
      } else {
        currentParent = currentParent.parentNode;
      }
    }
  }

  if (!el) throw new Error('No selector / element found.')

  // Check if the provided element already has a datepicker attached.
  if (datepickers.some(function(picker) { return picker.el === el })) throw new Error('A datepicker already exists on that element.')

  /*
    `noPosition` tells future logic to avoid trying to style the parent element of datepicker.
    Otherwise, it will conditionally add `position: relative` styling to the parent.
    For instance, if datepicker's selector was 'body', there is no parent element to do any
    styling to. And there's nothing to position datepicker relative to. It will just be appended to the body.

    This property also prevents `calculatePosition()` from doing anything.
    `noPosition` will false when using a shadow DOM.
  */
  var noPosition = el === document.body;

  /*
    `parent` is the element that datepicker will be attached to in the DOM.

    In the case of `noPosition`, it will be the <body>. If datepicker was passed a top-level element
    in the shadow DOM (meaning the element's direct parent IS the shadow DOM), the parent will be the
    shadow DOM. Otherwise, `parent` is assigned the parent of the element that was passed to datepicker
    in the first place (usually an <input>).
  */
  var parent = shadowDom ? (el.parentElement || shadowDom) : noPosition ? document.body : el.parentElement;

  /*
    The calendar needs to be positioned relative `el`. Since we position the calendar absolutely, we need
    something up the chain to have explicit positioning on it. `positionedEl` will conditionally get that
    explicit positioning below via inline styles if it doesn't already have it. That positioning, if applied,
    will be removed (cleaned up) down the line. `calculatePosition` will use the coordinates for `positionedEl`
    and `el` to correctly position the calendar.

    If `noPosition` is true, this value will be ignored further down the chain.
    If `parent` is a shadow DOM, this could be the custom element associated with that shadow DOM.

    If the next element up the chain (el.parentElement) IS the shadow DOM, el.parentElement will be null
    since a shadow DOM isn't an element. Hence why we go even further up the chain and assign customElement.
  */
  var positionedEl = shadowDom ? (el.parentElement || customElement) : parent;


  var calendarContainer = document.createElement('div');
  var calendar = document.createElement('div');

  /*
    The calendar scales relative to the font-size of the container.
    The user can provide a class name that sets font-size, or a theme perhaps,
    thereby controlling the overall size and look of the calendar.
  */
  calendarContainer.className = 'qs-datepicker-container qs-hidden';
  calendar.className = 'qs-datepicker';


  var instance = {
    // If a datepicker is used within a shadow DOM, this will be populated with it.
    shadowDom: shadowDom,

    // If a datepicker is used within a shadow DOM, this will be populated with the web component custom element.
    // This is not used internally, but provided as a convenience for users who might want a reference.
    customElement: customElement,



    // Used to help calculate the position of the calendar.
    positionedEl: positionedEl,

    // The calendar will become a sibling to this element in the DOM and be positioned relative to it (except when <body>).
    el: el,

    // The element that datepicker will be child of in the DOM. Used to calculate datepicker's position and might get inline styles.
    parent: parent,

    // Indicates whether the calendar is used with an <input> or not. Affects login in the event listener.
    nonInput: el.nodeName !== 'INPUT',

    // Flag indicating if `el` is 'body'. Used below and by `calculatePosition`.
    noPosition: noPosition,

    // Calendar position relative to `el`.
    position: noPosition ? false : options.position,

    // Date obj used to indicate what month to start the calendar on.
    startDate: options.startDate,

    // Starts the calendar with a date selected.
    dateSelected: options.dateSelected,

    // An array of dates to disable - these are unix timestamps and not date objects (converted in `sanitizeOptions`).
    disabledDates: options.disabledDates,

    // Low end of selectable dates - overriden for daterange pairs below.
    minDate: options.minDate,

    // High end of selectable dates - overriden for daterange pairs below.
    maxDate: options.maxDate,

    // Disabled the ability to select days on the weekend.
    noWeekends: !!options.noWeekends,

    // Indices for "Saturday" and "Sunday" repsectively.
    weekendIndices: options.weekendIndices,

    // The containing element to our calendar.
    calendarContainer: calendarContainer,

    // The element our calendar is constructed in.
    calendar: calendar,

    // Month of `startDate` or `dateSelected` (as a number).
    currentMonth: (options.startDate || options.dateSelected).getMonth(),

    // Month name in plain english - or not.
    currentMonthName: (options.months || months)[(options.startDate || options.dateSelected).getMonth()],

    // Year of `startDate` or `dateSelected`.
    currentYear: (options.startDate || options.dateSelected).getFullYear(),

    // Events will show a small circle on calendar days.
    events: options.events || {},

    defaultView: options.defaultView,



    // Method to programmatically set the calendar's date.
    setDate: setDate,

    // Method that removes the calendar from the DOM along with associated events.
    remove: remove,

    // Method to programmatically change the minimum selectable date.
    setMin: setMin,

    // Method to programmatically change the maximum selectable date.
    setMax: setMax,

    // Method to programmatically show the calendar.
    show: show$1,

    // Method to programmatically hide the calendar.
    hide: hide,

    // Method to programmatically navigate the calendar
    navigate: navigate,

    // Method to programmatically toggle the overlay.
    toggleOverlay: instanceToggleOverlay,



    // Callback fired when a date is selected - triggered in `selectDay`.
    onSelect: options.onSelect,

    // Callback fired when the calendar is shown - triggered in `showCal`.
    onShow: options.onShow,

    // Callback fired when the calendar is hidden - triggered in `hideCal`.
    onHide: options.onHide,

    // Callback fired when the month is changed - triggered in `changeMonthYear`.
    onMonthChange: options.onMonthChange,

    // Function to customize the date format updated on <input> elements - triggered in `setCalendarInputValue`.
    formatter: options.formatter,

    // Function with custom logic that determines wether a given date is disabled or not.
    disabler: options.disabler,



    // Labels for months - custom or default.
    months: options.months || months,

    // Labels for days - custom or default.
    days: options.customDays || days,

    // Start day of the week - indexed from `days` above.
    startDay: options.startDay,

    // Custom overlay months - only the first 3 characters are used.
    overlayMonths: options.overlayMonths || (options.months || months).map(function(m) { return m.slice(0, 3) }),

    // Custom overlay placeholder.
    overlayPlaceholder: options.overlayPlaceholder || '4-digit year',

    // Custom overlay submit button.
    overlayButton: options.overlayButton || 'Submit',

    // Disable the overlay for changing the year.
    disableYearOverlay: !!options.disableYearOverlay,

    // Disable the datepicker on mobile devices.
    // Allows the use of native datepicker if the input type is 'date'.
    disableMobile: !!options.disableMobile,

    // Used in conjuntion with `disableMobile` above within `oneHandler`.
    isMobile: 'ontouchstart' in window,

    // Prevents the calendar from hiding.
    alwaysShow: !!options.alwaysShow,

    // Used to connect 2 datepickers together to form a daterange picker.
    id: options.id,

    // Shows a date in every square rendered on the calendar (preceding and trailing month days).
    showAllDates: !!options.showAllDates,

    // Prevents Datepicker from selecting dates when attached to inputs that are `disabled` or `readonly`.
    respectDisabledReadOnly: !!options.respectDisabledReadOnly,



    // Indicates this is the 1st instance in a daterange pair.
    first: options.first,

    // Indicates this is the 2nd instance in a daterange pair.
    second: options.second
  };

  /*
    Daterange processing!
    Ensure both instances have a reference to one another.
    Set min/max and original min/max dates on each instance.
  */
  if (options.sibling) {
    /* If we're here, we're encountering the 2nd instance in a daterange pair. */
    var first = options.sibling;
    var second = instance;
    var minDate = first.minDate || second.minDate;
    var maxDate = first.maxDate || second.maxDate;

    // Store the 1st instance as a sibling on the 2nd.
    second.sibling = first;

    // Store the 2nd instance as a sibling on the 1st.
    first.sibling = second;

    /*
      Daterange pairs share a min & max date.
      The 1st instance overrides the 2nd.
    */
    first.minDate = minDate;
    first.maxDate = maxDate;
    second.minDate = minDate;
    second.maxDate = maxDate;

    // Used to restore the min / max dates when a date is deselected.
    first.originalMinDate = minDate;
    first.originalMaxDate = maxDate;
    second.originalMinDate = minDate;
    second.originalMaxDate = maxDate;

    // Add a method that returns an object with start & end date selections for the pair.
    first.getRange = getRange;
    second.getRange = getRange;
  }

  // Initially populate the <input> field / set attributes on the `el`.
  if (options.dateSelected) setCalendarInputValue(el, instance);

  // Find out what positioning `positionedEl` has so we can conditionally style it.
  var computedPosition = getComputedStyle(positionedEl).position;

  // Only add inline styles if `positionedEl` doesn't have any explicit positioning.
  if (!noPosition && (!computedPosition || computedPosition === 'static')) {
    // Indicate that inline styles have been set.
    instance.inlinePosition = true;

    /*
      Add inline position styles.
      I've seen that `element.style.position = '...'` isn't reliable.
      https://mzl.la/2Yi6hNG
    */
    positionedEl.style.setProperty('position', 'relative');
  }

  /*
    Ensure any pickers with a common `positionedEl` will ALL have the `inlinePosition` property.
    This will ensure the styling is removed ONLY when the LAST picker inside it is removed.
    This condition will trigger when subsequent pickers are instantiated inside `postionedEl`.
  */
  var pickersWithSamePositionedEl = datepickers.filter(function(picker) {
    return picker.positionedEl === instance.positionedEl
  });
  var somePickerHasInlinePosition = pickersWithSamePositionedEl.some(function(picker) {
    return picker.inlinePosition
  });

  if (somePickerHasInlinePosition) {
    instance.inlinePosition = true; // This instance is not in the datepickers array yet. Ensure it has this property.
    pickersWithSamePositionedEl.forEach(function(picker) {
      picker.inlinePosition = true;
    });
  }

  // Put our instance's calendar in the DOM.
  calendarContainer.appendChild(calendar);
  parent.appendChild(calendarContainer);

  // Conditionally show the calendar from the start.
  if (instance.alwaysShow) showCal(instance);

  return instance
}

/*
 *  Helper function to duplicate an object or array.
 *  Should help Babel avoid adding syntax that isn't IE compatible.
 */
function freshCopy(item) {
  if (Array.isArray(item)) return item.map(freshCopy)

  if (type$1(item) === '[object Object]') {
    return Object.keys(item).reduce(function(newObj, key) {
      newObj[key] = freshCopy(item[key]);
      return newObj
    }, {})
  }

  return item
}

/*
 *  Will run checks on the provided options object to ensure correct types.
 *  Returns an options object if everything checks out.
 */
function sanitizeOptions(opts) {
  // Avoid mutating the original object that was supplied by the user.
  var options = freshCopy(opts);

  /*
    Check and ensure all events in the provided array are JS dates.
    Store these on the instance as an object with JS datetimes as keys for fast lookup.
  */
  if (options.events) {
    options.events = options.events.reduce(function(acc, date) {
      if (!dateCheck(date)) throw new Error('"options.events" must only contain valid JavaScript Date objects.')
      acc[+stripTime(date)] = true;
      return acc
    }, {});
  }
['startDate', 'dateSelected', 'minDate', 'maxDate'].forEach(function(value) {
    var date = options[value];
    if (date && !dateCheck(date)) throw new Error('"options.' + value + '" needs to be a valid JavaScript Date object.')

    /*
      Strip the time from the date.
      For dates not supplied, stripTime will return undefined.
    */
    options[value] = stripTime(date);
  });

  var position = options.position;
  var maxDate = options.maxDate;
  var minDate = options.minDate;
  var dateSelected = options.dateSelected;
  var overlayPlaceholder = options.overlayPlaceholder;
  var overlayButton = options.overlayButton;
  var startDay = options.startDay;
  var id = options.id;

  options.startDate = stripTime(options.startDate || dateSelected || new Date());


  // Checks around disabled dates.
  options.disabledDates = (options.disabledDates || []).reduce(function(acc, date) {
    var newDateNum = +stripTime(date);

    if (!dateCheck(date)) throw new Error('You supplied an invalid date to "options.disabledDates".')
    if (newDateNum === +stripTime(dateSelected)) throw new Error('"disabledDates" cannot contain the same date as "dateSelected".')

    // Store a number because `createMonth` checks this array for a number match.
    acc[newDateNum] = 1;
    return acc
  }, {});

  // If id was provided, it cannot me null or undefined.
  if (options.hasOwnProperty('id') && id == null) {
    throw new Error('`id` cannot be `null` or `undefined`')
  }

  /*
    Daterange processing!
    No more than 2 pickers can have the same id.
    Later on in `createInstance` we'll process the daterange pair further.
    Store values for `originalMinDate` & `originalMaxDate`.
    Store a reference to the 1st instance on the 2nd in the options -
      the 1st will get its reference to the 2nd in `createInstance`.
  */
  if (id != null) {
    // Search through pickers already created and see if there's an id match for this one.
    var pickers = datepickers.filter(function(picker) { return picker.id === id });

    // No more than 2 pickers can have the same id.
    if (pickers.length > 1) throw new Error('Only two datepickers can share an id.')

    // 2nd - If we found a picker, THIS will be the 2nd in the pair. Set the sibling property on the options.
    if (pickers.length) {
      options.second = true;
      options.sibling = pickers[0];

    // 1st - If no pickers were found, this is the 1st in the pair.
    } else {
      options.first = true;
    }
  }

  /*
    Ensure the accuracy of `options.position` & call `establishPosition`.
    The 'c' option positions the calendar smack in the middle of the screen,
    *not* relative to the input. This can be desirable for mobile devices.
  */
  var positionFound = ['tr', 'tl', 'br', 'bl', 'c'].some(function(dir) { return position === dir });
  if (position && !positionFound) {
    throw new Error('"options.position" must be one of the following: tl, tr, bl, br, or c.')
  }
  options.position = establishPosition(position || 'bl');

  function dsErr(min) {
    var lessOrGreater = min ? 'less' : 'greater';
    throw new Error('"dateSelected" in options is ' + lessOrGreater + ' than "' + (min || 'max') + 'Date".')
  }

  // Check proper relationship between `minDate`, `maxDate`, & `dateSelected`.
  if (maxDate < minDate) throw new Error('"maxDate" in options is less than "minDate".')
  if (dateSelected) {
    if (minDate > dateSelected) dsErr('min');
    if (maxDate < dateSelected) dsErr();
  }

  // Callbacks - default to a noop function.
  ['onSelect', 'onShow', 'onHide', 'onMonthChange', 'formatter', 'disabler'].forEach(function(fxn) {
    if (typeof options[fxn] !== 'function') options[fxn] = noop; // `noop` defined at the top.
  })

  // Custom labels for months & days.
  ;['customDays', 'customMonths', 'customOverlayMonths'].forEach(function(label, i) {
    var custom = options[label];
    var num = i ? 12 : 7;

    // Do nothing if the user hasn't provided this custom option.
    if (!custom) return

    if (
      !Array.isArray(custom) || // Must be an array.
      custom.length !== num || // Must have the correct length.
      custom.some(function(item) { return typeof item !== 'string' }) // Must be an array of strings only.
    ) throw new Error('"' + label + '" must be an array with ' + num + ' strings.')

    options[!i ? 'days' : i < 2 ? 'months' : 'overlayMonths'] = custom;
  });

  /*
    Adjust days of the week for user-provided start day.
    If `startDay` is a bad value, it will simply be ignored.
  */
  if (startDay && startDay > 0 && startDay < 7) {
    // [sun, mon, tues, wed, thurs, fri, sat]             (1) - original supplied days of the week
    var daysCopy = (options.customDays || days).slice();

    // Example with startDay of 3 (Wednesday)
    // daysCopy => [wed, thurs, fri, sat]                 (2) - the 1st half of the new array
    // chunk    => [sun, mon, tues]                       (3) - the 2nd half of the new array
    var chunk = daysCopy.splice(0, startDay);

    // [wed, thurs, fri, sat, sun, mon, tues]             (4) - the new days of the week
    options.customDays = daysCopy.concat(chunk);

    options.startDay = +startDay;
    options.weekendIndices = [
      daysCopy.length - 1, // Last item in the 1st half of the edited array.
      daysCopy.length // Next item in the array, 1st item in the 2nd half of the edited array.
    ];
  } else {
    options.startDay = 0;
    options.weekendIndices = [6, 0]; // Indices of "Saturday" and "Sunday".
  }

  // Custom text for overlay placeholder & button.
  if (typeof overlayPlaceholder !== 'string') delete options.overlayPlaceholder;
  if (typeof overlayButton !== 'string') delete options.overlayButton;

  // Show either the calendar (default) or the overlay when the calendar is open.
  var defaultView = options.defaultView;
  if (defaultView && (defaultView !== 'calendar' && defaultView !== 'overlay')) {
    throw new Error('options.defaultView must either be "calendar" or "overlay".')
  }
  options.defaultView = defaultView || 'calendar';

  return options
}

/*
 *  Returns an object containing all the default settings.
 */
function defaults() {
  return {
    startDate: stripTime(new Date()),
    position: 'bl',
    defaultView: 'calendar',
  }
}

/*
 *  Returns an object representing the position of the calendar
 *  relative to the calendar's <input> element.
 */
function establishPosition(positions) {
  var p1 = positions[0];
  var p2 = positions[1];
  var obj = {};

  obj[sides[p1]] = 1;
  if (p2) obj[sides[p2]] = 1;

  return obj
}

/*
 *  Renders a calendar, defaulting to the current year & month of that calendar.
 *  Populates `calendar.innerHTML` with the contents of the calendar controls, month, and overlay.
 *  This method does NOT *show* the calendar on the screen. It only affects the html structure.
 */
function renderCalendar(instance, date) {
  var overlay = instance.calendar.querySelector('.qs-overlay');
  var overlayOpen = overlay && !overlay.classList.contains('qs-hidden');

  // Default to rendering the current month. This is helpful for re-renders.
  date = date || new Date(instance.currentYear, instance.currentMonth);

  instance.calendar.innerHTML = [
    createControls(date, instance, overlayOpen),
    createMonth(date, instance, overlayOpen),
    createOverlay(instance, overlayOpen)
  ].join('');

  /*
    When the overlay is open and we submit a year (or click a month), the calendar's
    html is recreated here. To make the overlay fade out the same way it faded in,
    we need to create it with the appropriate classes (triggered by `overlayOpen`),
    then wait for the next repaint, triggering a fade out.

    Good for IE >= 10.
  */
  if (overlayOpen) window.requestAnimationFrame(function() { toggleOverlay(true, instance); });
}

/*
 *  Creates the calendar controls.
 *  Returns a string representation of DOM elements.
 */
function createControls(date, instance, overlayOpen) {
  return [
    '<div class="qs-controls' + (overlayOpen ? ' qs-blur' : '') + '">',
    '<div class="qs-arrow qs-left"></div>',
    '<div class="qs-month-year' + (instance.disableYearOverlay ? ' qs-disabled-year-overlay' : '') + '">',
    '<span class="qs-month">' + instance.months[date.getMonth()] + '</span>',
    '<span class="qs-year">' + date.getFullYear() + '</span>',
    '</div>',
    '<div class="qs-arrow qs-right"></div>',
    '</div>'
  ].join('')
}

/*
 *  Creates the calendar month structure.
 *  Returns a string representation of DOM elements.
 */
function createMonth(date, instance, overlayOpen) {
  // Dynamic properties.
  var currentMonth = instance.currentMonth;
  var currentYear = instance.currentYear;
  var dateSelected = instance.dateSelected;
  var maxDate = instance.maxDate;
  var minDate = instance.minDate;
  var showAllDates = instance.showAllDates;

  // Static properties.
  var days = instance.days;
  var disabledDates = instance.disabledDates;
  var startDay = instance.startDay;
  var weekendIndices = instance.weekendIndices;
  var events = instance.events;

  // If we have a daterange picker, get the current range.
  var range = instance.getRange ? instance.getRange() : {};
  var start = +range.start;
  var end = +range.end;

  // 1st of the month for whatever date we've been provided.
  var copy = stripTime(new Date(date).setDate(1)); // 1st of the month.

  // copy.getDay() - day of the week, 0-indexed.
  // startDay      - day of the week the calendar starts on, 0-indexed.
  var offset = copy.getDay() - startDay; // Preceding empty squares.

  // Offsetting the start day may move back to a new 1st row.
  var precedingRow = offset < 0 ? 7 : 0;

  // Bump the provided date to the 1st of the next month.
  copy.setMonth(copy.getMonth() + 1);

  // Move the provided date back a single day, resulting in the last day of the provided month.
  copy.setDate(0);

  // Last day of the month = how many quares get a number on the calendar.
  var daysInMonth = copy.getDate(); // Squares with a number.

  // This array will contain string representations of HTML for all the calendar squares.
  var calendarSquares = [];

  // Fancy calculations for the total # of squares.
  // The pipe operator truncates any decimals.
  var totalSquares = precedingRow + (((offset + daysInMonth) / 7 | 0) * 7);
  totalSquares += (offset + daysInMonth) % 7 ? 7 : 0;

  /*
    Create all the numbered calendar days.
    Days of the week (top row) created below this loop.
  */
  for (var i = 1; i <= totalSquares; i++) {
    // The index of the day of the week that the current iteration is at.
    var weekdayIndex = (i - 1) % 7; // Round robin values of 0 - 6, back to 0 again.

    /*
      "Thu" - text name for the day of the week as displayed on the calendar.
      Added as a class name to each numbered day in the calendar.
    */
    var weekday = days[weekdayIndex];

    // Number displayed in the calendar for current iteration's day.
    var num = i - (offset >= 0 ? offset : (7 + offset));

    /*
      JavaScript date object for the current iteration's day.
      It has no time so we can compare accurately.
      Used to find out of the current iteration is today.
    */
    var thisDay = new Date(currentYear, currentMonth, num);

    // Does this iteration's date have an event?
    var hasEvent = events[+thisDay];

    /*
      Is the current iteration's date outside the current month?
      These fall into the before & after squares shown on the calendar.
    */
    var outsideOfCurrentMonth = num < 1 || num > daysInMonth;

    /*
      Days outside the current month need a [data-direction] attribute.
      In the case we're showing all dates, users can click dates outside the current
      month to navigate. This attribute tells the event handler the direction
      of the month to navigate to.
    */
    var direction = outsideOfCurrentMonth ? num < 1 ? -1 : 1 : 0;

    // Flag indicating the square on the calendar should be empty.
    var isEmpty = outsideOfCurrentMonth && !showAllDates;

    // The display number to this iteration's date - can be an empty square as well.
    var thisDayNum = isEmpty ? '' : thisDay.getDate();

    // Is this iteration's date currently selected?
    var isSelected = +thisDay === +dateSelected;

    // Is this day a weekend? Weekends for Datepicker are strictly Saturday & Sunday.
    var isWeekend = weekdayIndex === weekendIndices[0] || weekdayIndex === weekendIndices[1];

    // Is this iteration's date disabled?
    var isDisabled = disabledDates[+thisDay] ||
      instance.disabler(thisDay) ||
      (isWeekend && instance.noWeekends) ||
      (minDate && +thisDay < +minDate) ||
      (maxDate && +thisDay > +maxDate);

    // Is this iteration's date today?
    var isToday = +stripTime(new Date()) === +thisDay;

    // Daterange variables.
    var isRangeStart = +thisDay === start;
    var isRangeEnd = +thisDay === end;
    var isRangeMiddle = +thisDay > start && +thisDay < end;
    var rangeIsNotSingleDay = start !== end;

    // Base class name that every square will have.
    var className = 'qs-square ' + weekday;

    // Create the rest of the class name for our calendar day element.
    if (hasEvent && !isEmpty) className += ' qs-event'; // Don't show events on empty squares.
    if (outsideOfCurrentMonth) className += ' qs-outside-current-month';
    if (showAllDates || !outsideOfCurrentMonth) className += ' qs-num';
    if (isSelected) className += ' qs-active';
    if (isDisabled && !isEmpty) className += ' qs-disabled'; // Empty dates don't need the class name.
    if (isToday) className += ' qs-current';
    if (isRangeStart && end && rangeIsNotSingleDay) className += ' qs-range-start';
    if (isRangeMiddle) className += ' qs-range-middle';
    if (isRangeEnd && start && rangeIsNotSingleDay) className += ' qs-range-end';
    if (isEmpty) {
      className += ' qs-empty';
      thisDayNum = ''; // Don't show numbers for empty squares.
    }

    calendarSquares.push('<div class="' + className + '" data-direction="' + direction + '">' + thisDayNum + '</div>');
  }

  // Add the header row of days of the week.
  var daysAndSquares = days
    .map(function(day) { return '<div class="qs-square qs-day">' + day + '</div>' })
    .concat(calendarSquares);

  // Wrap it all in a tidy div.
  daysAndSquares.unshift('<div class="qs-squares' + (overlayOpen ? ' qs-blur' : '') + '">');
  daysAndSquares.push('</div>');
  return daysAndSquares.join('')
}

/*
 *  Creates the overlay for users to
 *  manually navigate to a month & year.
 */
function createOverlay(instance, overlayOpen) {
  var overlayPlaceholder = instance.overlayPlaceholder;
  var overlayButton = instance.overlayButton;
  var overlayMonths = instance.overlayMonths;
  var shortMonths = overlayMonths.map(function(m, i) {
    return '<div class="qs-overlay-month" data-month-num="' + i + '">' + m + '</div>'
  }).join('');

  return [
    '<div class="qs-overlay' + (overlayOpen ? '' : ' qs-hidden') + '">',
    '<div>',
    '<input class="qs-overlay-year" placeholder="' + overlayPlaceholder + '" inputmode="numeric" />',
    '<div class="qs-close">&#10005;</div>',
    '</div>',
    '<div class="qs-overlay-month-container">' + shortMonths + '</div>',
    '<div class="qs-submit qs-disabled">' + overlayButton + '</div>',
    '</div>'
  ].join('')
}

/*
 *  Highlights the selected date - or deselects it.
 *  Calls `setCalendarInputValue`.
 */
function selectDay(target, instance, deselect) {
  var el = instance.el;
  var active = instance.calendar.querySelector('.qs-active');
  var num = target.textContent;
  var sibling = instance.sibling;

  // Prevent Datepicker from selecting (or deselecting) dates.
  if ((el.disabled || el.readOnly) && instance.respectDisabledReadOnly) return

  // Keep track of the currently selected date.
  instance.dateSelected = deselect ? undefined : new Date(instance.currentYear, instance.currentMonth, num);

  // Re-establish the active (highlighted) date.
  if (active) active.classList.remove('qs-active');
  if (!deselect) target.classList.add('qs-active');

  /*
    Populate the <input> field (or not) with a readable value
    and store the individual date values as attributes.
  */
  setCalendarInputValue(el, instance, deselect);

  /*
    Hide the calendar after a day has been selected.
    Keep it showing if deselecting.
  */
  if (!deselect) hideCal(instance);

  if (sibling) {
    // Update minDate & maxDate of both calendars.
    adjustDateranges({ instance: instance, deselect: deselect });

    /*
      http://bit.ly/2VdRx0r
      Daterange - if we're selecting a date on the "start" calendar,
      navigate the "end" calendar to the same month & year only if
      no date has already been selected on the "end" calendar.
      We don't do the opposite - the start calendar is never auto-navigated.
    */
    if (instance.first && !sibling.dateSelected) {
      sibling.currentYear = instance.currentYear;
      sibling.currentMonth = instance.currentMonth;
      sibling.currentMonthName = instance.currentMonthName;
    }

    // Re-render both calendars.
    renderCalendar(instance);
    renderCalendar(sibling);
  }


  // Call the user-provided `onSelect` callback.
  // Passing in new date so there's no chance of mutating the original object.
  // In the case of a daterange, min & max dates are automatically set.
  instance.onSelect(instance, deselect ? undefined : new Date(instance.dateSelected));
}

/*
  When selecting / deselecting a date, this resets `minDate` or `maxDate` on
  both pairs of a daterange based upon `originalMinDate` or `originalMaxDate`.
*/
function adjustDateranges(args) {
  var first = args.instance.first ? args.instance : args.instance.sibling;
  var second = first.sibling;

  if (first === args.instance) {
    if (args.deselect) {
      first.minDate = first.originalMinDate;
      second.minDate = second.originalMinDate;
    } else {
      second.minDate = first.dateSelected;
    }
  } else {
    if (args.deselect) {
      second.maxDate = second.originalMaxDate;
      first.maxDate = first.originalMaxDate;
    } else {
      first.maxDate = second.dateSelected;
    }
  }
}

/*
 *  Populates the <input> fields with a readable value
 *  and stores the individual date values as attributes.
 */
function setCalendarInputValue(el, instance, deselect) {
  if (instance.nonInput) return
  if (deselect) return el.value = ''
  if (instance.formatter !== noop) return instance.formatter(el, instance.dateSelected, instance)
  el.value = instance.dateSelected.toDateString();
}

/*
 *  2 Scenarios:
 *
 *  Updates `this.currentMonth` & `this.currentYear` based on right or left arrows.
 *  Creates a `newDate` based on the updated month & year.
 *  Calls `renderCalendar` with the updated date.
 *
 *  Changes the calendar to a different year
 *  from a users manual input on the overlay.
 *  Calls `renderCalendar` with the updated date.
 */
function changeMonthYear(classList, instance, year, overlayMonthIndex) {
  // Overlay.
  if (year || overlayMonthIndex) {
    if (year) instance.currentYear = +year;
    if (overlayMonthIndex) instance.currentMonth = +overlayMonthIndex;

  // Month change.
  } else {
    instance.currentMonth += classList.contains('qs-right') ? 1 : -1;

    // Month = 0 - 11
    if (instance.currentMonth === 12) {
      instance.currentMonth = 0;
      instance.currentYear++;
    } else if (instance.currentMonth === -1) {
      instance.currentMonth = 11;
      instance.currentYear--;
    }
  }

  instance.currentMonthName = instance.months[instance.currentMonth];

  renderCalendar(instance);
  instance.onMonthChange(instance);
}

/*
 *  Sets the `top` & `left` inline styles on the container after doing calculations.
 *  Positions datepicker relative to `instance.el` using `instance.positionedEl` to
 *  derive calculations.
 */
function calculatePosition(instance) {
  // Don't try to position the calendar if its el is <body> or <html>.
  if (instance.noPosition) return

  var top = instance.position.top;
  var right = instance.position.right;
  var centered = instance.position.centered;

  /*
    This positions the calendar `fixed` in the middle of the screen,
    so we don't need to do any calculations. We just add the class to trigger styles.
  */
  if (centered) return instance.calendarContainer.classList.add('qs-centered')

  // Get the measurements.
  var positionedElRects = instance.positionedEl.getBoundingClientRect();
  var elRects = instance.el.getBoundingClientRect();
  var containerRects = instance.calendarContainer.getBoundingClientRect();

  // Calculate the position!
  var topStyle = elRects.top - positionedElRects.top + (top ? (containerRects.height * -1) : elRects.height) + 'px';
  var leftStyle = elRects.left - positionedElRects.left + (right ? (elRects.width - containerRects.width) : 0) + 'px';

  // Set the styles.
  instance.calendarContainer.style.setProperty('top', topStyle);
  instance.calendarContainer.style.setProperty('left', leftStyle);
}

/*
 *  Checks for a valid date object.
 */
function dateCheck(date) {
  return (
    type$1(date) === '[object Date]' &&
    date.toString() !== 'Invalid Date'
  )
}

/*
 *  Takes a date or number and returns a date stripped of its time (hh:mm:ss:ms).
 *  Returns a new date object.
 *  Returns undefined for invalid date objects.
 */
function stripTime(dateOrNum) {
  // NOTE: in `createMonth`, `stripTime` is passed a number.
  /*
    JavaScript gotcha:
      +(undefined) => NaN
      +(null) => 0
  */

  // Implicit `undefined` here, later checked elsewhere.
  if (!dateCheck(dateOrNum) && (typeof dateOrNum !== 'number' || isNaN(dateOrNum))) return

  var date = new Date(+dateOrNum);
  return new Date(date.getFullYear(), date.getMonth(), date.getDate())
}

/*
 *  Hides the calendar and calls the `onHide` callback.
 */
function hideCal(instance) {
  if (instance.disabled) return

  // Only trigger `onHide` for instances that are currently showing.
  var isShowing = !instance.calendarContainer.classList.contains('qs-hidden');

  if (isShowing && !instance.alwaysShow) {
    instance.defaultView !== 'overlay' && toggleOverlay(true, instance);
    instance.calendarContainer.classList.add('qs-hidden');
    instance.onHide(instance);
  }
}

/*
 *  Shows the calendar and calls the `onShow` callback.
 */
function showCal(instance) {
  if (instance.disabled) return

  instance.calendarContainer.classList.remove('qs-hidden');
  instance.defaultView === 'overlay' && toggleOverlay(false, instance);
  calculatePosition(instance);
  instance.onShow(instance);
}

/*
 *  Show / hide the change-year overlay.
 */
function toggleOverlay(closing, instance) {
  /*
    .qs-overlay  - The dark overlay element containing the year input & submit button.
    .qs-controls - The header of the calendar containing the left / right arrows & month / year.
    .qs-squares  - The container for all the squares making up the grid of the calendar.
  */

  var calendar = instance.calendar;
  var overlay = calendar.querySelector('.qs-overlay');
  var yearInput = overlay.querySelector('.qs-overlay-year');
  var controls = calendar.querySelector('.qs-controls');
  var squaresContainer = calendar.querySelector('.qs-squares');

  if (closing) {
    overlay.classList.add('qs-hidden');
    controls.classList.remove('qs-blur');
    squaresContainer.classList.remove('qs-blur');
    yearInput.value = '';
  } else {
    overlay.classList.remove('qs-hidden');
    controls.classList.add('qs-blur');
    squaresContainer.classList.add('qs-blur');
    yearInput.focus();
  }
}

/*
 *  Calls `changeMonthYear` when a year is submitted and
 *  conditionally enables / disables the submit button.
 */
function overlayYearEntry(e, input, instance, overlayMonthIndex) {
  // Fun fact: 275760 is the largest year for a JavaScript date. #TrialAndError

  var badDate = isNaN(+new Date().setFullYear(input.value || undefined));
  var value = badDate ? null : input.value;


  // Enter has been pressed OR submit was clicked.
  if (e.which === 13 || e.keyCode === 13 || e.type === 'click') {
    if (overlayMonthIndex) {
      changeMonthYear(null, instance, value, overlayMonthIndex);
    } else if (!badDate && !input.classList.contains('qs-disabled')) {
      changeMonthYear(null, instance, value);
    }

  // Enable / disabled the submit button.
  } else if (instance.calendar.contains(input)) { // Scope to one calendar instance.
    var submit = instance.calendar.querySelector('.qs-submit');
    submit.classList[badDate ? 'add' : 'remove']('qs-disabled');
  }
}

/*
 *  Returns the explicit type of something as a string.
 */
function type$1(thing) {
  return ({}).toString.call(thing)
}

/*
 *  Hides all instances aside from the one passed in.
 */
function hideOtherPickers(instance) {
  datepickers.forEach(function(picker) { if (picker !== instance) hideCal(picker); });
}


///////////////////
// EVENT HANDLER //
///////////////////

/*
 *  A single function to handle the 4 events we track - click, focusin, keydown, & input.
 *  Only one listener is applied to the document (not window). It is removed once
 *  all datepicker instances have had their `remove` method called.
 */
function oneHandler(e) {
  /*
    Prevent double-firing when events bubble from a shadow DOM.
    This works even if we have shadow DOMs within shadow DOMs within...
  */
  if (e.__qs_shadow_dom) return

  var keyCode = e.which || e.keyCode;
  var type = e.type;
  var target = e.target;
  var classList = target.classList;
  var instance = datepickers.filter(function(picker) {
    return picker.calendar.contains(target) || picker.el === target
  })[0];
  var onCal = instance && instance.calendar.contains(target);


  // Ignore event handling for mobile devices when disableMobile is true.
  if (instance && instance.isMobile && instance.disableMobile) return


  ////////////
  // EVENTS //
  ////////////

  if (type === 'click') {
    // Anywhere other than the calendar - close the calendar.
    if (!instance) return datepickers.forEach(hideCal)

    // Do nothing for disabled calendars.
    if (instance.disabled) return

    var calendar = instance.calendar;
    var calendarContainer = instance.calendarContainer;
    var disableYearOverlay = instance.disableYearOverlay;
    var nonInput = instance.nonInput;
    var input = calendar.querySelector('.qs-overlay-year');
    var overlayClosed = !!calendar.querySelector('.qs-hidden');
    var monthYearClicked = calendar.querySelector('.qs-month-year').contains(target);
    var newMonthIndex = target.dataset.monthNum;

    // Calendar's el is 'body'.
    // Anything but the calendar was clicked.
    if (instance.noPosition && !onCal) {
      // Show / hide a calendar whose el is html or body.
      var calendarClosed = calendarContainer.classList.contains('qs-hidden')
      ;(calendarClosed ? showCal : hideCal)(instance);

    // Clicking the arrow buttons - change the calendar month.
    } else if (classList.contains('qs-arrow')) {
      changeMonthYear(classList, instance);

    // Clicking the month/year - open the overlay.
    // Clicking the X on the overlay - close the overlay.
    } else if (monthYearClicked || classList.contains('qs-close')) {
      if (!disableYearOverlay) toggleOverlay(!overlayClosed, instance);

    // Clicking a month in the overlay - the <span> inside might have been clicked.
    } else if (newMonthIndex) {
      overlayYearEntry(e, input, instance, newMonthIndex);

    // Clicking a disabled square or disabled overlay submit button.
    } else if (classList.contains('qs-disabled')) {
      return

    // Clicking a number square - process whether to select that day or not.
    } else if (classList.contains('qs-num')) {
      var num = target.textContent;
      var monthDirection = +target.dataset.direction; // -1, 0, or 1.
      var dateInQuestion = new Date(instance.currentYear, instance.currentMonth + monthDirection, num);

      /*
        If the user clicked on a date within the previous or next month,
        reset the year, month, and month name on the instance so that
        the calendar will render the correct month.
      */
      if (monthDirection) {
        instance.currentYear = dateInQuestion.getFullYear();
        instance.currentMonth = dateInQuestion.getMonth();
        instance.currentMonthName = months[instance.currentMonth];

        // Re-render calendar to navigate to the new month.
        renderCalendar(instance);

        /*
          Since re-rendering the calendar re-creates all the html,
          the original target is gone. Reset it so that `selectDay`
          can highlight (or unhighlight) the correct DOM element.
        */
        var newDays = instance.calendar.querySelectorAll('[data-direction="0"]');
        var newTarget;
        var idx = 0;

        while (!newTarget) {
          var newDay = newDays[idx];
          if (newDay.textContent === num) newTarget = newDay;
          idx++;
        }

        target = newTarget;
      }

      if (+dateInQuestion === +instance.dateSelected) {
        selectDay(target, instance, true);
      } else if (!target.classList.contains('qs-disabled')) {
        selectDay(target, instance);
      }

      return

    // Clicking the submit button in the overlay.
    } else if (classList.contains('qs-submit')) {
      overlayYearEntry(e, input, instance);
    // Clicking the calendar's el for non-input's should show it.
    } else if (nonInput && target === instance.el) {
      showCal(instance);
      hideOtherPickers(instance);
    }

  /*
    Only pay attention to `focusin` events if the calendar's el is an <input>.
    We use the `focusin` event because it bubbles - `focus` does not bubble.
  */
  } else if (type === 'focusin' && instance) {
    // Show this intance.
    showCal(instance);

    // Hide all other instances.
    hideOtherPickers(instance);
  } else if (type === 'keydown' && keyCode === 9 && instance) {
    // Hide this instance on tab out.
    hideCal(instance);
  } else if (type === 'keydown' && instance && !instance.disabled) {
    var overlay = instance.calendar.querySelector('.qs-overlay');
    var overlayShowing = !overlay.classList.contains('qs-hidden');

    // Pressing enter while the overlay is open.
    if (keyCode === 13 && overlayShowing && onCal) {
      overlayYearEntry(e, target, instance);

    // ESC key pressed.
    } else if (keyCode === 27 && overlayShowing && onCal) {
      toggleOverlay(true, instance);
    }
  } else if (type === 'input') {
    // Avoid applying these restrictions to other inputs on the page.
    if (!instance || !instance.calendar.contains(target)) return

    // Only allow numbers & a max length of 4 characters.
    var submitButton = instance.calendar.querySelector('.qs-submit');
    var newValue = target.value
      .split('')
      // Prevent leading 0's.
      .reduce(function(acc, char) {
        if (!acc && char === '0') return ''
        return acc + (char.match(/[0-9]/) ? char : '')
      }, '')
      .slice(0, 4);

    // Set the new value of the input and conditionally enable / disable the submit button.
    target.value = newValue;
    submitButton.classList[newValue.length === 4 ? 'remove' : 'add']('qs-disabled');
  }
}

/*
 *
 *  In the case of a calendar being placed in a shadow DOM (web components), we need
 *  to keep the `oneHandler` listener on the document while having another listener
 *  on the shadow DOM. We set a property on the event object to indicate the event
 *  originated from a shadow DOM. This will ensure that once the event bubbles up to
 * `oneHandler` on the document, we know to ignore it.
 */
function shadowDomHandler(e) {
  oneHandler(e);
  e.__qs_shadow_dom = true;
}

/*
 *  Removes the event listeners on either the document or the shadow DOM.
 */
function removeEvents$1(node, listener) {
  events.forEach(function(event) { node.removeEventListener(event, listener); });
}


//////////////////////
// INSTANCE METHODS //
//////////////////////

/*
 *  Programmatically show the calendar.
 */
function show$1() {
  showCal(this);
}

/*
 *  Programmatically hide the calendar.
 */
function hide() {
  hideCal(this);
}

/*
 *  Programmatically sets the date on an instance
 *  and updates all associated properties.
 *  Will re-render the calendar if it is showing.
 */
function setDate(newDate, changeCalendar) {
  var date = stripTime(newDate); // Remove the time, creating a fresh date object.
  var currentYear = this.currentYear;
  var currentMonth = this.currentMonth;
  var sibling = this.sibling;

  // Removing the selected date.
  if (newDate == null) {
    // Remove the date.
    this.dateSelected = undefined;

    // Clear the associated input field.
    setCalendarInputValue(this.el, this, true);

    // Daterange processing!
    if (sibling) {
      adjustDateranges({ instance: this, deselect: true });
      renderCalendar(sibling);
    }

    // Re-render the calendar to clear the selected date.
    renderCalendar(this);

    // Return the instance to enable chaining methods.
    return this

  // Date isn't undefined or null but still falsey.
  } else if (!dateCheck(newDate)) {
    throw new Error('`setDate` needs a JavaScript Date object.')
  }


  /*
   * Anything below this line is for setting a new date.
   */


  // Check if the date is selectable.
  if (
    this.disabledDates[+date] ||
    date < this.minDate ||
    date > this.maxDate
  ) throw new Error("You can't manually set a date that's disabled.")

  // Keep track of the new date.
  this.dateSelected = date;

  /*
    These properties indicate to the instance where the calendar is currently at.
    Only change them if we're also navigating to the new date in the UI.
  */
  if (changeCalendar) {
    this.currentYear = date.getFullYear();
    this.currentMonth = date.getMonth();
    this.currentMonthName = this.months[date.getMonth()];
  }

  setCalendarInputValue(this.el, this);

  if (sibling) {
    // Adjust other date properties and re-render the sibling to show the same month as the other.
    adjustDateranges({ instance: this });

    // Re-render the sibling to reflect possible disabled dates due to a selection.
    renderCalendar(sibling);
  }

  var isSameMonth = currentYear === date.getFullYear() && currentMonth === date.getMonth();
  if (isSameMonth || changeCalendar) {
    renderCalendar(this, date);

  /*
    If we already have a date selected on the current month of the calendar
    and we're using `setDate` to select a date for a different month,
    we'll want to re-render the current calendar to remove the selected date
    AND keep the current month visible without switching.
    Effectively, we just want to de-select the date on the current month.
  */
  } else if (!isSameMonth) {
    renderCalendar(this, new Date(currentYear, currentMonth, 1));
  }

  return this
}

/*
 *  Programmatically changes the minimum selectable date.
 */
function setMin(date) {
  return changeMinOrMax(this, date, true)
}

/*
 *  Programmatically changes the maximum selectable date.
 */
function setMax(date) {
  return changeMinOrMax(this, date)
}

/*
 *  Called by `setMin` and `setMax`.
 */
function changeMinOrMax(instance, date, isMin) {
  var dateSelected = instance.dateSelected;
  var first = instance.first;
  var sibling = instance.sibling;
  var minDate = instance.minDate;
  var maxDate = instance.maxDate;
  var newDate = stripTime(date);
  var type = isMin ? 'Min' : 'Max';

  function origProp() { return 'original' + type + 'Date' }
  function prop() { return type.toLowerCase() + 'Date' }
  function method() { return 'set' + type }
  function throwOutOfRangeError() { throw new Error('Out-of-range date passed to ' + method()) }

  // Removing min / max.
  if (date == null) {
    /*
      Scenarios:
        * minDate
          * 1st && 1st selected
          * 2nd && 1st selected
        * maxDate
          * 2nd && 2nd selected
          * 1st && 2nd selected
    */

    // When removing a date, always remove the original min/max date.
    instance[origProp()] = undefined;

    // Daterange processing!
    if (sibling) {
      sibling[origProp()] = undefined; // Remove the original min/max date.

      // Removing the min.
      if (isMin) {
        if ((first && !dateSelected) || (!first && !sibling.dateSelected)) {
          instance.minDate = undefined;
          sibling.minDate = undefined;
        }

      // Removing the max.
      } else if ((first && !sibling.dateSelected) || (!first && !dateSelected)) {
        instance.maxDate = undefined;
        sibling.maxDate = undefined;
      }

    // Regular instances.
    } else {
      instance[prop()] = undefined;
    }

  // Throw an error for invalid dates.
  } else if (!dateCheck(date)) {
    throw new Error('Invalid date passed to ' + method())

  // Setting min / max.
  } else if (sibling) {
    /*
      Acceptable ranges for setting minDate or maxDate:
        * Daterange
          * minDate
            * -∞ -> (dateSelected || maxDate)
          * maxDate
            * (dateSelected || minDate) -> ∞
        * Regular
          * minDate
            * -∞ -> (dateSeleted || maxDate)
          * maxDate
            * (dateSelected || minDate) -> ∞
    */

    // Check for dates out of range for daterange pairs.
    if (
      // 1st instance checks.
      (first && isMin && newDate > (dateSelected || maxDate)) || // setMin
      (first && !isMin && newDate < (sibling.dateSelected || minDate)) || // setMax

      // 2nd instance checks.
      (!first && isMin && newDate > (sibling.dateSelected || maxDate)) || // setMin
      (!first && !isMin && newDate < (dateSelected || minDate)) // setMax
    ) throwOutOfRangeError();

    instance[origProp()] = newDate;
    sibling[origProp()] = newDate;

    if (
      //setMin
      (isMin && ((first && !dateSelected) || (!first && !sibling.dateSelected))) ||

      //setMax
      (!isMin && ((first && !sibling.dateSelected) || (!first && !dateSelected)))
    ) {
      instance[prop()] = newDate;
      sibling[prop()] = newDate;
    }

  // Individual instance.
  } else {
    // Check for dates our of range for single instances.
    if (
      (isMin && newDate > (dateSelected || maxDate)) || // minDate
      (!isMin && newDate < (dateSelected || minDate)) // maxDate
    ) throwOutOfRangeError();

    instance[prop()] = newDate;
  }

  if (sibling) renderCalendar(sibling);
  renderCalendar(instance);

  return instance
}

/**
 *
 *  Returns an object with start & end date selections.
 *  Available onCal daterange pairs only.
 */
function getRange() {
  var first = this.first ? this : this.sibling;
  var second = first.sibling;

  return {
    start: first.dateSelected,
    end: second.dateSelected
  }
}

/*
 *  Removes the current instance from the array of instances.
 *  Removes the instance calendar from the DOM.
 *  Removes the event listeners if this is the last instance.
 */
function remove() {
  var shadowDom = this.shadowDom;
  var positionedEl = this.positionedEl;
  var calendarContainer = this.calendarContainer;
  var sibling = this.sibling;
  var _this = this;

  /*
    Remove styling done to `positionedEl` and reset it back to its original
    only if there are no other instances with the same `positionedEl`.
  */
  if (this.inlinePosition) {
    var positionedElStillInUse = datepickers.some(function(picker) { return picker !== _this && picker.positionedEl === positionedEl });
    if (!positionedElStillInUse) positionedEl.style.setProperty('position', null);
  }

  // Remove the calendar from the DOM.
  calendarContainer.remove();

  // Remove this instance from the list.
  datepickers = datepickers.filter(function(picker) { return picker !== _this });

  // Remove siblings references.
  if (sibling) delete sibling.sibling;

  // If this was the last datepicker in the list, remove the event handlers.
  if (!datepickers.length) removeEvents$1(document, oneHandler);

  // Remove the shadow DOM listener if this was the last picker in that shadow DOM.
  var shadowDomStillInUse = datepickers.some(function(picker) { return picker.shadowDom === shadowDom });
  if (shadowDom && !shadowDomStillInUse) removeEvents$1(shadowDom, shadowDomHandler);

  // Empty this instance of all properties.
  for (var prop in this) delete this[prop];

  // If this was the last datepicker in the list, remove the event handlers.
  if (!datepickers.length) {
    events.forEach(function(event) { document.removeEventListener(event, oneHandler); });
  }
}

/*
 *  Navigates the calendar to a given year and month
 *  (parsed from the supplied date) without affecting any selections.
 */
function navigate(dateOrNum, triggerCb) {
  var date = new Date(dateOrNum);
  if (!dateCheck(date)) throw new Error('Invalid date passed to `navigate`')

  this.currentYear = date.getFullYear();
  this.currentMonth = date.getMonth();
  renderCalendar(this);

  if (triggerCb) {
    this.onMonthChange(this);
  }
}

/*
 *  Programmatically toggles the overlay.
 *  Only works when the calendar is open.
 */
function instanceToggleOverlay() {
  var calendarIsShowing = !this.calendarContainer.classList.contains('qs-hidden');
  var overlayIsShowing = !this.calendarContainer.querySelector('.qs-overlay').classList.contains('qs-hidden');

  calendarIsShowing && toggleOverlay(overlayIsShowing, this);
}

const addressFields = ['address1', 'address2', 'city', 'firstName',
    'lastName', 'postalCode', 'phoneNumber', 'state', 'country'];

/*
    This is meant to be a form reusable in a couple of different contexts, specifically the toggle widget in listings etc.,
    and the create and settings dialogs in the account management screens. Those have significantly different JSON data feeds and
    UI flows for their use cases. We want to simply isolate the form UI itself with validation and data collection.
 */
class GiftlistSettingsForm extends s {
    constructor() {
        super();

        this.onSubmit = (data) => {};
        this.isPublic = false;
        this.showPurchased = true;
        this.showPurchasedPublic = true;
        this.closeModal = (e) => {};
        this.headerText = '';
        this.displayName = '';
        this.giftlistType = GIFTLIST_TYPE_GIFTLIST;
        this.giftlist = {};
        this.selectedSuggestion = '';
        this.useShippingAddress = false;
        this.shippingAddressType = 'anonymous';
        this.shippingAddressAnonymous = true;
        this.shippingAddressPublic = false;
        this.isRegistry = false;
        this.store = {};
        this.featureFlags = {};
        this.formExceptions = [];
        this.eventDatePicker = null;
        this.nonFieldExceptions = [];

        this.handleSubmit = this.handleSubmit.bind(this);
        this.togglePublic = this.togglePublic.bind(this);
        this.toggleRegistry = this.toggleRegistry.bind(this);
        this.handleShowPurchased = this.handleShowPurchased.bind(this);
        this.handleShowPurchasedPublic = this.handleShowPurchasedPublic.bind(this);
        this.toggleUseShippingAddress = this.toggleUseShippingAddress.bind(this);
        this.autocompleteShouldQuery = this.autocompleteShouldQuery.bind(this);
        this.autocompleteFetchResults = this.autocompleteFetchResults.bind(this);
        this.autocompleteSelectResult = this.autocompleteSelectResult.bind(this);
        this.autocompleteClearState = this.autocompleteClearState.bind(this);
        this.handleClosed = this.handleClosed.bind(this);
        this.handleOpenEventDatePicker = this.handleOpenEventDatePicker.bind(this);
        this.toggleAddressVisibility = this.toggleAddressVisibility.bind(this);

        this.addEventListener(EVENT_NAME_POPUP_CLOSED, this.handleClosed);
    }

    static properties = {
        onSubmit: {type: Function, state: true,},
        closeModal: {type: Function, state: true,},
        headerText: {type: String, state: true,},
        displayName: {type: String, state: true,},
        giftlistType: {type: String, state: true,},
        isPublic: {type: Boolean, state: true,},
        showPurchased: {type: Boolean, state: true,},
        showPurchasedPublic: {type: Boolean, state: true,},
        useShippingAddress: {type: Boolean, state: true,},
        shippingAddressType: {type: String, state: true,},
        shippingAddressAnonymous: {type: Boolean, state: true,},
        shippingAddressPublic: {type: Boolean, state: true,},
        isRegistry: {type: Boolean, state: true,},
        selectedSuggestion: {type: String, state: true,},
        giftlist: {type: Object, state: true,},
        store: {type: Object, state: true,},
        featureFlags: {type: Object, state: true,},
        formExceptions: {type: Array, state: true,},
        nonFieldExceptions: {type: Array, state: true,},
    };

    static styles = [
        giftlistStyles,
        jsDatepickerStyles,
        i$3`.full-width{width:100%}.val-input{width:90%}.main-form{width:85%;background-color:#f2f2f2;padding:1em}.check-field{display:flex;align-items:flex-start;gap:.5em;font-size:.9rem}.footnote{font-size:.85rem}.row.footnote{white-space:normal}.required label{font-weight:600}.address-form{display:grid;grid-template-columns:repeat(1,1fr);grid-gap:.6rem}.f-address1,.f-address2,.f-country,.f-list-name,.f-use-address{grid-column:span 12}.f-event-date,.f-event-type,.f-first,.f-last,.f-phone{grid-column:span 12}.address-no-state .f-state{display:none}.f-city,.f-postcode,.f-state{grid-column:span 12}.address-no-state .f-city,.address-no-state .f-postcode{grid-column:span 12}.address-mx .f-postcode{grid-column-start:1;grid-column-end:13;grid-row-start:6}.address-mx .f-city{grid-column-start:1;grid-column-end:13;grid-row-start:7}.address-mx .f-state{grid-column-start:1;grid-column-end:13;grid-row-start:8}@media only screen and (min-width:768px){.address-form{display:grid;grid-template-columns:repeat(12,1fr)}.f-address1,.f-address2,.f-country,.f-list-name,.f-use-address{grid-column:span 12}.f-event-date,.f-event-type,.f-first,.f-last{grid-column:span 6}.f-city,.f-phone,.f-postcode,.f-state{grid-column:span 4}.ship-addr-phone{max-width:11em}.address-no-state .f-city,.address-no-state .f-postcode{grid-column:span 6}.address-mx .f-postcode{grid-column-start:1;grid-column-end:5;grid-row-start:5}.address-mx .f-city{grid-column-start:5;grid-column-end:9;grid-row-start:5}.address-mx .f-state{grid-column-start:9;grid-column-end:13;grid-row-start:5}}.address-form-slider{max-height:1200px;transition:max-height .5s}.registry-slider{max-height:600px;transition:max-height .5s;width:100%}.collapsed{max-height:0}.address-form input[type=tel],.address-form input[type=text]{width:90%}.date-container{position:relative}.date-container .calendar-logo{position:absolute;right:15px;top:9px;height:24px;width:24px}.error-box{display:flex;width:100%;justify-content:center;color:red}.addr-display-row{display:grid;grid-template-columns:auto 1fr 1fr;padding:.5rem}`
    ];

    // This is a little cumbersome and weird. We don't have a way at present to make popups target anything other than
    // their immediate slotted children for the close event, and that would get pretty messy across shadow DOM boundaries anyway.
    // Instead those children will need to turn around and fire another close event on *us*, and we need to be
    // sure to stop it to prevent an infinite event loop.
    handleClosed(e) {
        if (e) {
            e.stopPropagation();
        }
        this.resetForm();
    }

    /*
        We need a custom method here to undo any unsaved form manipulation. We can't use form.reset() because
        the default values are not correct. We aren't setting attributes because the backing values might
        not even be present on first render, e.g. before we receive a giftlist property.
     */
    resetForm() {
        let form = this.shadowRoot.getElementById('list-form');
        if (form) {
            if (this.giftlist && this.giftlist.id) {
                this.resetFormElement(form, 'isPrivate', !this.giftlist.isPublic);
                this.resetFormElement(form, 'isPublic', this.giftlist.isPublic);
                this.resetFormElement(form, 'listName', this.giftlist.displayName);
                this.resetFormElement(form, 'showPurchased', this.giftlist.showPurchased);
                this.resetFormElement(form, 'showPurchasedPublic', this.giftlist.showPurchasedPublic);
                this.useShippingAddress = !!this.giftlist.shippingAddress;
                this.resetFormElement(form, 'useShippingAddress', !!this.giftlist.shippingAddress);
                this.resetFormElement(form, 'shippingAddressAnonymous', this.giftlist.shippingAddressType === 'anonymous');
                this.resetFormElement(form, 'shippingAddressPublic', this.giftlist.shippingAddressType === 'public');
                this.isRegistry = this.giftlist.giftlistType === GIFTLIST_TYPE_REGISTRY;
                this.resetFormElement(form, 'isRegistry', this.giftlist.giftlistType === GIFTLIST_TYPE_REGISTRY);
                this.resetFormElement(form, 'eventType', this.giftlist.eventType);
                this.setEventDateFromTimestamp();
                this.resetFormElement(form, 'eventFirstName', this.giftlist.eventFirstName);
                this.resetFormElement(form, 'eventLastName', this.giftlist.eventLastName);
            }
            this.resetAddressForm(form);
        }
        this.formExceptions = [];
        this.nonFieldExceptions = [];
    }

    resetFormElement(form, name, value) {
        let field = form.elements.namedItem(name);
        if (field) {
            let fieldType = field.type;
            if (fieldType === 'checkbox') {
                //console.log('reset checkbox ' + name + ' to ' + value);
                field.checked = value;
            } else {
                //console.log('reset ' + name + ' to ' + value);
                field.value = value;
            }
        }
    }

    resetAddressForm(form) {
        addressFields.forEach((name) => {
            let field = form.elements.namedItem(name);
            if (field) {
                field.value = this.giftlist.shippingAddress ?
                    this.giftlist.shippingAddress[name] || '' : '';
            }
        });
    }

    willUpdate(changedProperties) {
        if (changedProperties.has('giftlist') && this.giftlist.displayName) {
            this.displayName = this.giftlist.displayName;
            this.giftlistType = this.giftlist.giftlistType;
            this.isPublic = this.giftlist.isPublic;
            this.showPurchased = this.giftlist.showPurchased;
            this.showPurchasedPublic = this.giftlist.showPurchasedPublic;
            this.useShippingAddress = this.giftlist.shippingAddress && this.giftlist.shippingAddress.address1;
            this.shippingAddressType = this.giftlist.shippingAddressType;
            this.shippingAddressAnonymous = (this.giftlist.shippingAddressType === 'anonymous');
            this.shippingAddressPublic = (this.giftlist.shippingAddressType === 'public');
            this.isRegistry = this.giftlist.giftlistType === GIFTLIST_TYPE_REGISTRY;
            this.resetForm();
        }
        if (changedProperties.has('formExceptions')) {
            let vForm = this.shadowRoot.querySelector('checkout-validated-form');
            if (this.formExceptions.length) {
                let fieldExceptions = [], otherExceptions = [];
                for (let i = 0; i < this.formExceptions.length; i++) {
                    let e = this.formExceptions[i];
                    if (e.key || e.group) {
                        fieldExceptions.push(e);
                    } else {
                        otherExceptions.push(e);
                    }
                }
                if (vForm && fieldExceptions.length) {
                    vForm.setExternalErrors(fieldExceptions);
                }
                if (otherExceptions.length) {
                    this.nonFieldExceptions = otherExceptions;
                }
            } else {
                if (vForm) {
                    vForm.clearExternalErrors();
                }
            }
        }
    }

    setEventDateFromTimestamp() {
        if (this.eventDatePicker && this.giftlist && this.giftlist.eventDateTimestamp) {
            let eventDate = new Date();
            eventDate.setTime(this.giftlist.eventDateTimestamp);
            this.eventDatePicker.setDate(eventDate);
        }
    }

    updated(changedProperties) {
        if (changedProperties.has('isRegistry')) {
            if (this.isRegistry) {
                let dateInput = this.shadowRoot.getElementById('eventDate');
                if (dateInput) {
                    this.eventDatePicker = datepicker(dateInput, {
                        formatter: (input, date, instance) => {
                            input.value = date.toLocaleDateString();
                        }
                    });
                    this.setEventDateFromTimestamp();
                }
            } else {
                if (this.eventDatePicker) {
                    this.eventDatePicker.remove();
                    this.eventDatePicker = null;
                }
            }
        }
    }

    // Functions copied from shipping address step. Ideally we should DRY this up with a mixin or something
    autocompleteShouldQuery() {
        return !!(this.store.smartyAutocompleteEnabled
            && this.store.smartyAutocompleteKey
            && this.store.smartyAutocompleteKey.length > 0
            && this.shadowRoot.getElementById('country').value === 'US');
    }

    get _addressAutocomplete() {
        return this.renderRoot?.querySelector('#addressAutocomplete') ?? null;
    }

    autocompleteFetchResults() {
        return new Promise((resolve, reject) => {
            const autocomplete = this._addressAutocomplete;
            if (autocomplete) {
                let opts = {
                    method: 'GET',
                    mode: 'cors',
                };

                const url = 'https://us-autocomplete-pro.api.smartystreets.com/lookup?search='
                    + encodeURIComponent(autocomplete.input.value)
                    + '&key='
                    + this.store.smartyAutocompleteKey
                    + '&selected='
                    + encodeURIComponent(this.selectedSuggestion)
                ;
                return fetch(url, opts)
                    .then((response) => response.json())
                    .then((json) => resolve(json.suggestions));
            }

            return reject('No #addressAutocomplete');
        });
    }

    // leaving this one returning a function due to weirdness with params. plus it needs no this reference.
    autocompleteFormatOption(omit) {
        return (option) => {
            let whiteSpace = "";
            let secondary = option.secondary;
            if (option.secondary) {
                if (option.entries > 1) {
                    secondary += " (" + option.entries + (omit ? "" : " entries") +")";
                }
                whiteSpace = " ";
            }
            return option.street_line + whiteSpace + secondary + " " + option.city + ", " + option.state + " " + option.zipcode;
        }
    }

    autocompleteSelectResult(option) {
        if (option.entries > 1) {
            this.selectedSuggestion = this.autocompleteFormatOption(true)(option);
            return false;
        } else {
            this.shadowRoot.getElementById('address1').value = option.street_line;
            this.shadowRoot.getElementById('address2').value = option.secondary;
            this.shadowRoot.getElementById('city').value = option.city;
            this.shadowRoot.getElementById('state').value = option.state;
            this.shadowRoot.getElementById('postalCode').value = option.zipcode;
            return true;
        }
    }

    autocompleteClearState() {
        this.selectedSuggestion = '';
    }

    // The flow here is - handleSubmit is specific to this component. It calls collectFormData to gather the post data.
    // onSubmit is a prop that takes the data and does context-specific processing.
    async handleSubmit(e) {
        if (e) {
            e.stopPropagation();
        }
        let formData = this.collectFormData();
        return this.onSubmit(formData);
    }

    collectFormData() {
        let formData = {};
        let form = this.shadowRoot.getElementById('list-form');
        if (form && form.elements) {
            // We will only return raw data here. Calling code can worry about encoding since
            // it may be collating data from multiple sources and would not want to multi-encode
            formData.displayName = form.elements.namedItem('listName').value;
            formData.isPublic = this.isPublic;
            formData.showPurchased = this.showPurchased;
            formData.showPurchasedPublic = this.showPurchasedPublic;
            if (this.useShippingAddress) {
                addressFields.forEach((fieldName) => {
                    formData[fieldName] = this.getFormValue(form, fieldName);
                });
            } else {
                formData.removeShippingAddress = true;
            }
            formData.shippingAddressType = this.shippingAddressAnonymous ? 'anonymous' : 'public';
            // giftlistType
            if (this.giftlist && this.giftlist.giftlistType === GIFTLIST_TYPE_WISHLIST) ; else {
                if (this.featureFlags['gift-list-type'] === FLAG_GIFTLIST_TYPE_REGISTRIES) {
                    formData.giftlistType = this.isRegistry ? GIFTLIST_TYPE_REGISTRY : GIFTLIST_TYPE_GIFTLIST;
                    if (this.isRegistry) {
                        formData.eventType = form.elements.namedItem('eventType').value;
                        formData.eventFirstName = form.elements.namedItem('eventFirstName').value || '';
                        formData.eventLastName = form.elements.namedItem('eventLastName').value || '';
                        if (this.eventDatePicker) {
                            if (this.eventDatePicker.dateSelected) {
                                formData.eventDateTimestamp = this.eventDatePicker.dateSelected.getTime();
                            } else {
                                console.log('no date selected!');
                            }
                        } else {
                            console.log('no date picker!');
                        }
                    } else {
                        formData.eventType = '';
                        formData.eventDate = '';
                        formData.eventFirstName = '';
                        formData.eventLastName = '';
                        formData.removeRegistry = true;
                    }
                } else {
                    formData.giftlistType = GIFTLIST_TYPE_GIFTLIST;
                }
            }
        }
        return formData;
    }

    getFormValue(form, fieldName) {
        let field = form.elements.namedItem(fieldName);
        if (field) {
            return field.value;
        }
        return undefined;
    }

    togglePublic(e) {
        this.isPublic = !this.isPublic;
    }

    toggleAddressVisibility() {
        this.shippingAddressAnonymous = !this.shippingAddressAnonymous;
        this.shippingAddressPublic = !this.shippingAddressPublic;
    }

    toggleRegistry() {
        this.isRegistry = !this.isRegistry;
    }

    async toggleUseShippingAddress() {
        this.useShippingAddress = !this.useShippingAddress;
        await this.updateComplete;
        this.shadowRoot.querySelectorAll('checkout-validated-form').forEach((el) => {
           el.dispatchEvent(new CustomEvent("validatedInputAdded"));
        });
    }

    handleShowPurchased(e) {
        this.showPurchased = e.target.checked;
    }

    handleOpenEventDatePicker(e) {
        e.stopPropagation();
        if (this.eventDatePicker) {
            this.eventDatePicker.show();
        }
    }

    handleShowPurchasedPublic(e) {
        this.showPurchasedPublic = e.target.checked;
    }

    renderAddressDisplayRow() {
        if (!this.useShippingAddress) {
            return T;
        }
        return x`<div class="addr-display-row"><div class="required"><label>Address Display:</label></div><div class="check-field" style="justify-self:center"><input type="checkbox" @click="${this.toggleAddressVisibility}" name="shippingAddressAnonymous" value="true" id="addr-anon-check" .checked="${this.shippingAddressAnonymous}"> <label for="addr-anon-check">Hidden</label></div><div class="check-field"><input type="checkbox" @click="${this.toggleAddressVisibility}" name="shippingAddressPublic" value="true" id="addr-pub-check" .checked="${this.shippingAddressPublic}"> <label for="addr-pub-check">Visible</label></div></div>`;
    }

    renderAddressFormContents() {
        if (!this.useShippingAddress) {
            return T;
        }
        let addressDefaults = this.giftlist.shippingAddress || {};
        return x`<div class="f-field f-country required"><checkout-postal-country self-load="true" .selectedCountry="${addressDefaults.country ?? ''}" .selectedState="${addressDefaults.state ?? ''}" context="shipping"><checkout-validated-input key="country" field-name="country" group="address" verb="select" validation="none"><label for="country">Country:</label> <select id="country" name="country"></select></checkout-validated-input></checkout-postal-country></div><div class="f-field f-first required"><checkout-validated-input key="firstName" field-name="first name" group="name" validation="required"><label for="firstName">First Name:</label> <input type="text" id="firstName" name="firstName" value="${addressDefaults.firstName ?? ''}" maxlength="40" autocapitalize="words" autocomplete="on"></checkout-validated-input></div><div class="f-field f-last required"><checkout-validated-input key="lastName" field-name="last name" group="name" validation="required"><label for="lastName">Last Name:</label> <input type="text" id="lastName" name="lastName" value="${addressDefaults.lastName ?? ''}" maxlength="65" autocapitalize="words" autocomplete="on"></checkout-validated-input></div><div class="f-field f-address1 required"><eline-autocomplete id="addressAutocomplete" min-length="3" .shouldQuery="${this.autocompleteShouldQuery}" .fetchResults="${this.autocompleteFetchResults}" .formatOption="${this.autocompleteFormatOption()}" .selectResult="${this.autocompleteSelectResult}" .clearParentState="${this.autocompleteClearState}"><checkout-validated-input key="address1" field-name="address" group="address" default-for-group="true" validation="required"><label for="address1">Address 1:</label> <input type="text" id="address1" name="address1" maxlength="30" autocapitalize="words" autocomplete="on" value="${addressDefaults.address1 ?? ''}" placeholder="Street Address or PO Box"></checkout-validated-input></eline-autocomplete></div><div class="f-field f-address2"><checkout-validated-input key="address2" field-name="address" group="address" validation="none"><label for="address2">Address 2 (Optional):</label> <input type="text" id="address2" name="address2" maxlength="30" autocapitalize="words" autocomplete="on" value="${addressDefaults.address2 ?? ''}" placeholder="Apt, suite, building, unit, floor, etc."></checkout-validated-input></div><div class="f-field f-city required"><checkout-validated-input key="city" field-name="city" group="address" validation="required"><label for="city">City:</label> <input type="text" id="city" name="city" maxlength="30" value="${addressDefaults.city ?? ''}" autocapitalize="words" autocomplete="on"></checkout-validated-input></div><div class="f-field f-state"><checkout-validated-input key="state" field-name="state" group="address" validation="required"><label for="state">State/Province/Region:</label> <select id="state" name="state"></select></checkout-validated-input></div><div class="f-field f-postcode required"><checkout-validated-input key="postalCode" field-name="zip/postal code" group="address" validation="required"><label for="postalCode"><dsp:valueof param="postalCodeLabel">Zip/Postal Code</dsp:valueof></label> <input type="text" id="postalCode" name="postalCode" value="${addressDefaults.postalCode ?? ''}" maxlength="10" autocapitalize="none" autocomplete="on"></checkout-validated-input></div><div class="f-field f-phone required"><checkout-validated-input key="phoneNumber" field-name="phone number" validation="required"><label for="phoneNumber">Phone:</label> <input type="tel" id="phoneNumber" name="phoneNumber" value="${addressDefaults.phoneNumber ?? ''}" maxlength="30" class="ship-addr-phone" autocapitalize="none" autocomplete="on"></checkout-validated-input></div>`;
    }

    renderAddressFields() {
        let addrFormSliderClasses = {"collapsed": !this.useShippingAddress};

        return !this.featureFlags["gift-list-addresses"] ? T :
            x`<div class="check-field full-width"><input type="checkbox" @click="${this.toggleUseShippingAddress}" .checked="${this.useShippingAddress}" name="useShippingAddress" id="useShippingAddress"> <label for="useShippingAddress">Add a shipping address<div class="footnote">Checking this box will allow you to enter a shipping address that purchasers can ship items from your list to. You will have the option of hiding the actual address from purchasers, or displaying it.</div></label></div><div class="address-form-slider ${e(addrFormSliderClasses)}">${this.renderAddressDisplayRow()}<div class="address-form">${this.renderAddressFormContents()}</div></div>`;
    }

    renderRegistryFormContents() {
        if (!this.isRegistry) {
            return T;
        }
        let eventTypeOptions = [];
        for (let i = 0; i < EVENT_TYPES_ORDERED.length; i++) {
            let eType = EVENT_TYPES_ORDERED[i];
            eventTypeOptions.push(x`<option ?selected="${this.giftlist && this.giftlist.eventType === eType.value}" value="${eType.value}">${eType.displayName}</option>`);
        }

        return x`<div class="f-field f-event-type required"><label for="eventType">Event Type</label> <select name="eventType" id="eventType">${eventTypeOptions}</select></div><div class="f-field f-event-date"><label for="eventDate">Event Date</label><div class="date-container"><input name="eventDate" id="eventDate" type="text"><div @click="${this.handleOpenEventDatePicker}" class="calendar-logo"><svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Z"/></svg></div></div></div><div class="f-field f-first"><label for="eventFirstName">First Name (optional)</label> <input name="eventFirstName" id="eventFirstName" type="text" value="${this.giftlist.eventFirstName || ''}"></div><div class="f-field f-last"><label for="eventLastName">Last Name (optional)</label> <input name="eventLastName" id="eventLastName" type="text" value="${this.giftlist.eventLastName || ''}"></div>`;
    }

    renderRegistryFields() {
        // If the store doesn't support registry, return nothing
        if (!(this.featureFlags["gift-list-type"] === FLAG_GIFTLIST_TYPE_REGISTRIES)) {
            return T;
        }
        // If this is the default wishlist, render nothing. These cannot change type
        if (this.giftlist && this.giftlist.giftlistType === GIFTLIST_TYPE_WISHLIST) {
            return T;
        }
        let sliderClasses = {"collapsed": !this.isRegistry};
        return x`<div class="check-field full-width"><input type="checkbox" @click="${this.toggleRegistry}" .checked="${this.isRegistry}" name="isRegistry" id="isRegistry"> <label for="isRegistry">Is this a registry?</label></div><div class="registry-slider ${e(sliderClasses)}"><div class="address-form">${this.renderRegistryFormContents()}</div></div>`;
    }

    renderError() {
        if (!(this.nonFieldExceptions.length)) {
            return T;
        }
        return x`<div class="error-box"><ul>${this.nonFieldExceptions.map((exc) =>
                            x`<li>${exc.message}</li>`
                    )}</ul></div>`;
    }

    render() {
        return x`${this.renderError()}<form id="list-form"><checkout-validated-form><div class="popup-content"><div class="row"><span class="popup-header">${this.headerText}</span></div><div class="check-row"><div class="check-field"><input type="checkbox" @click="${this.togglePublic}" name="isPrivate" value="false" id="private-check" .checked="${!this.isPublic}"> <label for="private-check">Private</label></div><div class="check-field"><input type="checkbox" @click="${this.togglePublic}" name="isPublic" value="true" id="public-check" .checked="${this.isPublic}"> <label for="public-check">Public<sup>*</sup></label></div></div><div class="main-form popup-content">${this.renderRegistryFields()}<div class="address-form full-width"><div class="f-field required f-list-name"><checkout-validated-input key="listName" field-name="list name" group="name" validation="required"><label for="listName">List Name</label> <input type="text" id="listName" name="listName" maxlength="254" autocapitalize="words" value="${this.displayName}"></checkout-validated-input></div></div><div class="check-field full-width"><input type="checkbox" @click="${this.handleShowPurchased}" .checked="${this.showPurchased}" name="showPurchased" id="showPurchased"> <label for="showPurchased">Show me when items are purchased</label></div><div class="check-field full-width"><input type="checkbox" @click="${this.handleShowPurchasedPublic}" .checked="${this.showPurchasedPublic}" name="showPurchasedPublic" id="showPurchasedPublic"> <label for="showPurchasedPublic">Show quantity desired and purchased on shared list</label></div>${this.renderAddressFields()}<div class="button-row"><button @click="${this.closeModal}" type="button">Cancel</button><checkout-validated-button><button @click="${this.handleSubmit}" type="button" class="btn-main-cta">Save</button></checkout-validated-button></div><div class="row footnote"><span><sup>*</sup>A public list can be viewed by anyone with the link</span></div></div></div></checkout-validated-form></form>`;
    }
}

customElements.define('giftlist-settings-form', GiftlistSettingsForm);

async function handleCreateGiftlist(postData) {
    let encodedPostData = encodePostData(postData);
    let opts = {
        method: 'POST',
        body: encodedPostData,
        mode: 'same-origin',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
    };
    let response = await fetch(CREATE_NEW_GIFTLIST_URL, opts);
    if (!response.ok) {
        throw new Error('fetch error');
    }
    let jsonData = await response.json();
    return jsonData;
}

class GiftlistSaveWidget extends BasePopupContent {
    constructor() {
        super();

        this.addComplete = false;
        this.removeComplete = false;
        this.addNewListComplete = false;
        this.lref = '';
        this.productId = '';
        this.selectedSkuId = '';
        this.giftlists = [];
        this.giftlistEntries = [];
        this.canCreateLists = false;
        this.profileStatus = 0;
        this.selectedList = '';
        this.inSelectedList = false;
        this.selectedListEntry = {};
        this.creatingList = false;
        this.giftlistsChangedListener = null;
        this.newListName = '';
        this.viewListId = '';
        this.store = {};
        this.featureFlags = {};
        this.formExceptions = [];

        this.handleSelectList = this.handleSelectList.bind(this);
        this.handleAddToList = this.handleAddToList.bind(this);
        this.handleHashChange = this.handleHashChange.bind(this);
        this.handleRemoveFromList = this.handleRemoveFromList.bind(this);
        this.handleCreatingList = this.handleCreatingList.bind(this);
        this.handleCreateList = this.handleCreateList.bind(this);
        this.handlePopupClose = this.handlePopupClose.bind(this);
        this.handleViewList = this.handleViewList.bind(this);

        this.addEventListener(EVENT_NAME_POPUP_CLOSED, this.handlePopupClose);
    }

    static properties = {
        addComplete: {type: Boolean, state: true,},
        removeComplete: {type: Boolean, state: true,},
        addNewListComplete: {type: Boolean, state: true,},
        lref: {type: String, state: true,},
        productId: {type: String, state: true,},
        selectedSkuId: {type: String, state: true,},
        giftlists: {type: Array, state: true,},
        giftlistEntries: {type: Array, state: true,}, // existing giftlist items for the current product
        canCreateLists: {type: Boolean, state: true,},
        profileStatus: {type: Number, state: true,},
        selectedList: {type: String, state: true,},
        inSelectedList: {type: Boolean, state: true,},
        selectedListEntry: {type: Object, state: true,},
        creatingList: {type: Boolean, state: true,},
        giftlistsChangedListener: {type: Function, state: true,},
        newListName: {type: String, state: true,},
        viewListId: {type: String, state: true,},
        store: {type: Object, state: true,},
        featureFlags: {type: Object, state: true,},
        formExceptions: {type: Array, state: true,},
    };

    static styles = [
        giftlistStyles,
        i$3`.message{font-size:1em}.error{color:red}.create-message a{color:var(--lists-heart-color);text-decoration:none}.sign-in{max-width:20em;flex-wrap:wrap;gap:.4em}`,
    ];

    connectedCallback() {
        super.connectedCallback();
        window.addEventListener('hashchange', this.handleHashChange);
    }

    handleHashChange(e) {
        if (location.hash === '#logdata') {
            this._logData();
        }
    }

    _logData() {
        console.log('giftlist-save-widget data:');
        console.log(`addComplete: ${this.addComplete}`);
        console.log(`removeComplete: ${this.removeComplete}`);
        console.log(`addNewListComplete: ${this.addNewListComplete}`);
        console.log(`lref: ${this.lref}`);
        console.log(`productId: ${this.productId}`);
        console.log(`selectedSkuId: ${this.selectedSkuId}`);
        console.log(`giftlists: ${JSON.stringify(this.giftlists, null, 2)}`);
        console.log(`giftlistEntries: ${JSON.stringify(this.giftlistEntries, null, 2)}`);
        console.log(`canCreateLists: ${this.canCreateLists}`);
        console.log(`selectedList: ${this.selectedList}`);
        console.log(`errorMessage: ${this.errorMessage}`);
        console.log(`inSelectedList: ${this.inSelectedList}`);
        console.log(`creatingList: ${this.creatingList}`);
    }

    willUpdate(changedProperties) {
        if (changedProperties.has('giftlists')) {
            this.computeSelectedList(changedProperties);
            // previous will call computeInSelectedList at end
        }
        if (changedProperties.has('selectedList') || changedProperties.has('giftlistEntries') ||
            changedProperties.has('selectedSkuId') || changedProperties.has('productId')) {
            this.computeInSelectedList();
        }
        if (changedProperties.has('creatingList') && this.creatingList) {
            resizeLightPopup("medium");
        }
        if (changedProperties.has('addNewListComplete') && this.addNewListComplete) {
            resizeLightPopup("small");
        }
    }

    handlePopupClose(e) {
        if (e) {
            e.stopPropagation();
        }
        this.resetProcessingState();
        let settingsForm = this.shadowRoot.querySelector('giftlist-settings-form');
        if (settingsForm) {
            let newEvent = new CustomEvent(EVENT_NAME_POPUP_CLOSED);
            if (e.detail) {
                newEvent.detail = e.detail;
            }
            settingsForm.dispatchEvent(newEvent);
        }
    }

    /* resets any state that prevents initial view from rendering (completed
        state, creating etc.
     */
    resetProcessingState() {
        this.addComplete = false;
        this.removeComplete = false;
        this.addNewListComplete = false;
        this.errorMessage = '';
        this.creatingList = false;
        this.newListName = '';
        this.viewListId = '';
        this.formExceptions = [];
    }

    computeSelectedList(changedProperties) {
        // I think we need to wait on the updateComplete promise because it seems maybe we're getting to
        // this point with new giftlists but without updated giftlistEntries
        (async () => {
            await this.updateComplete;

            if (!(this.giftlists && this.giftlists.length)) {
                this.selectedList = '';
            } else {
                // Algorithm:
                // If item is in current selected list, use current selected list.
                // Else if item is in some lists, select first of those
                // Else if selected list is still valid (not empty, or not just deleted) keep it
                // Finally fall back on first list
                let currentLists = this.findListIdsContainingItem(), currentIsValid = false, firstCurrentList = '';
                if (!(this.selectedList && currentLists[this.selectedList])) {
                    for (let i = 0; i < this.giftlists.length; i++) {
                        let list = this.giftlists[i];
                        if (list.id === this.selectedList) {
                            currentIsValid = true;
                        }
                        if (!firstCurrentList && currentLists[list.id]) {
                            firstCurrentList = list.id;
                        }
                    }
                    if (firstCurrentList) {
                        this.selectedList = firstCurrentList;
                    } else if (!currentIsValid) {
                        this.selectedList = this.giftlists[0].id;
                    }
                }
                this.setSelectedIndex();
            }
            this.computeInSelectedList();
        })();
    }

    /* this works around issues with the UI getting out of sync if the user selects a different list and this is
        remembered by the browser and the option.selected attribute no longer has any effect
     */
    setSelectedIndex() {
        if (this.shadowRoot) {
            let select = this.shadowRoot.querySelector('select');
            if (select) {
                let selectedIndex = -1;
                for (let i = 0; i < this.giftlists.length; i++) {
                    let giftlist = this.giftlists[i];
                    if (giftlist.id === this.selectedList) {
                        selectedIndex = i;
                        break;
                    }
                }
                select.selectedIndex = selectedIndex;
            }
        }
    }

    /* return a Map of list ID to true where that list contains this combo of product and sku */
    findListIdsContainingItem() {
        let lists = {};
        for (let i = 0; i < this.giftlistEntries.length; i++) {
            let entry = this.giftlistEntries[i];
            if (entry.productId === this.productId && entry.skuId === this.selectedSkuId) {
                lists[entry.giftlistId] = true;
            }
        }

        return lists;
    }

    computeInSelectedList() {
        if (this.giftlistEntries) {
            for (let i = 0; i < this.giftlistEntries.length; i++) {
                let entry = this.giftlistEntries[i];
                if (entry.productId === this.productId && entry.skuId === this.selectedSkuId &&
                    entry.giftlistId === this.selectedList) {
                    this.inSelectedList = true;
                    this.selectedListEntry = entry;
                    return;
                }
            }
        }
        this.inSelectedList = false;
    }

    getSelectedListName() {
        if (!this.selectedList || !this.giftlists.length) {
            return '';
        }
        for (let i = 0; i < this.giftlists.length; i++) {
            let giftlist = this.giftlists[i];
            if (giftlist.id === this.selectedList) {
                return giftlist.displayName;
            }
        }
        return '';
    }

    getSelectedGiftItemId() {
        if (!this.giftlistEntries.length) {
            return '';
        }
        for (let i = 0; i < this.giftlistEntries.length; i++) {
            let entry = this.giftlistEntries[i];
            if (entry.productId === this.productId && entry.skuId === this.selectedSkuId
                    && entry.giftlistId === this.selectedList) {
                return entry.giftlistItemId;
            }
        }
        return '';
    }

    handleSelectList(e) {
        e.stopPropagation();
        this.selectedList = e.target.value;
    }

    async handleAddToList(e) {
        let postData = {
            productId: this.productId,
            skuId: this.selectedSkuId,
            giftlistId: this.selectedList,
            lref: this.lref,
        };
        let encodedPostData = encodePostData(postData);
        let opts = {
            method: 'POST',
            body: encodedPostData,
            mode: 'same-origin',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
        };
        let response = await fetch(ADD_ITEM_TO_GIFTLIST_URL, opts);
        if (!response.ok) {
            throw new Error('fetch error');
        }
        let jsonData = await response.json();
        if (jsonData.result) {
            let newEvent = new CustomEvent(EVENT_NAME_GIFTLISTS_CHANGED,
                {bubbles: true, composed: true, detail: {giftlistEntries: jsonData.giftlistEntries}});
            let evtTarget = this.giftlistsChangedListener || this;
            evtTarget.dispatchEvent(newEvent);
            this.addComplete = true;
        } else {
            if (jsonData.message) {
                this.errorMessage = jsonData.message;
            } else if (jsonData.formExceptions) {
                this.errorMessage = x`<ul>${jsonData.formExceptions.map((exc) => 
                            x`<li>${exc.message}</li>`
                        )}</ul>`;
            }
        }
    }

    handleCreatingList(e) {
        e.preventDefault();
        e.stopPropagation();
        // I don't think there's any way to get back out of this view. This is meant to be a short-lived popup
        // in all cases anyway
        this.creatingList = true;
    }

    handleViewList(e) {
        if (e) {
            e.stopPropagation();
        }
        if (this.viewListId) {
            window.location = '/account/lists?list=' + this.viewListId;
        } else {
            console.log("handleViewList called with no listId!");
        }
    }

    async handleCreateList(data) {
        let postData = {
            productId: this.productId,
            skuId: this.selectedSkuId,
            lref: this.lref,
            displayName: data.displayName,
            giftlistType: data.giftlistType,
            public: data.isPublic,
            showPurchased: data.showPurchased,
            showPurchasedPublic: data.showPurchasedPublic,
            removeShippingAddress: data.removeShippingAddress,
            shippingAddressType: data.shippingAddressType || 'anonymous',
            firstName: data.firstName || '',
            lastName: data.lastName || '',
            address1: data.address1 || '',
            address2: data.address2 || '',
            city: data.city || '',
            state: data.state || '',
            postalCode: data.postalCode || '',
            country: data.country || '',
            phoneNumber: data.phoneNumber || '',
            eventType: data.eventType || '',
            eventDateTimestamp: data.eventDateTimestamp || '',
            eventFirstName: data.eventFirstName || '',
            eventLastName: data.eventLastName || '',
        };
        let jsonData = await handleCreateGiftlist(postData);
        if (jsonData.result) {
            /* This will eventually cascade down from the containing component after event handling. However I'm worried about trying to
                immediately render the success view and not having access to the name.
             */
            this.newListName = data.displayName;
            this.viewListId = jsonData.newGiftlistId;
            if (jsonData.giftlists) {
                this.giftlists = jsonData.giftlists;
            }
            if (jsonData.newGiftlistId) {
                this.selectedList = jsonData.newGiftlistId;
            }
            let newEvent = new CustomEvent(EVENT_NAME_GIFTLISTS_CHANGED,
                {bubbles: true, composed: true, detail: {
                    giftlistEntries: jsonData.giftlistEntries,
                    giftlists: jsonData.giftlists
                }});
            let evtTarget = this.giftlistsChangedListener || this;
            evtTarget.dispatchEvent(newEvent);
            this.addNewListComplete = true;
        } else {
            if (jsonData.formExceptions && jsonData.formExceptions.length) {
                this.formExceptions = jsonData.formExceptions;
            } else {
                this.errorMessage = 'There was an error creating the list';
            }
        }
    }

    async handleRemoveFromList(e) {
        /* We still want to send the productId value so the giftlist entries in the response
            are appropriately filtered
         */
        let postData = {
            productId: this.productId,
            giftlistId: this.selectedList,
            giftItemId: this.getSelectedGiftItemId(),
        };
        let encodedPostData = encodePostData(postData);
        let opts = {
            method: 'POST',
            body: encodedPostData,
            mode: 'same-origin',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
        };
        let response = await fetch(REMOVE_ITEM_FROM_GIFTLIST_URL, opts);
        if (!response.ok) {
            throw new Error('fetch error');
        }
        let jsonData = await response.json();
        if (jsonData.result) {
            let newEvent = new CustomEvent(EVENT_NAME_GIFTLISTS_CHANGED,
                {bubbles: true, composed: true, detail: {giftlistEntries: jsonData.giftlistEntries}});
            let evtTarget = this.giftlistsChangedListener || this;
            evtTarget.dispatchEvent(newEvent);
            this.removeComplete = true;
        } else {
            this.errorMessage = jsonData.message;
        }
    }

    renderDeleteOptions() {
        return x`<button class="btn-main-cta" @click="${this.handleRemoveFromList}" type="button">Remove</button>`;
    }

    renderSaveOptions() {
        return x`<button class="btn-main-cta" @click="${this.handleAddToList}" type="button">Save</button>`;
    }

    renderMessage() {
        if (this.errorMessage) {
            return x`<span class="error">${this.errorMessage}</span>`;
        } else if (this.inSelectedList) {
            let pName = !!this.selectedListEntry.skuDescriptiveText ?
                x`${this.selectedListEntry.productDisplayName} - ${this.selectedListEntry.skuDescriptiveText}` :
                x`${this.selectedListEntry.productDisplayName}`;
            return x`<span>${pName} is in this list</span>`;
        } else if (this.canCreateLists) {
            return x`<span class="create-message">or <a @click="${this.handleCreatingList}" href="#0">Create a new list</a></span>`;
        } else {
            return T;
        }
    }

    renderComplete(msg) {
        let viewButton = T;
        if (this.viewListId) {
            viewButton = x`<button type="button" @click="${this.handleViewList}">View List</button>`;
        }
        let tempListWarning = T;
        if (this.profileStatus < 2) {
            tempListWarning = x`<div class="row message create-message sign-in">This is a temporary list. To save please <a href="/login">Sign In</a> or <a href="/register">Create an Account</a></div>`;
        }
        return x`<div class="popup-content"><div class="icon-row"><i class="icon-check row-icon row-check"></i> <span class="popup-header">${msg}</span></div>${tempListWarning}<div class="button-row">${viewButton} <button class="btn-main-cta" @click="${this.closeModal}" type="button">Close</button></div></div>`;
    }

    renderCreateList() {
        return x`<giftlist-settings-form .headerText="${'Create a List'}" .onSubmit="${this.handleCreateList}" .store="${this.store}" .featureFlags="${this.featureFlags}" .formExceptions="${this.formExceptions}" .closeModal="${this.closeModal}">`;
    }

    render() {
        if (this.addNewListComplete) {
            /* There is some state issue where when computing selectedList
                we don't have the data that the current item is in the new list. Probably fixable, but is becoming
                a real time sink. Fix if time allows.
                Actually - there is probably an easy hack here of just pushing the new list name onto a new prop
             */
            return this.renderComplete(x`Saved to <em>${this.newListName}</em>`);
        } else if (this.addComplete) {
            this.viewListId = this.selectedList;
            return this.renderComplete(x`Saved to <em>${this.getSelectedListName()}</em>`);
        } else if (this.removeComplete) {
            this.viewListId = this.selectedList;
            return this.renderComplete(x`Removed from <em>${this.getSelectedListName()}</em>`);
        } else if (this.creatingList) {
            return this.renderCreateList();
        }
        let content = this.inSelectedList ? this.renderDeleteOptions() : this.renderSaveOptions();

        let giftlistOptions = [], itemLists = this.findListIdsContainingItem();
        for (let i = 0; i < this.giftlists.length; i++) {
            let giftlist = this.giftlists[i], isSelected = false;
            if (this.selectedList === giftlist.id) {
                isSelected = true;
            }
            giftlistOptions.push(x`<option ?selected="${isSelected}" value="${giftlist.id}">${itemLists[giftlist.id] ? x`❤ ` : T}${giftlist.displayName}</option>`);
        }
        return x`<div class="popup-content"><div class="row"><i class="icon-heart-filled row-icon"></i> <span class="popup-header">${this.inSelectedList ? 'Saved to' : 'Save to'}</span> <select @change="${this.handleSelectList}">${giftlistOptions}</select></div><div class="row message">${this.renderMessage()}</div><div class="button-row">${content}</div></div>`;
    }
}

customElements.define('giftlist-save-widget', GiftlistSaveWidget);

class GiftlistToggle extends s {

    constructor() {
        super();

        this.inList = false;
        this.showCaptions = false;
        this.lref = '';
        this.productId = '';
        this.selectedSkuId = '';
        this.giftlists = [];
        this.giftlistEntries = [];
        this.canCreateLists = false;
        this.profileStatus = 0;
        this.store = {};
        this.featureFlags = {};
        this.horizontal = false;

        this.handleClick = this.handleClick.bind(this);
        this.openModal = this.openModal.bind(this);
        this.closeModal = this.closeModal.bind(this);
        this.handleGiftlistsChanged = this.handleGiftlistsChanged.bind(this);
        this.handleHashChange = this.handleHashChange.bind(this);

        this.addEventListener(EVENT_NAME_GIFTLISTS_CHANGED, this.handleGiftlistsChanged);
    }

    static properties = {
        inList: {type: Boolean, attribute: "in-list", reflect: true,},
        showCaptions: {type: Boolean, attribute: 'show-captions',},
        lref: {type: String,},
        productId: {type: String, attribute: 'product-id',},
        // data attribute name used for coordination with sku picker code
        selectedSkuId: {type: String, attribute: 'data-selected-sku-id'},
        giftlists: {type: Array, state: true,},
        giftlistEntries: {type: Array, state: true,}, // existing giftlist items for the current product
        canCreateLists: {type: Boolean, state: true,},
        horizontal: {type: Boolean,},
        profileStatus: {type: Number, state: true,},
        store: {type: Object, state: true,},
        featureFlags: {type: Object, state: true,},
    };

    static styles = [
        giftlistStyles,
        i$3`.container{display:flex;flex-direction:column;align-items:center}.container.horizontal{flex-direction:row;gap:.25rem}.container .caption{font-size:.7em;white-space:nowrap}.giftlist-btn{font-size:22px;cursor:pointer;border:0;background:0 0;color:var(--lists-heart-color);box-shadow:none;padding:0;margin:.2rem}.giftlist-btn:focus{outline:0!important}.giftlist-btn:hover{color:var(--lists-heart-color);background:initial!important}.giftlist-btn:after{font-family:glicons!important;font-style:normal!important;font-weight:400!important;font-variant:normal!important;text-transform:none!important;speak:none;vertical-align:middle;-webkit-font-smoothing:antialiased}.not-in-list:after{content:"\\2e"}.in-list:after{content:"\\2d"}`];

    _logData() {
        console.log('giftlist-toggle data:');
        console.log(`inList: ${this.inList}`);
        console.log(`showCaptions: ${this.showCaptions}`);
        console.log(`lref: ${this.lref}`);
        console.log(`productId: ${this.productId}`);
        console.log(`selectedSkuId: ${this.selectedSkuId}`);
        console.log(`giftlists: ${JSON.stringify(this.giftlists, null, 2)}`);
        console.log(`giftlistEntries: ${JSON.stringify(this.giftlistEntries, null, 2)}`);
        console.log(`canCreateLists: ${this.canCreateLists}`);
        console.log(`profileStatus: ${this.profileStatus}`);
    }

    connectedCallback() {
        super.connectedCallback();
        window.addEventListener('hashchange', this.handleHashChange);
    }

    handleHashChange(e) {
        if (location.hash === '#logdata') {
            this._logData();
        }
    }

    handleGiftlistsChanged(e) {
        if (e.detail) {
            if (e.detail.giftlistEntries) {
                if (e.detail.giftlistEntries[this.productId]) {
                    let newGiftlistEntries = e.detail.giftlistEntries[this.productId];
                    this.giftlistEntries = newGiftlistEntries;
                    if (newGiftlistEntries.length) {
                        this.inList = true;
                    } else {
                        this.inList = false;
                    }
                } else {
                    this.giftlistEntries = [];
                    this.inList = false;
                }
            }
            if (e.detail.giftlists && Array.isArray(e.detail.giftlists)) {
                let newGiftlists = e.detail.giftlists;
                this.giftlists = newGiftlists;
            }
        } else {
            console.log('received giftlists changed event with no detail!');
        }
    }

    openModal(e) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        openLightPopup(x`<giftlist-save-widget .productId="${this.productId}" .selectedSkuId="${this.selectedSkuId}" .giftlists="${this.giftlists}" .profileStatus="${this.profileStatus}" .giftlistEntries="${this.giftlistEntries}" .canCreateLists="${this.canCreateLists}" .lref="${this.lref}" .closeModal="${this.closeModal}" .giftlistsChangedListener="${this}" .store="${this.store}" .featureFlags="${this.featureFlags}">`, 'small');
    }

    closeModal(e) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        closeLightPopup();
    }

    async handleClick(e) {
        let opts = {
            method: 'GET',
            mode: 'same-origin',
        };
        const url = LOAD_GIFTLIST_OPTIONS_URL + `?productId=${this.productId}`;
        let response = await fetch(url, opts);
        if (!response.ok) {
            throw new Error('fetch error');
        }
        let jsonData = await response.json();
        if (jsonData) {
            if (jsonData.giftlists) {
                this.giftlists = jsonData.giftlists;
            }
            if (jsonData.giftlistEntries) {
                this.giftlistEntries = jsonData.giftlistEntries[this.productId] || [];
            }
            if (jsonData.store) {
                this.store = jsonData.store;
            }
            if (jsonData.featureFlags) {
                this.featureFlags = jsonData.featureFlags;
            }
            this.canCreateLists = !!(jsonData.canCreateLists);
            this.profileStatus = jsonData.profileStatus || 0;
            this.openModal();
        } else {
            console.log('no json data!');
        }
    }

    render() {
        let classes = {
            'in-list': this.inList,
            'not-in-list': !this.inList,
        };
        let containerClasses = {
            'horizontal': this.horizontal,
        };
        let caption = T;
        if (this.showCaptions) {
            caption = this.inList ? x`<span class="caption">Saved to List</span>` : x`<span class="caption">Save to List</span>`;
        }
        let ariaLabel = this.inList ? 'Remove from your list' : 'Add to your list';
        return x`<div class="container ${e(containerClasses)}"><button @click="${this.handleClick}" aria-label="${ariaLabel}" class="giftlist-btn ${e(classes)}"></button> ${caption}</div>`;
    }
}

customElements.define('giftlist-toggle', GiftlistToggle);

const options$c = {
    hooks: {},
    navbar: {
        add: true,
        title: 'Menu',
        titleLink: 'parent'
    },
    slidingSubmenus: true
};

const configs$4 = {
    classNames: {
        divider: 'Divider',
        nolistview: 'NoListview',
        nopanel: 'NoPanel',
        panel: 'Panel',
        selected: 'Selected',
        vertical: 'Vertical'
    },
    language: null,
    panelNodetype: ['ul', 'ol', 'div'],
    screenReader: {
        closeSubmenu: 'Close submenu',
        openSubmenu: 'Open submenu',
        toggleSubmenu: 'Toggle submenu'
    }
};

/**
 * Deep extend an object with the given defaults.
 * Note that the extended object is not a clone, meaning the original object will also be updated.
 *
 * @param 	{object}	orignl	The object to extend to.
 * @param 	{object}	dfault	The object to extend from.
 * @return	{object}			The extended "orignl" object.
 */
const extend$1 = (orignl, dfault) => {
    if (type(orignl) != 'object') {
        orignl = {};
    }
    if (type(dfault) != 'object') {
        dfault = {};
    }
    for (let k in dfault) {
        if (!dfault.hasOwnProperty(k)) {
            continue;
        }
        if (typeof orignl[k] == 'undefined') {
            orignl[k] = dfault[k];
        }
        else if (type(orignl[k]) == 'object') {
            extend$1(orignl[k], dfault[k]);
        }
    }
    return orignl;
};
/**
 * Detect the touch / dragging direction on a touch device.
 *
 * @param   {HTMLElement} surface   The element to monitor for touch events.
 * @return  {object}                Object with "get" function.
 */
const touchDirection = (surface) => {
    let direction = '';
    let prevPosition = null;
    surface.addEventListener('touchstart', (evnt) => {
        if (evnt.touches.length === 1) {
            direction = '';
            prevPosition = evnt.touches[0].pageY;
        }
    });
    surface.addEventListener('touchend', (evnt) => {
        if (evnt.touches.length === 0) {
            direction = '';
            prevPosition = null;
        }
    });
    surface.addEventListener('touchmove', (evnt) => {
        direction = '';
        if (prevPosition &&
            evnt.touches.length === 1) {
            const currentPosition = evnt.changedTouches[0].pageY;
            if (currentPosition > prevPosition) {
                direction = 'down';
            }
            else if (currentPosition < prevPosition) {
                direction = 'up';
            }
            prevPosition = currentPosition;
        }
    });
    return {
        get: () => direction,
    };
};
/**
 * Get the type of any given variable. Improvement of "typeof".
 *
 * @param 	{any}		variable	The variable.
 * @return	{string}				The type of the variable in lowercase.
 */
const type = (variable) => {
    return {}.toString
        .call(variable)
        .match(/\s([a-zA-Z]+)/)[1]
        .toLowerCase();
};
/**
 * Get a (page wide) unique ID.
 */
const uniqueId = () => {
    return `mm-${__id++}`;
};
let __id = 0;
/**
 * Get a prefixed ID from a possibly orifinal ID.
 * @param id The possibly original ID.
 */
const cloneId = (id) => {
    if (id.slice(0, 9) == 'mm-clone-') {
        return id;
    }
    return `mm-clone-${id}`;
};
/**
 * Get the original ID from a possibly prefixed ID.
 * @param id The possibly prefixed ID.
 */
const originalId = (id) => {
    if (id.slice(0, 9) == 'mm-clone-') {
        return id.slice(9);
    }
    return id;
};

const translations = {};
/**
 * Show all translations.
 * @return {object}	The translations.
 */
const show = () => {
    return translations;
};
/**
 * Add translations to a language.
 * @param {object}  text        Object of key/value translations.
 * @param {string}  language    The translated language.
 */
const add$1 = (text, language) => {
    if (typeof translations[language] === 'undefined') {
        translations[language] = {};
    }
    extend$1(translations[language], text);
};
/**
 * Find a translated text in a language.
 * @param   {string} text       The text to find the translation for.
 * @param   {string} language   The language to search in.
 * @return  {string}            The translated text.
 */
const get = (text, language) => {
    if (typeof language === 'string' &&
        typeof translations[language] !== 'undefined') {
        return translations[language][text] || text;
    }
    return text;
};

var de$1 = {
    'Close submenu': 'Untermenü schließen',
    'Menu': 'Menü',
    'Open submenu': 'Untermenü öffnen',
    'Toggle submenu': 'Untermenü wechseln'
};

var fa$1 = {
    'Close submenu': 'بستن زیرمنو',
    'Menu': 'منو',
    'Open submenu': 'بازکردن زیرمنو',
    'Toggle submenu': 'سوییچ زیرمنو'
};

var nl$1 = {
    'Close submenu': 'Submenu sluiten',
    'Menu': 'Menu',
    'Open submenu': 'Submenu openen',
    'Toggle submenu': 'Submenu wisselen'
};

var pt_br$1 = {
    'Close submenu': 'Fechar submenu',
    'Menu': 'Menu',
    'Open submenu': 'Abrir submenu',
    'Toggle submenu': 'Alternar submenu'
};

var ru$1 = {
    'Close submenu': 'Закрыть подменю',
    'Menu': 'Меню',
    'Open submenu': 'Открыть подменю',
    'Toggle submenu': 'Переключить подменю'
};

var sk$1 = {
    'Close submenu': 'Zatvoriť submenu',
    'Menu': 'Menu',
    'Open submenu': 'Otvoriť submenu',
    'Toggle submenu': 'Prepnúť submenu'
};

var uk$1 = {
    'Close submenu': 'Закрити підменю',
    'Menu': 'Меню',
    'Open submenu': 'Відкрити підменю',
    'Toggle submenu': 'Перемкнути підменю'
};

function translate$1 () {
    add$1(de$1, 'de');
    add$1(fa$1, 'fa');
    add$1(nl$1, 'nl');
    add$1(pt_br$1, 'pt_br');
    add$1(ru$1, 'ru');
    add$1(sk$1, 'sk');
    add$1(uk$1, 'uk');
}

/**
 * Create an element with classname.
 *
 * @param 	{string}		selector	The nodeName and classnames for the element to create.
 * @return	{HTMLElement}				The created element.
 */
const create = (selector) => {
    const args = selector.split('.'), elem = document.createElement(args.shift());
    elem.classList.add(...args);
    return elem;
};
/**
 * Find all elements matching the selector.
 * Basically the same as element.querySelectorAll() but it returns an actuall array.
 *
 * @param 	{HTMLElement} 	element Element to search in.
 * @param 	{string}		filter	The filter to match.
 * @return	{array}					Array of elements that match the filter.
 */
const find = (element, filter) => {
    return filter.length ? [].slice.call(element.querySelectorAll(filter)) : [];
};
/**
 * Find all child elements matching the (optional) selector.
 *
 * @param 	{HTMLElement} 	element Element to search in.
 * @param 	{string}		filter	The filter to match.
 * @return	{array}					Array of child elements that match the filter.
 */
const children = (element, filter) => {
    const children = Array.prototype.slice.call(element.children);
    return filter
        ? children.filter((child) => child.matches(filter))
        : children;
};
/**
 * Find all text from direct child element.
 *
 * @param 	{HTMLElement} 	element Element to search in.
 * @return	{string}				The text.
 */
const childText = (element) => {
    return element
        ? [].slice.call(element.childNodes)
            .filter(node => node.nodeType === Node.TEXT_NODE)
            .map(node => node.nodeValue.trim())
            .join(' ')
        : '';
};
/**
 * Find text excluding text from within child elements.
 * @param   {HTMLElement}   element Element to search in.
 * @return  {string}                The text.
 */
const text = (element) => {
    return [].slice.call(element.childNodes)
        .filter((child) => !child.ariaHidden)
        .map((child) => child.textContent)
        .join(' ');
};
/**
 * Find all preceding elements matching the selector.
 *
 * @param 	{HTMLElement} 	element Element to start searching from.
 * @param 	{string}		filter	The filter to match.
 * @return	{array}					Array of preceding elements that match the selector.
 */
const parents = (element, filter) => {
    /** Array of preceding elements that match the selector. */
    let parents = [];
    /** Array of preceding elements that match the selector. */
    let parent = element.parentElement;
    while (parent) {
        parents.push(parent);
        parent = parent.parentElement;
    }
    return filter
        ? parents.filter((parent) => parent.matches(filter))
        : parents;
};
/**
 * Find all previous siblings matching the selecotr.
 *
 * @param 	{HTMLElement} 	element Element to start searching from.
 * @param 	{string}		filter	The filter to match.
 * @return	{array}					Array of previous siblings that match the selector.
 */
const prevAll = (element, filter) => {
    /** Array of previous siblings that match the selector. */
    let previous = [];
    /** Current element in the loop */
    let current = element.previousElementSibling;
    while (current) {
        if (!filter || current.matches(filter)) {
            previous.push(current);
        }
        current = current.previousElementSibling;
    }
    return previous;
};
/**
 * Filter out non-listitem listitems.
 * @param  {array} listitems 	Elements to filter.
 * @return {array}				The filtered set of listitems.
 */
const filterLI = (listitems) => {
    return listitems.filter((listitem) => !listitem.matches('.mm-hidden'));
};
/**
 * Find anchors in listitems (excluding anchor that open a sub-panel).
 * @param  {array} 	listitems 	Elements to filter.
 * @return {array}				The found set of anchors.
 */
const filterLIA = (listitems) => {
    let anchors = [];
    filterLI(listitems).forEach((listitem) => {
        anchors.push(...children(listitem, 'a.mm-listitem__text'));
    });
    return anchors.filter((anchor) => !anchor.matches('.mm-btn--next'));
};
/**
 * Refactor a classname on multiple elements.
 * @param {HTMLElement} element 	Element to refactor.
 * @param {string}		oldClass 	Classname to remove.
 * @param {string}		newClass 	Classname to add.
 */
const reClass = (element, oldClass, newClass) => {
    if (element.matches('.' + oldClass)) {
        element.classList.add(newClass);
    }
};

/** Collection of callback functions for media querys. */
let listeners = {};
/**
 * Bind functions to a matchMedia listener (subscriber).
 *
 * @param {string|number} 	query 	Media query to match or number for min-width.
 * @param {function} 		yes 	Function to invoke when the media query matches.
 * @param {function} 		no 		Function to invoke when the media query doesn't match.
 */
const add = (query, yes, no) => {
    if (typeof query == 'number') {
        query = '(min-width: ' + query + 'px)';
    }
    listeners[query] = listeners[query] || [];
    listeners[query].push({ yes, no });
};
/**
 * Initialize the matchMedia listener.
 */
const watch = () => {
    for (let query in listeners) {
        let mqlist = window.matchMedia(query);
        fire(query, mqlist);
        mqlist.onchange = (evnt) => {
            fire(query, mqlist);
        };
    }
};
/**
 * Invoke the "yes" or "no" function for a matchMedia listener (publisher).
 *
 * @param {string} 			query 	Media query to check for.
 * @param {MediaQueryList} 	mqlist 	Media query list to check with.
 */
const fire = (query, mqlist) => {
    var fn = mqlist.matches ? 'yes' : 'no';
    for (let m = 0; m < listeners[query].length; m++) {
        listeners[query][m][fn]();
    }
};

var __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, privateMap, value) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to set private field on non-instance");
    }
    privateMap.set(receiver, value);
    return value;
};
var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, privateMap) {
    if (!privateMap.has(receiver)) {
        throw new TypeError("attempted to get private field on non-instance");
    }
    return privateMap.get(receiver);
};
var _panelObserver, _listviewObserver, _listitemObserver;
//  Add the translations.
translate$1();
/**
 * Class for a mobile menu.
 */
class Mmenu {
    /**
     * Create a mobile menu.
     * @param {HTMLElement|string} 	menu		The menu node.
     * @param {object} 				[option]	Options for the menu.
     * @param {object} 				[configs]	Configuration options for the menu.
     */
    constructor(menu, options, configs) {
        /** MutationObserver for adding a listview to a panel. */
        _panelObserver.set(this, void 0);
        /** MutationObserver for adding a listitem to a listview. */
        _listviewObserver.set(this, void 0);
        /** MutationObserver for adding a listview to a listitem. */
        _listitemObserver.set(this, void 0);
        //	Extend options and configuration from defaults.
        this.opts = extend$1(options, options$c);
        this.conf = extend$1(configs, configs$4);
        //	Methods to expose in the API.
        this._api = [
            'i18n',
            'bind',
            'openPanel',
            'closePanel',
            'setSelected',
        ];
        //	Storage objects for nodes and hooks.
        this.node = {};
        this.hook = {};
        //	Get menu node from string or element.
        this.node.menu =
            typeof menu == 'string' ? document.querySelector(menu) : menu;
        if (typeof this._deprecatedWarnings == 'function') {
            this._deprecatedWarnings();
        }
        this.trigger('init:before');
        this._initObservers();
        this._initAddons();
        this._initHooks();
        this._initAPI();
        this._initMenu();
        this._initPanels();
        this._initOpened();
        watch();
        this.trigger('init:after');
        return this;
    }
    /**
     * Open a panel.
     * @param {HTMLElement} panel               Panel to open.
     * @param {boolean}     [animation=true]    Whether or not to use an animation.
     * @param {boolean}     [setfocus=true]     Whether or not to set focus to the panel.
     */
    openPanel(panel, animation = true, setfocus = true) {
        //	Find panel.
        if (!panel) {
            return;
        }
        panel = panel.closest('.mm-panel');
        //	Invoke "before" hook.
        this.trigger('openPanel:before', [panel, {
                animation,
                setfocus
            }]);
        /** Wrapping listitem (for a vertical panel) */
        const listitem = panel.closest('.mm-listitem--vertical');
        //	Open a "vertical" panel.
        if (listitem) {
            listitem.classList.add('mm-listitem--opened');
            /** The parent panel */
            const parent = listitem.closest('.mm-panel');
            this.openPanel(parent);
            //	Open a "horizontal" panel.
        }
        else {
            /** Currently opened panel. */
            const current = children(this.node.pnls, '.mm-panel--opened')[0];
            //  Ensure current panel stays on top while closing it.
            if (panel.matches('.mm-panel--parent') && current) {
                current.classList.add('mm-panel--highest');
            }
            //  Remove opened, parent, animation and highest classes from all panels.
            const remove = ['mm-panel--opened', 'mm-panel--parent'];
            const add = [];
            if (animation) {
                remove.push('mm-panel--noanimation');
            }
            else {
                add.push('mm-panel--noanimation');
            }
            children(this.node.pnls, '.mm-panel').forEach(pnl => {
                pnl.classList.add(...add);
                pnl.classList.remove(...remove);
                if (pnl !== current) {
                    pnl.classList.remove('mm-panel--highest');
                }
                // Set inert attribute.
                if (pnl === panel) {
                    pnl.removeAttribute('inert');
                }
                else {
                    pnl.setAttribute('inert', 'true');
                }
            });
            //  Open new panel.
            panel.classList.add('mm-panel--opened');
            /** The parent panel */
            let parent = find(this.node.pnls, `#${panel.dataset.mmParent}`)[0];
            //	Set parent panels as "parent".
            while (parent) {
                parent = parent.closest('.mm-panel');
                parent.classList.add('mm-panel--parent');
                parent = find(this.node.pnls, `#${parent.dataset.mmParent}`)[0];
            }
        }
        //	Invoke "after" hook.
        this.trigger('openPanel:after', [panel, {
                animation,
                setfocus
            }]);
    }
    /**
     * Close a panel.
     * @param {HTMLElement} panel               Panel to close.
     * @param {boolean}     [animation=true]    Whether or not to use an animation.
     * @param {boolean}     [setfocus=true]     Whether or not to set focus to the panel.
     */
    closePanel(panel, animation = true, setfocus = true) {
        if (!panel) {
            return;
        }
        if (!panel.matches('.mm-panel--opened') &&
            !panel.parentElement.matches('.mm-listitem--opened')) {
            return;
        }
        //	Invoke "before" hook.
        this.trigger('closePanel:before', [panel]);
        //	Close a "vertical" panel.
        if (panel.parentElement.matches('.mm-listitem--vertical')) {
            panel.parentElement.classList.remove('mm-listitem--opened');
            //  Close a "horizontal" panel...
        }
        else {
            //  ... open its parent...
            if (panel.dataset.mmParent) {
                const parent = find(this.node.pnls, `#${panel.dataset.mmParent}`)[0];
                this.openPanel(parent, animation, setfocus);
                // ... or the last opened
            }
            else {
                const lastPanel = children(this.node.pnls, '.mm-panel--parent').pop();
                if (lastPanel && lastPanel !== panel) {
                    this.openPanel(lastPanel, animation, setfocus);
                    // ... or the first panel.
                }
                else {
                    const firstPanel = children(this.node.pnls, '.mm-panel')[0];
                    if (firstPanel && firstPanel !== panel) {
                        this.openPanel(firstPanel, animation, setfocus);
                    }
                }
            }
        }
        //	Invoke "after" hook.
        this.trigger('closePanel:after', [panel]);
    }
    /**
     * Toggle a panel opened/closed.
     * @param {HTMLElement} panel Panel to open or close.
     */
    togglePanel(panel) {
        const listitem = panel.parentElement;
        /** The function to invoke (open or close). */
        let fn = 'openPanel';
        //	Toggle only works for "vertical" panels.
        if (listitem.matches('.mm-listitem--opened') ||
            panel.matches('.mm-panel--opened')) {
            fn = 'closePanel';
        }
        this[fn](panel);
    }
    /**
     * Display a listitem as being "selected".
     * @param {HTMLElement} listitem Listitem to mark.
     */
    setSelected(listitem) {
        //	Invoke "before" hook.
        this.trigger('setSelected:before', [listitem]);
        //	Remove the selected class from all listitems.
        find(this.node.menu, '.mm-listitem--selected').forEach((li) => {
            li.classList.remove('mm-listitem--selected');
        });
        //	Add the selected class to the provided listitem.
        listitem.classList.add('mm-listitem--selected');
        //	Invoke "after" hook.
        this.trigger('setSelected:after', [listitem]);
    }
    /**
     * Bind functions to a hook (subscriber).
     * @param {string} 		hook The hook.
     * @param {function} 	func The function.
     */
    bind(hook, func) {
        //	Create an array for the hook if it does not yet excist.
        this.hook[hook] = this.hook[hook] || [];
        //	Push the function to the array.
        this.hook[hook].push(func);
    }
    /**
     * Invoke the functions bound to a hook (publisher).
     * @param {string} 	hook  	The hook.
     * @param {array}	[args] 	Arguments for the function.
     */
    trigger(hook, args) {
        if (this.hook[hook]) {
            for (var h = 0, l = this.hook[hook].length; h < l; h++) {
                this.hook[hook][h].apply(this, args);
            }
        }
    }
    /**
     * Create the observers.
     */
    _initObservers() {
        __classPrivateFieldSet(this, _panelObserver, new MutationObserver((mutationsList) => {
            mutationsList.forEach((mutation) => {
                mutation.addedNodes.forEach((listview) => {
                    if (listview.matches(this.conf.panelNodetype.join(', '))) {
                        this._initListview(listview);
                    }
                });
            });
        }));
        __classPrivateFieldSet(this, _listviewObserver, new MutationObserver((mutationsList) => {
            mutationsList.forEach((mutation) => {
                mutation.addedNodes.forEach((listitem) => {
                    this._initListitem(listitem);
                });
            });
        }));
        __classPrivateFieldSet(this, _listitemObserver, new MutationObserver((mutationsList) => {
            mutationsList.forEach((mutation) => {
                mutation.addedNodes.forEach((subpanel) => {
                    if (subpanel === null || subpanel === void 0 ? void 0 : subpanel.matches(this.conf.panelNodetype.join(', '))) {
                        this._initSubPanel(subpanel);
                    }
                });
            });
        }));
    }
    /**
     * Create the API.
     */
    _initAPI() {
        //	We need this=that because:
        //	1) the "arguments" object can not be referenced in an arrow function in ES3 and ES5.
        const that = this;
        this.API = {};
        this._api.forEach((fn) => {
            this.API[fn] = function () {
                return that[fn].apply(that, arguments); // 1)
            };
        });
        //	Store the API in the HTML node for external usage.
        this.node.menu['mmApi'] = this.API;
    }
    /**
     * Bind the hooks specified in the options (publisher).
     */
    _initHooks() {
        for (let hook in this.opts.hooks) {
            this.bind(hook, this.opts.hooks[hook]);
        }
    }
    /**
     * Initialize all available add-ons.
     */
    _initAddons() {
        //	Invoke "before" hook.
        this.trigger('initAddons:before');
        for (let addon in Mmenu.addons) {
            Mmenu.addons[addon].call(this);
        }
        //	Invoke "after" hook.
        this.trigger('initAddons:after');
    }
    /**
     * Initialize the menu.
     */
    _initMenu() {
        //	Invoke "before" hook.
        this.trigger('initMenu:before');
        //	Add class to the wrapper.
        this.node.wrpr = this.node.wrpr || this.node.menu.parentElement;
        this.node.wrpr.classList.add('mm-wrapper');
        //	Add class to the menu.
        this.node.menu.classList.add('mm-menu');
        //	Add an ID to the menu if it does not yet have one.
        this.node.menu.id = this.node.menu.id || uniqueId();
        this.node.menu.setAttribute('aria-label', this.i18n(this.opts.navbar.title || 'Menu'));
        this.node.menu.setAttribute('aria-modal', 'true');
        this.node.menu.setAttribute('role', 'dialog');
        /** All panel nodes in the menu. */
        const panels = children(this.node.menu).filter((panel) => panel.matches(this.conf.panelNodetype.join(', ')));
        //	Wrap the panels in a node.
        this.node.pnls = create('div.mm-panels');
        this.node.menu.append(this.node.pnls);
        //  Initiate all panel like nodes
        panels.forEach((panel) => {
            this._initPanel(panel);
        });
        //	Invoke "after" hook.
        this.trigger('initMenu:after');
    }
    /**
     * Initialize panels.
     */
    _initPanels() {
        //	Invoke "before" hook.
        this.trigger('initPanels:before');
        //	Open / close panels.
        this.node.menu.addEventListener('click', event => {
            var _a, _b;
            /** The href attribute for the clicked anchor. */
            const href = ((_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a[href]')) === null || _b === void 0 ? void 0 : _b.getAttribute('href')) || '';
            if (href.slice(0, 1) === '#') {
                try {
                    /** The targeted panel */
                    const panel = find(this.node.menu, href)[0];
                    if (panel) {
                        event.preventDefault();
                        this.togglePanel(panel);
                    }
                }
                catch (err) { }
            }
        }, {
            // useCapture to ensure the logical order.
            capture: true
        });
        //	Invoke "after" hook.
        this.trigger('initPanels:after');
    }
    /**
     * Initialize a single panel.
     * @param  {HTMLElement} 		panel 	Panel to initialize.
     * @return {HTMLElement|null} 			Initialized panel.
     */
    _initPanel(panel) {
        var _a;
        if (panel.matches('.mm-panel')) {
            return;
        }
        //	Refactor panel classnames
        reClass(panel, this.conf.classNames.panel, 'mm-panel');
        reClass(panel, this.conf.classNames.nopanel, 'mm-nopanel');
        //	Stop if not supposed to be a panel.
        if (panel.matches('.mm-nopanel')) {
            return;
        }
        //	Invoke "before" hook.
        this.trigger('initPanel:before', [panel]);
        //  Must have an ID
        panel.id = panel.id || uniqueId();
        //	Wrap UL/OL in DIV
        if (panel.matches('ul, ol')) {
            /** The panel. */
            const wrapper = create('div');
            //  Transport the ID
            wrapper.id = panel.id;
            panel.removeAttribute('id');
            //  Transport "mm-" prefixed classnames.
            [].slice
                .call(panel.classList)
                .filter((classname) => classname.slice(0, 3) === 'mm-')
                .forEach((classname) => {
                wrapper.classList.add(classname);
                panel.classList.remove(classname);
            });
            //  Transport "mm" prefixed data attributes.
            Object.keys(panel.dataset)
                .filter((attr) => attr.slice(0, 2) === 'mm')
                .forEach(attr => {
                wrapper.dataset[attr] = panel.dataset[attr];
                delete panel.dataset[attr];
            });
            //	Wrap the listview in the panel.
            panel.before(wrapper);
            wrapper.append(panel);
            panel = wrapper;
        }
        panel.classList.add('mm-panel');
        //  Append to the panels node if not vertically expanding
        if (!((_a = panel.parentElement) === null || _a === void 0 ? void 0 : _a.matches('.mm-listitem--vertical'))) {
            this.node.pnls.append(panel);
        }
        //  Initialize tha navbar.
        this._initNavbar(panel);
        //  Initialize the listview(s).
        children(panel, 'ul, ol').forEach((listview) => {
            this._initListview(listview);
        });
        // Observe the panel for added listviews.
        __classPrivateFieldGet(this, _panelObserver).observe(panel, {
            childList: true,
        });
        //	Invoke "after" hook.
        this.trigger('initPanel:after', [panel]);
        return panel;
    }
    /**
     * Initialize a navbar.
     * @param {HTMLElement} panel Panel for the navbar.
     */
    _initNavbar(panel) {
        //	Only one navbar per panel.
        if (children(panel, '.mm-navbar').length) {
            return;
        }
        /** The parent listitem. */
        let parentListitem = null;
        /** The parent panel. */
        let parentPanel = null;
        //  The parent listitem and parent panel
        if (panel.dataset.mmParent) {
            parentListitem = find(this.node.pnls, `#${panel.dataset.mmParent}`)[0];
            parentPanel = parentListitem.closest('.mm-panel');
            while (parentPanel.closest('.mm-listitem--vertical')) {
                parentPanel = parentPanel.parentElement.closest('.mm-panel');
            }
        }
        //  No navbar needed for vertical submenus.
        if (parentListitem === null || parentListitem === void 0 ? void 0 : parentListitem.matches('.mm-listitem--vertical')) {
            return;
        }
        //	Invoke "before" hook.
        this.trigger('initNavbar:before', [panel]);
        /** The navbar element. */
        const navbar = create('div.mm-navbar');
        //  Hide navbar if specified in options.
        if (!this.opts.navbar.add) {
            navbar.classList.add('mm-hidden');
        }
        //  Add the back button.
        if (parentPanel) {
            /** The back button. */
            const prev = create('a.mm-btn.mm-btn--prev.mm-navbar__btn');
            prev.href = `#${parentPanel.id}`;
            prev.setAttribute('aria-label', this.i18n(this.conf.screenReader.closeSubmenu));
            navbar.append(prev);
        }
        /** The anchor that opens the panel. */
        let opener = null;
        //  The anchor is in a listitem.
        if (parentListitem) {
            opener = children(parentListitem, '.mm-listitem__text')[0];
        }
        //  The anchor is in a panel.
        else if (parentPanel) {
            opener = find(parentPanel, 'a[href="#' + panel.id + '"]')[0];
        }
        //  Add the title.
        /** The title */
        const title = create('a.mm-navbar__title');
        title.tabIndex = -1;
        title.setAttribute('aria-hidden', 'true');
        switch (this.opts.navbar.titleLink) {
            case 'anchor':
                if (opener) {
                    title.href = opener.getAttribute('href');
                }
                break;
            case 'parent':
                if (parentPanel) {
                    title.href = `#${parentPanel.id}`;
                }
                break;
        }
        /** Text in the title */
        const titleText = create('span');
        titleText.innerHTML =
            panel.dataset.mmTitle ||
                childText(opener) ||
                this.i18n(this.opts.navbar.title || 'Menu');
        //  Add to DOM
        panel.prepend(navbar);
        navbar.append(title);
        title.append(titleText);
        //	Invoke "after" hook.
        this.trigger('initNavbar:after', [panel]);
    }
    /**
     * Initialize a listview.
     * @param {HTMLElement} listview Listview to initialize.
     */
    _initListview(listview) {
        //  Assert UL
        if (!['htmlulistelement', 'htmlolistelement'].includes(type(listview))) {
            return;
        }
        if (listview.matches('.mm-listview')) {
            return;
        }
        reClass(listview, this.conf.classNames.nolistview, 'mm-nolistview');
        if (listview.matches('.mm-nolistview')) {
            return;
        }
        //	Invoke "before" hook.
        this.trigger('initListview:before', [listview]);
        listview.classList.add('mm-listview');
        //  Initiate the listitem(s).
        children(listview).forEach((listitem) => {
            this._initListitem(listitem);
        });
        // Observe the listview for added listitems.
        __classPrivateFieldGet(this, _listviewObserver).observe(listview, {
            childList: true,
        });
        //	Invoke "after" hook.
        this.trigger('initListview:after', [listview]);
    }
    /**
     * Initialte a listitem.
     * @param {HTMLElement} listitem Listitem to initiate.
     */
    _initListitem(listitem) {
        //  Assert LI
        if (!['htmllielement'].includes(type(listitem))) {
            return;
        }
        if (listitem.matches('.mm-listitem')) {
            return;
        }
        reClass(listitem, this.conf.classNames.divider, 'mm-divider');
        if (listitem.matches('.mm-divider')) {
            return;
        }
        //	Invoke "before" hook.
        this.trigger('initListitem:before', [listitem]);
        listitem.classList.add('mm-listitem');
        reClass(listitem, this.conf.classNames.selected, 'mm-listitem--selected');
        children(listitem, 'a, span').forEach((text) => {
            text.classList.add('mm-listitem__text');
        });
        //  Initiate the subpanel.
        children(listitem, this.conf.panelNodetype.join(', ')).forEach((subpanel) => {
            this._initSubPanel(subpanel);
        });
        // Observe the listitem for added listviews.
        __classPrivateFieldGet(this, _listitemObserver).observe(listitem, {
            childList: true,
        });
        //	Invoke "after" hook.
        this.trigger('initListitem:after', [listitem]);
    }
    /**
     * Initiate a subpanel.
     * @param {HTMLElement} subpanel Subpanel to initiate.
     */
    _initSubPanel(subpanel) {
        if (subpanel.matches('.mm-panel')) {
            return;
        }
        /** The parent element for the panel. */
        const listitem = subpanel.parentElement;
        /** Whether or not the listitem expands vertically */
        const vertical = subpanel.matches('.' + this.conf.classNames.vertical) ||
            !this.opts.slidingSubmenus;
        // Make it expand vertically
        if (vertical) {
            listitem.classList.add('mm-listitem--vertical');
        }
        //  Force an ID
        listitem.id = listitem.id || uniqueId();
        subpanel.id = subpanel.id || uniqueId();
        //  Store parent/child relation
        listitem.dataset.mmChild = subpanel.id;
        subpanel.dataset.mmParent = listitem.id;
        /** The open link. */
        let button = children(listitem, '.mm-btn')[0];
        //  Init item text
        if (!button) {
            button = create('a.mm-btn.mm-btn--next.mm-listitem__btn');
            children(listitem, 'a, span').forEach((text) => {
                //  If the item has no link,
                //      Replace the item with the open link.
                if (text.matches('span')) {
                    button.classList.add('mm-listitem__text');
                    button.innerHTML = text.innerHTML;
                    listitem.insertBefore(button, text.nextElementSibling);
                    text.remove();
                }
                //  Otherwise, insert the button after the text.
                else {
                    listitem.insertBefore(button, text.nextElementSibling);
                }
            });
            button.setAttribute('aria-label', this.i18n(this.conf.screenReader[listitem.matches('.mm-listitem--vertical')
                ? 'toggleSubmenu'
                : 'openSubmenu']));
        }
        button.href = `#${subpanel.id}`;
        this._initPanel(subpanel);
    }
    /**
     * Find and open the correct panel after creating the menu.
     */
    _initOpened() {
        //	Invoke "before" hook.
        this.trigger('initOpened:before');
        /** The selected listitem(s). */
        const listitem = find(this.node.pnls, '.mm-listitem--selected').pop();
        /**	The current opened panel. */
        let panel = children(this.node.pnls, '.mm-panel')[0];
        if (listitem) {
            this.setSelected(listitem);
            panel = listitem.closest('.mm-panel');
        }
        //	Open the current opened panel.
        this.openPanel(panel, false, false);
        //	Invoke "after" hook.
        this.trigger('initOpened:after');
    }
    /**
     * Get the translation for a text.
     * @param  {string}     text 	Text to translate.
     * @return {string}		        The translated text.
     */
    i18n(text) {
        return get(text, this.conf.language);
    }
    /**
     * Get all translations for the given language.
     * @return {object}	The translations.
     */
    static i18n(text = {}, language = '') {
        if (text && language) {
            add$1(text, language);
        }
        else {
            return show();
        }
    }
}
_panelObserver = new WeakMap(), _listviewObserver = new WeakMap(), _listitemObserver = new WeakMap();
/**	Available add-ons for the plugin. */
Mmenu.addons = {};
/**	Globally used HTML elements. */
Mmenu.node = {};
/** Globally used v. */
Mmenu.vars = {};

const options$b = {
    use: true,
    position: 'left'
};

const configs$3 = {
    clone: false,
    menu: {
        insertMethod: 'append',
        insertSelector: 'body'
    },
    page: {
        nodetype: 'div',
        selector: null,
        noSelector: []
    },
    screenReader: {
        closeMenu: 'Close menu',
        openMenu: 'Open menu',
    }
};

const possiblePositions = [
    'left',
    'left-front',
    'right',
    'right-front',
    'top',
    'bottom'
];
function offcanvas () {
    this.opts.offCanvas = this.opts.offCanvas || {};
    this.conf.offCanvas = this.conf.offCanvas || {};
    //	Extend options.
    const options = extend$1(this.opts.offCanvas, options$b);
    const configs = extend$1(this.conf.offCanvas, configs$3);
    if (!options.use) {
        return;
    }
    if (!possiblePositions.includes(options.position)) {
        options.position = possiblePositions[0];
    }
    //	Add methods to the API.
    this._api.push('open', 'close', 'setPage', 'position');
    //  Clone menu if needed.
    if (configs.clone) {
        //	Clone the original menu and store it.
        this.node.menu = this.node.menu.cloneNode(true);
        //	Prefix all ID's in the cloned menu.
        if (this.node.menu.id) {
            this.node.menu.id = cloneId(this.node.menu.id);
        }
        find(this.node.menu, '[id]').forEach((elem) => {
            elem.id = cloneId(elem.id);
        });
    }
    //  Prepend the menu to the <body>.
    this.bind('initMenu:before', () => {
        this.node.wrpr = document.querySelector(configs.menu.insertSelector);
        this.node.wrpr[configs.menu.insertMethod](this.node.menu);
    });
    //	Setup the UI blocker.
    if (!Mmenu.node.blck) {
        this.bind('initMenu:before', () => {
            /** The UI blocker node. */
            const blocker = create('a.mm-wrapper__blocker.mm-blocker.mm-slideout');
            blocker.id = uniqueId();
            blocker.setAttribute('aria-label', this.i18n(configs.screenReader.closeMenu));
            blocker.setAttribute('inert', 'true');
            //	Append the blocker node to the body.
            document.querySelector(configs.menu.insertSelector).append(blocker);
            //	Store the blocker node.
            Mmenu.node.blck = blocker;
        });
    }
    this.bind('initMenu:after', () => {
        //	Setup the page.
        this.setPage(Mmenu.node.page);
        //	Setup the menu.
        this.node.menu.classList.add('mm-menu--offcanvas');
        this.node.menu.setAttribute('inert', 'true');
        if (possiblePositions.includes(options.position)) {
            this.node.wrpr.classList.add(`mm-wrapper--position-${options.position}`);
            this.node.menu.classList.add(`mm-menu--position-${options.position}`);
        }
        //	Open if url hash equals menu id (usefull when user clicks the hamburger icon before the menu is created)
        let hash = window.location.hash;
        if (hash) {
            let id = originalId(this.node.menu.id);
            if (id && id == hash.slice(1)) {
                setTimeout(() => {
                    this.open();
                }, 1000);
            }
        }
    });
    //	Open / close the menu.
    document.addEventListener('click', event => {
        var _a;
        /** THe href attribute for the clicked anchor. */
        const href = (_a = event.target.closest('a')) === null || _a === void 0 ? void 0 : _a.getAttribute('href');
        switch (href) {
            //	Open menu if the clicked anchor links to the menu.
            case `#${originalId(this.node.menu.id)}`:
                event.preventDefault();
                this.open();
                break;
            //	Close menu if the clicked anchor links to the page.
            case `#${originalId(Mmenu.node.page.id)}`:
                event.preventDefault();
                this.close();
                break;
        }
    });
    //	Close the menu with ESC key.
    document.addEventListener('keyup', (event) => {
        if (event.key == 'Escape') {
            this.close();
        }
    });
}
/**
 * Open the menu.
 */
Mmenu.prototype.open = function () {
    if (this.node.menu.matches('.mm-menu--opened')) {
        return;
    }
    //	Invoke "before" hook.
    this.trigger('open:before');
    //	Open
    this.node.wrpr.classList.add('mm-wrapper--opened', `mm-wrapper--position-${this.opts.offCanvas.position}`);
    this.node.menu.classList.add('mm-menu--opened');
    this.node.menu.removeAttribute('inert');
    Mmenu.node.blck.removeAttribute('inert');
    Mmenu.node.page.setAttribute('inert', 'true');
    //  Store the last focesed element.
    this.node.open = document.activeElement;
    //	Invoke "after" hook.
    this.trigger('open:after');
};
/**
 * Close the menu.
 */
Mmenu.prototype.close = function () {
    var _a;
    if (!this.node.menu.matches('.mm-menu--opened')) {
        return;
    }
    //	Invoke "before" hook.
    this.trigger('close:before');
    this.node.wrpr.classList.remove('mm-wrapper--opened', `mm-wrapper--position-${this.opts.offCanvas.position}`);
    this.node.menu.classList.remove('mm-menu--opened');
    this.node.menu.setAttribute('inert', 'true');
    Mmenu.node.blck.setAttribute('inert', 'true');
    Mmenu.node.page.removeAttribute('inert');
    /** Element to focus. */
    const focus = this.node.open || document.querySelector(`[href="#${this.node.menu.id}"]`) || null;
    (_a = focus) === null || _a === void 0 ? void 0 : _a.focus();
    // Prevent html/body from scrolling due to focus.
    document.body.scrollLeft = 0;
    document.documentElement.scrollLeft = 0;
    //	Invoke "after" hook.
    this.trigger('close:after');
};
/**
 * Set the "page" node.
 *
 * @param {HTMLElement} page Element to set as the page.
 */
Mmenu.prototype.setPage = function (page) {
    /** Offcanvas config */
    const configs = this.conf.offCanvas;
    //	If no page was specified, find it.
    if (!page) {
        /** Array of elements that are / could be "the page". */
        let pages = typeof configs.page.selector == 'string'
            ? find(document.body, configs.page.selector)
            : children(document.body, configs.page.nodetype);
        //	Filter out elements that are absolutely not "the page".
        pages = pages.filter((page) => !page.matches('.mm-menu, .mm-wrapper__blocker'));
        //	Filter out elements that are configured to not be "the page".
        if (configs.page.noSelector.length) {
            pages = pages.filter((page) => !page.matches(configs.page.noSelector.join(', ')));
        }
        //	Wrap multiple pages in a single element.
        if (pages.length > 1) {
            let wrapper = create('div');
            pages[0].before(wrapper);
            pages.forEach((page) => {
                wrapper.append(page);
            });
            pages = [wrapper];
        }
        page = pages[0];
    }
    //	Invoke "before" hook.
    this.trigger('setPage:before', [page]);
    //  Set the classes
    page.classList.add('mm-page', 'mm-slideout');
    //  Set the ID.
    page.id = page.id || uniqueId();
    //	Sync the blocker to target the page.
    Mmenu.node.blck.setAttribute('href', `#${page.id}`);
    //	Store the page node.
    Mmenu.node.page = page;
    //	Invoke "after" hook.
    this.trigger('setPage:after', [page]);
};

const options$a = {
    fix: true
};

/** Whether or not touch gestures are supported by the browser. */
const touch = 'ontouchstart' in window ||
    (navigator.msMaxTouchPoints ? true : false) ||
    false;

function scrollBugFix () {
    //	The scrollBugFix add-on fixes a scrolling bug
    //		1) on touch devices
    //		2) in an off-canvas menu
    if (!touch || // 1
        !this.opts.offCanvas.use // 2
    ) {
        return;
    }
    this.opts.scrollBugFix = this.opts.scrollBugFix || {};
    //	Extend options.
    const options = extend$1(this.opts.scrollBugFix, options$a);
    if (!options.fix) {
        return;
    }
    /** The touch-direction instance. */
    const touchDir = touchDirection(this.node.menu);
    //  Prevent the page from scrolling when scrolling in the menu.
    this.node.menu.addEventListener('scroll', evnt => {
        evnt.preventDefault();
        evnt.stopPropagation();
    }, {
        //  Make sure to tell the browser the event will be prevented.
        passive: false,
    });
    //  Prevent the page from scrolling when dragging in the menu.
    this.node.menu.addEventListener('touchmove', evnt => {
        let wrapper = evnt.target.closest('.mm-panel, .mm-iconbar__top, .mm-iconbar__bottom');
        if (wrapper && wrapper.closest('.mm-listitem--vertical')) {
            wrapper = parents(wrapper, '.mm-panel').pop();
        }
        if (wrapper) {
            //  When dragging a non-scrollable panel/iconbar,
            //      we can simply stopPropagation.
            if (wrapper.scrollHeight === wrapper.offsetHeight) {
                evnt.stopPropagation();
            }
            //  When dragging a scrollable panel/iconbar,
            //      that is fully scrolled up (or down).
            //      It will not trigger the scroll event when dragging down (or up) (because you can't scroll up (or down)),
            //      so we need to match the dragging direction with the scroll position before preventDefault and stopPropagation,
            //      otherwise the panel would not scroll at all in any direction.
            else if (
            //  When scrolled up and dragging down
            (wrapper.scrollTop == 0 && touchDir.get() == 'down') ||
                //  When scrolled down and dragging up
                (wrapper.scrollHeight ==
                    wrapper.scrollTop + wrapper.offsetHeight &&
                    touchDir.get() == 'up')) {
                evnt.stopPropagation();
            }
            //  When dragging anything other than a panel/iconbar.
        }
        else {
            evnt.stopPropagation();
        }
    }, {
        //  Make sure to tell the browser the event can be prevented.
        passive: false,
    });
    //  Some small additional improvements
    //	Scroll the current opened panel to the top when opening the menu.
    this.bind('open:after', () => {
        var panel = children(this.node.pnls, '.mm-panel--opened')[0];
        if (panel) {
            panel.scrollTop = 0;
        }
    });
    //	Fix issue after device rotation change.
    window.addEventListener('orientationchange', (evnt) => {
        var panel = children(this.node.pnls, '.mm-panel--opened')[0];
        if (panel) {
            panel.scrollTop = 0;
            //	Apparently, changing the overflow-scrolling property triggers some event :)
            panel.style['-webkit-overflow-scrolling'] = 'auto';
            panel.style['-webkit-overflow-scrolling'] = 'touch';
        }
    });
}

const options$9 = 'light';

/** A list of available themes. */
const possibleThemes = [
    'light',
    'dark',
    'white',
    'black',
    'light-contrast',
    'dark-contrast',
    'white-contrast',
    'black-contrast'
];
function theme () {
    this.opts.theme = this.opts.theme || options$9;
    const theme = this.opts.theme;
    if (!possibleThemes.includes(theme)) {
        this.opts.theme = possibleThemes[0];
    }
    this._api.push('theme');
    this.bind('initMenu:after', () => {
        this.theme(this.opts.theme);
    });
}
/**
 * Get or set the theme for the menu.
 *
 * @param {string} [position] The theme for the menu.
 */
Mmenu.prototype.theme = function (theme = null) {
    /** The previously used theme */
    const oldTheme = this.opts.theme;
    if (theme) {
        if (possibleThemes.includes(theme)) {
            this.node.menu.classList.remove(`mm-menu--theme-${oldTheme}`);
            this.node.menu.classList.add(`mm-menu--theme-${theme}`);
            this.opts.theme = theme;
        }
    }
    else {
        return oldTheme;
    }
};

const options$8 = {
    close: false,
    open: false
};

function backButton () {
    this.opts.backButton = this.opts.backButton || {};
    if (!this.opts.offCanvas.use) {
        return;
    }
    //	Extend options.
    const options = extend$1(this.opts.backButton, options$8);
    const _menu = `#${this.node.menu.id}`;
    //	Close menu
    if (options.close) {
        let states = [];
        const setStates = () => {
            states = [_menu];
            children(this.node.pnls, '.mm-panel--opened, .mm-panel--parent').forEach((panel) => {
                states.push('#' + panel.id);
            });
        };
        this.bind('open:after', () => {
            history.pushState(null, '', location.pathname + location.search + _menu);
        });
        this.bind('open:after', setStates);
        this.bind('openPanel:after', setStates);
        this.bind('close:after', () => {
            states = [];
            history.back();
            history.pushState(null, '', location.pathname + location.search);
        });
        window.addEventListener('popstate', () => {
            if (this.node.menu.matches('.mm-menu--opened')) {
                if (states.length) {
                    states = states.slice(0, -1);
                    const hash = states[states.length - 1];
                    if (hash == _menu) {
                        this.close();
                    }
                    else {
                        this.openPanel(this.node.menu.querySelector(hash));
                        history.pushState(null, '', location.pathname + location.search + _menu);
                    }
                }
            }
        });
    }
    if (options.open) {
        window.addEventListener('popstate', (evnt) => {
            if (!this.node.menu.matches('.mm-menu--opened') && location.hash == _menu) {
                this.open();
            }
        });
    }
}

const options$7 = {
    add: false
};

function counters () {
    this.opts.counters = this.opts.counters || {};
    //	Extend options.
    const options = extend$1(this.opts.counters, options$7);
    if (!options.add) {
        return;
    }
    /**
     * Counting the visible listitems and setting it to the counter element.
     * @param {HTMLElement} panel Panel to count LIs in.
     */
    const count = (panel) => {
        /** Parent panel for the mutated listitem. */
        const parent = this.node.pnls.querySelector(`#${panel.dataset.mmParent}`);
        if (!parent) {
            return;
        }
        /** The counter for the listitem. */
        const counter = parent.querySelector('.mm-counter');
        if (!counter) {
            return;
        }
        /** The listitems */
        const listitems = [];
        children(panel, '.mm-listview').forEach((listview) => {
            listitems.push(...children(listview, '.mm-listitem'));
        });
        counter.innerHTML = filterLI(listitems).length.toString();
    };
    /** Mutation observer the the listitems. */
    const listitemObserver = new MutationObserver((mutationsList) => {
        mutationsList.forEach((mutation) => {
            if (mutation.attributeName == 'class') {
                count(mutation.target.closest('.mm-panel'));
            }
        });
    });
    //	Add the counters after a listview is initiated.
    this.bind('initListview:after', (listview) => {
        /** The panel where the listview is in. */
        const panel = listview.closest('.mm-panel');
        /** The parent LI for the panel */
        const parent = this.node.pnls.querySelector(`#${panel.dataset.mmParent}`);
        if (!parent) {
            return;
        }
        /** The button inside the parent LI */
        const button = children(parent, '.mm-btn')[0];
        if (!button) {
            return;
        }
        //	Check if no counter already excists.
        if (!children(button, '.mm-counter').length) {
            /** The counter for the listitem. */
            const counter = create('span.mm-counter');
            counter.setAttribute('aria-hidden', 'true');
            button.prepend(counter);
        }
        //  Count immediately.
        count(panel);
    });
    //  Count when LI classname changes.
    this.bind('initListitem:after', (listitem) => {
        /** The panel where the listitem is in. */
        const panel = listitem.closest('.mm-panel');
        if (!panel) {
            return;
        }
        /** The parent LI for the panel. */
        const parent = this.node.pnls.querySelector(`#${panel.dataset.mmParent}`);
        if (!parent) {
            return;
        }
        listitemObserver.observe(listitem, {
            attributes: true
        });
    });
}

const options$6 = {
    use: false,
    top: [],
    bottom: [],
    position: 'left',
    type: 'default'
};

function iconbar () {
    this.opts.iconbar = this.opts.iconbar || {};
    //	Extend options.
    const options = extend$1(this.opts.iconbar, options$6);
    if (!options.use) {
        return;
    }
    let iconbar;
    ['top', 'bottom'].forEach((position, n) => {
        let ctnt = options[position];
        //	Extend shorthand options
        if (type(ctnt) != 'array') {
            ctnt = [ctnt];
        }
        //	Create node
        const part = create('div.mm-iconbar__' + position);
        //	Add content
        for (let c = 0, l = ctnt.length; c < l; c++) {
            if (typeof ctnt[c] == 'string') {
                part.innerHTML += ctnt[c];
            }
            else {
                part.append(ctnt[c]);
            }
        }
        if (part.children.length) {
            if (!iconbar) {
                iconbar = create('div.mm-iconbar');
            }
            iconbar.append(part);
        }
    });
    //	Add to menu
    if (iconbar) {
        //	Add the iconbar.
        this.bind('initMenu:after', () => {
            this.node.menu.prepend(iconbar);
        });
        //	En-/disable the iconbar.
        let classname = 'mm-menu--iconbar-' + options.position;
        let enable = () => {
            this.node.menu.classList.add(classname);
        };
        let disable = () => {
            this.node.menu.classList.remove(classname);
        };
        if (typeof options.use == 'boolean') {
            this.bind('initMenu:after', enable);
        }
        else {
            add(options.use, enable, disable);
        }
        //	Tabs
        if (options.type == 'tabs') {
            iconbar.classList.add('mm-iconbar--tabs');
            iconbar.addEventListener('click', (evnt) => {
                const anchor = evnt.target.closest('.mm-iconbar__tab');
                if (!anchor) {
                    return;
                }
                if (anchor.matches('.mm-iconbar__tab--selected')) {
                    evnt.stopImmediatePropagation();
                    return;
                }
                try {
                    const panel = find(this.node.menu, `${anchor.getAttribute('href')}.mm-panel`)[0];
                    if (panel) {
                        evnt.preventDefault();
                        evnt.stopImmediatePropagation();
                        this.openPanel(panel, false);
                    }
                }
                catch (err) { }
            });
            const selectTab = (panel) => {
                find(iconbar, 'a').forEach((anchor) => {
                    anchor.classList.remove('mm-iconbar__tab--selected');
                });
                const anchor = find(iconbar, '[href="#' + panel.id + '"]')[0];
                if (anchor) {
                    anchor.classList.add('mm-iconbar__tab--selected');
                }
                else {
                    const parent = find(this.node.pnls, `#${panel.dataset.mmParent}`)[0];
                    if (parent) {
                        selectTab(parent.closest('.mm-panel'));
                    }
                }
            };
            this.bind('openPanel:before', selectTab);
        }
    }
}

const options$5 = {
    add: false,
    blockPanel: true,
    visible: 3
};

function iconPanels () {
    this.opts.iconPanels = this.opts.iconPanels || {};
    //	Extend options.
    const options = extend$1(this.opts.iconPanels, options$5);
    let keepFirst = false;
    if (options.visible == 'first') {
        keepFirst = true;
        options.visible = 1;
    }
    options.visible = Math.min(3, Math.max(1, options.visible));
    options.visible++;
    //	Add the iconpanels
    if (options.add) {
        this.bind('initMenu:after', () => {
            this.node.menu.classList.add('mm-menu--iconpanel');
        });
        /** The classnames that can be set to a panel */
        const classnames = [
            'mm-panel--iconpanel-0',
            'mm-panel--iconpanel-1',
            'mm-panel--iconpanel-2',
            'mm-panel--iconpanel-3'
        ];
        //  Show only the main panel.
        if (keepFirst) {
            this.bind('initMenu:after', () => {
                var _a;
                (_a = children(this.node.pnls, '.mm-panel')[0]) === null || _a === void 0 ? void 0 : _a.classList.add('mm-panel--iconpanel-first');
            });
            //  Show parent panel(s).
        }
        else {
            this.bind('openPanel:after', (panel) => {
                //  Do nothing when opening a vertical submenu
                if (panel.closest('.mm-listitem--vertical')) {
                    return;
                }
                let panels = children(this.node.pnls, '.mm-panel');
                //	Filter out panels that are not opened.
                panels = panels.filter((panel) => panel.matches('.mm-panel--parent'));
                //	Add the current panel to the list.
                panels.push(panel);
                //	Slice the opened panels to the max visible amount.
                panels = panels.slice(-options.visible);
                //	Add the "iconpanel" classnames.
                panels.forEach((panel, p) => {
                    panel.classList.remove('mm-panel--iconpanel-first', ...classnames);
                    panel.classList.add(`mm-panel--iconpanel-${p}`);
                });
            });
        }
        // this.bind('initPanel:after', (panel: HTMLElement) => {
        //     if (!panel.closest('.mm-listitem--vertical') &&
        //         !DOM.children(panel, '.mm-panel__blocker')[0]
        //     ) {
        //         const blocker = DOM.create('div.mm-blocker.mm-panel__blocker') as HTMLElement;
        //         panel.prepend(blocker);
        //     }
        // });
    }
}

const configs$2 = {
    breadcrumbs: {
        separator: '/',
        removeFirst: false
    }
};

/**
 * Extend shorthand options.
 *
 * @param  {object} options The options to extend.
 * @return {object}			The extended options.
 */
function extendShorthandOptions(options) {
    if (typeof options == 'boolean' && options) {
        options = {};
    }
    if (typeof options != 'object') {
        options = {};
    }
    if (typeof options.content == 'undefined') {
        options.content = ['prev', 'title'];
    }
    if (!(options.content instanceof Array)) {
        options.content = [options.content];
    }
    if (typeof options.use == 'undefined') {
        options.use = true;
    }
    return options;
}

function breadcrumbs (navbar) {
    //	Add content
    var breadcrumbs = create('div.mm-navbar__breadcrumbs');
    navbar.append(breadcrumbs);
    this.bind('initNavbar:after', (panel) => {
        if (panel.querySelector('.mm-navbar__breadcrumbs')) {
            return;
        }
        children(panel, '.mm-navbar')[0].classList.add('mm-hidden');
        var crumbs = [], breadcrumbs = create('span.mm-navbar__breadcrumbs'), current = panel, first = true;
        while (current) {
            current = current.closest('.mm-panel');
            if (!current.parentElement.matches('.mm-listitem--vertical')) {
                let title = find(current, '.mm-navbar__title span')[0];
                if (title) {
                    let text = title.textContent;
                    if (text.length) {
                        crumbs.unshift(first
                            ? `<span>${text}</span>`
                            : `<a 
                                    href="#${current.id}" 
                                    title="${this.i18n(this.conf.screenReader.openSubmenu)}"
                                    >${text}</a>`);
                    }
                }
                first = false;
            }
            current = find(this.node.pnls, `#${current.dataset.mmParent}`)[0];
        }
        if (this.conf.navbars.breadcrumbs.removeFirst) {
            crumbs.shift();
        }
        breadcrumbs.innerHTML = crumbs.join('<span class="mm-separator">' +
            this.conf.navbars.breadcrumbs.separator +
            '</span>');
        children(panel, '.mm-navbar')[0].append(breadcrumbs);
    });
    //	Update for to opened panel
    this.bind('openPanel:before', (panel) => {
        var crumbs = panel.querySelector('.mm-navbar__breadcrumbs');
        breadcrumbs.innerHTML = crumbs ? crumbs.innerHTML : '';
    });
}

function close (navbar) {
    /** The close button. */
    const close = create('a.mm-btn.mm-btn--close.mm-navbar__btn');
    close.setAttribute('aria-label', this.i18n(this.conf.offCanvas.screenReader.closeMenu));
    //	Add the button to the navbar.
    navbar.append(close);
    //	Update to target the page node.
    this.bind('setPage:after', (page) => {
        close.href = `#${page.id}`;
    });
}

function prev (navbar) {
    /** The prev button. */
    let prev = create('a.mm-btn.mm-hidden');
    //	Add button to navbar.
    navbar.append(prev);
    //  Hide navbar in the panel.
    this.bind('initNavbar:after', (panel) => {
        children(panel, '.mm-navbar')[0].classList.add('mm-hidden');
    });
    // Update the button href when opening a panel.
    this.bind('openPanel:before', (panel) => {
        if (panel.parentElement.matches('.mm-listitem--vertical')) {
            return;
        }
        prev.classList.add('mm-hidden');
        /** Original button in the panel. */
        const original = panel.querySelector('.mm-navbar__btn.mm-btn--prev');
        if (original) {
            /** Clone of the original button in the panel. */
            const clone = original.cloneNode(true);
            prev.after(clone);
            prev.remove();
            prev = clone;
        }
    });
}

function searchfield$1 (navbar) {
    /** Empty wrapper for the searchfield. */
    let wrapper = create('div.mm-navbar__searchfield');
    wrapper.id = uniqueId();
    //	Add button to navbar.
    navbar.append(wrapper);
    this.opts.searchfield = this.opts.searchfield || {};
    this.opts.searchfield.add = true;
    this.opts.searchfield.addTo = `#${wrapper.id}`;
}

function title (navbar) {
    /** The title node in the navbar. */
    let title = create('a.mm-navbar__title');
    //	Add title to the navbar.
    navbar.append(title);
    //	Update the title to the opened panel.
    this.bind('openPanel:before', (panel) => {
        //	Do nothing in a vertically expanding panel.
        if (panel.parentElement.matches('.mm-listitem--vertical')) {
            return;
        }
        /** Original title in the panel. */
        const original = panel.querySelector('.mm-navbar__title');
        if (original) {
            /** Clone of the original title in the panel. */
            const clone = original.cloneNode(true);
            title.after(clone);
            title.remove();
            title = clone;
        }
    });
}

function tabs (navbar) {
    navbar.classList.add('mm-navbar--tabs');
    navbar.closest('.mm-navbars').classList.add('mm-navbars--has-tabs');
    children(navbar, 'a').forEach(anchor => {
        anchor.classList.add('mm-navbar__tab');
    });
    /**
     * Mark a tab as selected.
     * @param {HTMLElement} panel Opened panel.
     */
    function selectTab(panel) {
        /** The tab that links to the opened panel. */
        const anchor = children(navbar, `.mm-navbar__tab[href="#${panel.id}"]`)[0];
        if (anchor) {
            anchor.classList.add('mm-navbar__tab--selected');
            // @ts-ignore
            anchor.ariaExpanded = 'true';
        }
        else {
            /** The parent listitem. */
            const parent = find(this.node.pnls, `#${panel.dataset.mmParent}`)[0];
            if (parent) {
                selectTab.call(this, parent.closest('.mm-panel'));
            }
        }
    }
    this.bind('openPanel:before', (panel) => {
        //  Remove selected class.
        children(navbar, 'a').forEach(anchor => {
            anchor.classList.remove('mm-navbar__tab--selected');
            // @ts-ignore
            anchor.ariaExpanded = 'false';
        });
        selectTab.call(this, panel);
    });
    this.bind('initPanels:after', () => {
        //	Add animation class to panel.
        navbar.addEventListener('click', event => {
            var _a, _b, _c;
            /** The href for the clicked tab. */
            const href = (_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('.mm-navbar__tab')) === null || _b === void 0 ? void 0 : _b.getAttribute('href');
            try {
                (_c = find(this.node.pnls, `${href}.mm-panel`)[0]) === null || _c === void 0 ? void 0 : _c.classList.add('mm-panel--noanimation');
            }
            catch (err) { }
        }, {
            // useCapture to ensure the logical order.
            capture: true
        });
    });
}

Navbars.navbarContents = {
    breadcrumbs,
    close,
    prev,
    searchfield: searchfield$1,
    title,
};
Navbars.navbarTypes = {
    tabs,
};
function Navbars() {
    this.opts.navbars = this.opts.navbars || [];
    this.conf.navbars = this.conf.navbars || {};
    //	Extend options.
    extend$1(this.conf.navbars, configs$2);
    let navs = this.opts.navbars;
    if (typeof navs == 'undefined') {
        return;
    }
    if (!(navs instanceof Array)) {
        navs = [navs];
    }
    if (!navs.length) {
        return;
    }
    var navbars = {};
    navs.forEach((options) => {
        options = extendShorthandOptions(options);
        if (!options.use) {
            return;
        }
        //	Create the navbar element.
        const navbar = create('div.mm-navbar');
        //	Get the position for the navbar.
        let { position } = options;
        //	Restrict the position to either "bottom" or "top" (default).
        if (position !== 'bottom') {
            position = 'top';
        }
        //	Create the wrapper for the navbar position.
        if (!navbars[position]) {
            navbars[position] = create('div.mm-navbars.mm-navbars--' + position);
        }
        navbars[position].append(navbar);
        //	Add content to the navbar.
        for (let c = 0, l = options.content.length; c < l; c++) {
            const ctnt = options.content[c];
            //	The content is a string.
            if (typeof ctnt == 'string') {
                const func = Navbars.navbarContents[ctnt];
                //	The content refers to one of the navbar-presets ("prev", "title", etc).
                if (typeof func == 'function') {
                    //	Call the preset function.
                    func.call(this, navbar);
                    //	The content is just HTML.
                }
                else {
                    //	Add the HTML.
                    //  Wrap the HTML in a single node
                    let node = create('span');
                    node.innerHTML = ctnt;
                    //  If there was only a single node, use that.
                    const children$1 = children(node);
                    if (children$1.length == 1) {
                        node = children$1[0];
                    }
                    navbar.append(node);
                }
                //	The content is not a string, it must be an element.
            }
            else {
                navbar.append(ctnt);
            }
        }
        //	The type option is set.
        if (typeof options.type == 'string') {
            //	The function refers to one of the navbar-presets ("tabs").
            const func = Navbars.navbarTypes[options.type];
            if (typeof func == 'function') {
                //	Call the preset function.
                func.call(this, navbar);
            }
        }
        //	En-/disable the navbar.
        let enable = () => {
            navbar.classList.remove('mm-hidden');
        };
        let disable = () => {
            navbar.classList.add('mm-hidden');
        };
        if (typeof options.use == 'boolean') {
            this.bind('initMenu:after', enable);
        }
        else {
            add(options.use, enable, disable);
        }
    });
    //	Add to menu.
    this.bind('initMenu:after', () => {
        for (let position in navbars) {
            this.node.pnls[position == 'bottom' ? 'after' : 'before'](navbars[position]);
        }
    });
}

const options$4 = {
    scroll: false,
    update: false
};

const configs$1 = {
    scrollOffset: 0,
    updateOffset: 50
};

function pageScroll () {
    this.opts.pageScroll = this.opts.pageScroll || {};
    this.conf.pageScroll = this.conf.pageScroll || {};
    //	Extend options.
    const options = extend$1(this.opts.pageScroll, options$4);
    const configs = extend$1(this.conf.pageScroll, configs$1);
    /** The currently "active" section */
    var section;
    function scrollTo() {
        if (section) {
            // section.scrollIntoView({ behavior: 'smooth' });
            window.scrollTo({
                top: section.getBoundingClientRect().top +
                    document.scrollingElement.scrollTop -
                    configs.scrollOffset,
                behavior: 'smooth'
            });
        }
        section = null;
    }
    function anchorInPage(href) {
        try {
            if (href.slice(0, 1) == '#') {
                return find(Mmenu.node.page, href)[0];
            }
        }
        catch (err) { }
        return null;
    }
    if (this.opts.offCanvas.use && options.scroll) {
        //	Scroll to section after clicking menu item.
        this.bind('close:after', () => {
            scrollTo();
        });
        this.node.menu.addEventListener('click', event => {
            var _a, _b;
            const href = ((_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a[href]')) === null || _b === void 0 ? void 0 : _b.getAttribute('href')) || '';
            section = anchorInPage(href);
            if (section) {
                event.preventDefault();
                //	If the sidebar add-on is "expanded"...
                if (this.node.menu.matches('.mm-menu--sidebar-expanded') &&
                    this.node.wrpr.matches('.mm-wrapper--sidebar-expanded')) {
                    //	... scroll the page to the section.
                    scrollTo();
                    //	... otherwise...
                }
                else {
                    //	... close the menu.
                    this.close();
                }
            }
        });
    }
    //	Update selected menu item after scrolling.
    if (options.update) {
        let scts = [];
        this.bind('initListview:after', (listview) => {
            const listitems = children(listview, '.mm-listitem');
            filterLIA(listitems).forEach(anchor => {
                const section = anchorInPage(anchor.getAttribute('href'));
                if (section) {
                    scts.unshift(section);
                }
            });
        });
        let _selected = -1;
        window.addEventListener('scroll', evnt => {
            const scrollTop = window.scrollY;
            for (var s = 0; s < scts.length; s++) {
                if (scts[s].offsetTop < scrollTop + configs.updateOffset) {
                    if (_selected !== s) {
                        _selected = s;
                        let panel = children(this.node.pnls, '.mm-panel--opened')[0];
                        let listitems = find(panel, '.mm-listitem');
                        let anchors = filterLIA(listitems);
                        anchors = anchors.filter(anchor => anchor.matches('[href="#' + scts[s].id + '"]'));
                        if (anchors.length) {
                            this.setSelected(anchors[0].parentElement);
                        }
                    }
                    break;
                }
            }
        }, {
            passive: true
        });
    }
}

const options$3 = {
    add: false,
    addTo: 'panels',
    noResults: 'No results found.',
    placeholder: 'Search',
    search: true,
    searchIn: 'panels',
    splash: '',
    title: 'Search',
};

const configs = {
    cancel: true,
    clear: true,
    form: {},
    input: {},
    panel: {},
    submit: false
};

var de = {
    'cancel': 'abbrechen',
    'Cancel searching': 'Suche abbrechen',
    'Clear searchfield': 'Suchfeld löschen',
    'No results found.': 'Keine Ergebnisse gefunden.',
    'Search': 'Suche',
};

var fa = {
    'cancel': 'انصراف',
    'Cancel searching': 'لغو جستجو',
    'Clear searchfield': 'پاک کردن فیلد جستجو',
    'No results found.': 'نتیجه‌ای یافت نشد.',
    'Search': 'جستجو',
};

var nl = {
    'cancel': 'annuleren',
    'Cancel searching': 'Zoeken annuleren',
    'Clear searchfield': 'Zoekveld leeg maken',
    'No results found.': 'Geen resultaten gevonden.',
    'Search': 'Zoeken',
};

var pt_br = {
    'cancel': 'cancelar',
    'Cancel searching': 'Cancelar pesquisa',
    'Clear searchfield': 'Limpar campo de pesquisa',
    'No results found.': 'Nenhum resultado encontrado.',
    'Search': 'Buscar',
};

var ru = {
    'cancel': 'отменить',
    'Cancel searching': 'Отменить поиск',
    'Clear searchfield': 'Очистить поле поиска',
    'No results found.': 'Ничего не найдено.',
    'Search': 'Найти',
};

var sk = {
    'cancel': 'zrušiť',
    'Cancel searching': 'Zrušiť vyhľadávanie',
    'Clear searchfield': 'Vymazať pole vyhľadávania',
    'No results found.': 'Neboli nájdené žiadne výsledky.',
    'Search': 'Vyhľadávanie',
};

var uk = {
    'cancel': 'скасувати',
    'Cancel searching': 'Скасувати пошук',
    'Clear searchfield': 'Очистити поле пошуку',
    'No results found.': 'Нічого не знайдено.',
    'Search': 'Пошук',
};

function translate () {
    add$1(de, 'de');
    add$1(fa, 'fa');
    add$1(nl, 'nl');
    add$1(pt_br, 'pt_br');
    add$1(ru, 'ru');
    add$1(sk, 'sk');
    add$1(uk, 'uk');
}

//  Add the translations.
translate();
function searchfield () {
    this.opts.searchfield = this.opts.searchfield || {};
    this.conf.searchfield = this.conf.searchfield || {};
    //	Extend options.
    const options = extend$1(this.opts.searchfield, options$3);
    extend$1(this.conf.searchfield, configs);
    if (!options.add) {
        return;
    }
    switch (options.addTo) {
        case 'panels':
            options.addTo = '.mm-panel';
            break;
        case 'searchpanel':
            options.addTo = '.mm-panel--search';
            break;
    }
    switch (options.searchIn) {
        case 'panels':
            options.searchIn = '.mm-panel';
            break;
    }
    //  Add a searchfield to panels matching the "addTo" querySelector.
    this.bind('initPanel:after', (panel) => {
        if (panel.matches(options.addTo) &&
            !panel.closest('.mm-listitem--vertical')) {
            initPanel.call(this, panel);
        }
    });
    this.bind('initMenu:after', () => {
        //  Create the resultspanel.
        const resultspanel = createResultsPanel.call(this);
        initPanel.call(this, resultspanel);
        //  Add a searchfield to anything other than a panel (most likely a navbar).
        find(this.node.menu, options.addTo).forEach(wrapper => {
            if (!wrapper.matches('.mm-panel')) {
                /** The searchform. */
                const form = createSearchfield.call(this, true);
                //  Add the form to the panel.
                wrapper.append(form);
                /** The input node. */
                const input = find(form, 'input')[0];
                //  Bind events for opening and closing the resultspanel.
                // With a splash...
                if (options.splash.length) {
                    //  Open on focus.
                    input.addEventListener('focusin', () => {
                        this.openPanel(resultspanel, false, false);
                    });
                    //  Show cancel button if searchpanel is opened.
                    this.bind('openPanel:after', (panel) => {
                        if (panel.matches('.mm-panel--search')) {
                            form.classList.add('mm-searchfield--cancelable');
                        }
                        else {
                            form.classList.remove('mm-searchfield--cancelable');
                        }
                    });
                    // ...without splash.
                }
                else {
                    //  Open resultspanel when searching.
                    this.bind('search:after', () => {
                        this.openPanel(resultspanel, false, false);
                    });
                    //  Close resultspanel when resetting.
                    input.addEventListener('focusout', () => {
                        if (!input.value.length) {
                            this.closePanel(resultspanel, false);
                        }
                    });
                }
                //  Initialize searching.
                initSearch.call(this, form);
            }
        });
    });
    //	Blur searchfield
    this.bind('close:before', () => {
        find(this.node.menu, '.mm-searchfield input').forEach((input) => {
            input.blur();
        });
    });
}
/**
 * Create the searchpanel.
 * @param {Mmenu} this
 */
const createResultsPanel = function () {
    /** Options for the searchfield. */
    const options = this.opts.searchfield;
    /** Configs for the searchfield. */
    const configs = this.conf.searchfield;
    /** The panel. */
    let panel = children(this.node.pnls, '.mm-panel--search')[0];
    //	Only once
    if (panel) {
        return panel;
    }
    panel = create('div.mm-panel--search');
    //	Add attributes to the panel.    
    _addAttributes(panel, configs.panel);
    //  Add a title to the panel.
    if (options.title.length) {
        panel.dataset.mmTitle = this.i18n(options.title);
    }
    //  Add a listview to the panel.
    panel.append(create('ul'));
    this._initPanel(panel);
    return panel;
};
/**
 * Add a searchfield, splash message and no-results message to a panel.
 * @param {Mmenu}       this
 * @param {HTMLElement} panel The panel to initialise.
 */
const initPanel = function (panel) {
    /** Options for the searchfield. */
    const options = this.opts.searchfield;
    //	Create the searchfield.
    if (panel.matches(options.addTo)) {
        /** Whether or not the panel is the resultspanel */
        const isResultspanel = panel.matches('.mm-panel--search');
        //  Only one per panel.
        if (!find(panel, '.mm-searchfield').length) {
            /** The searchform. */
            const form = createSearchfield.call(this, isResultspanel);
            if (isResultspanel) {
                form.classList.add('mm-searchfield--cancelable');
            }
            //  Add the form to the panel.
            panel.prepend(form);
            //  Initialize searching.
            initSearch.call(this, form);
        }
    }
    //	Create the splash content.
    if (options.splash.length &&
        panel.matches('.mm-panel--search')) {
        //  Only one per panel.
        if (!find(panel, '.mm-panel__splash').length) {
            /** The splash content node. */
            const splash = create('div.mm-panel__splash');
            splash.innerHTML = options.splash;
            panel.append(splash);
        }
    }
    //  Add no results message.
    if (options.noResults.length) {
        //	Only once per panel.
        if (!find(panel, '.mm-panel__noresults').length) {
            /** The no results message node. */
            const message = create('div.mm-panel__noresults');
            message.innerHTML = this.i18n(options.noResults);
            panel.append(message);
        }
    }
};
/**
 * Create the searchfield.
 * @param {Mmenu}   this
 * @param {boolean} [addCancel=false] Whether or not to add the cancel button
 */
const createSearchfield = function (addCancel = false) {
    /** Options for the searchfield. */
    const options = this.opts.searchfield;
    /** Configs for the searchfield. */
    const configs = this.conf.searchfield;
    /** The form node. */
    const form = create('form.mm-searchfield');
    //	Add attributes to the form
    _addAttributes(form, configs.form);
    /** The fieldset node. */
    const field = create('div.mm-searchfield__input');
    form.append(field);
    /** The input node. */
    const input = create('input');
    field.append(input);
    //	Add attributes to the input
    input.type = 'text';
    input.autocomplete = 'off';
    input.placeholder = this.i18n(options.placeholder);
    input.setAttribute('aria-label', this.i18n(options.placeholder));
    _addAttributes(input, configs.input);
    //	Add a button to submit to the form.
    if (configs.submit) {
        /** The submit button. */
        const submit = create('button.mm-btnreset.mm-btn.mm-btn--next.mm-searchfield__btn');
        submit.type = 'submit';
        field.append(submit);
    }
    //	Add a button to clear the searchfield.
    else if (configs.clear) {
        /** The reset button. */
        const reset = create('button.mm-btnreset.mm-btn.mm-btn--close.mm-searchfield__btn');
        reset.type = 'reset';
        reset.setAttribute('aria-label', this.i18n('Clear searchfield'));
        field.append(reset);
        //  Apparently, resetting a form doesn't trigger any event on the input,
        //  so we manually dispatch the event, one frame later :/
        form.addEventListener('reset', () => {
            window.requestAnimationFrame(() => {
                input.dispatchEvent(new Event('input'));
            });
        });
    }
    // Add a button to close the searchpanel.
    if (configs.cancel && addCancel) {
        /** The cancel button. */
        const cancel = create('a.mm-searchfield__cancel');
        cancel.href = '#';
        cancel.setAttribute('aria-label', this.i18n('Cancel searching'));
        cancel.textContent = this.i18n('cancel');
        form.append(cancel);
        // Close the search panel.
        cancel.addEventListener('click', event => {
            event.preventDefault();
            this.closePanel(children(this.node.pnls, '.mm-panel--search')[0], false);
        });
    }
    return form;
};
/**
 * Initialize the searching.
 * @param {Mmenu}       this
 * @param {HTMLElement} form The searchform.
 */
const initSearch = function (form) {
    /** Options for the searchfield. */
    const options = this.opts.searchfield;
    /** The panel the results will be in. */
    const resultspanel = form.closest('.mm-panel') || find(this.node.pnls, '.mm-panel--search')[0];
    /** The input node. */
    const input = find(form, 'input')[0];
    /** Where to search. */
    let searchIn = resultspanel.matches('.mm-panel--search')
        ? children(this.node.pnls, options.searchIn)
        : [resultspanel];
    //  Filter out the resultspanel
    searchIn = searchIn.filter(panel => !panel.matches('.mm-panel--search'));
    /** Search */
    const search = () => {
        /** The searchquery */
        const query = input.value.toLowerCase().trim();
        if (query.length) {
            form.classList.add('mm-searchfield--searching');
        }
        else {
            form.classList.remove('mm-searchfield--searching');
        }
        if (!options.search) {
            return;
        }
        /** All listitems */
        const listitems = [];
        searchIn.forEach(panel => {
            //  Scroll all panels to top.
            panel.scrollTop = 0;
            //  Find listitems.
            listitems.push(...find(panel, '.mm-listitem'));
        });
        //	Search
        if (query.length) {
            // Trigger event.
            this.trigger('search:before');
            resultspanel.classList.add('mm-panel--searching');
            //	Add data attribute to the matching listitems.
            listitems.forEach((listitem) => {
                const text$1 = children(listitem, '.mm-listitem__text')[0];
                if (!text$1 || text(text$1).toLowerCase().indexOf(query) > -1) {
                    listitem.dataset.mmSearchresult = query;
                }
            });
            /** The number of matching results. */
            let count = 0;
            //  Resultspanel: Copy results to resultspanel.
            if (resultspanel.matches('.mm-panel--search')) {
                count = _searchResultsPanel(resultspanel, query, searchIn);
                //  Search per panel: Hide the non-matching listitems.
            }
            else {
                count = _searchPerPanel(query, searchIn);
            }
            resultspanel.classList[count == 0 ? 'add' : 'remove']('mm-panel--noresults');
            // Trigger event.
            this.trigger('search:after');
            //  Don't search, reset all.
        }
        else {
            // Trigger event.
            this.trigger('clear:before');
            resultspanel.classList.remove('mm-panel--searching', 'mm-panel--noresults');
            //  Resultspanel.
            if (resultspanel.matches('.mm-panel--search')) {
                _resetResultsPanel(resultspanel);
                if (!options.splash) {
                    this.closePanel(resultspanel, false, false);
                }
                //  Search per panel: Show all listitems and dividers.
            }
            else {
                _resetPerPanel(searchIn);
            }
            // Trigger event.
            this.trigger('clear:after');
        }
    };
    input.addEventListener('input', search);
    search();
};
const _searchResultsPanel = (resultspanel, query, searchIn) => {
    /** The listview for the results/ */
    const listview = find(resultspanel, '.mm-listview')[0];
    //  Clear listview.
    listview.innerHTML = '';
    /** Amount of resutls found. */
    let count = 0;
    searchIn.forEach(panel => {
        /** The results in this panel. */
        const results = find(panel, `[data-mm-searchresult="${query}"]`);
        count += results.length;
        if (results.length) {
            /** Title for the panel. */
            const title = find(panel, '.mm-navbar__title')[0];
            //  Add a divider to indicate in what panel the results are.
            if (title) {
                const divider = create('li.mm-divider');
                divider.innerHTML = title.innerHTML;
                listview.append(divider);
            }
            //  Add the results
            results.forEach((result) => {
                const clone = result.cloneNode(true);
                listview.append(clone);
            });
        }
    });
    //  Remove inline subpanels.
    find(listview, '.mm-panel').forEach(panel => {
        panel.remove();
    });
    //  Remove ID's and data-attributes
    ['id', 'data-mm-parent', 'data-mm-child'].forEach(attr => {
        find(listview, `[${attr}]`).forEach(elem => {
            elem.removeAttribute(attr);
        });
    });
    //  Remove "opened" class
    find(listview, '.mm-listitem--opened').forEach(listitem => {
        listitem.classList.remove('mm-listitem--opened');
    });
    return count;
};
const _resetResultsPanel = (resultspanel) => {
    /** The listview for the results. */
    const listview = find(resultspanel, '.mm-listview')[0];
    //  Clear listview.
    listview.innerHTML = '';
};
const _searchPerPanel = (query, searchIn) => {
    /** Amount of resutls found. */
    let count = 0;
    searchIn.forEach(panel => {
        /** The results in this panel. */
        const results = find(panel, `[data-mm-searchresult="${query}"]`);
        count += results.length;
        if (results.length) {
            //  Add first preceeding divider to the results.
            results.forEach(result => {
                const divider = prevAll(result, '.mm-divider')[0];
                if (divider) {
                    divider.dataset.mmSearchresult = query;
                }
            });
        }
        find(panel, '.mm-listitem, .mm-divider').forEach(item => {
            //  Hide all
            item.classList.add('mm-hidden');
            //  Show matching + its parents.
            if (item.dataset.mmSearchresult === query) {
                [item, ...parents(item, '.mm-listitem')].forEach(item2 => {
                    item2.classList.remove('mm-hidden');
                });
            }
        });
    });
    return count;
};
const _resetPerPanel = (searchIn) => {
    searchIn.forEach((panel) => {
        find(panel, '.mm-listitem, .mm-divider').forEach(item => {
            item.classList.remove('mm-hidden');
        });
    });
};
/**
 * Add array of attributes to an element.
 * @param {HTMLEement}  element     The element to add the attributes to.
 * @param {Object}      attributes  The attributes to add.
 */
const _addAttributes = (element, attributes) => {
    if (attributes) {
        Object.keys(attributes).forEach(a => {
            element[a] = attributes[a];
        });
    }
};

const options$2 = {
    add: false,
    addTo: 'panels'
};

function sectionIndexer () {
    this.opts.sectionIndexer = this.opts.sectionIndexer || {};
    //	Extend options.
    const options = extend$1(this.opts.sectionIndexer, options$2);
    if (!options.add) {
        return;
    }
    this.bind('initPanels:after', () => {
        //	Add the indexer, only if it does not allready excists
        if (!this.node.indx) {
            let buttons = '';
            'abcdefghijklmnopqrstuvwxyz'.split('').forEach(letter => {
                buttons += '<a href="#">' + letter + '</a>';
            });
            let indexer = create('div.mm-sectionindexer');
            indexer.innerHTML = buttons;
            this.node.pnls.prepend(indexer);
            this.node.indx = indexer;
            //	Prevent default behavior when clicking an anchor
            this.node.indx.addEventListener('click', evnt => {
                const anchor = evnt.target;
                if (anchor.matches('a')) {
                    evnt.preventDefault();
                }
            });
            //	Scroll onMouseOver / onTouchStart
            let mouseOverEvent = evnt => {
                if (!evnt.target.matches('a')) {
                    return;
                }
                const letter = evnt.target.textContent;
                const panel = children(this.node.pnls, '.mm-panel--opened')[0];
                let newTop = -1, oldTop = panel.scrollTop;
                panel.scrollTop = 0;
                find(panel, '.mm-divider')
                    .filter(divider => !divider.matches('.mm-hidden'))
                    .forEach(divider => {
                    if (newTop < 0 &&
                        letter ==
                            divider.textContent
                                .trim()
                                .slice(0, 1)
                                .toLowerCase()) {
                        newTop = divider.offsetTop;
                    }
                });
                panel.scrollTop = newTop > -1 ? newTop : oldTop;
            };
            if (touch) {
                this.node.indx.addEventListener('touchstart', mouseOverEvent);
                this.node.indx.addEventListener('touchmove', mouseOverEvent);
            }
            else {
                this.node.indx.addEventListener('mouseover', mouseOverEvent);
            }
        }
        //	Show or hide the indexer
        this.bind('openPanel:before', (panel) => {
            const active = find(panel, '.mm-divider').filter(divider => !divider.matches('.mm-hidden')).length;
            this.node.indx.classList[active ? 'add' : 'remove']('mm-sectionindexer--active');
        });
    });
}

const options$1 = {
    current: true,
    hover: false,
    parent: false
};

function setSelected () {
    this.opts.setSelected = this.opts.setSelected || {};
    //	Extend options.
    const options = extend$1(this.opts.setSelected, options$1);
    //	Find current by URL
    if (options.current == 'detect') {
        const findCurrent = (url) => {
            url = url.split('?')[0].split('#')[0];
            const anchor = this.node.menu.querySelector('a[href="' + url + '"], a[href="' + url + '/"]');
            if (anchor) {
                this.setSelected(anchor.parentElement);
            }
            else {
                const arr = url.split('/').slice(0, -1);
                if (arr.length) {
                    findCurrent(arr.join('/'));
                }
            }
        };
        this.bind('initMenu:after', () => {
            findCurrent.call(this, window.location.href);
        });
        //	Remove current selected item
    }
    else if (!options.current) {
        this.bind('initListview:after', (listview) => {
            children(listview, '.mm-listitem--selected').forEach((listitem) => {
                listitem.classList.remove('mm-listitem--selected');
            });
        });
    }
    //	Add :hover effect on items
    if (options.hover) {
        this.bind('initMenu:after', () => {
            this.node.menu.classList.add('mm-menu--selected-hover');
        });
    }
    //	Set parent item selected for submenus
    if (options.parent) {
        this.bind('openPanel:after', (panel) => {
            //	Remove all
            find(this.node.pnls, '.mm-listitem--selected-parent').forEach((listitem) => {
                listitem.classList.remove('mm-listitem--selected-parent');
            });
            //	Move up the DOM tree
            let current = panel;
            while (current) {
                let li = find(this.node.pnls, `#${current.dataset.mmParent}`)[0];
                current = li === null || li === void 0 ? void 0 : li.closest('.mm-panel');
                if (li && !li.matches('.mm-listitem--vertical')) {
                    li.classList.add('mm-listitem--selected-parent');
                }
            }
        });
        this.bind('initMenu:after', () => {
            this.node.menu.classList.add('mm-menu--selected-parent');
        });
    }
}

const options = {
    collapsed: {
        use: false,
    },
    expanded: {
        use: false,
        initial: 'open'
    }
};

function sidebar () {
    // Only for off-canvas menus.
    if (!this.opts.offCanvas.use) {
        return;
    }
    this.opts.sidebar = this.opts.sidebar || {};
    //	Extend options.
    const options$1 = extend$1(this.opts.sidebar, options);
    //	Collapsed
    if (options$1.collapsed.use) {
        //	Make the menu collapsable.
        this.bind('initMenu:after', () => {
            this.node.menu.classList.add('mm-menu--sidebar-collapsed');
        });
        /** Enable the collapsed sidebar */
        let enable = () => {
            this.node.wrpr.classList.add('mm-wrapper--sidebar-collapsed');
        };
        /** Disable the collapsed sidebar */
        let disable = () => {
            this.node.wrpr.classList.remove('mm-wrapper--sidebar-collapsed');
        };
        if (typeof options$1.collapsed.use === 'boolean') {
            this.bind('initMenu:after', enable);
        }
        else {
            add(options$1.collapsed.use, enable, disable);
        }
    }
    //	Expanded
    if (options$1.expanded.use) {
        //	Make the menu expandable
        this.bind('initMenu:after', () => {
            this.node.menu.classList.add('mm-menu--sidebar-expanded');
        });
        let expandedEnabled = false;
        /** Enable the expanded sidebar */
        let enable = () => {
            expandedEnabled = true;
            this.node.wrpr.classList.add('mm-wrapper--sidebar-expanded');
            this.node.menu.removeAttribute('aria-modal');
            this.open();
            Mmenu.node.page.removeAttribute('inert');
        };
        /** Disable the expanded sidebar */
        let disable = () => {
            expandedEnabled = false;
            this.node.wrpr.classList.remove('mm-wrapper--sidebar-expanded');
            this.node.menu.setAttribute('aria-modal', 'true');
            this.close();
        };
        if (typeof options$1.expanded.use == 'boolean') {
            this.bind('initMenu:after', enable);
        }
        else {
            add(options$1.expanded.use, enable, disable);
        }
        //  Store exanded state when opening and closing the menu.
        this.bind('close:after', () => {
            if (expandedEnabled) {
                window.sessionStorage.setItem('mmenuExpandedState', 'closed');
            }
        });
        this.bind('open:after', () => {
            if (expandedEnabled) {
                window.sessionStorage.setItem('mmenuExpandedState', 'open');
                Mmenu.node.page.removeAttribute('inert');
            }
        });
        //  Set the initial state
        let initialState = options$1.expanded.initial;
        const state = window.sessionStorage.getItem('mmenuExpandedState');
        switch (state) {
            case 'open':
            case 'closed':
                initialState = state;
                break;
        }
        if (initialState === 'closed') {
            this.bind('init:after', () => {
                this.close();
            });
        }
    }
}

/*!
 * mmenu.js
 * mmenujs.com
 *
 * Copyright (c) Fred Heusschen
 * frebsite.nl
 */



Mmenu.addons = {
    //	Core add-ons
    offcanvas,
    scrollBugFix,
    theme,

    //	Add-ons
    backButton,
    counters,
    iconbar,
    iconPanels,
    navbars: Navbars,
    pageScroll,
    searchfield,
    sectionIndexer,
    setSelected,
    sidebar
};

//	Global namespace
if (window) {
    window.Mmenu = Mmenu;
}

const CLOSE_EVENT = 'Close',
    BEFORE_CLOSE_EVENT = 'BeforeClose',
    AFTER_CLOSE_EVENT = 'AfterClose',
    BEFORE_APPEND_EVENT = 'BeforeAppend',
    MARKUP_PARSE_EVENT = 'MarkupParse',
    OPEN_EVENT = 'Open',
    CHANGE_EVENT = 'Change',
    NS = 'mfp',
    EVENT_NS = '.' + NS, // note this constant is also used as part of CSS selectors - should probably change that
    READY_CLASS = 'mfp-ready',
    REMOVING_CLASS = 'mfp-removing',
    INLINE_NS = 'inline',
    AJAX_NS = 'ajax',
    _emptyPage = '//about:blank',
    PREVENT_CLOSE_CLASS = 'mfp-prevent-close';
const DOM_PARSER = new DOMParser();
function dlog(msg) {
}

class MagnificPopup {
    constructor() {
        this.ev = document; // ev seems to be the main target for event handlers
        // keep track of all event handlers added with _mfpOn, so we can remove them on close (since we don't have access to .off() with namespaces)
        this.evListeners = {};
        this.st = {};       // st seems to be the main configuration options for the current popup. it gets populated upon open
        this.isOpen = false;
        this.items = [];
        this.index = 0;
        this.direction = true; // for gallery
        this.types = [];
        this.currTemplate = null; // Note - original behavior seems to have been to set this to null upon close.
        this._currPopupType = undefined; // this replaces a global
        this._prevContentType = undefined; // replaces another global
        this._prevStatus = undefined;
        this._wrapClasses = []; // another global replacement. Note in original plugin this was a space concatenated string
        this.wrap = null;
        this.currItem = null;
        this.container = null;
        this.contentContainer = null;
        this.fixedContentPos = undefined;
        this.bgOverlay = undefined;
        this.preloader = undefined;
        this.wH = undefined;
        this.scrollbarSize = undefined;
        this._lastFocusedEl = undefined;
        this._inlinePlaceholder = null;
        this._lastInlineElement = null;
        this._hiddenClass = null;
        // Note that this property is deprecated - revisit this at some point
        let appVersion = navigator.appVersion;
        this.isAndroid = (/android/gi).test(appVersion);
        this.isIOS = (/iphone|ipad|ipod/gi).test(appVersion);
        this.probablyMobile = (this.isAndroid || this.isIOS || /(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent) );
        this.popupsCache = {};
        this._ajaxCur = undefined;
        this.abortController = null;
        this._imgInterval = undefined;
        this.arrowLeft = undefined;
        this.arrowRight = undefined;
        this._preloadTimeout = undefined;

        this.handleCheckThenClose = this.handleCheckThenClose.bind(this);
        this.handleClose = this.handleClose.bind(this);
        this.handleCloseOnEscapeKey = this.handleCloseOnEscapeKey.bind(this);
        this.handleUpdateSize = this.handleUpdateSize.bind(this);
        this.handleAddReadyClass = this.handleAddReadyClass.bind(this);
        this._onFocusIn = this._onFocusIn.bind(this);
        this.handleUnderscoreClose = this.handleUnderscoreClose.bind(this);
        this._putInlineElementsBack = this._putInlineElementsBack.bind(this);
        this._destroyAjaxRequest = this._destroyAjaxRequest.bind(this);
        this.resizeImage = this.resizeImage.bind(this);
        this.handleNavigateByImgClick = this.handleNavigateByImgClick.bind(this);
        this.initInline = this.initInline.bind(this);
        this.initAjax = this.initAjax.bind(this);
        this.initImage = this.initImage.bind(this);
        this.initIframe = this.initIframe.bind(this);
        this.initGallery = this.initGallery.bind(this);
    }

    static defaults = {
        disableOn: 0,
        key: null,
        midClick: false,
        mainClass: '',
        preloader: true,
        focus: '', // CSS selector of input to focus after popup is opened
        closeOnContentClick: false,
        closeOnBgClick: true,
        closeBtnInside: true,
        showCloseBtn: true,
        enableEscapeKey: true,
        modal: false,
        alignTop: false,
        alignBottom: false,
        removalDelay: 0,
        prependTo: null,
        fixedContentPos: 'auto',
        fixedBgPos: 'auto',
        overflowY: 'auto',
        closeMarkup: '<button title="%title%" type="button" class="mfp-close">&#215;</button>',
        tClose: 'Close (Esc)',
        tLoading: 'Loading...',
        autoFocusLast: true,
        inline: {
            hiddenClass: 'hide', // will be appended with `mfp-` prefix
            markup: '',
            tNotFound: 'Content not found'
        },
        ajax: {
            cursor: 'mfp-ajax-cur',
            tError: '<a href="%url%">The content</a> could not be loaded.'
        },
        image: {
            markup: '<div class="mfp-figure">'+
                '<div class="mfp-close"></div>'+
                '<figure>'+
                '<div class="mfp-img"></div>'+
                '<figcaption>'+
                '<div class="mfp-bottom-bar">'+
                '<div class="mfp-title"></div>'+
                '<div class="mfp-counter"></div>'+
                '</div>'+
                '</figcaption>'+
                '</figure>'+
                '</div>',
            cursor: 'mfp-zoom-out-cur',
            titleSrc: 'title',
            verticalFit: true,
            tError: '<a href="%url%">The image</a> could not be loaded.'
        },
        gallery: {
            enabled: false,
            arrowMarkup: '<button title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir%"></button>',
            preload: [0,2],
            navigateByImgClick: true,
            arrows: true,
            tPrev: 'Previous (Left arrow key)',
            tNext: 'Next (Right arrow key)',
            tCounter: '%curr% of %total%'
        },
        iframe: {
            markup: '<div class="mfp-iframe-scaler">'+
                '<div class="mfp-close"></div>'+
                '<iframe class="mfp-iframe" src="//about:blank" frameborder="0" allowfullscreen></iframe>'+
                '</div>',

            srcAction: 'iframe_src',

            // we don't care and support only one default type of URL by default
            patterns: {
                youtube: {
                    index: 'youtube.com',
                    id: 'v=',
                    src: '//www.youtube.com/embed/%id%?autoplay=1'
                },
                vimeo: {
                    index: 'vimeo.com/',
                    id: '/',
                    src: '//player.vimeo.com/video/%id%?autoplay=1'
                },
                gmaps: {
                    index: '//maps.google.',
                    src: '%id%&output=embed'
                }
            }
        }
    }

    get instance() {
        return this;
    }

    // replacement for open() function defined on the jQuery plugin object
    open(options, index) {
        if(!options) {
            options = {};
        } else {
            options = deepExtend({}, options);
        }

        options.isObj = true;
        if (typeof index === 'number') {
            options.index = index;
        } else if (!options.hasOwnProperty('index')) {
            options.index = 0;
        }
        dlog(`call openInternal with options.index = ${options.index}`);
        return this.openInternal(options);
    }

    // Renamed from open() in the original prototype due to the MagnificPopup class and the plugin object both having
    // a function with that name
    openInternal(data) {
        let i;
        if(data.isObj === false) {
            this.items = data.items;

            this.index = 0;
            let items = data.items,
                item;
            for(i = 0; i < items.length; i++) {
                item = items[i];
                if(item.parsed) {
                    item = item.el;
                }
                if(item === data.el) {
                    this.index = i;
                    break;
                }
            }
            dlog('after loop this.index is ' + this.index);
        } else {
            this.items = Array.isArray(data.items) ? data.items : [data.items];
            this.index = data.index || 0;
            dlog('this.index is ' + this.index);
        }

        // if popup is already opened - we just update the content
        if(this.isOpen) {
            this.updateItemHTML();
            return;
        }

        this.types = [];
        this._wrapClasses = [];
        if(data.mainEl && data.mainEl) {
            this.ev = data.mainEl;
        } else {
            this.ev = document;
        }

        if(data.key) {
            if(!this.popupsCache[data.key]) {
                this.popupsCache[data.key] = {};
            }
            this.currTemplate = this.popupsCache[data.key];
        } else {
            this.currTemplate = {};
        }

        this.st = deepExtend({}, MagnificPopup.defaults, data );
        dlog(`adjusting this.fixedContentPos - current value ${this.fixedContentPos}, this.st.fixedContentPos: ${this.st.fixedContentPos}, this.probablyMobile: ${this.probablyMobile}`);
        this.fixedContentPos = this.st.fixedContentPos === 'auto' ? !this.probablyMobile : this.st.fixedContentPos;
        dlog(`after adjustment, this.fixedContentPos is ${this.fixedContentPos}`);

        if(this.st.modal) {
            this.st.closeOnContentClick = false;
            this.st.closeOnBgClick = false;
            this.st.showCloseBtn = false;
            this.st.enableEscapeKey = false;
        }

        // Building markup
        // main containers are created only once
        if(!this.bgOverlay) {
            // Dark overlay
            this.bgOverlay = this._createEl('bg');
            this.bgOverlay.addEventListener('click', this.handleClose);

            this.wrap = this._createEl('wrap');
            this.wrap.setAttribute('tabindex', -1);
            this.wrap.addEventListener('click', this.handleCheckThenClose);

            this.container = this._createEl('container', this.wrap);
        }

        this.contentContainer = this._createEl('content');
        if(this.st.preloader) {
            this.preloader = this._createEl('preloader', this.container, this.st.tLoading);
        }

        // Initializing modules
        // Original plugin used convoluted code to dynamically call init{Modulename} functions
        // for all modules registered. We will not be engaging in such shenanigans. Calling them directly.
        // zoom and retina modules were not ported.
        this.initInline();
        this.initAjax();
        this.initImage();
        this.initIframe();
        this.initGallery();
        this._mfpTrigger('BeforeOpen');

        if(this.st.showCloseBtn) {
            // Close button
            if(!this.st.closeBtnInside) {
                this.wrap.append( this._getCloseBtn() );
            } else {
                this._mfpOn(MARKUP_PARSE_EVENT, function(e) {
                    let [template, values, item] = e.detail;
                    values.close_replaceWith = mp._getCloseBtn(item.type);
                });
                this._wrapClasses.push('mfp-close-btn-in');
            }
        }
        if(this.st.alignTop) {
            this._wrapClasses.push('mfp-align-top');
        }

        if(this.st.alignBottom) {
            this._wrapClasses.push('mfp-align-bottom');
        }

        if(this.fixedContentPos) {
            this.wrap.style.overflow = this.st.overflowY;
            this.wrap.style.overflowX = 'hidden';
            this.wrap.style.overflowY = this.st.overflowY;
        } else {
            //this.wrap.style.top = window.scrollY;
            let scrollY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
            this.wrap.style.top = `${scrollY}px`;
            dlog(`this.wrap.style.top set to ${getComputedStyle(this.wrap)['top']}`);
            this.wrap.style.position = 'absolute';
        }
        if( this.st.fixedBgPos === false || (this.st.fixedBgPos === 'auto' && !this.fixedContentPos) ) {
            let docElHeight = document.documentElement.getBoundingClientRect().height,
                docBodyHeight = document.body.getBoundingClientRect().height,
                maxDocHeight = Math.max(docElHeight, docBodyHeight);
            this.bgOverlay.style.height = `${maxDocHeight}px`;
            dlog(`this.bgOverlay.style.height set to ${getComputedStyle(this.bgOverlay)['height']}`);
            this.bgOverlay.style.position = 'absolute';
        }

        if(this.st.enableEscapeKey) {
            // Close on ESC key
            document.addEventListener('keyup', this.handleCloseOnEscapeKey);
        }

        window.addEventListener('resize', this.handleUpdateSize);

        if(!this.st.closeOnContentClick) {
            this._wrapClasses.push('mfp-auto-cursor');
        }

        if (this._wrapClasses) {
            this.wrap.classList.add(...this._wrapClasses);
        }

        let windowHeight = this.wH = window.innerHeight;

        let windowStyles = {};
        let htmlEl = document.querySelector('html');
        if( this.fixedContentPos ) {
            // replaces _hasScrollBar
            if (document.body.scrollHeight > windowHeight) {
                let s = this._getScrollbarSize();
                if (s) {
                    windowStyles.marginRight = s;
                }
            }
            windowStyles.overflow = 'hidden';
        }

        if (this.st.mainClass) {
            this._addClassToMFP(this.st.mainClass?.split(' ') ?? []);
        }

        // add content
        this.updateItemHTML();

        this._mfpTrigger('BuildControls');

        for (let styleKey in windowStyles) {
            htmlEl.style[styleKey] = windowStyles[styleKey];
        }

        // add everything to DOM
        // hope I get this right
        // st.prependTo seems to be an option which we are not using, skipping it
        document.body.prepend(this.bgOverlay);
        document.body.prepend(this.wrap);

        // Save last focused element
        this._lastFocusedEl = document.activeElement;

        // Wait for next cycle to allow CSS transition
        setTimeout(this.handleAddReadyClass, 16);

        this.isOpen = true;
        this.updateSize(windowHeight);
        this._mfpTrigger(OPEN_EVENT);

        return data;
    }

    close() {
        if(!this.isOpen) {
            return;
        }
        this._mfpTrigger(BEFORE_CLOSE_EVENT);
        this.isOpen = false;
        // skipping obsolete checks for low IE and transition support
        if(this.st.removalDelay) {
            this._addClassToMFP(REMOVING_CLASS);
            setTimeout(this.handleUnderscoreClose, this.st.removalDelay);
        } else {
            this._close();
        }
    }

    /**
     * Helper for close() function
     */
    _close() {
        this._mfpTrigger(CLOSE_EVENT);
        let classesToRemove = [];
        classesToRemove.push(REMOVING_CLASS);
        classesToRemove.push(READY_CLASS);

        this.bgOverlay.remove();
        this.wrap.remove();
        this.container.replaceChildren();
        if(this.st.mainClass) {
            classesToRemove.push(...(this.st.mainClass?.split(' ') ?? []));
        }

        this._removeClassFromMFP(classesToRemove);

        if(this.fixedContentPos) {
            let htmlEl = document.querySelector('html');
            htmlEl.style.marginRight = '';
            htmlEl.style.overflow = '';
        }

        if (this.st.enableEscapeKey) {
            document.removeEventListener('keyup', this.handleCloseOnEscapeKey);
        }
        document.removeEventListener('focusin', this._onFocusIn);
        this._mfpClearListeners();

        this.wrap.classList.remove(...this.wrap.classList);
        this.wrap.classList.add('mfp-wrap');
        this.wrap.removeAttribute('style');

        this.bgOverlay.classList.remove(...this.bgOverlay.classList);
        this.bgOverlay.classList.add('mfp-bg');
        this.container.classList.remove(...this.container.classList);
        this.container.classList.add('mfp-container');

        // remove close button from target element
        if(this.st.showCloseBtn &&
            (!this.st.closeBtnInside || this.currTemplate[this.currItem.type] === true)) {
            if(this.currTemplate.closeBtn) {
                this.currTemplate.closeBtn.remove();
            }
        }

        if(this.st.autoFocusLast && this._lastFocusedEl) {
            this._lastFocusedEl.focus(); // put tab focus back
        }

        this.currItem = null;
        this.content = null;
        this.currTemplate = null;
        this.prevHeight = 0;

        this._mfpTrigger(AFTER_CLOSE_EVENT);
    }

    _parseMarkup(template, values, item) {
        let arr;
        if(item.data) {
            values = {...item.data, ...values};
        }
        this._mfpTrigger(MARKUP_PARSE_EVENT, [template, values, item] );

        for (const [key, value] of Object.entries(values)) {
            if(value === undefined || value === false) {
                return true;
            }
            arr = key.split('_');
            if(arr.length > 1) {
                let el = template.querySelector(EVENT_NS + '-' + arr[0]);
                if (el) {
                    let attr = arr[1];
                    if(attr === 'replaceWith') {
                        if(el !== value) {
                            el.replaceWith(value);
                        }
                    } else if(attr === 'img') {
                        if(el.matches('img')) {
                            el.setAttribute('src', value);
                        } else {
                            let newImg = document.createElement('img');
                            newImg.setAttribute('src', value);
                            newImg.setAttribute('class', el.getAttribute('class'));
                            el.replaceWith(newImg);
                        }
                    } else {
                        el.setAttribute(arr[1], value);
                    }
                }
            } else {
                let keyEl = template.querySelector(EVENT_NS + '-' + key);
                if (keyEl) {
                    keyEl.innerHTML = value;
                }
            }
        }
    }

    /**
     * Updates text on preloader
     */
    updateStatus(status, text) {
        if(this.preloader) {
            if(this._prevStatus !== status) {
                this.container.classList.remove('mfp-s-'+this._prevStatus);
            }

            if(!text && status === 'loading') {
                text = this.st.tLoading;
            }

            let data = {
                status,
                text
            };
            // allows to modify status
            this._mfpTrigger('UpdateStatus', data);

            status = data.status;
            text = data.text;

            this.preloader.innerHTML = text;

            let preloadAnchors = this.preloader.querySelectorAll('a');
            preloadAnchors.forEach((preloadAnchor) => {
                preloadAnchor.addEventListener('click', function(e) {
                    e.stopImmediatePropagation();
                });
            });

            this.container.classList.add('mfp-s-'+status);
            this._prevStatus = status;
        }
    }

    // Replacement for jQuery function
    initializePopup(el, options) {
        // Not supporting the section where options is a passed string. We are not using it
        if (typeof options === "string" ) {
            console.log('warning - passed a string as options to initializePopup - this is not supported!');
        } else {
            // clone options obj
            options = deepExtend({}, options);

            el.magnificPopup = options;

            this.addGroup(el, options);
        }
    }

    _openClick(e, el, options) {
        let midClick = options.midClick !== undefined ? options.midClick : MagnificPopup.defaults.midClick;

        if(!midClick && ( e.which === 2 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey ) ) {
            return;
        }

        let disableOn = options.disableOn !== undefined ? options.disableOn : MagnificPopup.defaults.disableOn;

        if(disableOn) {
            if(typeof disableOn === 'function') {
                if( !disableOn.call(this) ) {
                    return true;
                }
            } else { // else it's number
                if( window.innerWidth < disableOn ) {
                    return true;
                }
            }
        }

        if(e.type) {
            e.preventDefault();

            // This will prevent popup from closing if element is inside and popup is already opened
            if(this.isOpen) {
                e.stopPropagation();
            }
        }

        options.el = e.mfpEl;
        // not supporting delegate - we are not using it
        this.open(options);
    }

    /**
     * Initializes single popup or a group of popups
     */
    addGroup(el, options) {
        let eHandler = function(e) {
            e.mfpEl = this;
            mp._openClick(e, el, options);
        };

        if(!options) {
            options = {};
        }

        options.mainEl = el;

        if(options.items) {
            options.isObj = true;
        } else {
            options.isObj = false;
            // not supporting delegate - we are not using it
            options.items = el;
        }
        if (el.magnificClickHandler) {
            el.removeEventListener('click', el.magnificClickHandler);
        }
        el.magnificClickHandler = eHandler;
        el.addEventListener('click', eHandler);
    }

    handleAddReadyClass() {
        if(this.content) {
            this._addClassToMFP(READY_CLASS);
            this._setFocus();
        } else {
            // if content is not defined (not loaded e.t.c) we add class only for BG
            this.bgOverlay.classList.add(READY_CLASS);
        }
        // Trap the focus in popup
        document.addEventListener('focusin', this._onFocusIn);
    }

    _addClassToMFP(classNames) {
        let klassNames = classNames;
        if (!Array.isArray(classNames)) {
            klassNames = classNames ? classNames.split(' ') : [];
        }

        this.bgOverlay.classList.add(...klassNames);
        this.wrap.classList.add(...klassNames);
    }

    _removeClassFromMFP(classNames) {
        let klassNames = classNames;
        if (!Array.isArray(classNames)) {
            klassNames = classNames ? classNames.split(' ') : [];
        }

        this.bgOverlay.classList.remove(...klassNames);
        this.wrap.classList.remove(...klassNames);
    }

    _setFocus() {
        let focusTarget = this.st.focus ? this.content.querySelector(this.st.focus) : this.wrap;
        if (focusTarget) {
            focusTarget.focus();
        }
    }
    _onFocusIn(e) {
        if(e.target !== this.wrap && !this.wrap.contains(e.target)) {
            this._setFocus();
            return false;
        }
    }

    _getScrollbarSize() {
        // thx David
        if(this.scrollbarSize === undefined) {
            let scrollDiv = document.createElement("div");
            scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
            document.body.appendChild(scrollDiv);
            this.scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth;
            document.body.removeChild(scrollDiv);
        }
        return this.scrollbarSize;
    }

    updateSize(winHeight) {
        if(this.isIOS) {
            // fixes iOS nav bars https://github.com/dimsemenov/Magnific-Popup/issues/2
            let zoomLevel = document.documentElement.clientWidth / window.innerWidth;
            let height = window.innerHeight * zoomLevel;
            this.wrap.style.height = height;
            this.wH = height;
        } else {
            this.wH = winHeight || window.innerHeight
                || document.documentElement.clientHeight
                || document.body.clientHeight;
        }
        // Fixes #84: popup incorrectly positioned with position:relative on body
        if(!this.fixedContentPos) {
            this.wrap.style.height = `${this.wH}px`;
            dlog(`set this.wrap.style.height to ${getComputedStyle(this.wrap)['height']}, this.wH is ${this.wH}`);
        }

        this._mfpTrigger('Resize');
    }

    handleUnderscoreClose() {
        this._close();
    }

    handleClose(e) {
        this.close();
    }

    handleCheckThenClose(e) {
        if(this._checkIfClose(e.target)) {
            this.close();
        }
    }

    handleCloseOnEscapeKey(e) {
        if(e.keyCode === 27) {
            this.close();
        }
    }

    handleUpdateSize(e) {
        this.updateSize();
    }

    _checkIfClose(target) {
        if(target.classList.contains(PREVENT_CLOSE_CLASS)) {
            // this WAS just returning void. I don't think changing to false this should affect anything.
            return false;
        }

        let closeOnContent = this.st.closeOnContentClick;
        let closeOnBg = this.st.closeOnBgClick;

        if(closeOnContent && closeOnBg) {
            return true;
        } else {
            // We close the popup if click is on close button or on preloader. Or if there is no content.
            if(!this.content || target.classList.contains('mfp-close') || (this.preloader && target === this.preloader) ) {
                return true;
            }

            // if click is outside the content
            if((target !== this.content && !this.content.contains(target))) {
                if(closeOnBg) {
                    // last check, if the clicked element is in DOM, (in case it's removed onclick)
                    if( document.contains(target) ) {
                        return true;
                    }
                }
            } else if(closeOnContent) {
                return true;
            }

        }
        return false;
    }

    _mfpOn(name, f) {
        let fullName = NS + name;
        this.ev.addEventListener(fullName, f);
        this.evListeners[fullName] = f;
    }

    _mfpClearListeners() {
        for (let key in this.evListeners) {
            this.ev.removeEventListener(key, this.evListeners[key]);
        }
        this.evListeners = {};
    }

    // renaming this from _getEl which was not a good name
    // note that the default behavior of the original was to return a wrapper object
    _createEl(className, appendTo, html) {
        let el = document.createElement('div');
        el.className = 'mfp-'+className;
        if(html) {
            el.innerHTML = html;
        }
        if (appendTo) {
            appendTo.appendChild(el);
        }
        return el;
    }

    _mfpTrigger(e, data) {
        let fullEventName = NS + e;
        const event = new CustomEvent(fullEventName, {detail: data});
        this.ev.dispatchEvent(event);

        if(this.st.callbacks) {
            // converts "mfpEventName" to "eventName" callback and triggers it if it's present
            e = e.charAt(0).toLowerCase() + e.slice(1);
            if(this.st.callbacks[e]) {
                this.st.callbacks[e].apply(this, Array.isArray(data) ? data : [data]);
            }
        }
    }

    _getCloseBtn(type) {
        if(type !== this._currPopupType || !this.currTemplate.closeBtn) {
            this.currTemplate.closeBtn = this.fromMarkup(this.st.closeMarkup.replace('%title%', this.st.tClose ));
            this._currPopupType = type;
        }
        return this.currTemplate.closeBtn;
    }

    /**
     * Creates Magnific Popup data object based on given data
     * @param  {int} index Index of item to parse
     */
    parseEl(index) {
        let item = this.items[index],
            type;

        if(item.tagName) {
            item = { el: item };
        } else {
            type = item.type;
            item = { data: item, src: item.src };
        }

        if(item.el) {
            let types = this.types;

            // check for 'mfp-TYPE' class
            for(let i = 0; i < types.length; i++) {
                if( item.el.classList.contains('mfp-'+types[i]) ) {
                    type = types[i];
                    break;
                }
            }

            item.src = item.el.getAttribute('data-mfp-src');
            if(!item.src) {
                item.src = item.el.getAttribute('href');
            }
        }

        item.type = type || this.st.type || 'inline';
        item.index = index;
        item.parsed = true;
        this.items[index] = item;
        this._mfpTrigger('ElementParse', item);

        return this.items[index];
    }

    appendContent(newContent, type) {
        this.content = newContent;

        if(newContent) {
            if(this.st.showCloseBtn && this.st.closeBtnInside &&
                this.currTemplate[type] === true) {
                // if there is no markup, we just append close button element inside
                if(!this.content.querySelector('.mfp-close')) {
                    this.content.append(this._getCloseBtn());
                }
            } else {
                this.content = newContent;
            }
            // Run any scripts found in new content. I hate this but things are dependent on it.
            /* This is not working. This may not be the right place to do it - at any rate, this is a horrible
               way to write code so let's just buckle down and fix any breakage caused to be implemented more sensibly.
            newContent.querySelectorAll('script', (scriptEl) => {
                let newScript = document.createElement('script');
                newScript.type = scriptEl.type;
                if (scriptEl.innerHTML) {
                    newScript.innerHTML = scriptEl.innerHTML;
                } else if (scriptEl.src) {
                    newScript.src = scriptEl.src;
                }
                document.body.appendChild(newScript);
                //console.log(`appended script ${newScript.outerHTML}`);
            });*/
        } else {
            this.content = '';
        }

        this._mfpTrigger(BEFORE_APPEND_EVENT);
        this.container.classList.add('mfp-'+type+'-holder');

        this.contentContainer.append(this.content);
    }

    updateItemHTML() {
        let item = this.items[this.index];

        // Detach and perform modifications
        this.contentContainer.remove();

        if(this.content) {
            this.content.remove();
        }

        if(!item.parsed) {
            item = this.parseEl( this.index );
        }

        let type = item.type;

        this._mfpTrigger('BeforeChange', [this.currItem ? this.currItem.type : '', type]);
        // BeforeChange event works like so:
        // _mfpOn('BeforeChange', function(e, prevType, newType) { });

        this.currItem = item;

        if(!this.currTemplate[type]) {
            let markup = this.st[type] ? this.st[type].markup : false;

            // allows to modify markup
            this._mfpTrigger('FirstMarkupParse', markup);

            if(markup) {
                this.currTemplate[type] = this.fromMarkup(markup);
            } else {
                // if there is no markup found we just define that template is parsed
                this.currTemplate[type] = true;
            }
        }

        if(this.container && this._prevContentType && this._prevContentType !== item.type) {
            this.container.classList.remove('mfp-'+this._prevContentType+'-holder');
        }

        // this calls the module-specific get{Modulename} functions (hopefully)
        let newContent = this['get' + type.charAt(0).toUpperCase() + type.slice(1)](item, this.currTemplate[type]);
        this.appendContent(newContent, type);

        item.preloaded = true;

        this._mfpTrigger(CHANGE_EVENT, item);
        this._prevContentType = item.type;

        // Append container back after its content changed
        this.container.prepend(this.contentContainer);

        this._mfpTrigger('AfterChange');
    }

    // This is a new helper function to remove the ambiguity involved in passing a string as src
    // to inline elements. A string will now always be assumed to be a CSS selector. To pass in an element
    // from markup instead, use this.
    fromMarkup(htmlString) {
        let tempDoc = DOM_PARSER.parseFromString(htmlString, 'text/html');
        let children = tempDoc.body.children;
        if (!children || children.length === 0) {
            return null;
        }
        if (children.length === 1) {
            return children[0];
        }
        // generally - code calling this is expecting an Element returned, and detecting and dealing with an HTMLCollection
        // instead is non-trivial. Instead we will just wrap with a div.
        let container = document.createElement('div');
        for (let i = 0; i < children.length; i++) {
            container.appendChild(children.item(i));
        }
        return container;
    }

    /*
     *   Code migrated from modules
     */
    _putInlineElementsBack() {
        if(this._lastInlineElement) {
            this._lastInlineElement.classList.add(this._hiddenClass);
            this._inlinePlaceholder.after(this._lastInlineElement);
            this._inlinePlaceholder.remove();
            this._lastInlineElement = null;
        }
    }

    initInline() {
        this.types.push(INLINE_NS);

        this._mfpOn(CLOSE_EVENT,this._putInlineElementsBack);
    }

    getInline(item, template) {
        this._putInlineElementsBack();

        if(item.src) {
            let inlineSt = this.st.inline;
            let el;
            if (typeof item.src === 'string') {
                el = document.querySelector(item.src);
            } else {
                // assume a constructed element
                el = item.src;
            }

            if (el) {
                // If target element has parent - we replace it with placeholder and put it back after popup is closed
                let parent = el.parentNode;

                if(parent && parent.tagName) {
                    if(!this._inlinePlaceholder) {
                        this._hiddenClass = inlineSt.hiddenClass;
                        this._inlinePlaceholder = this._createEl(this._hiddenClass);
                        this._hiddenClass = 'mfp-'+this._hiddenClass;
                    }
                    // replace target inline element with placeholder
                    el.after(this._inlinePlaceholder);
                    el.remove();
                    el.classList.remove(this._hiddenClass);
                    this._lastInlineElement = el;
                }

                this.updateStatus('ready');
            } else {
                this.updateStatus('error', inlineSt.tNotFound);
                el = document.createElement('div');
            }

            item.inlineElement = el;
            return el;
        }
        this.updateStatus('ready');
        this._parseMarkup(template, {}, item);
        return template;
    }

    _removeAjaxCursor() {
        if(this._ajaxCur) {
            document.body.classList.remove(this._ajaxCur);
        }
    }

    initAjax() {
        this.types.push(AJAX_NS);
        this._ajaxCur = this.st.ajax.cursor;

        this._mfpOn(CLOSE_EVENT, this._destroyAjaxRequest);
        this._mfpOn('BeforeChange', this._destroyAjaxRequest);
    }

    _destroyAjaxRequest() {
        if (this.abortController) {
            this.abortController.abort();
            this.abortController = null;
        }
    }

    getAjax(item) {
        if(this._ajaxCur) {
            document.body.classList.add(this._ajaxCur);
        }

        this.updateStatus('loading');

        this.abortController = new AbortController();
        let opts = {
            method: 'GET',
            mode: 'same-origin',
            signal: this.abortController.signal,
        };

        fetch(item.src, opts)
            .then((response) => {
                if (!response.ok) {
                    this._removeAjaxCursor();
                    item.finished = item.loadError = true;
                    this.abortController = null;
                    this.updateStatus('error', this.st.ajax.tError.replace('%url%', item.src));
                    return Promise.reject(Error('fetch error'));
                } else {
                    return response.text();
                }
            })
            .then((textData) => {
                let temp = {
                    data:textData,
                };
                this._mfpTrigger('ParseAjax', temp);
                this.appendContent( this.fromMarkup(textData), AJAX_NS );
                item.finished = true;
                this.abortController = null;
                this._removeAjaxCursor();
                this._setFocus();
                setTimeout(() => {
                    this.wrap.classList.add(READY_CLASS);
                }, 16);
                this.updateStatus('ready');
                this._mfpTrigger('AjaxContentAdded');
            })
            .catch(error => {
                console.error('error:', error);
            });

        return '';
    }

    initImage() {
        let imgSt = this.st.image;

        this.types.push('image');

        this._mfpOn(OPEN_EVENT, function() {
            if(mp.currItem.type === 'image' && imgSt.cursor) {
                document.body.classList.add(imgSt.cursor);
            }
        });

        this._mfpOn(CLOSE_EVENT, function() {
            if(imgSt.cursor) {
                document.body.classList.remove(imgSt.cursor);
            }
            window.removeEventListener('resize', this.resizeImage);
        });

        this._mfpOn('Resize', this.resizeImage);
    }

    resizeImage() {
        let item = this.currItem;
        if(!item || !item.img) return;

        if(this.st.image.verticalFit) {
            item.img.style.maxHeight = this.wH;
        }
    }

    _getTitle(item) {
        if(item.data && item.data.title !== undefined)
            return item.data.title;

        let src = this.st.image.titleSrc;

        if(src) {
            if(typeof src === 'function') {
                return src.call(this, item);
            } else if(item.el) {
                return item.el.getAttribute(src) || '';
            }
        }
        return '';
    }

    _onImageHasSize(item) {
        if(item.img) {
            item.hasSize = true;

            if(this._imgInterval) {
                clearInterval(this._imgInterval);
            }

            item.isCheckingImgSize = false;

            this._mfpTrigger('ImageHasSize', item);

            if(item.imgHidden) {
                if(this.content)
                    this.content.classList.remove('mfp-loading');

                item.imgHidden = false;
            }
        }
    }

    /**
     * Function that loops until the image has size to display elements that rely on it asap
     */
    findImageSize(item) {
        if (!item.img) {
            console.log(`item.img not found, item is ${JSON.stringify(item)}`);
            return;
        }
        let counter = 0,
            img = item.img,
            mfpSetInterval = function(delay) {
                if(mp._imgInterval) {
                    clearInterval(mp._imgInterval);
                }
                // decelerating interval that checks for size of an image
                mp._imgInterval = setInterval(function() {
                    if(img.naturalWidth > 0) {
                        mp._onImageHasSize(item);
                        return;
                    }

                    if(counter > 200) {
                        clearInterval(mp._imgInterval);
                    }

                    counter++;
                    if(counter === 3) {
                        mfpSetInterval(10);
                    } else if(counter === 40) {
                        mfpSetInterval(50);
                    } else if(counter === 100) {
                        mfpSetInterval(500);
                    }
                }, delay);
            };

        mfpSetInterval(1);
    }

    getImage(item, template) {
        let guard = 0;
        // image load complete handler
        // keeping these handlers inline and referencing "mp" rather than "this" due to closure on item param
        function onLoadComplete() {
            if(item) {
                if (item.img.complete) {
                    item.img.removeEventListener('load', onLoadComplete);
                    item.img.removeEventListener('error', onLoadError);

                    if(item === mp.currItem){
                        mp._onImageHasSize(item);

                        mp.updateStatus('ready');
                    }

                    item.hasSize = true;
                    item.loaded = true;

                    mp._mfpTrigger('ImageLoadComplete');
                }
                else {
                    // if image complete check fails 200 times (20 sec), we assume that there was an error.
                    guard++;
                    if(guard < 200) {
                        setTimeout(onLoadComplete,100);
                    } else {
                        onLoadError();
                    }
                }
            }
        }

        // image error handler
        function onLoadError() {
            if(item) {
                item.img.removeEventListener('load', onLoadComplete);
                item.img.removeEventListener('error', onLoadError);
                if(item === mp.currItem){
                    mp._onImageHasSize(item);
                    mp.updateStatus('error', imgSt.tError.replace('%url%', item.src) );
                }

                item.hasSize = true;
                item.loaded = true;
                item.loadError = true;
            }
        }
        let imgSt = this.st.image;

        let el = template.querySelector('.mfp-img');
        if(el) {
            let img = document.createElement('img');
            img.className = 'mfp-img';
            if(item.el && item.el.querySelector('img')) {
                img.alt = item.el.querySelector('img').getAttribute('alt');
            }
            img.addEventListener('load', onLoadComplete);
            img.addEventListener('error', onLoadError);
            item.img = img;
            img.src = item.src;

            // without clone() "error" event is not firing when IMG is replaced by new IMG
            // TODO: find a way to avoid such cloning. ETR note: TODO is from original code
            if(el.matches('img')) {
                item.img = item.img.cloneNode(true);
            }

            img = item.img;
            if(img.naturalWidth > 0) {
                item.hasSize = true;
            } else if(!img.width) {
                item.hasSize = false;
            }
        } else {
            console.log('did not find .mfp-img!');
        }

        this._parseMarkup(template, {
            title: this._getTitle(item),
            img_replaceWith: item.img
        }, item);

        this.resizeImage();

        if(item.hasSize) {
            if(this._imgInterval) clearInterval(this._imgInterval);

            if(item.loadError) {
                template.classList.add('mfp-loading');
                this.updateStatus('error', imgSt.tError.replace('%url%', item.src) );
            } else {
                template.classList.remove('mfp-loading');
                this.updateStatus('ready');
            }
            return template;
        }

        this.updateStatus('loading');
        item.loading = true;

        if(!item.hasSize) {
            item.imgHidden = true;
            template.classList.add('mfp-loading');
            this.findImageSize(item);
        }

        return template;
    }

    _getLoopedId(index) {
        let numSlides = this.items.length;
        if(index > numSlides - 1) {
            return index - numSlides;
        } else  if(index < 0) {
            return numSlides + index;
        }
        return index;
    }

    handleNavigateByImgClick(e) {
        if (!e.target) {
            return;
        }
        const el = e.target.closest('.mfp-img');
        if (el) {
            if(this.items.length > 1) {
                this.next();
                return false;
            }
        }
    }

    initGallery() {
        let gSt = this.st.gallery;

        this.direction = true; // true - next, false - prev

        if(!gSt || !gSt.enabled ) return false;

        this._wrapClasses.push('mfp-gallery');

        this._mfpOn(OPEN_EVENT, function() {
            if(gSt.navigateByImgClick) {
                mp.wrap.addEventListener('click', mp.handleNavigateByImgClick);
            }
            // Omitting keydown listener. It was using deprecated keyCode property, and I doubt anyone even uses it
        });

        this._mfpOn('UpdateStatus', function(e) {
            let data = e.detail;
            if(data.text) {
                data.text = _replaceCurrTotal(data.text, mp.currItem.index, mp.items.length);
            }
        });

        this._mfpOn(MARKUP_PARSE_EVENT, function(e) {
            let [element, values, item] = e.detail;
            let l = mp.items.length;
            values.counter = l > 1 ? _replaceCurrTotal(gSt.tCounter, item.index, l) : '';
        });

        this._mfpOn('BuildControls', function() {
            if(mp.items.length > 1 && gSt.arrows && !mp.arrowLeft) {
                let markup = gSt.arrowMarkup;
                let arrowLeft = mp.arrowLeft = mp.fromMarkup( markup.replace(/%title%/gi, gSt.tPrev).replace(/%dir%/gi, 'left') );
                arrowLeft.classList.add(PREVENT_CLOSE_CLASS);
                let arrowRight = mp.arrowRight = mp.fromMarkup( markup.replace(/%title%/gi, gSt.tNext).replace(/%dir%/gi, 'right') );
                arrowRight.classList.add(PREVENT_CLOSE_CLASS);

                arrowLeft.addEventListener('click', function() {
                    mp.prev();
                });
                arrowRight.addEventListener('click', function() {
                    mp.next();
                });

                mp.container.append(arrowLeft);
                mp.container.append(arrowRight);
            }
        });

        this._mfpOn(CHANGE_EVENT, function() {
            if(mp._preloadTimeout) clearTimeout(mp._preloadTimeout);

            mp._preloadTimeout = setTimeout(function() {
                mp.preloadNearbyImages();
                mp._preloadTimeout = null;
            }, 16);
        });

        this._mfpOn(CLOSE_EVENT, function() {
            mp.wrap.removeEventListener('click', mp.handleNavigateByImgClick);
            mp.arrowRight = mp.arrowLeft = null;
        });
    }

    next() {
        this.direction = true;
        this.index = this._getLoopedId(this.index + 1);
        this.updateItemHTML();
    }

    prev() {
        this.direction = false;
        this.index = this._getLoopedId(this.index - 1);
        this.updateItemHTML();
    }

    goTo(newIndex) {
        this.direction = (newIndex >= this.index);
        this.index = newIndex;
        this.updateItemHTML();
    }

    preloadNearbyImages() {
        let p = this.st.gallery.preload,
            preloadBefore = Math.min(p[0], this.items.length),
            preloadAfter = Math.min(p[1], this.items.length),
            i;

        for(i = 1; i <= (this.direction ? preloadAfter : preloadBefore); i++) {
            this._preloadItem(this.index+i);
        }
        for(i = 1; i <= (this.direction ? preloadBefore : preloadAfter); i++) {
            this._preloadItem(this.index-i);
        }
    }

    _preloadItem(index) {
        index = this._getLoopedId(index);

        if(this.items[index].preloaded) {
            return;
        }

        let item = this.items[index];
        if(!item.parsed) {
            item = this.parseEl( index );
        }

        this._mfpTrigger('LazyLoad', item);

        if(item.type === 'image') {
            let newImg = document.createElement('img');
            newImg.className = 'mfp-img';
            newImg.addEventListener('load', function() {
                item.hasSize = true;
            });
            newImg.addEventListener('error', function() {
                item.hasSize = true;
                item.loadError = true;
                mp._mfpTrigger('LazyLoadError', item);
            });
            newImg.setAttribute('src', item.src);
            item.img = newImg;
        }

        item.preloaded = true;
    }

    _fixIframeBugs(isShowing) {
        if(this.currTemplate['iframe']) {
            let el = this.currTemplate['iframe'].querySelector('iframe');
            if(el) {
                // reset src after the popup is closed to avoid "video keeps playing after popup is closed" bug
                if(!isShowing) {
                    el.src = _emptyPage;
                }
            }
        }
    }

    initIframe() {
        this.types.push('iframe');

        this._mfpOn('BeforeChange', function(e) {
            let [prevType,newType] = e.detail;
            if(prevType !== newType) {
                if(prevType === 'iframe') {
                    mp._fixIframeBugs(); // iframe if removed
                } else if(newType === 'iframe') {
                    mp._fixIframeBugs(true); // iframe is showing
                }
            }// else {
            // iframe source is switched, don't do anything
            //}
        });

        this._mfpOn(CLOSE_EVENT, function() {
            mp._fixIframeBugs();
        });
    }

    getIframe(item, template) {
        let embedSrc = item.src;
        let iframeSt = this.st.iframe;

        for (const [key, value] of Object.entries(iframeSt.patterns)) {
            if(embedSrc.indexOf( value.index ) > -1) {
                if (value.id) {
                    if(typeof value.id === 'string') {
                        // TODO figure out WTF this is doing and switch to non-deprecated API
                        embedSrc = embedSrc.substr(embedSrc.lastIndexOf(value.id)+value.id.length, embedSrc.length);
                    } else {
                        embedSrc = value.id.call( value, embedSrc );
                    }
                }
                embedSrc = value.src.replace('%id%', embedSrc );
                return false;
            }
        }

        let dataObj = {};
        if(iframeSt.srcAction) {
            dataObj[iframeSt.srcAction] = embedSrc;
        }
        this._parseMarkup(template, dataObj, item);

        this.updateStatus('ready');

        return template;
    }
} /* end MagnificPopup definition */

function _replaceCurrTotal(text, curr, total) {
    return text.replace(/%curr%/gi, curr + 1).replace(/%total%/gi, total);
}

function deepExtend(out, ...arguments_) {
    if (!out) {
        return {};
    }

    for (const obj of arguments_) {
        if (!obj) {
            continue;
        }

        for (const [key, value] of Object.entries(obj)) {
            switch (Object.prototype.toString.call(value)) {
                case '[object Object]':
                    out[key] = out[key] || {};
                    out[key] = deepExtend(out[key], value);
                    break;
                case '[object Array]':
                    out[key] = deepExtend(new Array(value.length), value);
                    break;
                default:
                    out[key] = value;
            }
        }
    }

    return out;
}

function initializeMagnific() {
    /*
        window.mp is a less-typing-required alias. This is also used internally to access the singleton instance
        from callbacks where trying to rewrite to make "this" available would have been prohibitively difficult.
     */
    window.mp = window.magnificPopup = new MagnificPopup();
    initializePopupLaunchers();
}

function initializePopupLaunchers(selector) {
    let root = document;
    if (selector) {
        let selRoot = document.querySelector(selector);
        if (selRoot) {
            root = selRoot;
        }
    }
    root.querySelectorAll('.popup-launch').forEach((el) => {
        mp.initializePopup(el, {
            type:'inline',
            midClick: true
        });
    });
    root.querySelectorAll('.ajax-popup-launch').forEach((el) => {
        mp.initializePopup(el, {
            type:'ajax',
            midClick: true
        });
    });
    root.querySelectorAll('.your_content_link').forEach((el) => {
        mp.initializePopup(el, {
            type:'inline',
            midClick: true
        });
    });
}

let mmenuInitialized = false;
const STORAGE_KEY = 'mmenuNavTemplate';

async function preloadNavTemplate() {
    let navTemplate = sessionStorage.getItem(STORAGE_KEY);
    if (navTemplate) {
        return;
    }
    navTemplate = await loadNavTemplate();
    if (navTemplate) {
        sessionStorage.setItem(STORAGE_KEY, navTemplate);
    }
}

async function loadNavTemplate() {
    let fetchOptions = {
        headers: {
            "Accept": "application/json",
        },
        method: "GET",
        mode: 'same-origin',
    };
    let response = await fetch("/public/handler/oc_mmenu_template.jsp", fetchOptions);
    if (!response.ok) {
        console.log('bad response! ' + response);
        return null;
    }
    let jsonData = await response.json();
    if (jsonData && jsonData.navTemplate) {
        return jsonData.navTemplate;
    }
    return null;
}

async function initializeMenus() {
    let storedTemplate = sessionStorage.getItem(STORAGE_KEY);
    if (!storedTemplate) {
        storedTemplate = await loadNavTemplate();
    }

    if (storedTemplate) {
        let templateDoc = DOM_PARSER$1.parseFromString(storedTemplate, "text/html");
        const navTemplate = templateDoc.firstElementChild.querySelector('#oc-mmenu-template');
        if (navTemplate) {
            const navMenu = navTemplate.content.firstElementChild.cloneNode(true);
            document.body.appendChild(navMenu);
            new Mmenu("#oc-mmenu", {
                theme: 'dark',
                keyboardNavigation: {
                    enable: "default",
                    enhance: true
                },
                offCanvas: {
                    position: window.innerWidth <= 668 ? "bottom" : "left-front"
                },
                navbars: [
                    {
                        "position": "top",
                        "content": [
                            "prev",
                            "title",
                            "close"
                        ]
                    }
                ]
            }, {
                offCanvas: {
                    page: {
                        selector: '.page-wrap'
                    }
                }
            });
            initializePopupLaunchers("#oc-mmenu");
        } else {
            console.log('no navTemplate!');
        }
    } else {
        console.log('no storedTemplate!');
    }
}

/* add a click listener to do first time initialization */
function addInitListener(el) {
    el.addEventListener('click', (e) => {
        if (!mmenuInitialized) {
            e.preventDefault();
            e.stopPropagation();
            initializeMenus().then(() => {
                mmenuInitialized = true;
                if (e.target) {
                    e.target.click();
                }
            });
        }
    });
}

function initializeOffCanvasMenu() {
    {
        runWhenDOMContentLoaded(() => {
            void preloadNavTemplate();
            document.querySelectorAll(('a[href="#oc-mmenu"]')).forEach(addInitListener);
        });
    }
}

/*! js-cookie v3.0.5 | MIT */
/* eslint-disable no-var */
function assign (target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];
    for (var key in source) {
      target[key] = source[key];
    }
  }
  return target
}
/* eslint-enable no-var */

/* eslint-disable no-var */
var defaultConverter = {
  read: function (value) {
    if (value[0] === '"') {
      value = value.slice(1, -1);
    }
    return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
  },
  write: function (value) {
    return encodeURIComponent(value).replace(
      /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,
      decodeURIComponent
    )
  }
};
/* eslint-enable no-var */

/* eslint-disable no-var */

function init$1 (converter, defaultAttributes) {
  function set (name, value, attributes) {
    if (typeof document === 'undefined') {
      return
    }

    attributes = assign({}, defaultAttributes, attributes);

    if (typeof attributes.expires === 'number') {
      attributes.expires = new Date(Date.now() + attributes.expires * 864e5);
    }
    if (attributes.expires) {
      attributes.expires = attributes.expires.toUTCString();
    }

    name = encodeURIComponent(name)
      .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
      .replace(/[()]/g, escape);

    var stringifiedAttributes = '';
    for (var attributeName in attributes) {
      if (!attributes[attributeName]) {
        continue
      }

      stringifiedAttributes += '; ' + attributeName;

      if (attributes[attributeName] === true) {
        continue
      }

      // Considers RFC 6265 section 5.2:
      // ...
      // 3.  If the remaining unparsed-attributes contains a %x3B (";")
      //     character:
      // Consume the characters of the unparsed-attributes up to,
      // not including, the first %x3B (";") character.
      // ...
      stringifiedAttributes += '=' + attributes[attributeName].split(';')[0];
    }

    return (document.cookie =
      name + '=' + converter.write(value, name) + stringifiedAttributes)
  }

  function get (name) {
    if (typeof document === 'undefined' || (arguments.length && !name)) {
      return
    }

    // To prevent the for loop in the first place assign an empty array
    // in case there are no cookies at all.
    var cookies = document.cookie ? document.cookie.split('; ') : [];
    var jar = {};
    for (var i = 0; i < cookies.length; i++) {
      var parts = cookies[i].split('=');
      var value = parts.slice(1).join('=');

      try {
        var found = decodeURIComponent(parts[0]);
        jar[found] = converter.read(value, found);

        if (name === found) {
          break
        }
      } catch (e) {}
    }

    return name ? jar[name] : jar
  }

  return Object.create(
    {
      set,
      get,
      remove: function (name, attributes) {
        set(
          name,
          '',
          assign({}, attributes, {
            expires: -1
          })
        );
      },
      withAttributes: function (attributes) {
        return init$1(this.converter, assign({}, this.attributes, attributes))
      },
      withConverter: function (converter) {
        return init$1(assign({}, this.converter, converter), this.attributes)
      }
    },
    {
      attributes: { value: Object.freeze(defaultAttributes) },
      converter: { value: Object.freeze(converter) }
    }
  )
}

var api$1 = init$1(defaultConverter, { path: '/' });

function initializeCookies() {
    window.Cookies = api$1;
}

let defaultOpts = {
    selectorIdPrefix: 'attr',
    attrTextIdPrefix: 'attrText',
    radioIdPrefix: 'sku',
    rushOrderMessageSelector: '.avail-sub',
    priceDisplaySelector: '.prod-price',
    latencyDisplaySelector: '.available',
    addToCartSelector: '.btn-add-to-cart',
    addToCartWarnSelector: '.add-cart-warn',
    hiddenSezzlePriceDisplaySelector: '.hidden-sezzle-price',
    paypalDataAmountSelector: 'data-pp-amount',
    titleSkuDescriptionSeparator: ' - ',
    afterpayAmountSelector: '.afterpay-amount-selector',
    displayDataCandyPoints: false,
    initializedFromFacets: false,
    updateExtend: false,
    updatePartNumbers: false,
    updateStickyAddToCart: false,
    updateSaleRibbon: false,
    changeHeroImageLinks: false,
    interestFreeWidgetAmountSelector: '.interest-free-widget-amount'
};

const loyPotentialPointsPropertyName = 'loyPotentialPoints';

function logDebug(msg) {
}

/* Main initialization function. */
function init(opts) {
    logDebug('init opts: ' + JSON.stringify(opts, null, 2));

    let attrSet = new AttributeSet(opts);

    //attrSet.logAttributes();

    /* set up click listeners */
    let selectors = opts.container.querySelectorAll('.attrSelector');
    selectors.forEach(function(selector) {
        //logDebug("setup selector with id " + selector.getAttribute('id'));
        selector.addEventListener('click', function () {

            let attrId = this.getAttribute('id');
            if (opts.selectorIdPrefix && attrId.startsWith(opts.selectorIdPrefix)) {
                attrId = attrId.substring(opts.selectorIdPrefix.length);
            }
            let attr = attrSet.getAttributeById(attrId);
            if (attr) {
                if (!attr.selected) {
                    attr.select(true);
                }
            }
        });
        selector.addEventListener('mouseenter', function() {
            let valSpan = attrSet.getAttributeTextDisplay(this.dataset['attrType']);
            if (valSpan && !(this.classList.contains('selected'))) {
                valSpan.textContent = this.dataset['displayName'];
            }
        });
        selector.addEventListener('mouseleave', function() {
            let valSpan = attrSet.getAttributeTextDisplay(this.dataset['attrType']),
                selectedAttr = attrSet.getSelectedAttributeOfType(this.dataset['attrType']);
            if (selectedAttr) {
                selectedAttr.displayAttributeText();
            } else {
                if (valSpan) {
                    valSpan.textContent = '';
                }
            }
        });
        selector.addEventListener('keypress', function(evt) {
            let code = evt.charCode || evt.keyCode || evt.which;
            if (code === 13) {
                evt.preventDefault();
                this.click();
            }
        });
        selector.addEventListener('focus', function() {
            this.dispatchEvent(new MouseEvent('mouseenter'));
            this.classList.add('attrSelector-by-js');
        });
        selector.addEventListener('blur', function() {
            this.dispatchEvent(new MouseEvent('mouseleave'));
            this.classList.remove('attrSelector-by-js');
        });
    });
    // for listing page
    let swatchExpander = opts.container.querySelector('.expand');
    if (swatchExpander) {
        swatchExpander.addEventListener('click', function() {
            let moreDiv = opts.container.querySelector('.more-swatches');
            if (moreDiv) {
                let currDisp = moreDiv.style.display;
                if (currDisp === 'none') {
                    moreDiv.style.display = 'flex';
                    swatchExpander.innerHTML = '<i class="icon icon-minus"></i>';
                } else {
                    moreDiv.style.display = 'none';
                    swatchExpander.innerHTML = `<i class="icon icon-plus"></i>${swatchExpander.dataset['remainingCount']}`;
                }
            }
        });
    }

    // set up mutation observer on Extend section
    let extendDiv = opts.container.querySelector('.extend-offer');
    if (extendDiv) {
        function heightCheck(node) {
            let compStyle = getComputedStyle(node),
                newDisplay = compStyle.getPropertyValue('display');
            if (newDisplay === 'none') {
                extendDiv.style.height = '0';
            } else {
                extendDiv.style.height = '106px';
            }
        }
        /* this adds an attribute observer to the div that Extend adds inside the extend-offer div */
        function addDisplayObserver (newNode) {
            let displayObserver = new MutationObserver((mutList) => {
                if (mutList) {
                    mutList.forEach((mutation) => {
                        if (mutation.type === 'attributes') {
                            //console.log(`attributes mutation, name is ${mutation.attributeName}`);
                            // set a timeout here. Extend sets display none and block again every time
                            // a new plan is loaded. This is the main cause of the "jumpiness" when switching
                            // skus. Setting a timeout will allow a delay so we don't jump if a new plan was loaded.
                            setTimeout(heightCheck, 1000, newNode); // observing the Extend API call to take in the 500-700 ms range
                        }
                    });
                }
            });
            displayObserver.observe(newNode, {attributes: true});
        }
        let observer = new MutationObserver((mutationsList, observer) => {
            if (mutationsList) {
                for (let i = 0; i < mutationsList.length; i++ ) {
                    let mutation = mutationsList[i];
                    switch (mutation.type) {
                        case 'childList':
                            //console.log(`childList mutation added: ${mutation.addedNodes}, removed: ${mutation.removedNodes}`);
                            let newNode = extendDiv.children.length ? extendDiv.children[0] : null;
                            if (newNode) {
                                //console.log('found newNode - adding display observer');
                                addDisplayObserver(newNode);
                                setTimeout(heightCheck, 800, newNode);
                            } else {
                                //console.log('no newNode!');
                                extendDiv.style.height = 0;
                            }
                            break;
                    }
                }
            }
        });
        observer.observe(extendDiv, {
            childList: true,
        });
    }

    let selOption = attrSet.getSelectedPurchaseOption();
    if (selOption) {
        attrSet.selectSelected();
    } else {
        attrSet.clearSelectedSku();
    }
}

/* Constructor */
/* An AttributeSet is a container for all of the attributes or "ways" associated with a product. */
function AttributeSet(opts) {
    this.attributeLists = {}; /* map of attribute type to all attributes of that type */
    this.attributes = {}; /* map of attrId to that attribute */
    this.purchaseOptions = opts.purchaseOptions;
    this.opts = opts;
    this.container = opts.container;
    this.selectorIdPrefix = opts.selectorIdPrefix || '';
    this.attrTextIdPrefix = opts.attrTextIdPrefix || '';
    this.radioIdPrefix = opts.radioIdPrefix || '';
    this.addAllFromJson(opts.attributes);
    this.overlaySkuImages = opts.overlaySkuImages;
    this.updateBrowserUrl = opts.updateBrowserUrl;
    this.reloadHash = opts.reloadHash || false;
    this.productId = opts.productId;
    this.titleSkuDescriptionSeparator = opts.titleSkuDescriptionSeparator;
    this.titleTailSeparator = ' | '; // separates product+sku text from whatever is after
    this.displayDataCandyPoints = opts.displayDataCandyPoints || false;
    this.initializedFromFacets = opts.initializedFromFacets || false;
    this.initLoyPotentialPoints = this.displayDataCandyPoints;
    // changeHeroImageLinks - this should only be true on product page
    this.changeHeroImageLinks = opts.changeHeroImageLinks || false;
    this.changeOptionImages = opts.changeOptionImages || false;
    this.skuChangeListenerSelectors = opts.skuChangeListenerSelectors || [];
    this.updateLinkSelectors = opts.updateLinkSelectors || [];
    /* skipExcludedTreatment - in cases e.g. listing pages where not all attribute types are being
        displayed, don't apply the "not available in x" treatment based on invisibly selected options
     */
    this.skipExcludedTreatment = opts.skipExcludedTreatment || false;
    this.heroImageLinkSelector = opts.heroImageLinkSelector || null;
    // It refers to the hero image in the listing page and could be confused with the product page hero image
}

AttributeSet.prototype.updateLinksForOption = function(purchaseOption) {
    for (let i = 0; i < this.updateLinkSelectors.length; i++) {
        document.querySelectorAll(this.updateLinkSelectors[i]).forEach((el) => {
            try {
                let current = el.href;
                let url = new URL(current);
                let newPathname = purchaseOption.purchaseURL;
                el.href = url.protocol + '//' + url.hostname + newPathname + url.search + url.hash;
            } catch(e) {
                console.log(e);
            }
        });
    }
};

AttributeSet.prototype.notifySkuChangeListeners = function(skuId) {
    for (let i = 0; i < this.skuChangeListenerSelectors.length; i++) {
        document.querySelectorAll(this.skuChangeListenerSelectors[i]).forEach((el) => {
            el.dataset['selectedSkuId'] = skuId;
        });
    }
};

AttributeSet.prototype.changeOptionImage = function (purchaseOption) {
    if (this.changeOptionImages) {
        if (this.heroImageLinkSelector) {
            if (purchaseOption.imageURL) {
                let heroLink = document.querySelector(this.heroImageLinkSelector);
                if (heroLink) {
                    let hiddenImg, shownImg;
                    heroLink.querySelectorAll('img').forEach((image) => {
                        /* this should work since direct property access picks up the new value immediately.
                            It is necessary to use getComputedStyle to pick up actual values during transition but
                            we don't want that.
                         */
                        if (parseInt(image.style.opacity) === 1) {
                            shownImg = image;
                        } else {
                            hiddenImg = image;
                        }
                    });
                    if (shownImg && hiddenImg && shownImg.src !== purchaseOption.imageURL) {
                        hiddenImg.title = purchaseOption.displayName;
                        hiddenImg.alt = purchaseOption.displayName;
                        hiddenImg.onload = () => {
                            hiddenImg.style.opacity = 1;
                            shownImg.style.opacity = 0;
                        };
                        hiddenImg.src = purchaseOption.imageURL;
                    }
                } else  {
                    logDebug(`did not find element for ${this.heroImageLinkSelector}`);
                }
            }
        } else {
            console.log('changeOptionImage called but no selector!');
        }
    }
};

AttributeSet.prototype.changeOverlaySkuImage = function(skuId) {
    if (this.overlaySkuImages && skuId && this.overlaySkuImages[skuId]) {
        let skuImage = this.overlaySkuImages[skuId];
        let img = document.querySelector('#overlay-hero img');
        img.src = skuImage.imageUrl;
        img.title = skuImage.imageTitle;
        img.alt = skuImage.imageTitle;
    }
};

AttributeSet.prototype.hideCartWarning = function() {
    this.container.querySelectorAll(this.opts.addToCartWarnSelector).forEach((warning) => {
        warning.classList.add('hide');
    });
};

AttributeSet.prototype.getAttributeTextDisplay = function(attrType) {
    return this.container.querySelector('#' + this.attrTextIdPrefix + attrType + ' .attrValue');
};

AttributeSet.prototype.initDataCandyPoints = function() {
    try {
        // Try to avoid using global variables, please
        fetch('/public/handler/data_candy_product_points_estimate.jsp?productId=' + this.productId, {
            mode: 'same-origin',
            credentials: 'same-origin'
        }).then(response => response.json())
            .then(data => {
                if (data.success) {
                    Object.keys(this.purchaseOptions).forEach(key => {
                        this.purchaseOptions[key][loyPotentialPointsPropertyName] = data.dcPointsEstimate[key];
                    });
                }
                this.updateLoyPotentialPoints();
            });
    } catch(e) {
        console.log(e);
    }
};

AttributeSet.prototype.updateLoyPotentialPoints = function() {
    if (!this.displayDataCandyPoints) {
        return;
    }
    let loyPotentialBoxSelector = '.loy-potential-box';
    let loyPotentialPointsSelector = '.loy-potential-points';
    let selOption = this.getSelectedPurchaseOption();

    if (selOption) {
        if (selOption.hasOwnProperty(loyPotentialPointsPropertyName)) {
            let loyPotentialPointsDisplay = selOption[loyPotentialPointsPropertyName];
            this.container.querySelectorAll(loyPotentialPointsSelector).forEach((selector) => {
                if (loyPotentialPointsDisplay) {
                    selector.innerHTML = loyPotentialPointsDisplay;
                } else {
                    selector.innerHTML = 0;
                }
                selector.closest(loyPotentialBoxSelector).style.display = 'block';
            });
        } else {
            /* hide section */
            this.container.querySelectorAll(loyPotentialPointsSelector).forEach((selector) => {
                selector.closest(loyPotentialBoxSelector).style.display = 'none';
            });

            /* init */
            if (this.initLoyPotentialPoints) {
                this.initLoyPotentialPoints = false;
                this.initDataCandyPoints();
            }
        }
    } else {
        /* hide section */
        this.container.querySelectorAll(loyPotentialPointsSelector).forEach((selector) => {
            selector.closest(loyPotentialBoxSelector).style.display = 'none';
        });
    }
};

AttributeSet.prototype.updatePriceDisplay = function() {
    if (!this.opts.priceDisplaySelector && !this.opts.hiddenSezzlePriceDisplaySelector
        && !this.opts.paypalDataAmountSelector && !this.opts.afterpayAmountSelector) {
        return;
    }
    let selOption = this.getSelectedPurchaseOption(),
        priceDisplay = '',
        altSezzlePriceDisplay = '',
        optionPrice = '',
        optionSplitPriceFormatted = '';
    if (selOption && selOption.priceStatus) {
        if (selOption.priceStatus.onSale) {
            priceDisplay = '<strike>' + selOption.priceStatus.listPriceFormatted
                + '</strike><span class="on-sale">' + selOption.priceStatus.salePriceFormatted + '</span>';
            altSezzlePriceDisplay = selOption.priceStatus.salePriceFormatted;
        } else {
            priceDisplay = selOption.priceStatus.listPriceFormatted;
        }
        optionPrice = selOption.priceStatus.price;
        optionSplitPriceFormatted = selOption.priceStatus.splitPriceFormatted;
    }

    if (this.opts.priceDisplaySelector) {
        this.container.querySelectorAll(this.opts.priceDisplaySelector).forEach((selector) => {
            selector.innerHTML = priceDisplay;
        });
    }

    if (this.opts.hiddenSezzlePriceDisplaySelector) {
        this.container.querySelectorAll(this.opts.hiddenSezzlePriceDisplaySelector).forEach((selector) => {
            selector.innerHTML = altSezzlePriceDisplay.length ? altSezzlePriceDisplay : priceDisplay;
        });
    }

    if (this.opts.paypalDataAmountSelector) {
        this.container.querySelectorAll('[' + this.opts.paypalDataAmountSelector + ']').forEach((selector) => {
            selector.setAttribute(this.opts.paypalDataAmountSelector, optionPrice);
        });
    }

    if (this.opts.afterpayAmountSelector) {
        this.container.querySelectorAll(this.opts.afterpayAmountSelector).forEach((selector) => {
            selector.innerHTML = optionPrice;
        });
    }

    if (this.opts.interestFreeWidgetAmountSelector) {
        this.container.querySelectorAll(this.opts.interestFreeWidgetAmountSelector).forEach((selector) => {
            selector.innerHTML = optionSplitPriceFormatted;
        });
    }
};

AttributeSet.prototype.selectSku = function(skuId) {
    let radioButton = this.container.querySelector('#' + this.radioIdPrefix + skuId);
    if (radioButton) {
        radioButton.checked = true;
    }
    this.notifySkuChangeListeners(skuId);
};

AttributeSet.prototype.updateBrowserStateForOption = function(purchaseOption) {
    if (!this.updateBrowserUrl) {
        return;
    }
    let newPathname = purchaseOption.purchaseURL;

    let loc = window.location;
    let newUrl = loc.protocol + '//' + loc.hostname + newPathname + window.location.search + window.location.hash;
    //console.log('updating to newUrl ' + newUrl);
    history.replaceState(null, '', newUrl);

    // compute the new document title
    let title = document.title;
    if (title && purchaseOption.displayName && purchaseOption.productDisplayName && title.toLowerCase().startsWith(purchaseOption.productDisplayName.toLowerCase())) {
        let newTitle = '';
        newTitle += title.substring(0, purchaseOption.productDisplayName.length);
        newTitle += this.titleSkuDescriptionSeparator;
        newTitle += purchaseOption.displayName;
        if (title.length > purchaseOption.productDisplayName.length) {
            let tail = title.substring(purchaseOption.productDisplayName.length);
            // the tail as computed here may begin with a prior sku description. look for the pipe string
            let pipeLoc = tail.lastIndexOf(this.titleTailSeparator);
            if (pipeLoc === -1) ; else {
                newTitle += tail.substring(pipeLoc);
            }
        }
        document.title = newTitle;
    }

};

AttributeSet.prototype.clearSelectedSku = function() {
    let checkedButton = this.container.querySelector('input[name="skuId"]:checked');
    if (checkedButton) {
        checkedButton.checked = false;
    }
};

AttributeSet.prototype.updateLatency = function(purchaseOption) {
    if (purchaseOption && purchaseOption.latency) {
        let latencyEl = this.container.querySelector(this.opts.latencyDisplaySelector);
        if (latencyEl) {
            latencyEl.style.display = '';
            latencyEl.innerHTML = purchaseOption.latency;
        }
    } else {
        this.clearLatency();
    }
};

AttributeSet.prototype.clearLatency = function() {
    let latencyEl = this.container.querySelector(this.opts.latencyDisplaySelector);
    if (latencyEl) {
        latencyEl.innerHTML = '';
        latencyEl.style.display = 'none';
    }
};

AttributeSet.prototype.updateExtend = function(purchaseOption) {
    if (this.opts.updateExtend && document.querySelector('#extend-offer')) {
        let localProductId = this.productId;
        extendProxy(function () {
            const component = Extend.buttons.instance('#extend-offer');
            const referenceId = localProductId + '-' + purchaseOption.skuId;
            if (component) {
                component.setActiveProduct(referenceId);
            } else {
                const extendOpts = {referenceId};
                Extend.buttons.render('#extend-offer', extendOpts);
            }
        });
    }
};

AttributeSet.prototype.updateSaleRibbon = function(purchaseOption) {
    if (this.opts.updateSaleRibbon && this.opts.saleRibbonLocator && purchaseOption && purchaseOption.priceStatus) {
        let badgeEl = null;
        if (typeof this.opts.saleRibbonLocator === 'string') {
            badgeEl = document.querySelector(this.opts.saleRibbonLocator);
        }
        if (!badgeEl) {
            return;
        }
        if (purchaseOption.priceStatus.onSale) {
            badgeEl.style.display = '';
            badgeEl.querySelectorAll('.pct-amt').forEach((el) => {
                el.innerHTML = purchaseOption.priceStatus.salePercentageOff + '%';
            });
        } else {
            if (typeof badgeEl.dataset.ribbonType !== 'undefined' && badgeEl.dataset.ribbonType === 'sale') {
                badgeEl.style.display = 'none';
            }
        }
    }
};

AttributeSet.prototype.updateStickyAddToCart = function(purchaseOption) {
    let stickyElem = document.querySelector('.stick-add-to-cart');
    if (this.opts.updateStickyAddToCart && stickyElem) {
        logDebug(`updateStickyAddToCart: purchaseOption is ${JSON.stringify(purchaseOption)}`);
        let skuId = purchaseOption.skuId;
        let imageEl = document.querySelector('.hero-image-link[data-for*="' + skuId + '"] img');
        if (imageEl == null) {
            imageEl = document.querySelector('.hero-image-link img');
        }
        if (imageEl != null) {
            stickyElem.querySelector('.stick-img img').src = imageEl.src;
        }

        let attrTypes = this.getAttributeTypes();
        for (let i = 0; i < attrTypes.length; i++) {
            let attrType = attrTypes[i];
            let attrElem = stickyElem.querySelector('#stick-attr-' + attrType);
            /* case isSingleSkuProduct */
            if (attrElem != null) {
                let selectedOfType = this.getSelectedAttributeOfType(attrType);
                if (selectedOfType != null) {
                    attrElem.querySelector('.stick-atc-skus-opt').innerHTML = selectedOfType.displayName;
                }
            }
        }

        if (purchaseOption.priceStatus) {
            stickyElem.querySelector('#stick-button .btn-price').innerHTML = purchaseOption.priceStatus.onSale ?
                purchaseOption.priceStatus.salePriceFormatted : purchaseOption.priceStatus.listPriceFormatted;
        } else {
            console.log('updateStickyAddToCart no priceStatus!');
        }
    }
};

AttributeSet.prototype.clearExtend = function() {
    if (this.opts.updateExtend) {
        extendProxy(function () {
            const component = Extend.buttons.instance('#extend-offer');
            if (component) {
                component.setActiveProduct('');
            }
        });
    }
};

AttributeSet.prototype.updatePartNumbers = function(purchaseOption) {
    let pnElem = document.querySelector('.part-numbers');
    if (this.opts.updatePartNumbers && pnElem) {
        let html = '';
        if (purchaseOption) {
            if (purchaseOption.upc) {
                html += '<strong>UPC</strong>: ' + purchaseOption.upc + '<br>';
            }
            if (purchaseOption.ean) {
                html += '<strong>EAN</strong>: ' + purchaseOption.ean + '<br>';
            }
            if (purchaseOption.mpn) {
                html += '<strong>MPN</strong>: ' + purchaseOption.mpn + '<br>';
            }
        }
        pnElem.innerHTML = html;
    }
};


AttributeSet.prototype.logAttributes = function() {
    for (let type in this.attributeLists) {
        console.log('attributes of type ' + type);
        let lizt = this.attributeLists[type];
        for (let i = 0; i < lizt.length; i++) {
            lizt[i].logJson();
        }
    }
    for (let attrId in this.attributes) {
        console.log('attribute for id: ' + attrId + " is: ");
        this.attributes[attrId].logJson();
    }
};

AttributeSet.prototype.getSelectedPurchaseOption = function() {
    let attrTypes = this.getAttributeTypes();
    if (attrTypes && attrTypes.length) {
        let skuCounts = {};
        // algorithm - keep a map of skuId to count of how many attributes reference it
        // once done, find the one with count equal to the number of attribute types
        for (let i = 0; i < attrTypes.length; i++) {
            let type = attrTypes[i];
            let attr = this.getSelectedAttributeOfType(type);
            if (!attr) {
                return null;
            }
            for (let j = 0; j < attr.catalogAvailableSkuIds.length; j++) {
                let skuId = attr.catalogAvailableSkuIds[j];
                let count = skuCounts[skuId];
                if (count) {
                    count = count + 1;
                } else {
                    count = 1;
                }
                skuCounts[skuId] = count;
            }
        }
        for (let key in skuCounts) {
            let skuCount = skuCounts[key];
            if (skuCount === attrTypes.length) {
                return this.purchaseOptions[key];
            }
        }
    }

    return null;
};

AttributeSet.prototype.getAttributeTypes = function() {
    let typeList = [];
    for (let type in this.attributeLists) {
        typeList.push(type);
    }
    return typeList;
};

AttributeSet.prototype.getAttributeById = function(attrId) {
    return this.attributes[attrId];
};

AttributeSet.prototype.getAttributesOfType = function(type) {
    return this.attributeLists[type];
};

AttributeSet.prototype.getSelectedAttributeOfType = function(type) {
    let attrList = this.getAttributesOfType(type);
    if (!attrList) {
        return null;
    }
    for (let i = 0; i < attrList.length; i++) {
        let attr = attrList[i];
        if (attr.selected) {
            return attr;
        }
    }
    return null;
};

AttributeSet.prototype.addAttribute = function(attribute) {
    attribute.attributeSet = this;
    let attrList = this.attributeLists[attribute.type];
    if (!attrList) {
        attrList = [];
        this.attributeLists[attribute.type] = attrList;
    }
    attrList.push(attribute);
    this.attributes[attribute.attrId] = attribute;
};

AttributeSet.prototype.addAttributeFromJson = function(json) {
    let newAttribute = new Attribute();
    newAttribute.initFromJson(json);
    this.addAttribute(newAttribute);
};

AttributeSet.prototype.addAllFromJson = function(jsonArr) {
    if (jsonArr && jsonArr.length) {
        for (let i = 0; i < jsonArr.length; i++) {
            this.addAttributeFromJson(jsonArr[i]);
        }
        // sort the attribute lists
        for (let attrType in this.attributeLists) {
            let attrList = this.attributeLists[attrType];
            attrList.sort(function(a,b) {
                return a.orderInList - b.orderInList;
            });
        }
    }
};

/* this is a funny name no? basically - some attributes come through as selected=true in the JSON, but a lot of other
 * UI stuff doesn't happen properly based on that (excluding incompatible attributes, displaying text etc). Rather than try to
 * duplicate all that I want to try just calling select() on the ones that are flagged as selected during the init process -
 * hopefully that takes care of all that.
 */
AttributeSet.prototype.selectSelected = function() {
    for (let attrId in this.attributes) {
        let attr = this.attributes[attrId];
        if (attr && attr.selected) {
            attr.select();
        }
    }
};

/* Constructor */
/* An Attribute is a particular attribute or "way" associated with a product, e.g. a color or size */
function Attribute() {
    this.type = '';
    this.typeDisplayName = '';
    this.abbreviation = '';
    this.colorCode = '';
    this.imageUrl = '';
    this.swatch = false;
    this.attributeSet = null;
    this.displayName = '';
    this.excluded = false; /* is this attribute excluded from possible options based on other selections */
    this.disabled = false; /* is this disabled entirely. this is here due to desire to "gray out" unavailable options, e.g. there are no red SKUs available */
    this.selected = false;
    this.orderInList = -1;
}

Attribute.prototype.logJson = function() {
    /* extend a new object so we can remove the backreference to set and avoid cycles */
    let tempattr = spExtend({}, this);
    delete tempattr.attributeSet;
    console.log(JSON.stringify(tempattr));
};

Attribute.prototype.getSelectorId = function() {
    return this.attributeSet.selectorIdPrefix + this.attrId;
};

Attribute.prototype.getElement = function() {
    return document.querySelector('#' + this.getSelectorId());
};

Attribute.prototype.initFromJson = function(json) {
    this.type = json.type || this.type;
    this.typeDisplayName = json.typeDisplayName || this.typeDisplayName;
    this.attrId = json.attrId || this.attrId;
    this.attributeSet = json.attributeSet || this.attributeSet;
    this.displayName = json.displayName || this.displayName;
    this.catalogAvailableSkuIds = json.catalogAvailableSkuIds || this.catalogAvailableSkuIds;
    this.disabled = json.disabled || this.disabled;
    this.selected = json.selected || this.selected;
    this.abbreviation = json.abbreviation || this.abbreviation;
    this.colorCode = json.colorCode || this.colorCode;
    this.imageUrl = json.imageUrl || this.imageUrl;
    this.swatch = json.swatch || this.swatch;
    this.orderInList = json.orderInList || this.orderInList;
};

Attribute.prototype.hasSharedSku = function(otherAttr) {
    if (this.disabled || otherAttr.disabled) {
        return false;
    }
    for (let i = 0; i < this.catalogAvailableSkuIds.length; i++) {
        let skuId = this.catalogAvailableSkuIds[i];
        if (otherAttr.catalogAvailableSkuIds.includes(skuId)) {
            return true;
        }
    }

    return false;
};

Attribute.prototype.displayAttributeText = function() {
    this.setAttributeText(this.displayName);
};

Attribute.prototype.setAttributeText = function(txt) {
    let el = this.attributeSet.getAttributeTextDisplay(this.type);
    if (el) {
        el.textContent = txt;
    }
};

Attribute.prototype.getAttributeText = function() {
    let el = this.attributeSet.getAttributeTextDisplay(this.type);
    if (el) {
        return el.textContent;
    }
};

Attribute.prototype.toString = function() {
    return this.displayName + ' ' + this.attrId;
};

Attribute.prototype.select = function(userTriggered) {
    //logDebug('select() attribute is: ' + this.toString());
    if (this.disabled) ; else {
        this.include();
        this.selected = true;
        let el = this.getElement();
        if (el) {
            el.classList.add('selected');
        }
        this.displayAttributeText();
        let attrsOfThisType = this.attributeSet.getAttributesOfType(this.type);
        /* deselect any previously selected attribute of this type */
        if (attrsOfThisType) {
            for (let i = 0; i < attrsOfThisType.length; i++) {
                let attrOfThisType = attrsOfThisType[i];
                if (attrOfThisType.disabled) {
                    continue;
                }
                if (attrOfThisType.attrId !== this.attrId) {
                    logDebug('deselect ' + attrOfThisType.toString());
                    attrOfThisType.deselect();
                    //attrOfThisType.include();
                }
            }
        }
        let attrTypes = this.attributeSet.getAttributeTypes();
        for (let i = 0; i < attrTypes.length; i++) {
            let attrType = attrTypes[i];
            if (attrType !== this.type) {
                let selectedOfType = this.attributeSet.getSelectedAttributeOfType(attrType);
                /* if the selected attr is not compatible with our new selection, deselect it. Then select the first
                 * one that *is* compatible
                 */
                if (selectedOfType && !this.hasSharedSku(selectedOfType)) {
                    logDebug('selected ' + selectedOfType.toString() + ' does not share a sku with selection. Deselecting');
                    selectedOfType.deselect();
                    selectedOfType = null;
                    let attrList = this.attributeSet.attributeLists[attrType];
                    for (let j = 0; j < attrList.length; j++) {
                        let candidateAttr = attrList[j];
                        if (this.hasSharedSku(candidateAttr)) {
                            logDebug('candidate ' + candidateAttr.toString() + ' has a shared sku. Calling select');
                            candidateAttr.select(); /* this makes me nervous given we are still within select() for a different attr... */
                            selectedOfType = candidateAttr;
                            break;
                        } else {
                            logDebug('candidate ' + candidateAttr.toString() + ' has no shared sku.');
                        }
                    }
                }
                /* only update include/exclude if they have not selected a value for that type already */
                /* why was that? there was a reason...let's test it */
                /* The reason was, it was assumed that the user had explicitly selected an option (color) first so it
                 * was a weird experience to then exclude other colors when they selected a size. With defaults selected on
                 * page load this is no longer the case.
                 */
                /*if (!selectedOfType) {*/
                let attrsOfType = this.attributeSet.getAttributesOfType(attrType);
                for (let j = 0; j < attrsOfType.length; j++) {
                    let attrOfType = attrsOfType[j];
                    if (attrOfType.disabled) {
                        continue;
                    }
                    if (!this.hasSharedSku(attrOfType)) {
                        logDebug('attribute ' + attrOfType.toString() + ' has no shared sku. Excluding');
                        attrOfType.exclude('Not available in ' + this.displayName);
                    } else {
                        logDebug('attribute ' + attrOfType.toString() + ' has a shared sku. Including');
                        attrOfType.include();
                    }
                }
                //}
            }
        }


        let selOption = this.attributeSet.getSelectedPurchaseOption();
        // update if selOption, and otherwise clear
        if (selOption) {
            // always update price
            this.attributeSet.updatePriceDisplay();
            if (this.attributeSet.displayDataCandyPoints) {
                this.attributeSet.updateLoyPotentialPoints();
            }
            if (this.attributeSet.updatePartNumbers) {
                this.attributeSet.updatePartNumbers(selOption);
            }
            // console.log('found selected option: ' + JSON.stringify(selOption));
            this.attributeSet.selectSku(selOption.skuId);
            if (userTriggered) {
                this.attributeSet.updateBrowserStateForOption(selOption);
            }
            if (userTriggered || this.attributeSet.initializedFromFacets) {
                this.attributeSet.updateLinksForOption(selOption);
            }
            if (this.attributeSet.changeHeroImageLinks) {
                logDebug('call changeHeroImageLink for ' + selOption.skuId);
                changeHeroImageLink(selOption.skuId);
            }
            this.attributeSet.changeOverlaySkuImage(selOption.skuId);
            if (this.swatch) { /* this was double-firing for both color and the hidden size options.
				 Note that this will still be a problem if skus get set up with multiple types of swatch options. */
                this.attributeSet.changeOptionImage(selOption);
            }
            this.attributeSet.updateLatency(selOption);
            this.attributeSet.hideCartWarning();
            this.attributeSet.updateExtend(selOption);
            this.attributeSet.updateStickyAddToCart(selOption);
            this.attributeSet.updateSaleRibbon(selOption);
        } else {
            this.attributeSet.clearSelectedSku();
            this.attributeSet.clearLatency();
            this.attributeSet.clearExtend();
        }
    }

    logDebug('exit select for ' + this.toString());
};

Attribute.prototype.deselect = function() {
    this.selected = false;
    if (this.getAttributeText() === this.displayName) {
        this.setAttributeText('');
    }
    const el = this.getElement();
    if (el) {
        el.classList.remove('selected');
    }
};

/* exclude this attribute based on prior selection */
Attribute.prototype.exclude = function(titleMsg) {
    if (this.attributeSet.skipExcludedTreatment) {
        return;
    }
    this.excluded = true;
    let el = this.getElement();
    if (el) {
        el.classList.add('excluded');
        if (titleMsg) {
            el.title = titleMsg;
        }
    }
};

/* reverse of exclude */
Attribute.prototype.include = function() {
    this.excluded = false;
    const el = this.getElement();
    if (el) {
        el.classList.remove('excluded');
        el.title = '';
    }
};

function changeHeroImageLink(pValue) {
    let isOwl = document.querySelectorAll('.owl-thumbs .hero-image-link').length;
    if (isOwl) {
        let jumpIndex = 0;
        const heroImageLinks = document.querySelectorAll('.hero-image-link[data-for*="' + pValue + '"]');
        if (heroImageLinks.length > 0) {
            jumpIndex = Array.from(document.querySelectorAll('.hero-image-link')).indexOf(heroImageLinks[0]);
        }
        //console.log(`for owl and sku ${pValue} - computed jumpIndex ${jumpIndex}`);
        safeOwlJump(jumpIndex);
    } else {
        const visibleHeroImageLinks = Array.from(document.querySelectorAll('.hero-image-link'))
            .filter(link => link.offsetParent !== null);
        visibleHeroImageLinks.forEach(link => link.style.display = 'none');

        const heroImageLinksWithValue = Array.from(document.querySelectorAll('.hero-image-link[data-for*="' + pValue + '"]'));
        if (heroImageLinksWithValue.length > 0) {
            heroImageLinksWithValue[0].style.display = 'block';
        } else {
            const allHeroImageLinks = document.querySelectorAll('.hero-image-link');
            if (allHeroImageLinks.length > 0) {
                allHeroImageLinks[0].style.display = 'block';
            }
        }
    }

    function safeOwlJump(jumpIndex) {
        /* prodImageSlider defined in product.mjs initializeImageSwitch */
        if (typeof prodImageSlider === 'object') {
            //console.log('safeOwlJump using prodImageSlider to jumpIndex ' + jumpIndex);
            prodImageSlider.goTo(jumpIndex);
        } else {
            //console.log('safeOwlJump setting timeout');
            setTimeout(safeOwlJump, 10, jumpIndex);
        }
    }
}

function initSkuPicker(pOpts) {
    let opts = spExtend({}, defaultOpts, pOpts);
    init(opts);
}

var tinySlider = {};

Object.defineProperty(tinySlider, '__esModule', { value: true });

var win$1 = window;
var raf = win$1.requestAnimationFrame || win$1.webkitRequestAnimationFrame || win$1.mozRequestAnimationFrame || win$1.msRequestAnimationFrame || function (cb) {
  return setTimeout(cb, 16);
};

var win = window;
var caf = win.cancelAnimationFrame || win.mozCancelAnimationFrame || function (id) {
  clearTimeout(id);
};

function extend() {
  var obj,
      name,
      copy,
      target = arguments[0] || {},
      i = 1,
      length = arguments.length;

  for (; i < length; i++) {
    if ((obj = arguments[i]) !== null) {
      for (name in obj) {
        copy = obj[name];

        if (target === copy) {
          continue;
        } else if (copy !== undefined) {
          target[name] = copy;
        }
      }
    }
  }

  return target;
}

function checkStorageValue(value) {
  return ['true', 'false'].indexOf(value) >= 0 ? JSON.parse(value) : value;
}

function setLocalStorage(storage, key, value, access) {
  if (access) {
    try {
      storage.setItem(key, value);
    } catch (e) {}
  }

  return value;
}

function getSlideId() {
  var id = window.tnsId;
  window.tnsId = !id ? 1 : id + 1;
  return 'tns' + window.tnsId;
}

function getBody() {
  var doc = document,
      body = doc.body;

  if (!body) {
    body = doc.createElement('body');
    body.fake = true;
  }

  return body;
}

var docElement = document.documentElement;

function setFakeBody(body) {
  var docOverflow = '';

  if (body.fake) {
    docOverflow = docElement.style.overflow; //avoid crashing IE8, if background image is used

    body.style.background = ''; //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible

    body.style.overflow = docElement.style.overflow = 'hidden';
    docElement.appendChild(body);
  }

  return docOverflow;
}

function resetFakeBody(body, docOverflow) {
  if (body.fake) {
    body.remove();
    docElement.style.overflow = docOverflow; // Trigger layout so kinetic scrolling isn't disabled in iOS6+
    // eslint-disable-next-line

    docElement.offsetHeight;
  }
}

// get css-calc 
function calc() {
  var doc = document,
      body = getBody(),
      docOverflow = setFakeBody(body),
      div = doc.createElement('div'),
      result = false;
  body.appendChild(div);

  try {
    var str = '(10px * 10)',
        vals = ['calc' + str, '-moz-calc' + str, '-webkit-calc' + str],
        val;

    for (var i = 0; i < 3; i++) {
      val = vals[i];
      div.style.width = val;

      if (div.offsetWidth === 100) {
        result = val.replace(str, '');
        break;
      }
    }
  } catch (e) {}

  body.fake ? resetFakeBody(body, docOverflow) : div.remove();
  return result;
}

// get subpixel support value
function percentageLayout() {
  // check subpixel layout supporting
  var doc = document,
      body = getBody(),
      docOverflow = setFakeBody(body),
      wrapper = doc.createElement('div'),
      outer = doc.createElement('div'),
      str = '',
      count = 70,
      perPage = 3,
      supported = false;
  wrapper.className = "tns-t-subp2";
  outer.className = "tns-t-ct";

  for (var i = 0; i < count; i++) {
    str += '<div></div>';
  }

  outer.innerHTML = str;
  wrapper.appendChild(outer);
  body.appendChild(wrapper);
  supported = Math.abs(wrapper.getBoundingClientRect().left - outer.children[count - perPage].getBoundingClientRect().left) < 2;
  body.fake ? resetFakeBody(body, docOverflow) : wrapper.remove();
  return supported;
}

function mediaquerySupport() {
  if (window.matchMedia || window.msMatchMedia) {
    return true;
  }

  var doc = document,
      body = getBody(),
      docOverflow = setFakeBody(body),
      div = doc.createElement('div'),
      style = doc.createElement('style'),
      rule = '@media all and (min-width:1px){.tns-mq-test{position:absolute}}',
      position;
  style.type = 'text/css';
  div.className = 'tns-mq-test';
  body.appendChild(style);
  body.appendChild(div);

  if (style.styleSheet) {
    style.styleSheet.cssText = rule;
  } else {
    style.appendChild(doc.createTextNode(rule));
  }

  position = window.getComputedStyle ? window.getComputedStyle(div).position : div.currentStyle['position'];
  body.fake ? resetFakeBody(body, docOverflow) : div.remove();
  return position === "absolute";
}

// create and append style sheet
function createStyleSheet(media, nonce) {
  // Create the <style> tag
  var style = document.createElement("style"); // style.setAttribute("type", "text/css");
  // Add a media (and/or media query) here if you'd like!
  // style.setAttribute("media", "screen")
  // style.setAttribute("media", "only screen and (max-width : 1024px)")

  if (media) {
    style.setAttribute("media", media);
  } // Add nonce attribute for Content Security Policy


  if (nonce) {
    style.setAttribute("nonce", nonce);
  } // WebKit hack :(
  // style.appendChild(document.createTextNode(""));
  // Add the <style> element to the page


  document.querySelector('head').appendChild(style);
  return style.sheet ? style.sheet : style.styleSheet;
}

// cross browsers addRule method
function addCSSRule(sheet, selector, rules, index) {
  // return raf(function() {
  'insertRule' in sheet ? sheet.insertRule(selector + '{' + rules + '}', index) : sheet.addRule(selector, rules, index); // });
}

// cross browsers addRule method
function removeCSSRule(sheet, index) {
  // return raf(function() {
  'deleteRule' in sheet ? sheet.deleteRule(index) : sheet.removeRule(index); // });
}

function getCssRulesLength(sheet) {
  var rule = 'insertRule' in sheet ? sheet.cssRules : sheet.rules;
  return rule.length;
}

function toDegree(y, x) {
  return Math.atan2(y, x) * (180 / Math.PI);
}

function getTouchDirection(angle, range) {
  var direction = false,
      gap = Math.abs(90 - Math.abs(angle));

  if (gap >= 90 - range) {
    direction = 'horizontal';
  } else if (gap <= range) {
    direction = 'vertical';
  }

  return direction;
}

// https://toddmotto.com/ditch-the-array-foreach-call-nodelist-hack/
function forEach(arr, callback, scope) {
  for (var i = 0, l = arr.length; i < l; i++) {
    callback.call(scope, arr[i], i);
  }
}

var classListSupport = ('classList' in document.createElement('_'));

var hasClass = classListSupport ? function (el, str) {
  return el.classList.contains(str);
} : function (el, str) {
  return el.className.indexOf(str) >= 0;
};

var addClass = classListSupport ? function (el, str) {
  if (!hasClass(el, str)) {
    el.classList.add(str);
  }
} : function (el, str) {
  if (!hasClass(el, str)) {
    el.className += ' ' + str;
  }
};

var removeClass = classListSupport ? function (el, str) {
  if (hasClass(el, str)) {
    el.classList.remove(str);
  }
} : function (el, str) {
  if (hasClass(el, str)) {
    el.className = el.className.replace(str, '');
  }
};

function hasAttr(el, attr) {
  return el.hasAttribute(attr);
}

function getAttr(el, attr) {
  return el.getAttribute(attr);
}

function isNodeList(el) {
  // Only NodeList has the "item()" function
  return typeof el.item !== "undefined";
}

function setAttrs(els, attrs) {
  els = isNodeList(els) || els instanceof Array ? els : [els];

  if (Object.prototype.toString.call(attrs) !== '[object Object]') {
    return;
  }

  for (var i = els.length; i--;) {
    for (var key in attrs) {
      els[i].setAttribute(key, attrs[key]);
    }
  }
}

function removeAttrs(els, attrs) {
  els = isNodeList(els) || els instanceof Array ? els : [els];
  attrs = attrs instanceof Array ? attrs : [attrs];
  var attrLength = attrs.length;

  for (var i = els.length; i--;) {
    for (var j = attrLength; j--;) {
      els[i].removeAttribute(attrs[j]);
    }
  }
}

function arrayFromNodeList(nl) {
  var arr = [];

  for (var i = 0, l = nl.length; i < l; i++) {
    arr.push(nl[i]);
  }

  return arr;
}

function hideElement(el, forceHide) {
  if (el.style.display !== 'none') {
    el.style.display = 'none';
  }
}

function showElement(el, forceHide) {
  if (el.style.display === 'none') {
    el.style.display = '';
  }
}

function isVisible(el) {
  return window.getComputedStyle(el).display !== 'none';
}

function whichProperty(props) {
  if (typeof props === 'string') {
    var arr = [props],
        Props = props.charAt(0).toUpperCase() + props.substr(1),
        prefixes = ['Webkit', 'Moz', 'ms', 'O'];
    prefixes.forEach(function (prefix) {
      if (prefix !== 'ms' || props === 'transform') {
        arr.push(prefix + Props);
      }
    });
    props = arr;
  }

  var el = document.createElement('fakeelement');
      props.length;

  for (var i = 0; i < props.length; i++) {
    var prop = props[i];

    if (el.style[prop] !== undefined) {
      return prop;
    }
  }

  return false; // explicit for ie9-
}

function has3DTransforms(tf) {
  if (!tf) {
    return false;
  }

  if (!window.getComputedStyle) {
    return false;
  }

  var doc = document,
      body = getBody(),
      docOverflow = setFakeBody(body),
      el = doc.createElement('p'),
      has3d,
      cssTF = tf.length > 9 ? '-' + tf.slice(0, -9).toLowerCase() + '-' : '';
  cssTF += 'transform'; // Add it to the body to get the computed style

  body.insertBefore(el, null);
  el.style[tf] = 'translate3d(1px,1px,1px)';
  has3d = window.getComputedStyle(el).getPropertyValue(cssTF);
  body.fake ? resetFakeBody(body, docOverflow) : el.remove();
  return has3d !== undefined && has3d.length > 0 && has3d !== "none";
}

// get transitionend, animationend based on transitionDuration
// @propin: string
// @propOut: string, first-letter uppercase
// Usage: getEndProperty('WebkitTransitionDuration', 'Transition') => webkitTransitionEnd
function getEndProperty(propIn, propOut) {
  var endProp = false;

  if (/^Webkit/.test(propIn)) {
    endProp = 'webkit' + propOut + 'End';
  } else if (/^O/.test(propIn)) {
    endProp = 'o' + propOut + 'End';
  } else if (propIn) {
    endProp = propOut.toLowerCase() + 'end';
  }

  return endProp;
}

// Test via a getter in the options object to see if the passive property is accessed
var supportsPassive = false;

try {
  var opts = Object.defineProperty({}, 'passive', {
    get: function () {
      supportsPassive = true;
    }
  });
  window.addEventListener("test", null, opts);
} catch (e) {}

var passiveOption = supportsPassive ? {
  passive: true
} : false;

function addEvents(el, obj, preventScrolling) {
  for (var prop in obj) {
    var option = ['touchstart', 'touchmove'].indexOf(prop) >= 0 && !preventScrolling ? passiveOption : false;
    el.addEventListener(prop, obj[prop], option);
  }
}

function removeEvents(el, obj) {
  for (var prop in obj) {
    var option = ['touchstart', 'touchmove'].indexOf(prop) >= 0 ? passiveOption : false;
    el.removeEventListener(prop, obj[prop], option);
  }
}

function Events() {
  return {
    topics: {},
    on: function (eventName, fn) {
      this.topics[eventName] = this.topics[eventName] || [];
      this.topics[eventName].push(fn);
    },
    off: function (eventName, fn) {
      if (this.topics[eventName]) {
        for (var i = 0; i < this.topics[eventName].length; i++) {
          if (this.topics[eventName][i] === fn) {
            this.topics[eventName].splice(i, 1);
            break;
          }
        }
      }
    },
    emit: function (eventName, data) {
      data.type = eventName;

      if (this.topics[eventName]) {
        this.topics[eventName].forEach(function (fn) {
          fn(data, eventName);
        });
      }
    }
  };
}

function jsTransform(element, attr, prefix, postfix, to, duration, callback) {
  var tick = Math.min(duration, 10),
      unit = to.indexOf('%') >= 0 ? '%' : 'px',
      to = to.replace(unit, ''),
      from = Number(element.style[attr].replace(prefix, '').replace(postfix, '').replace(unit, '')),
      positionTick = (to - from) / duration * tick;
  setTimeout(moveElement, tick);

  function moveElement() {
    duration -= tick;
    from += positionTick;
    element.style[attr] = prefix + from + unit + postfix;

    if (duration > 0) {
      setTimeout(moveElement, tick);
    } else {
      callback();
    }
  }
}

// Object.keys
if (!Object.keys) {
  Object.keys = function (object) {
    var keys = [];

    for (var name in object) {
      if (Object.prototype.hasOwnProperty.call(object, name)) {
        keys.push(name);
      }
    }

    return keys;
  };
} // ChildNode.remove


if (!("remove" in Element.prototype)) {
  Element.prototype.remove = function () {
    if (this.parentNode) {
      this.parentNode.removeChild(this);
    }
  };
}
var tns = function (options) {
  options = extend({
    container: '.slider',
    mode: 'carousel',
    axis: 'horizontal',
    items: 1,
    gutter: 0,
    edgePadding: 0,
    fixedWidth: false,
    autoWidth: false,
    viewportMax: false,
    slideBy: 1,
    center: false,
    controls: true,
    controlsPosition: 'top',
    controlsText: ['prev', 'next'],
    controlsContainer: false,
    prevButton: false,
    nextButton: false,
    nav: true,
    navPosition: 'top',
    navContainer: false,
    navAsThumbnails: false,
    arrowKeys: false,
    speed: 300,
    autoplay: false,
    autoplayPosition: 'top',
    autoplayTimeout: 5000,
    autoplayDirection: 'forward',
    autoplayText: ['start', 'stop'],
    autoplayHoverPause: false,
    autoplayButton: false,
    autoplayButtonOutput: true,
    autoplayResetOnVisibility: true,
    animateIn: 'tns-fadeIn',
    animateOut: 'tns-fadeOut',
    animateNormal: 'tns-normal',
    animateDelay: false,
    loop: true,
    rewind: false,
    autoHeight: false,
    responsive: false,
    lazyload: false,
    lazyloadSelector: '.tns-lazy-img',
    touch: true,
    mouseDrag: false,
    swipeAngle: 15,
    nested: false,
    preventActionWhenRunning: false,
    preventScrollOnTouch: false,
    freezable: true,
    onInit: false,
    useLocalStorage: true,
    nonce: false
  }, options || {});
  var doc = document,
      win = window,
      KEYS = {
    ENTER: 13,
    SPACE: 32,
    LEFT: 37,
    RIGHT: 39
  },
      tnsStorage = {},
      localStorageAccess = options.useLocalStorage;

  if (localStorageAccess) {
    // check browser version and local storage access
    var browserInfo = navigator.userAgent;
    var uid = new Date();

    try {
      tnsStorage = win.localStorage;

      if (tnsStorage) {
        tnsStorage.setItem(uid, uid);
        localStorageAccess = tnsStorage.getItem(uid) == uid;
        tnsStorage.removeItem(uid);
      } else {
        localStorageAccess = false;
      }

      if (!localStorageAccess) {
        tnsStorage = {};
      }
    } catch (e) {
      localStorageAccess = false;
    }

    if (localStorageAccess) {
      // remove storage when browser version changes
      if (tnsStorage['tnsApp'] && tnsStorage['tnsApp'] !== browserInfo) {
        ['tC', 'tPL', 'tMQ', 'tTf', 't3D', 'tTDu', 'tTDe', 'tADu', 'tADe', 'tTE', 'tAE'].forEach(function (item) {
          tnsStorage.removeItem(item);
        });
      } // update browserInfo


      localStorage['tnsApp'] = browserInfo;
    }
  }

  var CALC = tnsStorage['tC'] ? checkStorageValue(tnsStorage['tC']) : setLocalStorage(tnsStorage, 'tC', calc(), localStorageAccess),
      PERCENTAGELAYOUT = tnsStorage['tPL'] ? checkStorageValue(tnsStorage['tPL']) : setLocalStorage(tnsStorage, 'tPL', percentageLayout(), localStorageAccess),
      CSSMQ = tnsStorage['tMQ'] ? checkStorageValue(tnsStorage['tMQ']) : setLocalStorage(tnsStorage, 'tMQ', mediaquerySupport(), localStorageAccess),
      TRANSFORM = tnsStorage['tTf'] ? checkStorageValue(tnsStorage['tTf']) : setLocalStorage(tnsStorage, 'tTf', whichProperty('transform'), localStorageAccess),
      HAS3DTRANSFORMS = tnsStorage['t3D'] ? checkStorageValue(tnsStorage['t3D']) : setLocalStorage(tnsStorage, 't3D', has3DTransforms(TRANSFORM), localStorageAccess),
      TRANSITIONDURATION = tnsStorage['tTDu'] ? checkStorageValue(tnsStorage['tTDu']) : setLocalStorage(tnsStorage, 'tTDu', whichProperty('transitionDuration'), localStorageAccess),
      TRANSITIONDELAY = tnsStorage['tTDe'] ? checkStorageValue(tnsStorage['tTDe']) : setLocalStorage(tnsStorage, 'tTDe', whichProperty('transitionDelay'), localStorageAccess),
      ANIMATIONDURATION = tnsStorage['tADu'] ? checkStorageValue(tnsStorage['tADu']) : setLocalStorage(tnsStorage, 'tADu', whichProperty('animationDuration'), localStorageAccess),
      ANIMATIONDELAY = tnsStorage['tADe'] ? checkStorageValue(tnsStorage['tADe']) : setLocalStorage(tnsStorage, 'tADe', whichProperty('animationDelay'), localStorageAccess),
      TRANSITIONEND = tnsStorage['tTE'] ? checkStorageValue(tnsStorage['tTE']) : setLocalStorage(tnsStorage, 'tTE', getEndProperty(TRANSITIONDURATION, 'Transition'), localStorageAccess),
      ANIMATIONEND = tnsStorage['tAE'] ? checkStorageValue(tnsStorage['tAE']) : setLocalStorage(tnsStorage, 'tAE', getEndProperty(ANIMATIONDURATION, 'Animation'), localStorageAccess); // get element nodes from selectors

  var supportConsoleWarn = win.console && typeof win.console.warn === "function",
      tnsList = ['container', 'controlsContainer', 'prevButton', 'nextButton', 'navContainer', 'autoplayButton'],
      optionsElements = {};
  tnsList.forEach(function (item) {
    if (typeof options[item] === 'string') {
      var str = options[item],
          el = doc.querySelector(str);
      optionsElements[item] = str;

      if (el && el.nodeName) {
        options[item] = el;
      } else {
        if (supportConsoleWarn) {
          console.warn('Can\'t find', options[item]);
        }

        return;
      }
    }
  }); // make sure at least 1 slide

  if (options.container.children.length < 1) {
    if (supportConsoleWarn) {
      console.warn('No slides found in', options.container);
    }

    return;
  } // update options


  var responsive = options.responsive,
      nested = options.nested,
      carousel = options.mode === 'carousel' ? true : false;

  if (responsive) {
    // apply responsive[0] to options and remove it
    if (0 in responsive) {
      options = extend(options, responsive[0]);
      delete responsive[0];
    }

    var responsiveTem = {};

    for (var key in responsive) {
      var val = responsive[key]; // update responsive
      // from: 300: 2
      // to:
      //   300: {
      //     items: 2
      //   }

      val = typeof val === 'number' ? {
        items: val
      } : val;
      responsiveTem[key] = val;
    }

    responsive = responsiveTem;
    responsiveTem = null;
  } // update options


  function updateOptions(obj) {
    for (var key in obj) {
      if (!carousel) {
        if (key === 'slideBy') {
          obj[key] = 'page';
        }

        if (key === 'edgePadding') {
          obj[key] = false;
        }

        if (key === 'autoHeight') {
          obj[key] = false;
        }
      } // update responsive options


      if (key === 'responsive') {
        updateOptions(obj[key]);
      }
    }
  }

  if (!carousel) {
    updateOptions(options);
  } // === define and set variables ===


  if (!carousel) {
    options.axis = 'horizontal';
    options.slideBy = 'page';
    options.edgePadding = false;
    var animateIn = options.animateIn,
        animateOut = options.animateOut,
        animateDelay = options.animateDelay,
        animateNormal = options.animateNormal;
  }

  var horizontal = options.axis === 'horizontal' ? true : false,
      outerWrapper = doc.createElement('div'),
      innerWrapper = doc.createElement('div'),
      middleWrapper,
      container = options.container,
      containerParent = container.parentNode,
      containerHTML = container.outerHTML,
      slideItems = container.children,
      slideCount = slideItems.length,
      breakpointZone,
      windowWidth = getWindowWidth(),
      isOn = false;

  if (responsive) {
    setBreakpointZone();
  }

  if (carousel) {
    container.className += ' tns-vpfix';
  } // fixedWidth: viewport > rightBoundary > indexMax


  var autoWidth = options.autoWidth,
      fixedWidth = getOption('fixedWidth'),
      edgePadding = getOption('edgePadding'),
      gutter = getOption('gutter'),
      viewport = getViewportWidth(),
      center = getOption('center'),
      items = !autoWidth ? Math.floor(getOption('items')) : 1,
      slideBy = getOption('slideBy'),
      viewportMax = options.viewportMax || options.fixedWidthViewportWidth,
      arrowKeys = getOption('arrowKeys'),
      speed = getOption('speed'),
      rewind = options.rewind,
      loop = rewind ? false : options.loop,
      autoHeight = getOption('autoHeight'),
      controls = getOption('controls'),
      controlsText = getOption('controlsText'),
      nav = getOption('nav'),
      touch = getOption('touch'),
      mouseDrag = getOption('mouseDrag'),
      autoplay = getOption('autoplay'),
      autoplayTimeout = getOption('autoplayTimeout'),
      autoplayText = getOption('autoplayText'),
      autoplayHoverPause = getOption('autoplayHoverPause'),
      autoplayResetOnVisibility = getOption('autoplayResetOnVisibility'),
      sheet = createStyleSheet(null, getOption('nonce')),
      lazyload = options.lazyload,
      lazyloadSelector = options.lazyloadSelector,
      slidePositions,
      // collection of slide positions
  slideItemsOut = [],
      cloneCount = loop ? getCloneCountForLoop() : 0,
      slideCountNew = !carousel ? slideCount + cloneCount : slideCount + cloneCount * 2,
      hasRightDeadZone = (fixedWidth || autoWidth) && !loop ? true : false,
      rightBoundary = fixedWidth ? getRightBoundary() : null,
      updateIndexBeforeTransform = !carousel || !loop ? true : false,
      // transform
  transformAttr = horizontal ? 'left' : 'top',
      transformPrefix = '',
      transformPostfix = '',
      // index
  getIndexMax = function () {
    if (fixedWidth) {
      return function () {
        return center && !loop ? slideCount - 1 : Math.ceil(-rightBoundary / (fixedWidth + gutter));
      };
    } else if (autoWidth) {
      return function () {
        for (var i = 0; i < slideCountNew; i++) {
          if (slidePositions[i] >= -rightBoundary) {
            return i;
          }
        }
      };
    } else {
      return function () {
        if (center && carousel && !loop) {
          return slideCount - 1;
        } else {
          return loop || carousel ? Math.max(0, slideCountNew - Math.ceil(items)) : slideCountNew - 1;
        }
      };
    }
  }(),
      index = getStartIndex(getOption('startIndex')),
      indexCached = index;
      getCurrentSlide();
      var indexMin = 0,
      indexMax = !autoWidth ? getIndexMax() : null,
      preventActionWhenRunning = options.preventActionWhenRunning,
      swipeAngle = options.swipeAngle,
      moveDirectionExpected = swipeAngle ? '?' : true,
      running = false,
      onInit = options.onInit,
      events = new Events(),
      // id, class
  newContainerClasses = ' tns-slider tns-' + options.mode,
      slideId = container.id || getSlideId(),
      disable = getOption('disable'),
      disabled = false,
      freezable = options.freezable,
      freeze = freezable && !autoWidth ? getFreeze() : false,
      frozen = false,
      controlsEvents = {
    'click': onControlsClick,
    'keydown': onControlsKeydown
  },
      navEvents = {
    'click': onNavClick,
    'keydown': onNavKeydown
  },
      hoverEvents = {
    'mouseover': mouseoverPause,
    'mouseout': mouseoutRestart
  },
      visibilityEvent = {
    'visibilitychange': onVisibilityChange
  },
      docmentKeydownEvent = {
    'keydown': onDocumentKeydown
  },
      touchEvents = {
    'touchstart': onPanStart,
    'touchmove': onPanMove,
    'touchend': onPanEnd,
    'touchcancel': onPanEnd
  },
      dragEvents = {
    'mousedown': onPanStart,
    'mousemove': onPanMove,
    'mouseup': onPanEnd,
    'mouseleave': onPanEnd
  },
      hasControls = hasOption('controls'),
      hasNav = hasOption('nav'),
      navAsThumbnails = autoWidth ? true : options.navAsThumbnails,
      hasAutoplay = hasOption('autoplay'),
      hasTouch = hasOption('touch'),
      hasMouseDrag = hasOption('mouseDrag'),
      slideActiveClass = 'tns-slide-active',
      slideClonedClass = 'tns-slide-cloned',
      imgCompleteClass = 'tns-complete',
      imgEvents = {
    'load': onImgLoaded,
    'error': onImgFailed
  },
      imgsComplete,
      liveregionCurrent,
      preventScroll = options.preventScrollOnTouch === 'force' ? true : false; // controls


  if (hasControls) {
    var controlsContainer = options.controlsContainer,
        controlsContainerHTML = options.controlsContainer ? options.controlsContainer.outerHTML : '',
        prevButton = options.prevButton,
        nextButton = options.nextButton,
        prevButtonHTML = options.prevButton ? options.prevButton.outerHTML : '',
        nextButtonHTML = options.nextButton ? options.nextButton.outerHTML : '',
        prevIsButton,
        nextIsButton;
  } // nav


  if (hasNav) {
    var navContainer = options.navContainer,
        navContainerHTML = options.navContainer ? options.navContainer.outerHTML : '',
        navItems,
        pages = autoWidth ? slideCount : getPages(),
        pagesCached = 0,
        navClicked = -1,
        navCurrentIndex = getCurrentNavIndex(),
        navCurrentIndexCached = navCurrentIndex,
        navActiveClass = 'tns-nav-active',
        navStr = 'Carousel Page ',
        navStrCurrent = ' (Current Slide)';
  } // autoplay


  if (hasAutoplay) {
    var autoplayDirection = options.autoplayDirection === 'forward' ? 1 : -1,
        autoplayButton = options.autoplayButton,
        autoplayButtonHTML = options.autoplayButton ? options.autoplayButton.outerHTML : '',
        autoplayHtmlStrings = ['<span class=\'tns-visually-hidden\'>', ' animation</span>'],
        autoplayTimer,
        animating,
        autoplayHoverPaused,
        autoplayUserPaused,
        autoplayVisibilityPaused;
  }

  if (hasTouch || hasMouseDrag) {
    var initPosition = {},
        lastPosition = {},
        translateInit,
        panStart = false,
        rafIndex,
        getDist = horizontal ? function (a, b) {
      return a.x - b.x;
    } : function (a, b) {
      return a.y - b.y;
    };
  } // disable slider when slidecount <= items


  if (!autoWidth) {
    resetVariblesWhenDisable(disable || freeze);
  }

  if (TRANSFORM) {
    transformAttr = TRANSFORM;
    transformPrefix = 'translate';

    if (HAS3DTRANSFORMS) {
      transformPrefix += horizontal ? '3d(' : '3d(0px, ';
      transformPostfix = horizontal ? ', 0px, 0px)' : ', 0px)';
    } else {
      transformPrefix += horizontal ? 'X(' : 'Y(';
      transformPostfix = ')';
    }
  }

  if (carousel) {
    container.className = container.className.replace('tns-vpfix', '');
  }

  initStructure();
  initSheet();
  initSliderTransform(); // === COMMON FUNCTIONS === //

  function resetVariblesWhenDisable(condition) {
    if (condition) {
      controls = nav = touch = mouseDrag = arrowKeys = autoplay = autoplayHoverPause = autoplayResetOnVisibility = false;
    }
  }

  function getCurrentSlide() {
    var tem = carousel ? index - cloneCount : index;

    while (tem < 0) {
      tem += slideCount;
    }

    return tem % slideCount + 1;
  }

  function getStartIndex(ind) {
    ind = ind ? Math.max(0, Math.min(loop ? slideCount - 1 : slideCount - items, ind)) : 0;
    return carousel ? ind + cloneCount : ind;
  }

  function getAbsIndex(i) {
    if (i == null) {
      i = index;
    }

    if (carousel) {
      i -= cloneCount;
    }

    while (i < 0) {
      i += slideCount;
    }

    return Math.floor(i % slideCount);
  }

  function getCurrentNavIndex() {
    var absIndex = getAbsIndex(),
        result;
    result = navAsThumbnails ? absIndex : fixedWidth || autoWidth ? Math.ceil((absIndex + 1) * pages / slideCount - 1) : Math.floor(absIndex / items); // set active nav to the last one when reaches the right edge

    if (!loop && carousel && index === indexMax) {
      result = pages - 1;
    }

    return result;
  }

  function getItemsMax() {
    // fixedWidth or autoWidth while viewportMax is not available
    if (autoWidth || fixedWidth && !viewportMax) {
      return slideCount - 1; // most cases
    } else {
      var str = fixedWidth ? 'fixedWidth' : 'items',
          arr = [];

      if (fixedWidth || options[str] < slideCount) {
        arr.push(options[str]);
      }

      if (responsive) {
        for (var bp in responsive) {
          var tem = responsive[bp][str];

          if (tem && (fixedWidth || tem < slideCount)) {
            arr.push(tem);
          }
        }
      }

      if (!arr.length) {
        arr.push(0);
      }

      return Math.ceil(fixedWidth ? viewportMax / Math.min.apply(null, arr) : Math.max.apply(null, arr));
    }
  }

  function getCloneCountForLoop() {
    var itemsMax = getItemsMax(),
        result = carousel ? Math.ceil((itemsMax * 5 - slideCount) / 2) : itemsMax * 4 - slideCount;
    result = Math.max(itemsMax, result);
    return hasOption('edgePadding') ? result + 1 : result;
  }

  function getWindowWidth() {
    return win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth;
  }

  function getInsertPosition(pos) {
    return pos === 'top' ? 'afterbegin' : 'beforeend';
  }

  function getClientWidth(el) {
    if (el == null) {
      return;
    }

    var div = doc.createElement('div'),
        rect,
        width;
    el.appendChild(div);
    rect = div.getBoundingClientRect();
    width = rect.right - rect.left;
    div.remove();
    return width || getClientWidth(el.parentNode);
  }

  function getViewportWidth() {
    var gap = edgePadding ? edgePadding * 2 - gutter : 0;
    return getClientWidth(containerParent) - gap;
  }

  function hasOption(item) {
    if (options[item]) {
      return true;
    } else {
      if (responsive) {
        for (var bp in responsive) {
          if (responsive[bp][item]) {
            return true;
          }
        }
      }

      return false;
    }
  } // get option:
  // fixed width: viewport, fixedWidth, gutter => items
  // others: window width => all variables
  // all: items => slideBy


  function getOption(item, ww) {
    if (ww == null) {
      ww = windowWidth;
    }

    if (item === 'items' && fixedWidth) {
      return Math.floor((viewport + gutter) / (fixedWidth + gutter)) || 1;
    } else {
      var result = options[item];

      if (responsive) {
        for (var bp in responsive) {
          // bp: convert string to number
          if (ww >= parseInt(bp)) {
            if (item in responsive[bp]) {
              result = responsive[bp][item];
            }
          }
        }
      }

      if (item === 'slideBy' && result === 'page') {
        result = getOption('items');
      }

      if (!carousel && (item === 'slideBy' || item === 'items')) {
        result = Math.floor(result);
      }

      return result;
    }
  }

  function getSlideMarginLeft(i) {
    return CALC ? CALC + '(' + i * 100 + '% / ' + slideCountNew + ')' : i * 100 / slideCountNew + '%';
  }

  function getInnerWrapperStyles(edgePaddingTem, gutterTem, fixedWidthTem, speedTem, autoHeightBP) {
    var str = '';

    if (edgePaddingTem !== undefined) {
      var gap = edgePaddingTem;

      if (gutterTem) {
        gap -= gutterTem;
      }

      str = horizontal ? 'margin: 0 ' + gap + 'px 0 ' + edgePaddingTem + 'px;' : 'margin: ' + edgePaddingTem + 'px 0 ' + gap + 'px 0;';
    } else if (gutterTem && !fixedWidthTem) {
      var gutterTemUnit = '-' + gutterTem + 'px',
          dir = horizontal ? gutterTemUnit + ' 0 0' : '0 ' + gutterTemUnit + ' 0';
      str = 'margin: 0 ' + dir + ';';
    }

    if (!carousel && autoHeightBP && TRANSITIONDURATION && speedTem) {
      str += getTransitionDurationStyle(speedTem);
    }

    return str;
  }

  function getContainerWidth(fixedWidthTem, gutterTem, itemsTem) {
    if (fixedWidthTem) {
      return (fixedWidthTem + gutterTem) * slideCountNew + 'px';
    } else {
      return CALC ? CALC + '(' + slideCountNew * 100 + '% / ' + itemsTem + ')' : slideCountNew * 100 / itemsTem + '%';
    }
  }

  function getSlideWidthStyle(fixedWidthTem, gutterTem, itemsTem) {
    var width;

    if (fixedWidthTem) {
      width = fixedWidthTem + gutterTem + 'px';
    } else {
      if (!carousel) {
        itemsTem = Math.floor(itemsTem);
      }

      var dividend = carousel ? slideCountNew : itemsTem;
      width = CALC ? CALC + '(100% / ' + dividend + ')' : 100 / dividend + '%';
    }

    width = 'width:' + width; // inner slider: overwrite outer slider styles

    return nested !== 'inner' ? width + ';' : width + ' !important;';
  }

  function getSlideGutterStyle(gutterTem) {
    var str = ''; // gutter maybe interger || 0
    // so can't use 'if (gutter)'

    if (gutterTem !== false) {
      var prop = horizontal ? 'padding-' : 'margin-',
          dir = horizontal ? 'right' : 'bottom';
      str = prop + dir + ': ' + gutterTem + 'px;';
    }

    return str;
  }

  function getCSSPrefix(name, num) {
    var prefix = name.substring(0, name.length - num).toLowerCase();

    if (prefix) {
      prefix = '-' + prefix + '-';
    }

    return prefix;
  }

  function getTransitionDurationStyle(speed) {
    return getCSSPrefix(TRANSITIONDURATION, 18) + 'transition-duration:' + speed / 1000 + 's;';
  }

  function getAnimationDurationStyle(speed) {
    return getCSSPrefix(ANIMATIONDURATION, 17) + 'animation-duration:' + speed / 1000 + 's;';
  }

  function initStructure() {
    var classOuter = 'tns-outer',
        classInner = 'tns-inner';
        hasOption('gutter');
    outerWrapper.className = classOuter;
    innerWrapper.className = classInner;
    outerWrapper.id = slideId + '-ow';
    innerWrapper.id = slideId + '-iw'; // set container properties

    if (container.id === '') {
      container.id = slideId;
    }

    newContainerClasses += PERCENTAGELAYOUT || autoWidth ? ' tns-subpixel' : ' tns-no-subpixel';
    newContainerClasses += CALC ? ' tns-calc' : ' tns-no-calc';

    if (autoWidth) {
      newContainerClasses += ' tns-autowidth';
    }

    newContainerClasses += ' tns-' + options.axis;
    container.className += newContainerClasses; // add constrain layer for carousel

    if (carousel) {
      middleWrapper = doc.createElement('div');
      middleWrapper.id = slideId + '-mw';
      middleWrapper.className = 'tns-ovh';
      outerWrapper.appendChild(middleWrapper);
      middleWrapper.appendChild(innerWrapper);
    } else {
      outerWrapper.appendChild(innerWrapper);
    }

    if (autoHeight) {
      var wp = middleWrapper ? middleWrapper : innerWrapper;
      wp.className += ' tns-ah';
    }

    containerParent.insertBefore(outerWrapper, container);
    innerWrapper.appendChild(container); // add id, class, aria attributes
    // before clone slides

    forEach(slideItems, function (item, i) {
      addClass(item, 'tns-item');

      if (!item.id) {
        item.id = slideId + '-item' + i;
      }

      if (!carousel && animateNormal) {
        addClass(item, animateNormal);
      }

      setAttrs(item, {
        'aria-hidden': 'true',
        'tabindex': '-1'
      });
    }); // ## clone slides
    // carousel: n + slides + n
    // gallery:      slides + n

    if (cloneCount) {
      var fragmentBefore = doc.createDocumentFragment(),
          fragmentAfter = doc.createDocumentFragment();

      for (var j = cloneCount; j--;) {
        var num = j % slideCount,
            cloneFirst = slideItems[num].cloneNode(true);
        addClass(cloneFirst, slideClonedClass);
        removeAttrs(cloneFirst, 'id');
        fragmentAfter.insertBefore(cloneFirst, fragmentAfter.firstChild);

        if (carousel) {
          var cloneLast = slideItems[slideCount - 1 - num].cloneNode(true);
          addClass(cloneLast, slideClonedClass);
          removeAttrs(cloneLast, 'id');
          fragmentBefore.appendChild(cloneLast);
        }
      }

      container.insertBefore(fragmentBefore, container.firstChild);
      container.appendChild(fragmentAfter);
      slideItems = container.children;
    }
  }

  function initSliderTransform() {
    // ## images loaded/failed
    if (hasOption('autoHeight') || autoWidth || !horizontal) {
      var imgs = container.querySelectorAll('img'); // add img load event listener

      forEach(imgs, function (img) {
        var src = img.src;

        if (!lazyload) {
          // not data img
          if (src && src.indexOf('data:image') < 0) {
            img.src = '';
            addEvents(img, imgEvents);
            addClass(img, 'loading');
            img.src = src; // data img
          } else {
            imgLoaded(img);
          }
        }
      }); // set imgsComplete

      raf(function () {
        imgsLoadedCheck(arrayFromNodeList(imgs), function () {
          imgsComplete = true;
        });
      }); // reset imgs for auto height: check visible imgs only

      if (hasOption('autoHeight')) {
        imgs = getImageArray(index, Math.min(index + items - 1, slideCountNew - 1));
      }

      lazyload ? initSliderTransformStyleCheck() : raf(function () {
        imgsLoadedCheck(arrayFromNodeList(imgs), initSliderTransformStyleCheck);
      });
    } else {
      // set container transform property
      if (carousel) {
        doContainerTransformSilent();
      } // update slider tools and events


      initTools();
      initEvents();
    }
  }

  function initSliderTransformStyleCheck() {
    if (autoWidth && slideCount > 1) {
      // check styles application
      var num = loop ? index : slideCount - 1;

      (function stylesApplicationCheck() {
        var left = slideItems[num].getBoundingClientRect().left;
        var right = slideItems[num - 1].getBoundingClientRect().right;
        Math.abs(left - right) <= 1 ? initSliderTransformCore() : setTimeout(function () {
          stylesApplicationCheck();
        }, 16);
      })();
    } else {
      initSliderTransformCore();
    }
  }

  function initSliderTransformCore() {
    // run Fn()s which are rely on image loading
    if (!horizontal || autoWidth) {
      setSlidePositions();

      if (autoWidth) {
        rightBoundary = getRightBoundary();

        if (freezable) {
          freeze = getFreeze();
        }

        indexMax = getIndexMax(); // <= slidePositions, rightBoundary <=

        resetVariblesWhenDisable(disable || freeze);
      } else {
        updateContentWrapperHeight();
      }
    } // set container transform property


    if (carousel) {
      doContainerTransformSilent();
    } // update slider tools and events


    initTools();
    initEvents();
  }

  function initSheet() {
    // gallery:
    // set animation classes and left value for gallery slider
    if (!carousel) {
      for (var i = index, l = index + Math.min(slideCount, items); i < l; i++) {
        var item = slideItems[i];
        item.style.left = (i - index) * 100 / items + '%';
        addClass(item, animateIn);
        removeClass(item, animateNormal);
      }
    } // #### LAYOUT
    // ## INLINE-BLOCK VS FLOAT
    // ## PercentageLayout:
    // slides: inline-block
    // remove blank space between slides by set font-size: 0
    // ## Non PercentageLayout:
    // slides: float
    //         margin-right: -100%
    //         margin-left: ~
    // Resource: https://docs.google.com/spreadsheets/d/147up245wwTXeQYve3BRSAD4oVcvQmuGsFteJOeA5xNQ/edit?usp=sharing


    if (horizontal) {
      if (PERCENTAGELAYOUT || autoWidth) {
        addCSSRule(sheet, '#' + slideId + ' > .tns-item', 'font-size:' + win.getComputedStyle(slideItems[0]).fontSize + ';', getCssRulesLength(sheet));
        addCSSRule(sheet, '#' + slideId, 'font-size:0;', getCssRulesLength(sheet));
      } else if (carousel) {
        forEach(slideItems, function (slide, i) {
          slide.style.marginLeft = getSlideMarginLeft(i);
        });
      }
    } // ## BASIC STYLES


    if (CSSMQ) {
      // middle wrapper style
      if (TRANSITIONDURATION) {
        var str = middleWrapper && options.autoHeight ? getTransitionDurationStyle(options.speed) : '';
        addCSSRule(sheet, '#' + slideId + '-mw', str, getCssRulesLength(sheet));
      } // inner wrapper styles


      str = getInnerWrapperStyles(options.edgePadding, options.gutter, options.fixedWidth, options.speed, options.autoHeight);
      addCSSRule(sheet, '#' + slideId + '-iw', str, getCssRulesLength(sheet)); // container styles

      if (carousel) {
        str = horizontal && !autoWidth ? 'width:' + getContainerWidth(options.fixedWidth, options.gutter, options.items) + ';' : '';

        if (TRANSITIONDURATION) {
          str += getTransitionDurationStyle(speed);
        }

        addCSSRule(sheet, '#' + slideId, str, getCssRulesLength(sheet));
      } // slide styles


      str = horizontal && !autoWidth ? getSlideWidthStyle(options.fixedWidth, options.gutter, options.items) : '';

      if (options.gutter) {
        str += getSlideGutterStyle(options.gutter);
      } // set gallery items transition-duration


      if (!carousel) {
        if (TRANSITIONDURATION) {
          str += getTransitionDurationStyle(speed);
        }

        if (ANIMATIONDURATION) {
          str += getAnimationDurationStyle(speed);
        }
      }

      if (str) {
        addCSSRule(sheet, '#' + slideId + ' > .tns-item', str, getCssRulesLength(sheet));
      } // non CSS mediaqueries: IE8
      // ## update inner wrapper, container, slides if needed
      // set inline styles for inner wrapper & container
      // insert stylesheet (one line) for slides only (since slides are many)

    } else {
      // middle wrapper styles
      update_carousel_transition_duration(); // inner wrapper styles

      innerWrapper.style.cssText = getInnerWrapperStyles(edgePadding, gutter, fixedWidth, autoHeight); // container styles

      if (carousel && horizontal && !autoWidth) {
        container.style.width = getContainerWidth(fixedWidth, gutter, items);
      } // slide styles


      var str = horizontal && !autoWidth ? getSlideWidthStyle(fixedWidth, gutter, items) : '';

      if (gutter) {
        str += getSlideGutterStyle(gutter);
      } // append to the last line


      if (str) {
        addCSSRule(sheet, '#' + slideId + ' > .tns-item', str, getCssRulesLength(sheet));
      }
    } // ## MEDIAQUERIES


    if (responsive && CSSMQ) {
      for (var bp in responsive) {
        // bp: convert string to number
        bp = parseInt(bp);
        var opts = responsive[bp],
            str = '',
            middleWrapperStr = '',
            innerWrapperStr = '',
            containerStr = '',
            slideStr = '',
            itemsBP = !autoWidth ? getOption('items', bp) : null,
            fixedWidthBP = getOption('fixedWidth', bp),
            speedBP = getOption('speed', bp),
            edgePaddingBP = getOption('edgePadding', bp),
            autoHeightBP = getOption('autoHeight', bp),
            gutterBP = getOption('gutter', bp); // middle wrapper string

        if (TRANSITIONDURATION && middleWrapper && getOption('autoHeight', bp) && 'speed' in opts) {
          middleWrapperStr = '#' + slideId + '-mw{' + getTransitionDurationStyle(speedBP) + '}';
        } // inner wrapper string


        if ('edgePadding' in opts || 'gutter' in opts) {
          innerWrapperStr = '#' + slideId + '-iw{' + getInnerWrapperStyles(edgePaddingBP, gutterBP, fixedWidthBP, speedBP, autoHeightBP) + '}';
        } // container string


        if (carousel && horizontal && !autoWidth && ('fixedWidth' in opts || 'items' in opts || fixedWidth && 'gutter' in opts)) {
          containerStr = 'width:' + getContainerWidth(fixedWidthBP, gutterBP, itemsBP) + ';';
        }

        if (TRANSITIONDURATION && 'speed' in opts) {
          containerStr += getTransitionDurationStyle(speedBP);
        }

        if (containerStr) {
          containerStr = '#' + slideId + '{' + containerStr + '}';
        } // slide string


        if ('fixedWidth' in opts || fixedWidth && 'gutter' in opts || !carousel && 'items' in opts) {
          slideStr += getSlideWidthStyle(fixedWidthBP, gutterBP, itemsBP);
        }

        if ('gutter' in opts) {
          slideStr += getSlideGutterStyle(gutterBP);
        } // set gallery items transition-duration


        if (!carousel && 'speed' in opts) {
          if (TRANSITIONDURATION) {
            slideStr += getTransitionDurationStyle(speedBP);
          }

          if (ANIMATIONDURATION) {
            slideStr += getAnimationDurationStyle(speedBP);
          }
        }

        if (slideStr) {
          slideStr = '#' + slideId + ' > .tns-item{' + slideStr + '}';
        } // add up


        str = middleWrapperStr + innerWrapperStr + containerStr + slideStr;

        if (str) {
          sheet.insertRule('@media (min-width: ' + bp / 16 + 'em) {' + str + '}', sheet.cssRules.length);
        }
      }
    }
  }

  function initTools() {
    // == slides ==
    updateSlideStatus(); // == live region ==

    outerWrapper.insertAdjacentHTML('afterbegin', '<div class="tns-liveregion tns-visually-hidden" aria-live="polite" aria-atomic="true">slide <span class="current">' + getLiveRegionStr() + '</span>  of ' + slideCount + '</div>');
    liveregionCurrent = outerWrapper.querySelector('.tns-liveregion .current'); // == autoplayInit ==

    if (hasAutoplay) {
      var txt = autoplay ? 'stop' : 'start';

      if (autoplayButton) {
        setAttrs(autoplayButton, {
          'data-action': txt
        });
      } else if (options.autoplayButtonOutput) {
        outerWrapper.insertAdjacentHTML(getInsertPosition(options.autoplayPosition), '<button type="button" data-action="' + txt + '">' + autoplayHtmlStrings[0] + txt + autoplayHtmlStrings[1] + autoplayText[0] + '</button>');
        autoplayButton = outerWrapper.querySelector('[data-action]');
      } // add event


      if (autoplayButton) {
        addEvents(autoplayButton, {
          'click': toggleAutoplay
        });
      }

      if (autoplay) {
        startAutoplay();

        if (autoplayHoverPause) {
          addEvents(container, hoverEvents);
        }

        if (autoplayResetOnVisibility) {
          addEvents(container, visibilityEvent);
        }
      }
    } // == navInit ==


    if (hasNav) {
      // will not hide the navs in case they're thumbnails

      if (navContainer) {
        setAttrs(navContainer, {
          'aria-label': 'Carousel Pagination'
        });
        navItems = navContainer.children;
        forEach(navItems, function (item, i) {
          setAttrs(item, {
            'data-nav': i,
            'tabindex': '-1',
            'aria-label': navStr + (i + 1),
            'aria-controls': slideId
          });
        }); // generated nav
      } else {
        var navHtml = '',
            hiddenStr = navAsThumbnails ? '' : 'style="display:none"';

        for (var i = 0; i < slideCount; i++) {
          // hide nav items by default
          navHtml += '<button type="button" data-nav="' + i + '" tabindex="-1" aria-controls="' + slideId + '" ' + hiddenStr + ' aria-label="' + navStr + (i + 1) + '"></button>';
        }

        navHtml = '<div class="tns-nav" aria-label="Carousel Pagination">' + navHtml + '</div>';
        outerWrapper.insertAdjacentHTML(getInsertPosition(options.navPosition), navHtml);
        navContainer = outerWrapper.querySelector('.tns-nav');
        navItems = navContainer.children;
      }

      updateNavVisibility(); // add transition

      if (TRANSITIONDURATION) {
        var prefix = TRANSITIONDURATION.substring(0, TRANSITIONDURATION.length - 18).toLowerCase(),
            str = 'transition: all ' + speed / 1000 + 's';

        if (prefix) {
          str = '-' + prefix + '-' + str;
        }

        addCSSRule(sheet, '[aria-controls^=' + slideId + '-item]', str, getCssRulesLength(sheet));
      }

      setAttrs(navItems[navCurrentIndex], {
        'aria-label': navStr + (navCurrentIndex + 1) + navStrCurrent
      });
      removeAttrs(navItems[navCurrentIndex], 'tabindex');
      addClass(navItems[navCurrentIndex], navActiveClass); // add events

      addEvents(navContainer, navEvents);
    } // == controlsInit ==


    if (hasControls) {
      if (!controlsContainer && (!prevButton || !nextButton)) {
        outerWrapper.insertAdjacentHTML(getInsertPosition(options.controlsPosition), '<div class="tns-controls" aria-label="Carousel Navigation" tabindex="0"><button type="button" data-controls="prev" tabindex="-1" aria-controls="' + slideId + '">' + controlsText[0] + '</button><button type="button" data-controls="next" tabindex="-1" aria-controls="' + slideId + '">' + controlsText[1] + '</button></div>');
        controlsContainer = outerWrapper.querySelector('.tns-controls');
      }

      if (!prevButton || !nextButton) {
        prevButton = controlsContainer.children[0];
        nextButton = controlsContainer.children[1];
      }

      if (options.controlsContainer) {
        setAttrs(controlsContainer, {
          'aria-label': 'Carousel Navigation',
          'tabindex': '0'
        });
      }

      if (options.controlsContainer || options.prevButton && options.nextButton) {
        setAttrs([prevButton, nextButton], {
          'aria-controls': slideId,
          'tabindex': '-1'
        });
      }

      if (options.controlsContainer || options.prevButton && options.nextButton) {
        setAttrs(prevButton, {
          'data-controls': 'prev'
        });
        setAttrs(nextButton, {
          'data-controls': 'next'
        });
      }

      prevIsButton = isButton(prevButton);
      nextIsButton = isButton(nextButton);
      updateControlsStatus(); // add events

      if (controlsContainer) {
        addEvents(controlsContainer, controlsEvents);
      } else {
        addEvents(prevButton, controlsEvents);
        addEvents(nextButton, controlsEvents);
      }
    } // hide tools if needed


    disableUI();
  }

  function initEvents() {
    // add events
    if (carousel && TRANSITIONEND) {
      var eve = {};
      eve[TRANSITIONEND] = onTransitionEnd;
      addEvents(container, eve);
    }

    if (touch) {
      addEvents(container, touchEvents, options.preventScrollOnTouch);
    }

    if (mouseDrag) {
      addEvents(container, dragEvents);
    }

    if (arrowKeys) {
      addEvents(doc, docmentKeydownEvent);
    }

    if (nested === 'inner') {
      events.on('outerResized', function () {
        resizeTasks();
        events.emit('innerLoaded', info());
      });
    } else if (responsive || fixedWidth || autoWidth || autoHeight || !horizontal) {
      addEvents(win, {
        'resize': onResize
      });
    }

    if (autoHeight) {
      if (nested === 'outer') {
        events.on('innerLoaded', doAutoHeight);
      } else if (!disable) {
        doAutoHeight();
      }
    }

    doLazyLoad();

    if (disable) {
      disableSlider();
    } else if (freeze) {
      freezeSlider();
    }

    events.on('indexChanged', additionalUpdates);

    if (nested === 'inner') {
      events.emit('innerLoaded', info());
    }

    if (typeof onInit === 'function') {
      onInit(info());
    }

    isOn = true;
  }

  function destroy() {
    // sheet
    sheet.disabled = true;

    if (sheet.ownerNode) {
      sheet.ownerNode.remove();
    } // remove win event listeners


    removeEvents(win, {
      'resize': onResize
    }); // arrowKeys, controls, nav

    if (arrowKeys) {
      removeEvents(doc, docmentKeydownEvent);
    }

    if (controlsContainer) {
      removeEvents(controlsContainer, controlsEvents);
    }

    if (navContainer) {
      removeEvents(navContainer, navEvents);
    } // autoplay


    removeEvents(container, hoverEvents);
    removeEvents(container, visibilityEvent);

    if (autoplayButton) {
      removeEvents(autoplayButton, {
        'click': toggleAutoplay
      });
    }

    if (autoplay) {
      clearInterval(autoplayTimer);
    } // container


    if (carousel && TRANSITIONEND) {
      var eve = {};
      eve[TRANSITIONEND] = onTransitionEnd;
      removeEvents(container, eve);
    }

    if (touch) {
      removeEvents(container, touchEvents);
    }

    if (mouseDrag) {
      removeEvents(container, dragEvents);
    } // cache Object values in options && reset HTML


    var htmlList = [containerHTML, controlsContainerHTML, prevButtonHTML, nextButtonHTML, navContainerHTML, autoplayButtonHTML];
    tnsList.forEach(function (item, i) {
      var el = item === 'container' ? outerWrapper : options[item];

      if (typeof el === 'object' && el) {
        var prevEl = el.previousElementSibling ? el.previousElementSibling : false,
            parentEl = el.parentNode;
        el.outerHTML = htmlList[i];
        options[item] = prevEl ? prevEl.nextElementSibling : parentEl.firstElementChild;
      }
    }); // reset variables

    tnsList = animateIn = animateOut = animateDelay = animateNormal = horizontal = outerWrapper = innerWrapper = container = containerParent = containerHTML = slideItems = slideCount = breakpointZone = windowWidth = autoWidth = fixedWidth = edgePadding = gutter = viewport = items = slideBy = viewportMax = arrowKeys = speed = rewind = loop = autoHeight = sheet = lazyload = slidePositions = slideItemsOut = cloneCount = slideCountNew = hasRightDeadZone = rightBoundary = updateIndexBeforeTransform = transformAttr = transformPrefix = transformPostfix = getIndexMax = index = indexCached = indexMin = indexMax = swipeAngle = moveDirectionExpected = running = onInit = events = newContainerClasses = slideId = disable = disabled = freezable = freeze = frozen = controlsEvents = navEvents = hoverEvents = visibilityEvent = docmentKeydownEvent = touchEvents = dragEvents = hasControls = hasNav = navAsThumbnails = hasAutoplay = hasTouch = hasMouseDrag = slideActiveClass = imgCompleteClass = imgEvents = imgsComplete = controls = controlsText = controlsContainer = controlsContainerHTML = prevButton = nextButton = prevIsButton = nextIsButton = nav = navContainer = navContainerHTML = navItems = pages = pagesCached = navClicked = navCurrentIndex = navCurrentIndexCached = navActiveClass = navStr = navStrCurrent = autoplay = autoplayTimeout = autoplayDirection = autoplayText = autoplayHoverPause = autoplayButton = autoplayButtonHTML = autoplayResetOnVisibility = autoplayHtmlStrings = autoplayTimer = animating = autoplayHoverPaused = autoplayUserPaused = autoplayVisibilityPaused = initPosition = lastPosition = translateInit = panStart = rafIndex = getDist = touch = mouseDrag = null; // check variables
    // [animateIn, animateOut, animateDelay, animateNormal, horizontal, outerWrapper, innerWrapper, container, containerParent, containerHTML, slideItems, slideCount, breakpointZone, windowWidth, autoWidth, fixedWidth, edgePadding, gutter, viewport, items, slideBy, viewportMax, arrowKeys, speed, rewind, loop, autoHeight, sheet, lazyload, slidePositions, slideItemsOut, cloneCount, slideCountNew, hasRightDeadZone, rightBoundary, updateIndexBeforeTransform, transformAttr, transformPrefix, transformPostfix, getIndexMax, index, indexCached, indexMin, indexMax, resizeTimer, swipeAngle, moveDirectionExpected, running, onInit, events, newContainerClasses, slideId, disable, disabled, freezable, freeze, frozen, controlsEvents, navEvents, hoverEvents, visibilityEvent, docmentKeydownEvent, touchEvents, dragEvents, hasControls, hasNav, navAsThumbnails, hasAutoplay, hasTouch, hasMouseDrag, slideActiveClass, imgCompleteClass, imgEvents, imgsComplete, controls, controlsText, controlsContainer, controlsContainerHTML, prevButton, nextButton, prevIsButton, nextIsButton, nav, navContainer, navContainerHTML, navItems, pages, pagesCached, navClicked, navCurrentIndex, navCurrentIndexCached, navActiveClass, navStr, navStrCurrent, autoplay, autoplayTimeout, autoplayDirection, autoplayText, autoplayHoverPause, autoplayButton, autoplayButtonHTML, autoplayResetOnVisibility, autoplayHtmlStrings, autoplayTimer, animating, autoplayHoverPaused, autoplayUserPaused, autoplayVisibilityPaused, initPosition, lastPosition, translateInit, disX, disY, panStart, rafIndex, getDist, touch, mouseDrag ].forEach(function(item) { if (item !== null) { console.log(item); } });

    for (var a in this) {
      if (a !== 'rebuild') {
        this[a] = null;
      }
    }

    isOn = false;
  } // === ON RESIZE ===
  // responsive || fixedWidth || autoWidth || !horizontal


  function onResize(e) {
    raf(function () {
      resizeTasks(getEvent(e));
    });
  }

  function resizeTasks(e) {
    if (!isOn) {
      return;
    }

    if (nested === 'outer') {
      events.emit('outerResized', info(e));
    }

    windowWidth = getWindowWidth();
    var bpChanged,
        breakpointZoneTem = breakpointZone,
        needContainerTransform = false;

    if (responsive) {
      setBreakpointZone();
      bpChanged = breakpointZoneTem !== breakpointZone; // if (hasRightDeadZone) { needContainerTransform = true; } // *?

      if (bpChanged) {
        events.emit('newBreakpointStart', info(e));
      }
    }

    var indChanged,
        itemsChanged,
        itemsTem = items,
        disableTem = disable,
        freezeTem = freeze,
        arrowKeysTem = arrowKeys,
        controlsTem = controls,
        navTem = nav,
        touchTem = touch,
        mouseDragTem = mouseDrag,
        autoplayTem = autoplay,
        autoplayHoverPauseTem = autoplayHoverPause,
        autoplayResetOnVisibilityTem = autoplayResetOnVisibility,
        indexTem = index;

    if (bpChanged) {
      var fixedWidthTem = fixedWidth,
          autoHeightTem = autoHeight,
          controlsTextTem = controlsText,
          centerTem = center,
          autoplayTextTem = autoplayText;

      if (!CSSMQ) {
        var gutterTem = gutter,
            edgePaddingTem = edgePadding;
      }
    } // get option:
    // fixed width: viewport, fixedWidth, gutter => items
    // others: window width => all variables
    // all: items => slideBy


    arrowKeys = getOption('arrowKeys');
    controls = getOption('controls');
    nav = getOption('nav');
    touch = getOption('touch');
    center = getOption('center');
    mouseDrag = getOption('mouseDrag');
    autoplay = getOption('autoplay');
    autoplayHoverPause = getOption('autoplayHoverPause');
    autoplayResetOnVisibility = getOption('autoplayResetOnVisibility');

    if (bpChanged) {
      disable = getOption('disable');
      fixedWidth = getOption('fixedWidth');
      speed = getOption('speed');
      autoHeight = getOption('autoHeight');
      controlsText = getOption('controlsText');
      autoplayText = getOption('autoplayText');
      autoplayTimeout = getOption('autoplayTimeout');

      if (!CSSMQ) {
        edgePadding = getOption('edgePadding');
        gutter = getOption('gutter');
      }
    } // update options


    resetVariblesWhenDisable(disable);
    viewport = getViewportWidth(); // <= edgePadding, gutter

    if ((!horizontal || autoWidth) && !disable) {
      setSlidePositions();

      if (!horizontal) {
        updateContentWrapperHeight(); // <= setSlidePositions

        needContainerTransform = true;
      }
    }

    if (fixedWidth || autoWidth) {
      rightBoundary = getRightBoundary(); // autoWidth: <= viewport, slidePositions, gutter
      // fixedWidth: <= viewport, fixedWidth, gutter

      indexMax = getIndexMax(); // autoWidth: <= rightBoundary, slidePositions
      // fixedWidth: <= rightBoundary, fixedWidth, gutter
    }

    if (bpChanged || fixedWidth) {
      items = getOption('items');
      slideBy = getOption('slideBy');
      itemsChanged = items !== itemsTem;

      if (itemsChanged) {
        if (!fixedWidth && !autoWidth) {
          indexMax = getIndexMax();
        } // <= items
        // check index before transform in case
        // slider reach the right edge then items become bigger


        updateIndex();
      }
    }

    if (bpChanged) {
      if (disable !== disableTem) {
        if (disable) {
          disableSlider();
        } else {
          enableSlider(); // <= slidePositions, rightBoundary, indexMax
        }
      }
    }

    if (freezable && (bpChanged || fixedWidth || autoWidth)) {
      freeze = getFreeze(); // <= autoWidth: slidePositions, gutter, viewport, rightBoundary
      // <= fixedWidth: fixedWidth, gutter, rightBoundary
      // <= others: items

      if (freeze !== freezeTem) {
        if (freeze) {
          doContainerTransform(getContainerTransformValue(getStartIndex(0)));
          freezeSlider();
        } else {
          unfreezeSlider();
          needContainerTransform = true;
        }
      }
    }

    resetVariblesWhenDisable(disable || freeze); // controls, nav, touch, mouseDrag, arrowKeys, autoplay, autoplayHoverPause, autoplayResetOnVisibility

    if (!autoplay) {
      autoplayHoverPause = autoplayResetOnVisibility = false;
    }

    if (arrowKeys !== arrowKeysTem) {
      arrowKeys ? addEvents(doc, docmentKeydownEvent) : removeEvents(doc, docmentKeydownEvent);
    }

    if (controls !== controlsTem) {
      if (controls) {
        if (controlsContainer) {
          showElement(controlsContainer);
        } else {
          if (prevButton) {
            showElement(prevButton);
          }

          if (nextButton) {
            showElement(nextButton);
          }
        }
      } else {
        if (controlsContainer) {
          hideElement(controlsContainer);
        } else {
          if (prevButton) {
            hideElement(prevButton);
          }

          if (nextButton) {
            hideElement(nextButton);
          }
        }
      }
    }

    if (nav !== navTem) {
      if (nav) {
        showElement(navContainer);
        updateNavVisibility();
      } else {
        hideElement(navContainer);
      }
    }

    if (touch !== touchTem) {
      touch ? addEvents(container, touchEvents, options.preventScrollOnTouch) : removeEvents(container, touchEvents);
    }

    if (mouseDrag !== mouseDragTem) {
      mouseDrag ? addEvents(container, dragEvents) : removeEvents(container, dragEvents);
    }

    if (autoplay !== autoplayTem) {
      if (autoplay) {
        if (autoplayButton) {
          showElement(autoplayButton);
        }

        if (!animating && !autoplayUserPaused) {
          startAutoplay();
        }
      } else {
        if (autoplayButton) {
          hideElement(autoplayButton);
        }

        if (animating) {
          stopAutoplay();
        }
      }
    }

    if (autoplayHoverPause !== autoplayHoverPauseTem) {
      autoplayHoverPause ? addEvents(container, hoverEvents) : removeEvents(container, hoverEvents);
    }

    if (autoplayResetOnVisibility !== autoplayResetOnVisibilityTem) {
      autoplayResetOnVisibility ? addEvents(doc, visibilityEvent) : removeEvents(doc, visibilityEvent);
    }

    if (bpChanged) {
      if (fixedWidth !== fixedWidthTem || center !== centerTem) {
        needContainerTransform = true;
      }

      if (autoHeight !== autoHeightTem) {
        if (!autoHeight) {
          innerWrapper.style.height = '';
        }
      }

      if (controls && controlsText !== controlsTextTem) {
        prevButton.innerHTML = controlsText[0];
        nextButton.innerHTML = controlsText[1];
      }

      if (autoplayButton && autoplayText !== autoplayTextTem) {
        var i = autoplay ? 1 : 0,
            html = autoplayButton.innerHTML,
            len = html.length - autoplayTextTem[i].length;

        if (html.substring(len) === autoplayTextTem[i]) {
          autoplayButton.innerHTML = html.substring(0, len) + autoplayText[i];
        }
      }
    } else {
      if (center && (fixedWidth || autoWidth)) {
        needContainerTransform = true;
      }
    }

    if (itemsChanged || fixedWidth && !autoWidth) {
      pages = getPages();
      updateNavVisibility();
    }

    indChanged = index !== indexTem;

    if (indChanged) {
      events.emit('indexChanged', info());
      needContainerTransform = true;
    } else if (itemsChanged) {
      if (!indChanged) {
        additionalUpdates();
      }
    } else if (fixedWidth || autoWidth) {
      doLazyLoad();
      updateSlideStatus();
      updateLiveRegion();
    }

    if (itemsChanged && !carousel) {
      updateGallerySlidePositions();
    }

    if (!disable && !freeze) {
      // non-mediaqueries: IE8
      if (bpChanged && !CSSMQ) {
        // middle wrapper styles
        // inner wrapper styles
        if (edgePadding !== edgePaddingTem || gutter !== gutterTem) {
          innerWrapper.style.cssText = getInnerWrapperStyles(edgePadding, gutter, fixedWidth, speed, autoHeight);
        }

        if (horizontal) {
          // container styles
          if (carousel) {
            container.style.width = getContainerWidth(fixedWidth, gutter, items);
          } // slide styles


          var str = getSlideWidthStyle(fixedWidth, gutter, items) + getSlideGutterStyle(gutter); // remove the last line and
          // add new styles

          removeCSSRule(sheet, getCssRulesLength(sheet) - 1);
          addCSSRule(sheet, '#' + slideId + ' > .tns-item', str, getCssRulesLength(sheet));
        }
      } // auto height


      if (autoHeight) {
        doAutoHeight();
      }

      if (needContainerTransform) {
        doContainerTransformSilent();
        indexCached = index;
      }
    }

    if (bpChanged) {
      events.emit('newBreakpointEnd', info(e));
    }
  } // === INITIALIZATION FUNCTIONS === //


  function getFreeze() {
    if (!fixedWidth && !autoWidth) {
      var a = center ? items - (items - 1) / 2 : items;
      return slideCount <= a;
    }

    var width = fixedWidth ? (fixedWidth + gutter) * slideCount : slidePositions[slideCount],
        vp = edgePadding ? viewport + edgePadding * 2 : viewport + gutter;

    if (center) {
      vp -= fixedWidth ? (viewport - fixedWidth) / 2 : (viewport - (slidePositions[index + 1] - slidePositions[index] - gutter)) / 2;
    }

    return width <= vp;
  }

  function setBreakpointZone() {
    breakpointZone = 0;

    for (var bp in responsive) {
      bp = parseInt(bp); // convert string to number

      if (windowWidth >= bp) {
        breakpointZone = bp;
      }
    }
  } // (slideBy, indexMin, indexMax) => index


  var updateIndex = function () {
    return loop ? carousel ? // loop + carousel
    function () {
      var leftEdge = indexMin,
          rightEdge = indexMax;
      leftEdge += slideBy;
      rightEdge -= slideBy; // adjust edges when has edge paddings
      // or fixed-width slider with extra space on the right side

      if (edgePadding) {
        leftEdge += 1;
        rightEdge -= 1;
      } else if (fixedWidth) {
        if ((viewport + gutter) % (fixedWidth + gutter)) {
          rightEdge -= 1;
        }
      }

      if (cloneCount) {
        if (index > rightEdge) {
          index -= slideCount;
        } else if (index < leftEdge) {
          index += slideCount;
        }
      }
    } : // loop + gallery
    function () {
      if (index > indexMax) {
        while (index >= indexMin + slideCount) {
          index -= slideCount;
        }
      } else if (index < indexMin) {
        while (index <= indexMax - slideCount) {
          index += slideCount;
        }
      }
    } : // non-loop
    function () {
      index = Math.max(indexMin, Math.min(indexMax, index));
    };
  }();

  function disableUI() {
    if (!autoplay && autoplayButton) {
      hideElement(autoplayButton);
    }

    if (!nav && navContainer) {
      hideElement(navContainer);
    }

    if (!controls) {
      if (controlsContainer) {
        hideElement(controlsContainer);
      } else {
        if (prevButton) {
          hideElement(prevButton);
        }

        if (nextButton) {
          hideElement(nextButton);
        }
      }
    }
  }

  function enableUI() {
    if (autoplay && autoplayButton) {
      showElement(autoplayButton);
    }

    if (nav && navContainer) {
      showElement(navContainer);
    }

    if (controls) {
      if (controlsContainer) {
        showElement(controlsContainer);
      } else {
        if (prevButton) {
          showElement(prevButton);
        }

        if (nextButton) {
          showElement(nextButton);
        }
      }
    }
  }

  function freezeSlider() {
    if (frozen) {
      return;
    } // remove edge padding from inner wrapper


    if (edgePadding) {
      innerWrapper.style.margin = '0px';
    } // add class tns-transparent to cloned slides


    if (cloneCount) {
      var str = 'tns-transparent';

      for (var i = cloneCount; i--;) {
        if (carousel) {
          addClass(slideItems[i], str);
        }

        addClass(slideItems[slideCountNew - i - 1], str);
      }
    } // update tools


    disableUI();
    frozen = true;
  }

  function unfreezeSlider() {
    if (!frozen) {
      return;
    } // restore edge padding for inner wrapper
    // for mordern browsers


    if (edgePadding && CSSMQ) {
      innerWrapper.style.margin = '';
    } // remove class tns-transparent to cloned slides


    if (cloneCount) {
      var str = 'tns-transparent';

      for (var i = cloneCount; i--;) {
        if (carousel) {
          removeClass(slideItems[i], str);
        }

        removeClass(slideItems[slideCountNew - i - 1], str);
      }
    } // update tools


    enableUI();
    frozen = false;
  }

  function disableSlider() {
    if (disabled) {
      return;
    }

    sheet.disabled = true;
    container.className = container.className.replace(newContainerClasses.substring(1), '');
    removeAttrs(container, ['style']);

    if (loop) {
      for (var j = cloneCount; j--;) {
        if (carousel) {
          hideElement(slideItems[j]);
        }

        hideElement(slideItems[slideCountNew - j - 1]);
      }
    } // vertical slider


    if (!horizontal || !carousel) {
      removeAttrs(innerWrapper, ['style']);
    } // gallery


    if (!carousel) {
      for (var i = index, l = index + slideCount; i < l; i++) {
        var item = slideItems[i];
        removeAttrs(item, ['style']);
        removeClass(item, animateIn);
        removeClass(item, animateNormal);
      }
    } // update tools


    disableUI();
    disabled = true;
  }

  function enableSlider() {
    if (!disabled) {
      return;
    }

    sheet.disabled = false;
    container.className += newContainerClasses;
    doContainerTransformSilent();

    if (loop) {
      for (var j = cloneCount; j--;) {
        if (carousel) {
          showElement(slideItems[j]);
        }

        showElement(slideItems[slideCountNew - j - 1]);
      }
    } // gallery


    if (!carousel) {
      for (var i = index, l = index + slideCount; i < l; i++) {
        var item = slideItems[i],
            classN = i < index + items ? animateIn : animateNormal;
        item.style.left = (i - index) * 100 / items + '%';
        addClass(item, classN);
      }
    } // update tools


    enableUI();
    disabled = false;
  }

  function updateLiveRegion() {
    var str = getLiveRegionStr();

    if (liveregionCurrent.innerHTML !== str) {
      liveregionCurrent.innerHTML = str;
    }
  }

  function getLiveRegionStr() {
    var arr = getVisibleSlideRange(),
        start = arr[0] + 1,
        end = arr[1] + 1;
    return start === end ? start + '' : start + ' to ' + end;
  }

  function getVisibleSlideRange(val) {
    if (val == null) {
      val = getContainerTransformValue();
    }

    var start = index,
        end,
        rangestart,
        rangeend; // get range start, range end for autoWidth and fixedWidth

    if (center || edgePadding) {
      if (autoWidth || fixedWidth) {
        rangestart = -(parseFloat(val) + edgePadding);
        rangeend = rangestart + viewport + edgePadding * 2;
      }
    } else {
      if (autoWidth) {
        rangestart = slidePositions[index];
        rangeend = rangestart + viewport;
      }
    } // get start, end
    // - check auto width


    if (autoWidth) {
      slidePositions.forEach(function (point, i) {
        if (i < slideCountNew) {
          if ((center || edgePadding) && point <= rangestart + 0.5) {
            start = i;
          }

          if (rangeend - point >= 0.5) {
            end = i;
          }
        }
      }); // - check percentage width, fixed width
    } else {
      if (fixedWidth) {
        var cell = fixedWidth + gutter;

        if (center || edgePadding) {
          start = Math.floor(rangestart / cell);
          end = Math.ceil(rangeend / cell - 1);
        } else {
          end = start + Math.ceil(viewport / cell) - 1;
        }
      } else {
        if (center || edgePadding) {
          var a = items - 1;

          if (center) {
            start -= a / 2;
            end = index + a / 2;
          } else {
            end = index + a;
          }

          if (edgePadding) {
            var b = edgePadding * items / viewport;
            start -= b;
            end += b;
          }

          start = Math.floor(start);
          end = Math.ceil(end);
        } else {
          end = start + items - 1;
        }
      }

      start = Math.max(start, 0);
      end = Math.min(end, slideCountNew - 1);
    }

    return [start, end];
  }

  function doLazyLoad() {
    if (lazyload && !disable) {
      var arg = getVisibleSlideRange();
      arg.push(lazyloadSelector);
      getImageArray.apply(null, arg).forEach(function (img) {
        if (!hasClass(img, imgCompleteClass)) {
          // stop propagation transitionend event to container
          var eve = {};

          eve[TRANSITIONEND] = function (e) {
            e.stopPropagation();
          };

          addEvents(img, eve);
          addEvents(img, imgEvents); // update src

          img.src = getAttr(img, 'data-src'); // update srcset

          var srcset = getAttr(img, 'data-srcset');

          if (srcset) {
            img.srcset = srcset;
          }

          addClass(img, 'loading');
        }
      });
    }
  }

  function onImgLoaded(e) {
    imgLoaded(getTarget(e));
  }

  function onImgFailed(e) {
    imgFailed(getTarget(e));
  }

  function imgLoaded(img) {
    addClass(img, 'loaded');
    imgCompleted(img);
  }

  function imgFailed(img) {
    addClass(img, 'failed');
    imgCompleted(img);
  }

  function imgCompleted(img) {
    addClass(img, imgCompleteClass);
    removeClass(img, 'loading');
    removeEvents(img, imgEvents);
  }

  function getImageArray(start, end, imgSelector) {
    var imgs = [];

    if (!imgSelector) {
      imgSelector = 'img';
    }

    while (start <= end) {
      forEach(slideItems[start].querySelectorAll(imgSelector), function (img) {
        imgs.push(img);
      });
      start++;
    }

    return imgs;
  } // check if all visible images are loaded
  // and update container height if it's done


  function doAutoHeight() {
    var imgs = getImageArray.apply(null, getVisibleSlideRange());
    raf(function () {
      imgsLoadedCheck(imgs, updateInnerWrapperHeight);
    });
  }

  function imgsLoadedCheck(imgs, cb) {
    // execute callback function if all images are complete
    if (imgsComplete) {
      return cb();
    } // check image classes


    imgs.forEach(function (img, index) {
      if (!lazyload && img.complete) {
        imgCompleted(img);
      } // Check image.complete


      if (hasClass(img, imgCompleteClass)) {
        imgs.splice(index, 1);
      }
    }); // execute callback function if selected images are all complete

    if (!imgs.length) {
      return cb();
    } // otherwise execute this functiona again


    raf(function () {
      imgsLoadedCheck(imgs, cb);
    });
  }

  function additionalUpdates() {
    doLazyLoad();
    updateSlideStatus();
    updateLiveRegion();
    updateControlsStatus();
    updateNavStatus();
  }

  function update_carousel_transition_duration() {
    if (carousel && autoHeight) {
      middleWrapper.style[TRANSITIONDURATION] = speed / 1000 + 's';
    }
  }

  function getMaxSlideHeight(slideStart, slideRange) {
    var heights = [];

    for (var i = slideStart, l = Math.min(slideStart + slideRange, slideCountNew); i < l; i++) {
      heights.push(slideItems[i].offsetHeight);
    }

    return Math.max.apply(null, heights);
  } // update inner wrapper height
  // 1. get the max-height of the visible slides
  // 2. set transitionDuration to speed
  // 3. update inner wrapper height to max-height
  // 4. set transitionDuration to 0s after transition done


  function updateInnerWrapperHeight() {
    var maxHeight = autoHeight ? getMaxSlideHeight(index, items) : getMaxSlideHeight(cloneCount, slideCount),
        wp = middleWrapper ? middleWrapper : innerWrapper;

    if (wp.style.height !== maxHeight) {
      wp.style.height = maxHeight + 'px';
    }
  } // get the distance from the top edge of the first slide to each slide
  // (init) => slidePositions


  function setSlidePositions() {
    slidePositions = [0];
    var attr = horizontal ? 'left' : 'top',
        attr2 = horizontal ? 'right' : 'bottom',
        base = slideItems[0].getBoundingClientRect()[attr];
    forEach(slideItems, function (item, i) {
      // skip the first slide
      if (i) {
        slidePositions.push(item.getBoundingClientRect()[attr] - base);
      } // add the end edge


      if (i === slideCountNew - 1) {
        slidePositions.push(item.getBoundingClientRect()[attr2] - base);
      }
    });
  } // update slide


  function updateSlideStatus() {
    var range = getVisibleSlideRange(),
        start = range[0],
        end = range[1];
    forEach(slideItems, function (item, i) {
      // show slides
      if (i >= start && i <= end) {
        if (hasAttr(item, 'aria-hidden')) {
          removeAttrs(item, ['aria-hidden', 'tabindex']);
          addClass(item, slideActiveClass);
        } // hide slides

      } else {
        if (!hasAttr(item, 'aria-hidden')) {
          setAttrs(item, {
            'aria-hidden': 'true',
            'tabindex': '-1'
          });
          removeClass(item, slideActiveClass);
        }
      }
    });
  } // gallery: update slide position


  function updateGallerySlidePositions() {
    var l = index + Math.min(slideCount, items);

    for (var i = slideCountNew; i--;) {
      var item = slideItems[i];

      if (i >= index && i < l) {
        // add transitions to visible slides when adjusting their positions
        addClass(item, 'tns-moving');
        item.style.left = (i - index) * 100 / items + '%';
        addClass(item, animateIn);
        removeClass(item, animateNormal);
      } else if (item.style.left) {
        item.style.left = '';
        addClass(item, animateNormal);
        removeClass(item, animateIn);
      } // remove outlet animation


      removeClass(item, animateOut);
    } // removing '.tns-moving'


    setTimeout(function () {
      forEach(slideItems, function (el) {
        removeClass(el, 'tns-moving');
      });
    }, 300);
  } // set tabindex on Nav


  function updateNavStatus() {
    // get current nav
    if (nav) {
      navCurrentIndex = navClicked >= 0 ? navClicked : getCurrentNavIndex();
      navClicked = -1;

      if (navCurrentIndex !== navCurrentIndexCached) {
        var navPrev = navItems[navCurrentIndexCached],
            navCurrent = navItems[navCurrentIndex];
        setAttrs(navPrev, {
          'tabindex': '-1',
          'aria-label': navStr + (navCurrentIndexCached + 1)
        });
        removeClass(navPrev, navActiveClass);
        setAttrs(navCurrent, {
          'aria-label': navStr + (navCurrentIndex + 1) + navStrCurrent
        });
        removeAttrs(navCurrent, 'tabindex');
        addClass(navCurrent, navActiveClass);
        navCurrentIndexCached = navCurrentIndex;
      }
    }
  }

  function getLowerCaseNodeName(el) {
    return el.nodeName.toLowerCase();
  }

  function isButton(el) {
    return getLowerCaseNodeName(el) === 'button';
  }

  function isAriaDisabled(el) {
    return el.getAttribute('aria-disabled') === 'true';
  }

  function disEnableElement(isButton, el, val) {
    if (isButton) {
      el.disabled = val;
    } else {
      el.setAttribute('aria-disabled', val.toString());
    }
  } // set 'disabled' to true on controls when reach the edges


  function updateControlsStatus() {
    if (!controls || rewind || loop) {
      return;
    }

    var prevDisabled = prevIsButton ? prevButton.disabled : isAriaDisabled(prevButton),
        nextDisabled = nextIsButton ? nextButton.disabled : isAriaDisabled(nextButton),
        disablePrev = index <= indexMin ? true : false,
        disableNext = !rewind && index >= indexMax ? true : false;

    if (disablePrev && !prevDisabled) {
      disEnableElement(prevIsButton, prevButton, true);
    }

    if (!disablePrev && prevDisabled) {
      disEnableElement(prevIsButton, prevButton, false);
    }

    if (disableNext && !nextDisabled) {
      disEnableElement(nextIsButton, nextButton, true);
    }

    if (!disableNext && nextDisabled) {
      disEnableElement(nextIsButton, nextButton, false);
    }
  } // set duration


  function resetDuration(el, str) {
    if (TRANSITIONDURATION) {
      el.style[TRANSITIONDURATION] = str;
    }
  }

  function getSliderWidth() {
    return fixedWidth ? (fixedWidth + gutter) * slideCountNew : slidePositions[slideCountNew];
  }

  function getCenterGap(num) {
    if (num == null) {
      num = index;
    }

    var gap = edgePadding ? gutter : 0;
    return autoWidth ? (viewport - gap - (slidePositions[num + 1] - slidePositions[num] - gutter)) / 2 : fixedWidth ? (viewport - fixedWidth) / 2 : (items - 1) / 2;
  }

  function getRightBoundary() {
    var gap = edgePadding ? gutter : 0,
        result = viewport + gap - getSliderWidth();

    if (center && !loop) {
      result = fixedWidth ? -(fixedWidth + gutter) * (slideCountNew - 1) - getCenterGap() : getCenterGap(slideCountNew - 1) - slidePositions[slideCountNew - 1];
    }

    if (result > 0) {
      result = 0;
    }

    return result;
  }

  function getContainerTransformValue(num) {
    if (num == null) {
      num = index;
    }

    var val;

    if (horizontal && !autoWidth) {
      if (fixedWidth) {
        val = -(fixedWidth + gutter) * num;

        if (center) {
          val += getCenterGap();
        }
      } else {
        var denominator = TRANSFORM ? slideCountNew : items;

        if (center) {
          num -= getCenterGap();
        }

        val = -num * 100 / denominator;
      }
    } else {
      val = -slidePositions[num];

      if (center && autoWidth) {
        val += getCenterGap();
      }
    }

    if (hasRightDeadZone) {
      val = Math.max(val, rightBoundary);
    }

    val += horizontal && !autoWidth && !fixedWidth ? '%' : 'px';
    return val;
  }

  function doContainerTransformSilent(val) {
    resetDuration(container, '0s');
    doContainerTransform(val);
  }

  function doContainerTransform(val) {
    if (val == null) {
      val = getContainerTransformValue();
    }

    container.style[transformAttr] = transformPrefix + val + transformPostfix;
  }

  function animateSlide(number, classOut, classIn, isOut) {
    var l = number + items;

    if (!loop) {
      l = Math.min(l, slideCountNew);
    }

    for (var i = number; i < l; i++) {
      var item = slideItems[i]; // set item positions

      if (!isOut) {
        item.style.left = (i - index) * 100 / items + '%';
      }

      if (animateDelay && TRANSITIONDELAY) {
        item.style[TRANSITIONDELAY] = item.style[ANIMATIONDELAY] = animateDelay * (i - number) / 1000 + 's';
      }

      removeClass(item, classOut);
      addClass(item, classIn);

      if (isOut) {
        slideItemsOut.push(item);
      }
    }
  } // make transfer after click/drag:
  // 1. change 'transform' property for mordern browsers
  // 2. change 'left' property for legacy browsers


  var transformCore = function () {
    return carousel ? function () {
      resetDuration(container, '');

      if (TRANSITIONDURATION || !speed) {
        // for morden browsers with non-zero duration or
        // zero duration for all browsers
        doContainerTransform(); // run fallback function manually
        // when duration is 0 / container is hidden

        if (!speed || !isVisible(container)) {
          onTransitionEnd();
        }
      } else {
        // for old browser with non-zero duration
        jsTransform(container, transformAttr, transformPrefix, transformPostfix, getContainerTransformValue(), speed, onTransitionEnd);
      }

      if (!horizontal) {
        updateContentWrapperHeight();
      }
    } : function () {
      slideItemsOut = [];
      var eve = {};
      eve[TRANSITIONEND] = eve[ANIMATIONEND] = onTransitionEnd;
      removeEvents(slideItems[indexCached], eve);
      addEvents(slideItems[index], eve);
      animateSlide(indexCached, animateIn, animateOut, true);
      animateSlide(index, animateNormal, animateIn); // run fallback function manually
      // when transition or animation not supported / duration is 0

      if (!TRANSITIONEND || !ANIMATIONEND || !speed || !isVisible(container)) {
        onTransitionEnd();
      }
    };
  }();

  function render(e, sliderMoved) {
    if (updateIndexBeforeTransform) {
      updateIndex();
    } // render when slider was moved (touch or drag) even though index may not change


    if (index !== indexCached || sliderMoved) {
      // events
      events.emit('indexChanged', info());
      events.emit('transitionStart', info());

      if (autoHeight) {
        doAutoHeight();
      } // pause autoplay when click or keydown from user


      if (animating && e && ['click', 'keydown'].indexOf(e.type) >= 0) {
        stopAutoplay();
      }

      running = true;
      transformCore();
    }
  }
  /*
   * Transfer prefixed properties to the same format
   * CSS: -Webkit-Transform => webkittransform
   * JS: WebkitTransform => webkittransform
   * @param {string} str - property
   *
   */


  function strTrans(str) {
    return str.toLowerCase().replace(/-/g, '');
  } // AFTER TRANSFORM
  // Things need to be done after a transfer:
  // 1. check index
  // 2. add classes to visible slide
  // 3. disable controls buttons when reach the first/last slide in non-loop slider
  // 4. update nav status
  // 5. lazyload images
  // 6. update container height


  function onTransitionEnd(event) {
    // check running on gallery mode
    // make sure trantionend/animationend events run only once
    if (carousel || running) {
      events.emit('transitionEnd', info(event));

      if (!carousel && slideItemsOut.length > 0) {
        for (var i = 0; i < slideItemsOut.length; i++) {
          var item = slideItemsOut[i]; // set item positions

          item.style.left = '';

          if (ANIMATIONDELAY && TRANSITIONDELAY) {
            item.style[ANIMATIONDELAY] = '';
            item.style[TRANSITIONDELAY] = '';
          }

          removeClass(item, animateOut);
          addClass(item, animateNormal);
        }
      }
      /* update slides, nav, controls after checking ...
       * => legacy browsers who don't support 'event'
       *    have to check event first, otherwise event.target will cause an error
       * => or 'gallery' mode:
       *   + event target is slide item
       * => or 'carousel' mode:
       *   + event target is container,
       *   + event.property is the same with transform attribute
       */


      if (!event || !carousel && event.target.parentNode === container || event.target === container && strTrans(event.propertyName) === strTrans(transformAttr)) {
        if (!updateIndexBeforeTransform) {
          var indexTem = index;
          updateIndex();

          if (index !== indexTem) {
            events.emit('indexChanged', info());
            doContainerTransformSilent();
          }
        }

        if (nested === 'inner') {
          events.emit('innerLoaded', info());
        }

        running = false;
        indexCached = index;
      }
    }
  } // # ACTIONS


  function goTo(targetIndex, e) {
    if (freeze) {
      return;
    } // prev slideBy


    if (targetIndex === 'prev') {
      onControlsClick(e, -1); // next slideBy
    } else if (targetIndex === 'next') {
      onControlsClick(e, 1); // go to exact slide
    } else {
      if (running) {
        if (preventActionWhenRunning) {
          return;
        } else {
          onTransitionEnd();
        }
      }

      var absIndex = getAbsIndex(),
          indexGap = 0;

      if (targetIndex === 'first') {
        indexGap = -absIndex;
      } else if (targetIndex === 'last') {
        indexGap = carousel ? slideCount - items - absIndex : slideCount - 1 - absIndex;
      } else {
        if (typeof targetIndex !== 'number') {
          targetIndex = parseInt(targetIndex);
        }

        if (!isNaN(targetIndex)) {
          // from directly called goTo function
          if (!e) {
            targetIndex = Math.max(0, Math.min(slideCount - 1, targetIndex));
          }

          indexGap = targetIndex - absIndex;
        }
      } // gallery: make sure new page won't overlap with current page


      if (!carousel && indexGap && Math.abs(indexGap) < items) {
        var factor = indexGap > 0 ? 1 : -1;
        indexGap += index + indexGap - slideCount >= indexMin ? slideCount * factor : slideCount * 2 * factor * -1;
      }

      index += indexGap; // make sure index is in range

      if (carousel && loop) {
        if (index < indexMin) {
          index += slideCount;
        }

        if (index > indexMax) {
          index -= slideCount;
        }
      } // if index is changed, start rendering


      if (getAbsIndex(index) !== getAbsIndex(indexCached)) {
        render(e);
      }
    }
  } // on controls click


  function onControlsClick(e, dir) {
    if (running) {
      if (preventActionWhenRunning) {
        return;
      } else {
        onTransitionEnd();
      }
    }

    var passEventObject;

    if (!dir) {
      e = getEvent(e);
      var target = getTarget(e);

      while (target !== controlsContainer && [prevButton, nextButton].indexOf(target) < 0) {
        target = target.parentNode;
      }

      var targetIn = [prevButton, nextButton].indexOf(target);

      if (targetIn >= 0) {
        passEventObject = true;
        dir = targetIn === 0 ? -1 : 1;
      }
    }

    if (rewind) {
      if (index === indexMin && dir === -1) {
        goTo('last', e);
        return;
      } else if (index === indexMax && dir === 1) {
        goTo('first', e);
        return;
      }
    }

    if (dir) {
      index += slideBy * dir;

      if (autoWidth) {
        index = Math.floor(index);
      } // pass e when click control buttons or keydown


      render(passEventObject || e && e.type === 'keydown' ? e : null);
    }
  } // on nav click


  function onNavClick(e) {
    if (running) {
      if (preventActionWhenRunning) {
        return;
      } else {
        onTransitionEnd();
      }
    }

    e = getEvent(e);
    var target = getTarget(e),
        navIndex; // find the clicked nav item

    while (target !== navContainer && !hasAttr(target, 'data-nav')) {
      target = target.parentNode;
    }

    if (hasAttr(target, 'data-nav')) {
      var navIndex = navClicked = Number(getAttr(target, 'data-nav')),
          targetIndexBase = fixedWidth || autoWidth ? navIndex * slideCount / pages : navIndex * items,
          targetIndex = navAsThumbnails ? navIndex : Math.min(Math.ceil(targetIndexBase), slideCount - 1);
      goTo(targetIndex, e);

      if (navCurrentIndex === navIndex) {
        if (animating) {
          stopAutoplay();
        }

        navClicked = -1; // reset navClicked
      }
    }
  } // autoplay functions


  function setAutoplayTimer() {
    autoplayTimer = setInterval(function () {
      onControlsClick(null, autoplayDirection);
    }, autoplayTimeout);
    animating = true;
  }

  function stopAutoplayTimer() {
    clearInterval(autoplayTimer);
    animating = false;
  }

  function updateAutoplayButton(action, txt) {
    setAttrs(autoplayButton, {
      'data-action': action
    });
    autoplayButton.innerHTML = autoplayHtmlStrings[0] + action + autoplayHtmlStrings[1] + txt;
  }

  function startAutoplay() {
    setAutoplayTimer();

    if (autoplayButton) {
      updateAutoplayButton('stop', autoplayText[1]);
    }
  }

  function stopAutoplay() {
    stopAutoplayTimer();

    if (autoplayButton) {
      updateAutoplayButton('start', autoplayText[0]);
    }
  } // programaitcally play/pause the slider


  function play() {
    if (autoplay && !animating) {
      startAutoplay();
      autoplayUserPaused = false;
    }
  }

  function pause() {
    if (animating) {
      stopAutoplay();
      autoplayUserPaused = true;
    }
  }

  function toggleAutoplay() {
    if (animating) {
      stopAutoplay();
      autoplayUserPaused = true;
    } else {
      startAutoplay();
      autoplayUserPaused = false;
    }
  }

  function onVisibilityChange() {
    if (doc.hidden) {
      if (animating) {
        stopAutoplayTimer();
        autoplayVisibilityPaused = true;
      }
    } else if (autoplayVisibilityPaused) {
      setAutoplayTimer();
      autoplayVisibilityPaused = false;
    }
  }

  function mouseoverPause() {
    if (animating) {
      stopAutoplayTimer();
      autoplayHoverPaused = true;
    }
  }

  function mouseoutRestart() {
    if (autoplayHoverPaused) {
      setAutoplayTimer();
      autoplayHoverPaused = false;
    }
  } // keydown events on document


  function onDocumentKeydown(e) {
    e = getEvent(e);
    var keyIndex = [KEYS.LEFT, KEYS.RIGHT].indexOf(e.keyCode);

    if (keyIndex >= 0) {
      onControlsClick(e, keyIndex === 0 ? -1 : 1);
    }
  } // on key control


  function onControlsKeydown(e) {
    e = getEvent(e);
    var keyIndex = [KEYS.LEFT, KEYS.RIGHT].indexOf(e.keyCode);

    if (keyIndex >= 0) {
      if (keyIndex === 0) {
        if (!prevButton.disabled) {
          onControlsClick(e, -1);
        }
      } else if (!nextButton.disabled) {
        onControlsClick(e, 1);
      }
    }
  } // set focus


  function setFocus(el) {
    el.focus();
  } // on key nav


  function onNavKeydown(e) {
    e = getEvent(e);
    var curElement = doc.activeElement;

    if (!hasAttr(curElement, 'data-nav')) {
      return;
    } // var code = e.keyCode,


    var keyIndex = [KEYS.LEFT, KEYS.RIGHT, KEYS.ENTER, KEYS.SPACE].indexOf(e.keyCode),
        navIndex = Number(getAttr(curElement, 'data-nav'));

    if (keyIndex >= 0) {
      if (keyIndex === 0) {
        if (navIndex > 0) {
          setFocus(navItems[navIndex - 1]);
        }
      } else if (keyIndex === 1) {
        if (navIndex < pages - 1) {
          setFocus(navItems[navIndex + 1]);
        }
      } else {
        navClicked = navIndex;
        goTo(navIndex, e);
      }
    }
  }

  function getEvent(e) {
    e = e || win.event;
    return isTouchEvent(e) ? e.changedTouches[0] : e;
  }

  function getTarget(e) {
    return e.target || win.event.srcElement;
  }

  function isTouchEvent(e) {
    return e.type.indexOf('touch') >= 0;
  }

  function preventDefaultBehavior(e) {
    e.preventDefault ? e.preventDefault() : e.returnValue = false;
  }

  function getMoveDirectionExpected() {
    return getTouchDirection(toDegree(lastPosition.y - initPosition.y, lastPosition.x - initPosition.x), swipeAngle) === options.axis;
  }

  function onPanStart(e) {
    if (running) {
      if (preventActionWhenRunning) {
        return;
      } else {
        onTransitionEnd();
      }
    }

    if (autoplay && animating) {
      stopAutoplayTimer();
    }

    panStart = true;

    if (rafIndex) {
      caf(rafIndex);
      rafIndex = null;
    }

    var $ = getEvent(e);
    events.emit(isTouchEvent(e) ? 'touchStart' : 'dragStart', info(e));

    if (!isTouchEvent(e) && ['img', 'a'].indexOf(getLowerCaseNodeName(getTarget(e))) >= 0) {
      preventDefaultBehavior(e);
    }

    lastPosition.x = initPosition.x = $.clientX;
    lastPosition.y = initPosition.y = $.clientY;

    if (carousel) {
      translateInit = parseFloat(container.style[transformAttr].replace(transformPrefix, ''));
      resetDuration(container, '0s');
    }
  }

  function onPanMove(e) {
    if (panStart) {
      var $ = getEvent(e);
      lastPosition.x = $.clientX;
      lastPosition.y = $.clientY;

      if (carousel) {
        if (!rafIndex) {
          rafIndex = raf(function () {
            panUpdate(e);
          });
        }
      } else {
        if (moveDirectionExpected === '?') {
          moveDirectionExpected = getMoveDirectionExpected();
        }

        if (moveDirectionExpected) {
          preventScroll = true;
        }
      }

      if ((typeof e.cancelable !== 'boolean' || e.cancelable) && preventScroll) {
        e.preventDefault();
      }
    }
  }

  function panUpdate(e) {
    if (!moveDirectionExpected) {
      panStart = false;
      return;
    }

    caf(rafIndex);

    if (panStart) {
      rafIndex = raf(function () {
        panUpdate(e);
      });
    }

    if (moveDirectionExpected === '?') {
      moveDirectionExpected = getMoveDirectionExpected();
    }

    if (moveDirectionExpected) {
      if (!preventScroll && isTouchEvent(e)) {
        preventScroll = true;
      }

      try {
        if (e.type) {
          events.emit(isTouchEvent(e) ? 'touchMove' : 'dragMove', info(e));
        }
      } catch (err) {}

      var x = translateInit,
          dist = getDist(lastPosition, initPosition);

      if (!horizontal || fixedWidth || autoWidth) {
        x += dist;
        x += 'px';
      } else {
        var percentageX = TRANSFORM ? dist * items * 100 / ((viewport + gutter) * slideCountNew) : dist * 100 / (viewport + gutter);
        x += percentageX;
        x += '%';
      }

      container.style[transformAttr] = transformPrefix + x + transformPostfix;
    }
  }

  function onPanEnd(e) {
    if (panStart) {
      if (rafIndex) {
        caf(rafIndex);
        rafIndex = null;
      }

      if (carousel) {
        resetDuration(container, '');
      }

      panStart = false;
      var $ = getEvent(e);
      lastPosition.x = $.clientX;
      lastPosition.y = $.clientY;
      var dist = getDist(lastPosition, initPosition);

      if (Math.abs(dist)) {
        // drag vs click
        if (!isTouchEvent(e)) {
          // prevent "click"
          var target = getTarget(e);
          addEvents(target, {
            'click': function preventClick(e) {
              preventDefaultBehavior(e);
              removeEvents(target, {
                'click': preventClick
              });
            }
          });
        }

        if (carousel) {
          rafIndex = raf(function () {
            if (horizontal && !autoWidth) {
              var indexMoved = -dist * items / (viewport + gutter);
              indexMoved = dist > 0 ? Math.floor(indexMoved) : Math.ceil(indexMoved);
              index += indexMoved;
            } else {
              var moved = -(translateInit + dist);

              if (moved <= 0) {
                index = indexMin;
              } else if (moved >= slidePositions[slideCountNew - 1]) {
                index = indexMax;
              } else {
                var i = 0;

                while (i < slideCountNew && moved >= slidePositions[i]) {
                  index = i;

                  if (moved > slidePositions[i] && dist < 0) {
                    index += 1;
                  }

                  i++;
                }
              }
            }

            render(e, dist);
            events.emit(isTouchEvent(e) ? 'touchEnd' : 'dragEnd', info(e));
          });
        } else {
          if (moveDirectionExpected) {
            onControlsClick(e, dist > 0 ? -1 : 1);
          }
        }
      }
    } // reset


    if (options.preventScrollOnTouch === 'auto') {
      preventScroll = false;
    }

    if (swipeAngle) {
      moveDirectionExpected = '?';
    }

    if (autoplay && !animating) {
      setAutoplayTimer();
    }
  } // === RESIZE FUNCTIONS === //
  // (slidePositions, index, items) => vertical_conentWrapper.height


  function updateContentWrapperHeight() {
    var wp = middleWrapper ? middleWrapper : innerWrapper;
    wp.style.height = slidePositions[index + items] - slidePositions[index] + 'px';
  }

  function getPages() {
    var rough = fixedWidth ? (fixedWidth + gutter) * slideCount / viewport : slideCount / items;
    return Math.min(Math.ceil(rough), slideCount);
  }
  /*
   * 1. update visible nav items list
   * 2. add "hidden" attributes to previous visible nav items
   * 3. remove "hidden" attrubutes to new visible nav items
   */


  function updateNavVisibility() {
    if (!nav || navAsThumbnails) {
      return;
    }

    if (pages !== pagesCached) {
      var min = pagesCached,
          max = pages,
          fn = showElement;

      if (pagesCached > pages) {
        min = pages;
        max = pagesCached;
        fn = hideElement;
      }

      while (min < max) {
        fn(navItems[min]);
        min++;
      } // cache pages


      pagesCached = pages;
    }
  }

  function info(e) {
    return {
      container: container,
      slideItems: slideItems,
      navContainer: navContainer,
      navItems: navItems,
      controlsContainer: controlsContainer,
      hasControls: hasControls,
      prevButton: prevButton,
      nextButton: nextButton,
      items: items,
      slideBy: slideBy,
      cloneCount: cloneCount,
      slideCount: slideCount,
      slideCountNew: slideCountNew,
      index: index,
      indexCached: indexCached,
      displayIndex: getCurrentSlide(),
      navCurrentIndex: navCurrentIndex,
      navCurrentIndexCached: navCurrentIndexCached,
      pages: pages,
      pagesCached: pagesCached,
      sheet: sheet,
      isOn: isOn,
      event: e || {}
    };
  }

  return {
    version: '2.9.4',
    getInfo: info,
    events: events,
    goTo: goTo,
    play: play,
    pause: pause,
    isOn: isOn,
    updateSliderHeight: updateInnerWrapperHeight,
    refresh: initSliderTransform,
    destroy: destroy,
    rebuild: function () {
      return tns(extend(options, optionsElements));
    }
  };
};

var tns_1 = tinySlider.tns = tns;

const solTnsSupportedOptions = [
    'solTnsControls', 'solTnsNav', 'solTnsNavPosition', 'solTnsLoop', 'solTnsRewind', 'solTnsItems',  'solTnsSlideBy',
    'solTnsAutoplay', 'solTnsAutoplayTimeout', 'solTnsAutoplayHoverPause', 'solTnsAutoplayButtonOutput',
    'solTnsNextButton', 'solTnsPrevButton', 'solTnsControlsContainer', 'solTnsSpeed',
    'solTnsFixedWidth', 'solTnsGutter', 'solTnsEdgePadding'
];

function overrideTnsOptions(options, container) {
    for (let i = 0; i < solTnsSupportedOptions.length; i++) {
        let optName = solTnsSupportedOptions[i];
        let dataOverride = container.dataset[optName];
        if (dataOverride !== undefined && dataOverride !== null) {
            let tnsOptName = optName.substring(6);
            tnsOptName = tnsOptName.slice(0,1).toLowerCase() + tnsOptName.slice(1);
            options[tnsOptName] = convertDatasetValue(dataOverride);
        }
    }
}

function tnsShowcaseConfig(i, el) {
    const options = {
        container: el,
        controls: false,
        navPosition: 'bottom',
        loop: false
    };

    const listing = el.closest('.listing');
    const count = listing.dataset.count;

    options.responsive = {
        0:{
            items:2
        },
        480:{
            items:count>3?3:count
        },
        668:{
            items:count>4?4:count
        },
        1025:{
            items:count
        }
    };

    let listingShowcase = el.closest('.listing.showcase');
    let wrapper = listingShowcase.parentElement;

    let leftButton, rightButton;
    if (wrapper) {
        overrideTnsOptions(options, wrapper);
        /* testing - try to shoehorn controls into div */
        if (options.controls) {
            leftButton = document.createElement('div');
            leftButton.className = 'carousel-btn prev';
            leftButton.innerHTML = '<i class="icon icon-chevron-left-2"></i>';

            rightButton = document.createElement('div');
            rightButton.className = 'carousel-btn next';
            rightButton.innerHTML = '<i class="icon icon-chevron-right-2"></i>';

            options.prevButton = leftButton;
            options.nextButton = rightButton;

            listingShowcase.prepend(leftButton);
            listingShowcase.append(rightButton);
        }
    }

    tns_1(options);
}

function initializeShowcases$1() {
    const isTouchDevice = 'ontouchstart' in document.documentElement;
    const buttonStateDelay = 200;
    document.querySelectorAll('.crl-cntr').forEach(container => {
        const crlHs = container.querySelector('.crl-hs');
        const leftButton = container.querySelector('.crl-btn-left');
        const rightButton = container.querySelector('.crl-btn-right');
        /* For the sake of simplicity here, I'm assuming buttons are always absent/present as a pair
        * and only verifying existence of the left one */
        function updateButtonVisibility() {
            if (leftButton) {
                if (crlHs.scrollWidth <= crlHs.clientWidth || isTouchDevice) {
                    leftButton.classList.add('crl-btn-hide');
                    rightButton.classList.add('crl-btn-hide');
                    crlHs.style.setProperty('overflow-x', 'auto');
                } else {
                    leftButton.classList.remove('crl-btn-hide');
                    rightButton.classList.remove('crl-btn-hide');
                    crlHs.style.setProperty('overflow-x', 'hidden');
                }
            }
            checkButtonState();
        }

        function checkButtonState() {
            if (leftButton) {
                if (crlHs.scrollLeft === 0) {
                    leftButton.classList.add('crl-btn-disabled');
                } else {
                    leftButton.classList.remove('crl-btn-disabled');
                }
                if (crlHs.scrollLeft + crlHs.clientWidth >= crlHs.scrollWidth) {
                    rightButton.classList.add('crl-btn-disabled');
                } else {
                    rightButton.classList.remove('crl-btn-disabled');
                }
            }
        }

        if (!isTouchDevice) {
            if (leftButton) {
                leftButton.addEventListener('click', () => {
                    crlHs.scrollBy({left: -crlHs.clientWidth, behavior: 'smooth'});
                    setTimeout(checkButtonState, buttonStateDelay);
                });
                rightButton.addEventListener('click', () => {
                    crlHs.scrollBy({left: crlHs.clientWidth, behavior: 'smooth'});
                    setTimeout(checkButtonState, buttonStateDelay);
                });
            }
            crlHs.addEventListener('scroll', checkButtonState);
        }

        updateButtonVisibility();
        window.addEventListener('resize', () => updateButtonVisibility());
    });
}

function initializeSliders() {
    initializeShowcases$1();

    document.querySelectorAll('.owl').forEach(function(element) {
        const options = {
            container: element,
            controls: false,
            nav: true,
            navPosition: 'bottom',
            loop: false,
            rewind: true,
            items: 1,
            autoplay: true,
            autoplayHoverPause: true,
            autoplayButtonOutput: false
        };

        overrideTnsOptions(options, element);

        /* this currently only happens so that we have a unique node to attach mutation listener to (below) */
        // that.wrap(`<div class="slider-wrapper${copiedClasses}"></div>`);
        let wrapper = document.createElement('div');
        wrapper.classList.add('slider-wrapper');

        // copy up any non-owl classes (grid classes, for example)
        // let copiedClasses = '',
        for (let i = 0; i < element.classList.length; i++) {
            let candidate = element.classList.item(i);
            if (candidate !== 'owl') {
                // copiedClasses += ` ${candidate}`;
                wrapper.classList.add(candidate);
            }
        }

        element.parentNode.insertBefore(wrapper, element);
        wrapper.appendChild(element);

        if (!options.nav) {
            options.controls = false;
        }

        /* for sliders, controls is really treated as an enhancement to nav and the controls are inserted beside the nav controls.
        * So both need to be true before we execute this code to do said insertion. */
        if (options.controls && options.nav) {
            let leftButton = document.createElement('div');
            leftButton.classList.add('carousel-btn', 'prev');
            leftButton.innerHTML = '<i class="icon icon-chevron-left-2"></i>';

            let rightButton = document.createElement('div');
            rightButton.classList.add('carousel-btn', 'next');
            rightButton.innerHTML = '<i class="icon icon-chevron-right-2"></i>';

            options.prevButton = leftButton;
            options.nextButton = rightButton;
            /* set up a listener so once .tns-nav gets added we can stuff our little buttons in it */
            let observer = new MutationObserver(function(mutationsList) {
                for (let i = 0; i < mutationsList.length; i++) {
                    let mut = mutationsList[i];
                    if (mut.type === "childList") {
                        mut.addedNodes.forEach(function(currentNode) {
                            if (currentNode.classList.contains('tns-nav')) {
                                // console.log("Found tns-nav in added nodes! %O", currentNode);
                                currentNode.appendChild(leftButton);
                                currentNode.appendChild(rightButton);
                            }
                        });
                    }
                }
            });
            observer.observe(element.parentNode, {childList: true, subtree: true});
        }

        tns_1(options);
    });
}

function initializeAddToCartOverlay(data, evt) {
    initializeAddToCart({
        pdp: false,
        productId: data.productId,
        gaProduct: data.gaProduct,
        overlaySkuImages: data.skuImages,
        updateLinkSelectors: ['#overlay-hero', '#overlay-header'],
    }, magnificPopup.instance.content, evt);
    initializeAdjustQuantity();
}

function initializeAdjustQuantity() {
    document.querySelectorAll('.adjust-quantity').forEach(function(element) {
        const quantityBox = element.querySelector('.val');
        let maxVal = quantityBox?.dataset.maxValue ? parseInt(quantityBox.dataset.maxValue) : undefined;
        let minVal = quantityBox?.dataset.minValue ? parseInt(quantityBox.dataset.minValue) : undefined;

        const updateQuantity = function (direction) {
            let currentVal = quantityBox?.value;

            if (currentVal) {
                currentVal = parseInt(currentVal);
                if (currentVal || currentVal === 0) {
                    if (direction === 'up') {
                        if (maxVal || maxVal === 0) {
                            if (currentVal < maxVal) {
                                currentVal++;
                            } else {
                                currentVal = maxVal;
                            }
                        } else {
                            currentVal++;
                        }
                    } else {
                        if (minVal || minVal === 0) {
                            if (currentVal > minVal) {
                                currentVal--;
                            } else {
                                currentVal = minVal;
                            }
                        } else {
                            currentVal--;
                        }
                    }
                    quantityBox.value = currentVal;
                }
            } else {
                quantityBox.value = 1;
            }
        };

        element.querySelector('.down')?.addEventListener('click', function(evt) {
            evt.preventDefault();
            updateQuantity('down');
        });

        element.querySelector('.up')?.addEventListener('click', function(evt) {
            evt.preventDefault();
            updateQuantity('up');
        });
    });
}

/* copied over from sku_picker where computation of this sort is going away, due to reintroduction of descriptive text.
    but this simple algorithm still works for the overlay URL which will not contain those.
 */
function computeNewOverlayPathname(pathname, skuId) {
    let urlSegments = pathname.split("/");
    let newPathname = '';
    let idCount = 0, inProduct = false;
    for (let i = 0; i < urlSegments.length; i++) {
        let segment = urlSegments[i];
        if (!segment) {
            continue;
        }
        if (inProduct && idCount < 2) {
            newPathname += '/' + (idCount < 1 ? segment : skuId);
            idCount++;
        } else {
            idCount = 0;
            newPathname += '/' + segment;
            if (segment === 'p') {
                inProduct = true;
            }
        }
    } /* end for loop on existing segments */

    return newPathname;
}

function initializeListingAddToCart() {
    document.querySelectorAll('.listing .item .addOrPlay .btn-choose-options').forEach((el) => {
        el.addEventListener('click', function(evt) {
            let targ = evt.target;
            let paramData = {
                lref: targ.dataset['lref']
            };
            let overlayUrl = targ.dataset['overlayUrl'];
            if (targ.dataset['selectedSkuId']) {
                overlayUrl = computeNewOverlayPathname(overlayUrl, targ.dataset['selectedSkuId']);
            }
            loadAddToCartOverlay(overlayUrl, paramData, evt);
        });
    });
    document.querySelectorAll('.listing .item .addOrPlay .btn-listing-add-to-cart').forEach((el) => {
        el.addEventListener('click', clickBtnListingAddToCart);
    });
    let swatchDivs = document.querySelectorAll('.listing .swatch-container');
    swatchDivs.forEach((swatchDiv) => {
        let attributeString = swatchDiv.dataset['attributesJson'];
        let attributeData = JSON.parse(attributeString);
        let attributes = attributeData.attributes;
        let purchaseOptions = attributeData.purchaseOptions;
        initSkuPicker({attributes: attributes,
            purchaseOptions: purchaseOptions,
            overlaySkuImages: null,
            updateBrowserUrl: false,
            productId: attributeData.productId,
            selectorIdPrefix: `pid${attributeData.productId}-attr`,
            priceDisplaySelector: null,
            hiddenSezzlePriceDisplaySelector: null,
            paypalDataAmountSelector: null,
            afterpayAmountSelector: null,
            displayDataCandyPoints: attributeData.displayDataCandyPoints,
            initializedFromFacets: attributeData.initializedFromFacets,
            changeOptionImages: true,
            skuChangeListenerSelectors: [`#btnco-${attributeData.productId}`, `.gl-toggle-${attributeData.productId}`],
            skipExcludedTreatment: true,
            heroImageLinkSelector: `#hl${attributeData.productId}`,
            updateLinkSelectors: [`#hl${attributeData.productId}`,`#nl${attributeData.productId}`,`#pl${attributeData.productId}`],
            container: swatchDiv});
    });
}

function loadAddToCartOverlay(productUrl, pParamData, evt) {
    showDefaultSpinner();
    const paramData = pParamData || {};
    fetch(`${productUrl}?${cleanURLSearchParams(paramData)}`, {
        headers: {
            "Accept": "application/json",
        },
        method: "GET",
        signal: AbortSignal.timeout(20000),
    }).then((response) => {
        if (response.ok) {
            return response.json();
        }
        return Promise.reject(response.statusText);
    }).then((data) => {
        magnificPopup.open({
            items: {
                src: mp.fromMarkup(data.overlayContent),
                type: 'inline'
            },
            callbacks: {
                open: function() {
                    initializeAddToCartOverlay(data, evt);
                }
            }
        });
    }).catch((error) => {
        console.error(error);
        magnificPopup.open({
            items:
                [
                    {
                        src: mp.fromMarkup('<div class="basic-popup">There was an error loading product data.</div>')
                    }
                ],
            type: 'inline'
        }, 0);
    }).finally(() => {
        hideDefaultSpinner();
    });
}

function clickBtnListingAddToCart(evt, addedCallback) {
    evt.preventDefault();
    let button = evt.target,
        skuId = button.dataset['skuId'],
        productId = button.dataset['productId'],
        gaProductName = button.dataset['gaProductName'],
        gaCategoryName = button.dataset['gaCategoryName'],
        gaStudioName = button.dataset['gaStudioName'],
        gaVariantName = button.dataset['gaVariantName'],
        gaPrice = button.dataset['gaPrice'],
        gaOnSale = evalBooleanish(button.dataset['gaOnSale']),
        lref = button.dataset['lref'],
        includeExtend = button.dataset['includeExtend'];

    if (includeExtend === undefined) {
        /* default to including */
        includeExtend = true;
    }

    let gaProduct = {
        id: productId,
        name: gaProductName,
        category: gaCategoryName,
        brand: gaStudioName,
        variant: gaVariantName,
        price: gaPrice
    };

    if (gaOnSale) {
        gaProduct['coupon'] = 'On Sale';
    }
    if (skuId != null) {
        const addData = {
            productId: productId,
            skuId: skuId,
            lref: lref
        };
        executeAddToCart(addData, gaProduct, function() {
            if (addedCallback) {
                addedCallback(productId, skuId, false);
            } else {
                loadAddedToCartOverlay(productId, skuId, false);
            }
        }, includeExtend ? '#extend-offer' : undefined);
    }
}

function loadAppendAddedToCartOverlay(productId, skuId, pPdp, xsellProductIds, xsellSkuIds) {
    /* This is a bit of hacking here. */
    /* Looking for in-the-cart in both the base page (pdp/plp) and the current overlay before addED */
    let skuIds = (xsellSkuIds && xsellSkuIds.length ? xsellSkuIds.split(',') : []);
    if (skuId) {
        skuIds.push(skuId);
    }
    skuIds.forEach(value => {
        const prodItem = document.querySelector('.cs-prod-item[data-sku="' + value + '"]');
        if (prodItem) {
            prodItem.classList.add('cs-selected', 'in-the-cart');
            const addToCartBtn = prodItem.querySelector('.btn-listing-add-to-cart');
            const csStatus = prodItem.querySelector('.cs-status');
            if (addToCartBtn) {
                addToCartBtn.style.display = 'none';
            }
            if (csStatus) {
                csStatus.style.display = 'block';
            }
        }
    });

    let addedHandlerUrl = '/public/handler/append_added_to_cart.jsp';
    let addedParams = {
        productId: productId,
        skuId: skuId,
        xsellProductIds: xsellProductIds,
        xsellSkuIds: xsellSkuIds
    };

    fetch(`${addedHandlerUrl}?${cleanURLSearchParams(addedParams)}`, {
        headers: {
            "Accept": "application/json",
        },
        method: "GET",
        signal: AbortSignal.timeout(20000),
    }).then((response) => {
        if (response.ok) {
            return response.json();
        }
        return Promise.reject(response.statusText);
    }).then((data) => {
        document.querySelectorAll('.menu-cart .cart-count').forEach((el) => {
            el.textContent = data.cartCount;
        });
        if (data.overlayContent) {
            if (magnificPopup.instance.content) {
                magnificPopup.instance.content.querySelectorAll('.header-choose').forEach((el) => {
                    el.before(mp.fromMarkup(data.overlayContent));
                });
            }
        } else {
            magnificPopup.open({
                items:
                    [
                        {
                            src: mp.fromMarkup('<div class="basic-popup">Nothing was added to the cart.</div>')
                        }
                    ],
                type: 'inline'
            }, 0);
        }
    }).catch((error) => {
        console.error(error);
        magnificPopup.open({
            items:
                [
                    {
                        src: mp.fromMarkup('<div class="basic-popup">There was an error retrieving your cart.</div>')
                    }
                ],
            type: 'inline'
        }, 0);
    });
}

function initializeCrossSellAddToCart(options, context) {
    let overlay = (options && options.overlay) || false;
    let contextElement = context || document;

    if (overlay) {
        contextElement.querySelectorAll('.cs-prod-item .btn-listing-add-to-cart').forEach((el) => {
            el.addEventListener('click', (evt) => {
                clickBtnListingAddToCart(evt, loadAppendAddedToCartOverlay);
            });
        });
        contextElement.querySelectorAll('.cs-prod-item .cs-info a').forEach((el) => {
            el.addEventListener('click', function(e) {
                e.preventDefault();
                window.location.href = this.dataset['overlayUrl'];
                return false;
            });
        });
    } else {
        contextElement.querySelectorAll('.cs-prod-item .btn-listing-add-to-cart').forEach((el) => {
            el.addEventListener('click', clickBtnListingAddToCart);
        });
        contextElement.querySelectorAll('.cs-prod-item .cs-info a').forEach((el) => {
            el.addEventListener('click', function (e) {
                e.preventDefault();
                loadAddToCartOverlay(this.dataset['overlayUrl']);
                return false;
            });
        });
    }
}

function initializeAddToCart(options, context, evt) {
    let contextElement = context || document;
    let localProductId = options.productId || productId;
    let overlaySkuImages = options.overlaySkuImages;

    let buyBox = contextElement.querySelector('.buy-box');
    let attributes, purchaseOptions;
    if (buyBox && buyBox.dataset['multiSelectUi']) {
        console.debug('initialize multiSelect UI');
        let attributeString = buyBox.dataset['attributesJson'];
        let attributeData = JSON.parse(attributeString);
        attributes = attributeData.attributes;
        purchaseOptions = attributeData.purchaseOptions;
        let updateLinkSelectors = options.updateLinkSelectors || [];
        //console.log('parsed attribute data: ' + JSON.stringify(attributeData, null, 2));
        initSkuPicker({attributes: attributes,
            purchaseOptions: purchaseOptions,
            overlaySkuImages: overlaySkuImages,
            changeHeroImageLinks: true,
            updateBrowserUrl: options.pdp,
            reloadHash: options.reloadHash,
            productId: attributeData.productId,
            updateExtend: true,
            updatePartNumbers : options.pdp,
            updateStickyAddToCart: options.pdp,
            updateSaleRibbon: true,
            saleRibbonLocator: '.badge',
            displayDataCandyPoints: attributeData.displayDataCandyPoints,
            skuChangeListenerSelectors: [`.gl-toggle-${attributeData.productId}`],
            updateLinkSelectors,
            container: buyBox});
    }

    /* Keep it as a direct event for the xsell 2nd chance - what? */
    contextElement.querySelectorAll('.btn-add-to-cart').forEach((el) => {
        el.addEventListener('click', (evt) => {
            // declare a local lref variable for retrieving from data on the element.
            // There is a global lref var declared by the product page - we should get rid of it if possible,
            // but I don't want to commit to the testing right now
            let localLref = el.dataset['lref'];
            let input = getSelectedBuyOption(contextElement);
            let skuId = (input != null ? input.value : null);
            let skuQtyEl = document.querySelector('#skuQty');
            let skuQty = skuQtyEl ? parseInt(skuQtyEl.value) : 1;
            if (skuId != null) {
                let gaProduct;
                if (options.gaProduct) {
                    gaProduct = options.gaProduct;
                } else {
                    gaProduct ={
                        'id': gaProductId,
                        'name': gaProductName,
                        'category': gaCategoryName,
                        'brand': gaStudioName
                    };
                }

                gaProduct['variant'] = input.dataset['type'];
                gaProduct['price'] = input.dataset['price'];

                if (evalBooleanish(input.dataset['onSale'])) {
                    gaProduct.coupon = 'On Sale';
                }

                if (buyBox && buyBox.dataset['multiSelectUi'] && purchaseOptions) {
                    let thisPurchaseOption = purchaseOptions[skuId];
                    gaProduct['variant'] = thisPurchaseOption.displayName;
                    gaProduct['price'] = thisPurchaseOption.priceStatus.price;
                    if (thisPurchaseOption.onSale) {
                        gaProduct.coupon = 'On Sale';
                    }
                }
                gaProduct.quantity = skuQty;

                const addData = {
                    productId: localProductId,
                    skuId: skuId,
                    lref: localLref,
                    qty: skuQty
                };
                executeAddToCart(addData, gaProduct, function() {
                    loadAddedToCartOverlay(localProductId, skuId, options.pdp);
                }, '#extend-offer');
            } else {
                contextElement.querySelectorAll('.add-cart-warn.hide').forEach((el) => {
                    el.classList.remove('hide');
                });
            }
        });
    });

    document.querySelectorAll('.btn-wish').forEach((el) => {
        el.addEventListener('click', function(e) {
            let logoutEl = document.querySelector('.logout');
            if (logoutEl) {
                let input = getSelectedBuyOption(contextElement);
                let skuId = (input != null ? input.value : null);
                if (skuId != null) {
                    ajaxSubmit({
                        url: '/public/handler/auto/add_to_wishlist.jsp',
                        data: {
                            productId: localProductId,
                            skuId: skuId,
                            wishlistId: wishlistId,
                            lref: this.dataset['lref']
                        },
                        success: function(data) {
                            if (data.result == true) {
                                window.location.href = data.redirectUrl;
                            } else {
                                magnificPopup.open({
                                    items:
                                        [
                                            {
                                                src: mp.fromMarkup('<div class="basic-popup">' + data.message + '</div>')
                                            }
                                        ],
                                    type: 'inline'
                                }, 0);
                            }
                        },
                        error: function() {
                            magnificPopup.open({
                                items:
                                    [
                                        {
                                            src: mp.fromMarkup('<div class="basic-popup">There was an error adding your item to the wishlist.</div>')
                                        }
                                    ],
                                type: 'inline'
                            }, 0);
                        }
                    });
                } else {
                    contextElement.querySelectorAll('.add-cart-warn.hide').forEach((warning) => {
                        warning.classList.remove('hide');
                    });
                }
            } else {
                magnificPopup.open({
                    items:
                        [
                            {
                                src: mp.fromMarkup('<div class="basic-popup"><p><a class="cust-btn" href="/s/login">Sign In</a> or <a class="cust-btn" href="/s/register">Create Account</a> to add to your wishlist.</p></div>')
                            }
                        ],
                    type: 'inline'
                }, 0);
            }
        });
    });

    /* due to the nested popup, the new popup's callback functions failed. I need to close the buy popup first. */
    let popupEl = contextElement.querySelector('#interest-free-popup-anchor');
    if (popupEl) {
        let popupHref = popupEl.getAttribute('href');
        if (popupHref) {
            popupEl.addEventListener('click', function(clkEvt) {
                clkEvt.preventDefault();

                let popupHrefDiv = contextElement.querySelector(popupHref);
                if (context) {
                    /* Need to clone because we need to close the buy popup first */
                    popupHrefDiv = popupHrefDiv.cloneNode(true);
                    document.querySelectorAll('.skipLinks').forEach((el) => {
                        el.after(popupHrefDiv);
                    });
                }
                magnificPopup.close();

                magnificPopup.open({
                    items: [
                        {
                            type:'inline',
                            src: popupHrefDiv
                        }
                    ],
                    midClick: true,
                    callbacks: {
                        afterClose: function() {
                            if (context) {
                                popupHrefDiv.remove();
                            }
                            if (evt != null) {
                                /* re-open the buy popup */
                                evt.target?.click();
                            }
                        }
                    }
                });
            });
        }
    }
}

/* pdp - optional parameter indicating if this add to cart happened on the product page. Behavior is different in this case. */
function loadAddedToCartOverlay(productId, skuId, pPdp, xsellProductIds, xsellSkuIds) {
    const pdp = pPdp || false;

    /* This is a bit of hacking here. */
    /* Looking for in-the-cart in both the base page (pdp/plp) and the current overlay before addED */
    const skuIds = (xsellSkuIds && xsellSkuIds.length ? xsellSkuIds.split(',') : []);
    if (skuId) {
        skuIds.push(skuId); // Add skuId to the skuIds array
    }

    skuIds.forEach(function(value) {
        const prodItem = document.querySelector('.cs-prod-item[data-sku="' + value + '"]');
        if (prodItem) {
            prodItem.classList.add('cs-selected', 'in-the-cart');
            const addToCartBtn = prodItem.querySelector('.btn-listing-add-to-cart');
            if (addToCartBtn) {
                addToCartBtn.style.display = 'none';
            }
            const statusElement = prodItem.querySelector('.cs-status');
            if (statusElement) {
                statusElement.style.display = 'block';
            }
        }
    });

    const hideCrossSellOptionsParam = (document.querySelectorAll('.cs-prod-item.in-the-cart')?.length > 0) ? '&hideCrossSellOptions=true' : '';

    let addedHandlerUrl = '/public/handler/added_to_cart.jsp?pdp=' + pdp + hideCrossSellOptionsParam;
    let addedParams = {
        productId: productId,
        skuId: skuId,
        xsellProductIds: xsellProductIds,
        xsellSkuIds: xsellSkuIds
    };
    fetch(`${addedHandlerUrl}&${cleanURLSearchParams(addedParams)}`, {
        headers: {
            "Accept": "application/json",
        },
        method: "GET",
        signal: AbortSignal.timeout(20000),
    }).then((response) => {
        if (response.ok) {
            return response.json();
        }
        return Promise.reject(response.statusText);
    }).then((data) => {
        document.querySelectorAll('.menu-cart .cart-count').forEach(function(el) {
            el.innerHTML = data.cartCount;
        });

        if (data.overlayContent) {
            const contentHandlerFunc = function () {
                document.getElementById('continue-button')?.addEventListener('click', function (evt) {
                    magnificPopup.close();
                });
                document.getElementById('view-cart-button')?.addEventListener('click', function (evt) {
                    window.location.href = this.dataset.cartUrl;
                });
                if (pdp) {
                    document.querySelectorAll('.added-cover').forEach(function (el) {
                        el.addEventListener('click', function (evt) {
                            magnificPopup.close();
                        });
                    });
                }
                if (magnificPopup.instance.content) {
                    magnificPopup.instance.content.querySelectorAll('.owl-showcase').forEach(function (el, i) {
                        tnsShowcaseConfig(i, el);
                    });
                    if (magnificPopup.instance.content.querySelector('.cs-prod-item .btn-listing-add-to-cart')) {
                        initializeCrossSellAddToCart({overlay: true}, magnificPopup.instance.content);
                    }
                }
            };

            /* Close before open. Reason being, sometimes we already have an open popup and calling open
             * in that scenario doesn't update the callbacks so everything fails. Closing when no popup
             * is open just does a noop and the UI doesn't seem to annoyingly flicker or anything like that
             * when a popup is open. Of course we want to have loaded the new data first.
             */
            magnificPopup.close();
            magnificPopup.open({
                items: {
                    src: mp.fromMarkup(data.overlayContent),
                    type: 'inline'
                },
                callbacks: {
                    open: contentHandlerFunc,
                    close: function () {
                        /* refresh only when cart page. afterClose has no more access to #view-cart-button */
                        const viewCartButton = document.querySelector('#view-cart-button');
                        if (viewCartButton && viewCartButton.dataset.cartUrl === window.location.pathname) {
                            window.location.href = viewCartButton.dataset.cartUrl;
                        }
                    }
                }
            });
        } else {
            magnificPopup.open({
                items:
                    [
                        {
                            src: mp.fromMarkup('<div class="basic-popup">Nothing was added to the cart.</div>')
                        }
                    ],
                type: 'inline'
            }, 0);
        }
    }).catch((error) => {
        console.error(error);
        magnificPopup.open({
            items:
                [
                    {
                        src: mp.fromMarkup('<div class="basic-popup">There was an error retrieving your cart.</div>')
                    }
                ],
            type: 'inline'
        }, 0);
    });
}

function getSelectedBuyOption(context) {
    let contextElement = context || document;
    return contextElement.querySelector('input[type="radio"][name="skuId"]:checked');
}

/*  primaryProductId - ID of product to find and link to in the cart already
	primarySkuId - ID of sku to find and link to in the cart already
 */
function addExtendPlan(primaryProductId, primarySkuId, lref, extendComponent = '#extend-offer', successCallback) {
    extendProxy(function() {
        const component = Extend.buttons.instance(extendComponent);
        if (component) {
            const plan = component.getPlanSelection();
            if (plan) {
                addExtendPlanFromPlan(primaryProductId, primarySkuId, lref, plan, successCallback);
            } else {
                if (extendAddModal) {
                    Extend.modal.open({
                        referenceId: primaryProductId + '-' + primarySkuId,
                        onClose: function(plan, product) {
                            if (plan && product) {
                                addExtendPlanFromPlan(primaryProductId, primarySkuId, lref, plan, successCallback);
                            }
                        }
                    });
                }
            }
        }
    });
}

function addExtendPlanFromPlan(primaryProductId, primarySkuId, lref, plan, successCallback) {
    if (plan) {
        const extendSku = extendSkus[plan.term];
        const gaProduct = {
            'id': 'extend',
            'name': 'Extend Warranty',
            'category': 'Warranty',
            'brand': 'Extend',
            'variant': extendSku.description,
            'price': plan.price / 100
        };

        const addData = {
            productId: 'extend',
            skuId: extendSku.skuId,
            lref: lref,
            primaryProductId: primaryProductId,
            primarySkuId: primarySkuId,
            externalJson: JSON.stringify(plan)
        };

        const nested = typeof successCallback === 'undefined';
        executeAddToCart(addData, gaProduct, successCallback, undefined, nested);
    }
}


function executeAddToCart(addData, gaProduct, successCallback, extendComponent, nested) {
    ajaxSubmit({
        url: '/public/handler/add_to_cart.jsp',
        data: addData,
        success: function(data) {
            if (data.result === true) {
                if (gaProduct) {
                    addToCartGA4(gaProduct);
                }

                if (extendComponent) {
                    addExtendPlan(addData.productId, addData.skuId, addData.lref, extendComponent);
                }

                if (successCallback) {
                    successCallback();
                }
            } else {
                magnificPopup.open({
                    items:
                        [
                            {
                                src: mp.fromMarkup('<div class="basic-popup">' + data.message + '</div>')
                            }
                        ],
                    type: 'inline'
                }, 0);
            }
        },
        error: function() {
            magnificPopup.open({
                items:
                    [
                        {
                            src: mp.fromMarkup('<div class="basic-popup">There was an error adding your item to the cart.</div>')
                        }
                    ],
                type: 'inline'
            }, 0);
        }
    }, nested);
}

/*
    This assigns a couple of functions that are used directly in data to the window object.
    We really ought to clean that up somehow.
 */
function initializeAddToCartGlobals() {
    window.executeAddToCart = executeAddToCart;
    window.loadAddedToCartOverlay = loadAddedToCartOverlay;
}

window.recaptchaLoaded= false;
window.recaptchaSuccessQueue = [];

function whenRecaptcha(f) {
    console.debug('whenRecaptcha');
    if (typeof window.recaptchaOnLoad !== 'function') {
        console.debug('initialize recaptchaOnLoad');
        window.recaptchaOnLoad = function() {
            window.recaptchaLoaded=true;
            console.debug('recaptchaOnLoad');
            window.recaptchaSuccessQueue.forEach((element) => {element();});
            window.recaptchaSuccessQueue.length = 0;
        };
    }
    if (!recaptchaLoaded) {
        window.recaptchaSuccessQueue.push(f);
    } else {
        f();
    }

    loadFeature({
        script: 'https://www.google.com/recaptcha/api.js?onload=recaptchaOnLoad&render=explicit'
    });
}

function openWhenRecaptcha(callback) {
    function open() {
        console.debug('openWhenRecaptcha open');
        magnificPopup.close();
        magnificPopup.open({
            items: {
                src: '/public/handler/recaptcha_popup.jsp',
                type: 'ajax'
            },
            callbacks: {
                ajaxContentAdded: function() {
                    console.debug('openWhenRecaptcha ajaxContentAdded');
                    const siteKey = document.querySelector('#the-captcha')?.dataset.sitekey;
                    grecaptcha.render('the-captcha', {
                        sitekey: siteKey,
                        callback: callback
                    });
                }
            }
        });
    }
    console.debug('openWhenRecaptcha');
    whenRecaptcha(open);
}

function initializeLogout() {
    const logoutHandler = function (e) {
        handleLogout(e, logoutURL);
    };

    document.querySelectorAll('.logout').forEach(function(element) {
        element.addEventListener('click', logoutHandler);
    });

    let observer = new MutationObserver(function(mutations) {
        let found = false;
        for (let i = 0; i < mutations.length; i++) {
            if (mutations[i].addedNodes.length > 0) {
                found = true;
                break;
            }
        }
        if (found) {
            document.querySelectorAll('.logout').forEach(function(element) {
                element.removeEventListener('click', logoutHandler);
                element.addEventListener('click', logoutHandler);
            });
        }
    });
    observer.observe(document, { childList: true, subtree: true });
}

function handleLogout(e, redirectURL, nested) {
    e.preventDefault();

    ajaxSubmit({
        url: '/public/handler/logout_handler.jsp',
        success: function(data) {
            if (data.result === false) {
                magnificPopup.open({
                    items:
                        [
                            {
                                src: mp.fromMarkup('<div class="basic-popup">' + data.message + '</div>')
                            }
                        ],
                    type: 'inline'
                }, 0);
            } else {
                if (redirectURL && redirectURL.length) {
                    window.location.href = document.createElement('div').appendChild(document.createTextNode(redirectURL)).parentNode.textContent;
                } else {
                    location.reload();
                }
            }
        },
        error: function(jqXHR, textStatus, errorThrown) {
            magnificPopup.open({
                items:
                    [
                        {
                            src: mp.fromMarkup('<div class="basic-popup">There was an error signing you out. Please try again.</div>')
                        }
                    ],
                type: 'inline'
            }, 0);
        }
    }, nested);
}

function initializeEmailSignup(context) {
    const thisContext = context || document;
    const signUpForms = thisContext.querySelectorAll('.email-sign-up-form');
    signUpForms.forEach(function(signUpForm) {
        signUpForm.addEventListener('submit', function(e) {
            e.preventDefault();
            const callback = function (captchaResponse) {
                submitEmailSignup(captchaResponse, signUpForm);
            };
            openWhenRecaptcha(callback);
        });
    });

    const emailInputs = thisContext.querySelectorAll('.email-sign-up-form .email-sign-up-input');
    emailInputs.forEach(function(input) {
        input.addEventListener('keyup', handleInputChange);
        input.addEventListener('keypress', handleInputChange);
    });

    function handleInputChange(e) {
        const input = e.currentTarget;
        const ageVerifyEmail = input?.closest('.email-sign-up-form')?.querySelector('.age-verify-email');

        if (ageVerifyEmail !== null && input.value) {
            ageVerifyEmail.style.display = 'block';
        }
    }

    openEmailSignupPopup();
}

/*
function initializeEmotiveSignupLink() {
    document.querySelectorAll('.emotive-popup-launch').forEach((el) => {
        mp.initializePopup(el, {
            type: 'ajax',
            midClick: true,
            callbacks: {
                ajaxContentAdded: function() {
                    initializeEmotiveSignup(magnificPopup.instance.content);
                }
            }
        })
    });
}
*/

function initializeEmotiveSignup(context) {
    const thisContext = context || document;
    const signUpForms = thisContext.querySelectorAll('.emotive-sign-up-form');
    signUpForms.forEach((signUpForm) => {
        signUpForm.addEventListener('submit', (e) => {
            e.preventDefault();
            const emotivePhoneNumberInput = signUpForm.querySelector('input[name="emotivePhoneNumber"]');
            const emotivePhoneNumber = emotivePhoneNumberInput?.value ?? '';
            const emotiveTextOptIn = !!signUpForm.querySelector('input[name="emotiveTextOptIn"]')?.checked;
            const phoneValidationSelector = emotivePhoneNumberInput?.dataset.phoneValidationSelector ?? '';

            if (emotiveTextOptIn && phoneValidationSelector) {
                const emotivePhoneNumberNumberOnly = emotivePhoneNumber.match(/\d+/g);
                if (emotivePhoneNumberNumberOnly && emotivePhoneNumberNumberOnly.join('').length === 10) {
                    signUpForm.querySelector(phoneValidationSelector).classList.remove('invalid-phone-number');
                } else {
                    signUpForm.querySelector(phoneValidationSelector).classList.add('invalid-phone-number');
                    return;
                }
            }

            const callback = (captchaResponse) => {
                submitEmotiveSignup(captchaResponse, signUpForm);
            };
            openWhenRecaptcha(callback);
        });
    });
}

function submitEmotiveSignup(captchaResponse, signUpForm) {
    ajaxSubmit({
        url: '/public/handler/emotive_signup.jsp',
        data: {
            captchaResponse: captchaResponse,
            emotivePhoneNumber: signUpForm.querySelector('input[name="emotivePhoneNumber"]')?.value ?? '',
            emotiveTextOptIn: !!signUpForm.querySelector('input[name="emotiveTextOptIn"]')?.checked
        },
        success: function(data) {
            magnificPopup.close();
            magnificPopup.open({
                items:
                    [
                        {
                            src: mp.fromMarkup('<div class="basic-popup emotive-popup-msg">' + data.message + '</div>')
                        }
                    ],
                type: 'inline'
            }, 0);
        },
        error: function() {
            magnificPopup.close();
            magnificPopup.open({
                items:
                    [
                        {
                            src: mp.fromMarkup('<div class="basic-popup emotive--popup-msg">There was an error registering your phone number. Please try again later.</div>')
                        }
                    ],
                type: 'inline'
            }, 0);
        }
    });
}

function createPopupConfig(signUpForm) {
    let defaultConfig = {
        cookieName: 'emailSignupPopupOpened',
        showMax: 2,
        cookieExpireDays: 1000,
        popupDelaySecs: 1,
        popupForceOpen: false
    };

    let customConfig = {};

    let cookieName = signUpForm.dataset.cookieName;
    if (typeof cookieName !== 'undefined' && cookieName !== null && cookieName !== 'null') {
        customConfig['cookieName'] = cookieName;
    }

    let showMax = signUpForm.dataset.showMax;
    if (typeof showMax !== 'undefined' && showMax !== null && showMax !== 'null') {
        customConfig['showMax'] = Number(showMax);
    }

    let cookieExpireDays = signUpForm.dataset.cookieExpireDays;
    if (typeof cookieExpireDays !== 'undefined' && cookieExpireDays !== null && cookieExpireDays !== 'null') {
        customConfig['cookieExpireDays'] = Number(cookieExpireDays);
    }

    let popupDelaySecs = signUpForm.dataset.popupDelaySecs;
    if (typeof popupDelaySecs !== 'undefined' && popupDelaySecs !== null && popupDelaySecs !== 'null') {
        customConfig['popupDelaySecs'] = Number(popupDelaySecs);
    }

    let popupForceOpen = signUpForm.dataset.popupForceOpen;
    if (typeof popupForceOpen !== 'undefined' && popupForceOpen !== null && popupForceOpen !== 'null') {
        customConfig['popupForceOpen'] = (popupForceOpen === 'true');
    }

    return { ...defaultConfig, ...customConfig };
}

function openEmailSignupPopup() {
    const thisContainer = document.querySelector('.email-popup-container');
    const thisFormDiv = document.querySelector('.email-sign-up');
    const thisReplaceWith = document.querySelector('.email-popup-container #replaceSignup');

    if (thisContainer !== null && thisFormDiv !== null && thisReplaceWith !== null) {
        const config = createPopupConfig(thisReplaceWith);

        let status = window.localStorage.getItem(config.cookieName);
        if (status === null) {
            status = {
                count: 0,
                expiration: Date.now() + config.cookieExpireDays * 24 * 60 * 60 * 1000,
            };
        } else {
            status = JSON.parse(status);
            if (status.expiration < Date.now()) {
                status = {
                    count: 0,
                    expiration: Date.now() + config.cookieExpireDays * 24 * 60 * 60 * 1000,
                };
            }
        }

        if (status.count < config.showMax && typeof rGLPopups !== 'undefined') {
            rGLPopups.add({
                popupPriority: 2,
                executePopup: function () {
                    /* Clone the form div. */
                    const thatFormDiv = thisFormDiv.cloneNode(true);
                    thatFormDiv.querySelectorAll('[id]').forEach(function(element) {
                        const id = element.getAttribute('id');
                        const newId = id + Date.now();
                        thatFormDiv.querySelectorAll('[for="' + id + '"]').forEach(function(label) {
                            label.setAttribute('for', newId);
                        });
                        element.setAttribute('id', newId);
                    });

                    const realFormDiv = thatFormDiv.querySelector('.email-sign-up-form');
                    Object.entries(config).forEach(function([key, value]) {
                        realFormDiv.dataset[key] = value;
                    });

                    initializeEmailSignup(thatFormDiv);

                    thisReplaceWith.parentNode.replaceChild(thatFormDiv, thisReplaceWith);

                    let params = {
                        items: {
                            src: thisContainer,
                            type: 'inline'
                        },
                        callbacks: {
                            open: function () {
                                status['count'] = status.count + 1;
                                window.localStorage.setItem(config.cookieName, JSON.stringify(status));
                            }
                        }
                    };

                    setTimeout(function() {
                        if (!magnificPopup.instance.isOpen || config.popupForceOpen) {
                            magnificPopup.open(params);
                        }
                    }, (config.popupDelaySecs * 1000));

                }
            });
        }
    }
}


function submitEmailSignup(captchaResponse, signUpForm) {
    const email = signUpForm.querySelector('.email-sign-up-input').value;
    const ageVerification = signUpForm.querySelector('[name="ageVerification"]').checked;
    ajaxSubmit({
        url: '/public/handler/email_signup.jsp',
        data: {
            email: email,
            ageVerification: ageVerification,
            captchaResponse: captchaResponse
        },
        success: function(data) {
            let config = createPopupConfig(signUpForm);

            /* They signed up, so don't bug them again for at least a year. */
            window.localStorage.setItem(config.cookieName, JSON.stringify({
                count: config.showMax,
                expiration: Date.now() + 365 * 24 * 60 * 60 * 1000,
            }));

            magnificPopup.close();
            magnificPopup.open({
                items:
                    [
                        {
                            src: mp.fromMarkup('<div class="basic-popup email-popup-msg">' + data.message + '</div>')
                        }
                    ],
                type: 'inline',
                callbacks: {
                    open: function() {
                        initializeEmotiveSignup(magnificPopup.instance.content);
                    }
                }
            }, 0);
        },
        error: function() {
            magnificPopup.close();
            magnificPopup.open({
                items:
                    [
                        {
                            src: mp.fromMarkup('<div class="basic-popup email-popup-msg">There was an error registering your email address. Please try again later.</div>')
                        }
                    ],
                type: 'inline'
            }, 0);
        }
    });
}

function initializeEmailSignupCode() {
    initializeEmailSignup();
    /*initializeEmotiveSignupLink();
    * Nothing is using this and Chris says it can be removed for now */
}

function initializeDocumentAcceptance() {
    if (typeof documentAcceptanceRequired === 'string' && documentAcceptanceRequired === 'true') {
        fetch("/document/acceptance", {
            headers: {
                "Accept": "application/json",
            },
            method: "GET",
            signal: AbortSignal.timeout(20000),
        }).then((response) => {
            if (response.ok) {
                return response.json();
            }
            return Promise.reject(response.statusText);
        }).then((data) => {
            if (data.popupContent) {
                magnificPopup.open({
                    items:
                        [
                            {
                                src: mp.fromMarkup(data.popupContent)
                            }
                        ],
                    type: 'inline',
                    modal: true,
                    callbacks: {
                        open: function() {
                            window.addEventListener('message', function(evt) {
                                // set up a message listener so the privacy policy page which can be opened in a new tab
                                // can notify us that the user accepted in the other tab
                                const data = evt.data;
                                /* validate the origin. Second case is for Chrome */
                                const origin = evt.origin;
                                if (origin !== (window.location.origin)) {
                                    return;
                                }
                                if ('privacy-policy-accepted' === data) {
                                    magnificPopup.close();
                                }
                            });

                            document.getElementById('doc-accept')?.addEventListener('click', function(evt) {
                                evt.preventDefault();

                                ajaxSubmit({
                                    url: '/public/handler/auto/log_doc_acceptance.jsp',
                                    data: {
                                        docTypes: 'privacy-policy'
                                    },
                                    success: function(data) {
                                        magnificPopup.close();
                                        if (!data.result) {
                                            magnificPopup.open({
                                                items: [
                                                    {
                                                        src: mp.fromMarkup('<div class="basic-popup">There was an error saving the data. Please try again.</div>')
                                                    }
                                                ],
                                                type: 'inline'
                                            });
                                        }
                                    },
                                    error: ajaxSubmitGenericError
                                });
                            });
                        }
                    }
                }, 0);
            } else {
                console.log("error! no popupContent");
            }
        }).catch((error) => {
            console.error(error);
        });
    }
}

function initializeHeaderScrolled() {
    document.addEventListener('scroll', function () {
        const header = document.querySelector('.res-top');
        if (header) {
            if (window.scrollY > 0) {
                header.classList.add('scrolled');
            } else {
                header.classList.remove('scrolled');
            }
        }
    }, { passive: true });
}

const days180 = 180 * 24 * 60 * 60 * 1000;

function initializeAgeCheck() {
    let ageCheckDiv = document.querySelector('.emotive-age-gate');
    if (ageCheckDiv) {
        let useSessionStorage = ageCheckDiv.dataset.ageCheckSession === 'true';
        let theStorage = useSessionStorage ? window.sessionStorage : window.localStorage;
        let ageCheckYes = ageCheckDiv.querySelector('#yes');
        if (ageCheckYes) {
            ageCheckYes.addEventListener('click', function (e) {
                e.preventDefault();
                theStorage.setItem('age_check', JSON.stringify({
                    accepted: true,
                    expiration: Date.now() + days180,
                }));
                magnificPopup.close();
            });
        }

        ageCheckDiv.querySelectorAll('.emotive-age-gate #no').forEach((el) => {
            el.addEventListener('click', function (e) {
                e.preventDefault();
                location.href = 'https://www.google.com/';
            });
        });

        let status = theStorage.getItem('age_check');
        if (status) {
            status = JSON.parse(status);
            if (!status.expiration || status.expiration < Date.now()) {
                status = null;
            }
        }

        if (!status) {
            if (typeof rGLPopups !== 'undefined') {
                rGLPopups.add({
                    popupPriority: 10000,
                    executePopup: function () {
                        magnificPopup.close();
                        magnificPopup.open({
                            items: {
                                src: '.emotive-age-gate',
                                type: 'inline'
                            },
                            modal: true,
                            mainClass: 'obscure-page'
                        }, 0);
                    }
                });
            } else {
                console.log('warning - popups object not found');
            }
        }
    }
}

function initializeMegaMenus() {
    const fullPageMega = document.querySelector('.full-page-mega');

    function hideSubNavWrapper(subNavWrapper) {
        if (fullPageMega) {
            fullPageMega.classList.remove('mega-header-bg');
        }

        if (subNavWrapper) {
            subNavWrapper.dataset.showing = 'false';
            //subNavWrapper.dataset.justShown = 'false';
            subNavWrapper.style.visibility = 'hidden';
        }
    }

    function showSubNavWrapper(subNavWrapper) {
        if (fullPageMega) {
            fullPageMega.classList.add('mega-header-bg');
        }

        if (subNavWrapper) {
            subNavWrapper.dataset.showing = 'true';
            //subNavWrapper.dataset.justShown = 'true';
            subNavWrapper.style.visibility = 'visible';
        }
    }

    function hideAllMenus() {
        document.querySelectorAll('.sub-nav-wrapper[data-showing="true"]').forEach(function(wrapper) {
            hideSubNavWrapper(wrapper);
        });
    }

    function showSubNav(subNav, toggle) {
        if (subNav) {
            let el = document.querySelector('.sub-nav-wrapper[data-sub-nav="' + subNav + '"]');
            if (el) {
                /* Must calculate before hiding. */
                let test = el.dataset.showing === 'true';
                hideAllMenus();
                if (!test || !toggle) {
                    showSubNavWrapper(el);
                }
            }
        } else {
            hideAllMenus();
        }
    }

    function hideShowingSubNav(subNav) {
        if (subNav) {
            let el = document.querySelector('.sub-nav-wrapper[data-sub-nav="' + subNav + '"]');
            if (el) {
                // const justShown = el.dataset.justShown === 'true';
                // if (justShown) {
                //     el.dataset.justShown = 'false';
                // } else {
                    const showing = el.dataset.showing === 'true';
                    if (showing) {
                        hideAllMenus();
                    }
                // }
            }
        }
    }

    document.querySelectorAll('.menu-item-parent').forEach(function(item) {
        const toggle = item.classList.contains('sub-nav-toggle');

        item.addEventListener('mouseenter', function(e) {
            let subNav = this.dataset.subNav;
            showSubNav(subNav, toggle);
        });

        /* On Apple touch screens, mouseenter will only be sent on the first tap until another interactive element is
         tapped. So, we have to detect click to close the menu when it should be toggled. This may be Android also, just
         found on iPads.
          */
        if (toggle) {
            item.addEventListener('click', function(e) {
                let subNav = this.dataset.subNav;
                hideShowingSubNav(subNav);
            });
        }
    });

    document.querySelectorAll('.sub-nav-wrapper').forEach(function(wrapper) {
        wrapper.addEventListener('mouseleave', function() {
            hideSubNavWrapper(this);
        });
    });

    document.querySelectorAll('.exit-mega').forEach((el) => {
        el.addEventListener('mouseenter', function() {
            hideAllMenus();
        });
    });
}

function initializeManageCookies() {
    document.querySelectorAll('a[data-manage-cookies]').forEach((link) => {
        link.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();
            CookieScript.instance.show();
        });
    });
}

function initializeCommon() {
    initializeMagnific();
    initializeOffCanvasMenu();
    initializeMegaMenus();
    initializeCookies();
    initializeAddToCartGlobals();
    initializeLogout();
    initializeAnnoyingPopups();
    initializeAgeCheck();
    initializeEmailSignupCode();
    initializeDocumentAcceptance();
    initializeHeaderScrolled();
    initializeManageCookies();
}

/*
    Function that initializes showcase dependencies
 */
function initializeShowcases() {
    initializeSliders();
    initializeListingAddToCart();
}

function initializePagination() {
    document.querySelectorAll('.pagination .pageInput').forEach(function(element) {
        element.addEventListener('focusin', function(e) {
            this.removeAttribute('placeholder');
        });
    });

    const outOfFocus = function(e) {
        this.setAttribute('placeholder', this.dataset.pageNumber);
        this.value = '';
    };
    document.querySelectorAll('.pagination .pageInput').forEach(function(element) {
        element.addEventListener('focusout', outOfFocus);
    });

    document.querySelectorAll('.pagination .paginationForm').forEach(function(form) {
        form.addEventListener('submit', function(e) {
            e.preventDefault();
            const numPages = this.dataset.numPages;
            const pageInput = this.querySelector('.pageInput');
            const requestedPage = parseInt(pageInput.value);
            if (!requestedPage || requestedPage < 1 || requestedPage > numPages) {
                pageInput.value = '';
            } else {
                pageInput.removeEventListener('focusout', outOfFocus);
                let url = this.getAttribute('action');
                if (url.indexOf('?') !== -1) {
                    url += '&';
                } else {
                    url += '?';
                }
                url += 'page=' + requestedPage;
                window.location.href = url;
            }
        });
    });
}

let api = {};
const nonFacetParams = ['st', 'q', 'sort', 'sale'];

/*
This sets up the initial value of history.state when the page is first requested. It ensures this also has a value
whether the user applies any facets or does anything that triggers us to do a pushState.
History contains these values:
- searchUrl - this is the value that the browser URL is updated to, and if refreshed or bookmarked should be the same content
- fragUrl - this is a modified version of searchUrl, which is used to pull in JSON format the new search results and fragments
            updating the content in the page
- appliedFacetIds - this is a Map the keys of which are IDs of applied (checked) facet inputs, and values are true.
 */
function setInitialState() {
    const searchUrl = window.location.pathname + window.location.search, fragUrl = createSearchFragmentsUrl(searchUrl);
    const direct = !(window.location.pathname.endsWith('/search'));
    let stateObj = {
        fragUrl,
        searchUrl,
        direct,
        sort: getUrlParameter('sort') || '',
        appliedFacetIds: createAppliedFacetIds()
    };
    history.replaceState(stateObj, '');
}

function initFilterBar() {
    document.querySelectorAll('button[id^="show-"]').forEach((elem) => {
            elem.addEventListener(
                "click", () => {
                    const panelId = elem.id.substring(5);
                    let p = document.querySelector('#' + panelId);
                    if (panelId === 'all-filters') {
                        p = document.querySelector('#all-filters .mm-panel');
                    }
                    document.querySelectorAll('#all-filters .mm-panel').forEach((panel) => {
                        api.closePanel(panel);
                    });
                    let innerSelected = document.querySelector('#' + panelId + ' input:checked');
                    if (innerSelected) {
                        let selectedParent = innerSelected.parentElement;
                        while (p !== selectedParent) {
                            if (selectedParent.classList.contains('mm-panel')) {
                                api.openPanel(selectedParent);
                            }
                            selectedParent = selectedParent.parentElement;
                        }
                    }
                    api.openPanel(p);
                    api.open();
                    p.parentElement.scrollIntoView();
                }
            );
        }
    );
    let saleBox = document.getElementById('on-sale-checkbox');
    if (saleBox) {
        saleBox.addEventListener('change', (evt) => {
            let saleForm = document.getElementById('on-sale-toggle-form');
            if (saleForm) {
                saleForm.submit();
            } else {
                console.log('no sale form!');
            }
        });
    }
    let saleForm = document.getElementById('on-sale-toggle-form');
    if (saleForm) {
        let formAction = saleForm.getAttribute("action");
        saleForm.setAttribute("action", stripJsonDecorator(formAction));
    }
    let sortForm = document.getElementById('sortForm');
    if (sortForm) {
        let formAction = sortForm.getAttribute("action"),
            strippedAction = stripJsonDecorator(formAction);
        sortForm.setAttribute("action", strippedAction);
    } else {
        console.log('no sort form!');
    }
}

/*
    When the fragments are rendered by the JSON jsp, some pick up the current URL with the JSON decorator
    and need to be adjusted.
 */
function fixPagination() {
    let pagiDiv = document.querySelector('.pagination');
    if (pagiDiv) {
        pagiDiv.querySelectorAll('a').forEach((elem) => {
            let href = elem.getAttribute('href');
            elem.setAttribute('href', stripJsonDecorator(href));
        });
        let pageForm = pagiDiv.querySelector('.paginationForm');
        if (pageForm) {
            let action = pageForm.getAttribute('action');
            pageForm.setAttribute('action', stripJsonDecorator(action));
        }
    }
}

function stripJsonDecorator(url) {
    if (!url) return url;
    let jsonLoc = url.indexOf('/json');
    if (jsonLoc != -1) {
        let qLoc = url.indexOf('?');
        if (qLoc != -1) {
            url = url.substring(0, jsonLoc) + url.substring(qLoc);
        } else {
            url = url.substring(0, jsonLoc);
        }
    }
    return url;
}

/*
    This creates a URL to apply some operation by the user. It is called from change listeners (currently on facet
    inputs, and the "clear all" button). In the case where a single facet is applied by a user and a direct URL
    is found for that (for example, a category URL) it may return that direct URL and also direct=true in the
    result object. In that case, calling code should send the browser immediately to the direct URL with a full
    page reload.
 */
function createSearchUrl(skipNonNavPathFacets) {
    // this value will apparently always be /search or /s/search
    let url = window.soldb.searchBaseUrl;
    const searchType = window.soldb.explicitSearchType || '',
        query = getUrlParameter('q') || '',
        sale = getUrlParameter('sale') || '';
    /* search type needs to be explicit. others now come out of preserveParams */
    if (searchType.length > 0) {
        url = addQueryParam(url, 'st', searchType);
    }

    let sortSelect = document.querySelector('#sortForm select');
    if (window.soldb && window.soldb.searchResults && window.soldb.searchResults.options.preserveParameters) {
        for (let i = 0; i < nonFacetParams.length; i++) {
            let key = nonFacetParams[i];
            if (key !== 'st') {
                let paramValues = window.soldb.searchResults.options.preserveParameters[key];
                if (key === 'sort') {
                    // here we check for replacement of an existing sort param. below this, there is another check
                    // for when there was not one but a non-default sort was just selected
                    if (paramValues && paramValues.length) {
                        // see if the value in the select is different from the query string - for change handling
                        let currentSort = paramValues[0];
                        if (sortSelect && sortSelect.value) {
                            currentSort = sortSelect.value;
                        }
                        url = addQueryParam(url, key, currentSort);
                    }
                } else {
                    // value is an array
                    if (paramValues) {
                        for (let i = 0; i < paramValues.length; i++) {
                            url = addQueryParam(url, key, paramValues[i]);
                        }
                    }
                }
            }
        }
    }
    // this is a check for if the sort was just changed and there was previously no param
    if (sortSelect && sortSelect.value && sortSelect.selectedIndex !== 0 && !urlContainsParamValue(url, 'sort', sortSelect.value)) {
        url = addQueryParam(url, 'sort', sortSelect.value);
    }

    let checkedFacetCount = 0;
    let directUrl = null;

    let addedFacetParams = {};
    document.querySelectorAll('.filter-checkbox:checked').forEach((elem) => {
        const paramName = elem.getAttribute("name");
        const paramValue = elem.getAttribute("value");
        if (paramName === 'navPath' || !skipNonNavPathFacets) {
            if (!urlContainsParamValue(url, paramName, paramValue)) {
                url = addQueryParam(url, paramName, paramValue);
                addedFacetParams[paramName] = true;
            }
            checkedFacetCount++;
            directUrl = elem.dataset.directUrl;
        }
    });

    if (window.soldb && window.soldb.searchResults && window.soldb.searchResults.options && window.soldb.searchResults.options.appliedFacets) {
        let appliedFacets = window.soldb.searchResults.options.appliedFacets;
        for (let facetKey in appliedFacets) {
            let facet = appliedFacets[facetKey];
            let paramName = facet.param;
            // Sometimes, for some reason, an applied navPath facet does not have an initially checked input present in the UI
            // but is also not a "hidden" category facet. This happens
            // with sex toys 1__st__ for example. We need to
            // check for allowMultiselect on the existing applied facets and allow a checked input (found above) to always trump.
            if (!facet.allowMultiselect && addedFacetParams[paramName]) {
                continue;
            }
            if (facet.buckets) {
                for (let i = 0; i < facet.buckets.length; i++) {
                    let bucketValue = facet.buckets[i].value;
                    // Need to check if a value was just unchecked
                    let inputId = `${paramName}-${bucketValue}`,
                        bucketInput = document.getElementById(inputId);
                    let justUnchecked = !!bucketInput ? !bucketInput.checked : false;
                    if (!justUnchecked && !urlContainsParamValue(url, paramName, bucketValue)) {
                        url = addQueryParam(url, paramName, bucketValue);
                        addedFacetParams[paramName] = true;
                    }
                }
            }
        }
    }



    if (checkedFacetCount === 1 && directUrl && query.length === 0) {
        if (sale.length > 0) {
            directUrl = addQueryParam(directUrl, 'sale', sale);
        }
        return {
            searchUrl: directUrl,
            direct: true
        }
    } else {
        return {
            searchUrl: url,
            direct: false
        };
    }
}

function createSearchFragmentsUrl(searchUrl) {
    if (!searchUrl) {
        console.log('warning - searchUrl null to createSearchFragmentsUrl - should not happen');
        return searchUrl;
    }
    const fragmentsDecorator = '/json/fragments';
    if (searchUrl) {
        if (searchUrl.indexOf('?') !== -1) {
            return searchUrl.replace('?', fragmentsDecorator + '?');
        } else {
            return searchUrl + fragmentsDecorator;
        }
    }
}

function runInlineScripts(elem) {
    if (elem) {
        let scripts = elem.querySelectorAll('script');
        scripts.forEach((scriptEl) => {
            const newScript = document.createElement("script");
            if (scriptEl.hasAttributes()) {
                for (const attr of scriptEl.attributes) {
                    newScript.setAttribute(attr.name, attr.value);
                }
            }
            newScript.appendChild(document.createTextNode(scriptEl.innerHTML));
            document.body.append(newScript);
        });
    } else {
        console.log('null elem passed to runInlineScripts!');
    }
}

/*
Takes in a state object and updates the document and browser URL accordingly.
This is done by requesting the stateObj.fragUrl to load the new search results and page contents.
This can be called from a facet click listener, in which pushState will be true and we will call history.pushState
to add the new state to the history chain. This can also happen in response to a popState event, in which case
pushState will be false and the old stateObj will be used to recreate the prior page state.
 */
function updateDocumentForFilters(stateObj, pushState) {
    if (!stateObj) {
        console.log('updateDocumentForFilters was passed null state obj! this is not expected');
        return;
    }
    if (JsLoadingOverlay) {
        JsLoadingOverlay.show({
            "spinnerIcon": "ball-spin-clockwise-fade",
            "lockScroll": true,
        });
    }
    fetch(stateObj.fragUrl, {
        mode: 'same-origin',
        credentials: 'same-origin'
    }).then((response) => {
        if (!response.ok) {
            if (JsLoadingOverlay) {
                JsLoadingOverlay.hide();
            }
            throw new Error('search error');
        }
        return response.json();
    }).then((responseJson) => {
        try {
            /* Update the filter menu first since that is what the user is interacting with */
            window.soldb.searchResults = responseJson['searchResults'];
            window.soldb.explicitSearchType = responseJson['explicitSearchType'];
            updateFilterMenu(responseJson['searchResults'], stateObj.appliedFacetIds);
            initializeHierarchyMenuListeners();
            /* Update page contents */
            if (responseJson['title'].content) {
                document.title = responseJson['title'].content;
            }
            let breadcrumbDiv = document.querySelector('.breadcrumb-container');
            if (breadcrumbDiv) {
                breadcrumbDiv.outerHTML = responseJson['searchBreadcrumbs'].content;
            } else if (responseJson['searchBreadcrumbs'].content) {
                let newBreadcrumbDiv = document.createElement('div'),
                    mainColumn = document.querySelector('.main-column');
                if (mainColumn) {
                    newBreadcrumbDiv.innerHTML = responseJson['searchBreadcrumbs'].content;
                    mainColumn.before(newBreadcrumbDiv);
                } else {
                    console.log('warning - no main column!');
                }
            }
            let headingDiv = document.querySelector('.search-results-heading');
            if (headingDiv) {
                headingDiv.outerHTML = responseJson['searchHeading'].content;
            }
            let filterEl = document.getElementById('filters-wrap');
            if (filterEl) {
                filterEl.outerHTML = responseJson['searchFilterBar'].content;
            }
            /* we need to find this again after replacing it (ref is now to old node) */
            filterEl = document.getElementById('filters-wrap');
            if (filterEl) {
                initClearAll(filterEl);
                initSortSelect(filterEl);
            }
            /* disable or enable clear all menu */
            if (stateObj.appliedFacetIds) {
                document.querySelectorAll('.clear-all-button').forEach((elem) => {
                    let isDisabled = elem.hasAttribute('disabled');
                    let ids = Object.keys(stateObj.appliedFacetIds);
                    let shouldBeDisabled = (ids.length == 0);
                    if (!shouldBeDisabled && ids.length == 1 && ids[0].includes('navPath')) {
                        shouldBeDisabled = true;
                    }
                    if (isDisabled != shouldBeDisabled) {
                        if (isDisabled) {
                            elem.removeAttribute('disabled');
                        } else {
                            elem.setAttribute('disabled', 'disabled');
                        }
                    }
                });
            }
            let listingDiv = document.querySelector('.listing-wrap');
            if (listingDiv) {
                listingDiv.outerHTML = responseJson['searchListing'].content;
            }
            listingDiv = document.querySelector('.listing-wrap');
            runInlineScripts(listingDiv);
            let pagiDiv = document.querySelector('.pagination');
            if (pagiDiv) {
                if (responseJson['searchPagination'].content) {
                    pagiDiv.outerHTML = responseJson['searchPagination'].content;
                    fixPagination();
                } else {
                    pagiDiv.remove();
                }
            } else if (responseJson['searchPagination'].content) {
                pagiDiv = document.createElement('div');
                document.querySelector('.listing').after(pagiDiv);
                pagiDiv.outerHTML = responseJson['searchPagination'].content;
                fixPagination();
            }
            initFilterBar();
            runWhenDOMContentLoaded(() => {
                initializeListingAddToCart();
                initializePagination();
            });
            if (pushState) {
                history.pushState(stateObj, '', stateObj.searchUrl);
            }
        } finally {
            if (JsLoadingOverlay) {
                JsLoadingOverlay.hide();
            }
        }
    });
}

function createAppliedFacetIds() {
    let facets = {};

    document.querySelectorAll('.filter-checkbox:checked').forEach((elem) => {
        facets[elem.id] = true;
    });

    return facets;
}

function findFacet(paramName) {
    if (window.soldb && window.soldb.searchResults && window.soldb.searchResults.facets) {
        for (let i = 0; i < window.soldb.searchResults.facets.length; i++) {
            let facet = window.soldb.searchResults.facets[i];
            if (facet.param === paramName) {
                return facet;
            }
        }
    }
    return null;
}

function findFacetFromInputID(inputId) {
    if (!inputId) {
        return inputId;
    }
    let paramName = inputId.substring(0, inputId.indexOf('-'));
    return findFacet(paramName);
}

function addFacetListener(elem) {
    elem.addEventListener('change', (evt) => {
        let facet = findFacetFromInputID(evt.currentTarget.id);
        const {searchUrl, direct} = createSearchUrl(), fragUrl = createSearchFragmentsUrl(searchUrl);

        let newAppliedFacetIds = Object.assign({}, history.state.appliedFacetIds);
        if (evt.currentTarget.checked) {
            newAppliedFacetIds[evt.currentTarget.id] = true;
            // Need to check if this was not a multiselect facet, and if not delete any previous
            if (facet) {
                if (!facet.allowMultiselect) {
                    let appliedFacetKeys = Object.keys(newAppliedFacetIds);
                    for (let i = 0; i < appliedFacetKeys.length; i++) {
                        let facetKey = appliedFacetKeys[i];
                        if (facetKey.startsWith(facet.param + '-') && facetKey !== evt.currentTarget.id) {
                            delete newAppliedFacetIds[facetKey];
                        }
                    }
                }
            } else {
                console.log('addFacetListener: failed to find facet!');
            }
        } else {
            delete newAppliedFacetIds[evt.currentTarget.id];
        }

        let stateObj = structuredClone(history.state);
        stateObj.fragUrl = fragUrl;
        stateObj.searchUrl = searchUrl;
        stateObj.direct = direct;
        stateObj.appliedFacetIds = newAppliedFacetIds;

        // full page transition on direct URL, or to search results from direct URL
        if (stateObj.direct || history.state.direct) {
            window.location = stateObj.searchUrl;
        } else {
            updateDocumentForFilters(stateObj, true);
        }
    });
}

function addFacetListenersNested(elem) {
    if (!elem) {
        console.log('warning - in addFacetListenersNested, elem is null');
        return;
    }
    elem.querySelectorAll('.filter-checkbox').forEach((elem) => {
        addFacetListener(elem);
    });
}

function initFilterMenu() {
    document.querySelectorAll('.filter-checkbox').forEach((elem) => {
        addFacetListener(elem);
    });
    window.addEventListener('popstate', (evt) => {
        if (evt.state) {
            /*
                In general with the code below, we only expect one block to do anything.
                e.g. only the change will only be one facet added or removed, or a change
                in sort. and only one call to updateDocumentForFilters.
             */
            /* this is a bit tricky. This event can be fired based on the back button
            OR the forward button afterward. Also, the back button could be clicked after removing
            a facet. So we may need to turn facets either on or off */
            let toggleFacets = {};
            /* find facets that need re-applying */
            for (let facetId in evt.state.appliedFacetIds) {
                let facetEl = document.getElementById(facetId);
                if (facetEl && !facetEl.checked) {
                    toggleFacets[facetId] = true;
                }
            }
            /* find facets that need removing */
            document.querySelectorAll('.filter-checkbox:checked').forEach((elem) => {
                if (!evt.state.appliedFacetIds[elem.id]) {
                    toggleFacets[elem.id] = true;
                }
            });
            for (let toggleFacetId in toggleFacets) {
                let toggleEl = document.getElementById(toggleFacetId);
                if (toggleEl) {
                    toggleEl.checked = !toggleEl.checked;
                    updateDocumentForFilters(evt.state, false);
                }
            }
            // handle sort change.
            let sortSelect = document.querySelector('#sortForm select');
            if (sortSelect && sortSelect.value !== evt.state.sort) {
                if (evt.state.sort) {
                    sortSelect.value = evt.state.sort;
                } else {
                    sortSelect.selectedIndex = 0;
                }
                updateDocumentForFilters(evt.state, false);
            }
        } else {
            console.log('no state obj in popstate handler! this is not expected. will refresh page');
            console.log('history.state is: ' + JSON.stringify(history.state));
            window.location.reload();
        }
    });
}

function updateFilterMenu(searchResults, appliedFacetIds) {
    if (!searchResults) {
        console.log('No search results! This is not expected');
        return;
    }
    document.querySelector('#show-results-count').textContent = '' + searchResults.totalResults;
    let pageHeader = document.querySelector('.page-heading');
    if (pageHeader) {
        pageHeader.innerHTML = `<h1>Showing <em>${searchResults.totalResults}</em> results</h1>`;
        if (!pageHeader.classList.contains('search-results-heading')) {
            pageHeader.classList.add('search-results-heading');
        }
    }
    const facets = searchResults.facets;
    const facetParams = facets.filter(facet => !(facet.customDisplay)).map(facet => facet.param);
    const outerList = document.querySelector('#all-filters ul'),
        facetHeaders = outerList.querySelectorAll('li.facet-header');
    for (let facetIndex = 0, headerIndex = 0; facetIndex < facets.length && headerIndex < facetHeaders.length; /* incrementors inline */) {
        let facet = null, header = null;
        if (facetIndex < facets.length) {
            facet = facets[facetIndex];
        }
        if (headerIndex < facetHeaders.length) {
            header = facetHeaders.item(headerIndex);
        }
        if (facet !== null && facet.customDisplay) {
            facetIndex++;
            continue;
        }
        if (facet == null) {
            /* facet no longer available - need to remove dom element */
            headerIndex++;
            if (header == null) {
                console.log('warning! both facet and header were null - should be impossible');
            } else {
                header.querySelector('div');
                header.remove();
            }
        } else if (header == null) {
            /* facet is newly available - need to insert a dom element for it */
            facetIndex++;
            if (facet == null) {
                console.log('warning! both header and facet were null - should be impossible');
            } else {
                if (facet.hierarchical) {
                    let newHeader = createHierarchicalFacetHeader(facet, appliedFacetIds);
                    outerList.append(newHeader);
                    addFacetListenersNested(newHeader);
                } else {
                    let newHeader = createFacetHeader(facet, appliedFacetIds);
                    outerList.append(newHeader);
                    addFacetListenersNested(newHeader);
                }
            }
        } else {
            /* we have a facet and a header. but they may not match. check that next */
            let wrapperDiv = header.querySelector('div');
            if (!wrapperDiv) {
                console.log('did not find wrapper div!');
            } else {
                if (wrapperDiv.id !== facet.param) {
                    if (facetParams.indexOf(wrapperDiv.id) > facetParams.indexOf(facet.param)) {
                        /* need to restore previously absent facet */
                        if (facet.hierarchical) {
                            let newHeader = createHierarchicalFacetHeader(facet, appliedFacetIds);
                            header.before(newHeader);
                            addFacetListenersNested(newHeader);
                        } else {
                            let newHeader = createFacetHeader(facet, appliedFacetIds);
                            header.before(newHeader);
                            addFacetListenersNested(newHeader);
                        }
                        facetIndex++;
                        continue;
                    } else {
                        header.remove();
                        headerIndex++;
                        continue;
                    }
                }
            }
            facetIndex++, headerIndex++;
            recreateFacetBuckets(facet, header, appliedFacetIds);
        }
    }
}

function recreateFacetBuckets(facet, header, appliedFacetIds) {
    let subList = header.querySelector('ol');
    if (facet.hierarchical) {
        // attempt to replace the entire sublist so we can listen for the mmenu changes to it, which
        // do not seem to fire if we just replace innerHTML
        let newSubList = document.createElement('ol');
        newSubList.innerHTML = createHierarchicalMarkup(facet, appliedFacetIds);
        if (facet.fullHierarchy) {
            newSubList.classList.add("full-hierarchy");
            const observerConfig = {attributes: true, childList: false, subtree: false, attributeOldValue: true, attributeFilter: ['class']};
            const observerCallback = (mutationList, observer) => {
                for (const mutation of mutationList) {
                    if (mutation.target.getAttribute('class') &&
                        mutation.target.getAttribute('class').includes('mm-') &&
                        (!mutation.oldValue || !mutation.oldValue.includes('mm-'))) {

                        setTimeout(() => {
                            observer.disconnect();
                            openHierarchyForSelected(mutation.target);
                        }, 100);
                    }
                }
            };
            const observer = new MutationObserver(observerCallback);
            observer.observe(newSubList, observerConfig);
        }

        subList.replaceWith(newSubList);
        addFacetListenersNested(newSubList);
    } else {
        subList.innerHTML = '';
        for (let i = 0; i < facet.buckets.length; i++) {
            let bucket = facet.buckets[i];
            let newLi = createBucketLI(facet, bucket, appliedFacetIds);
            if (newLi) {
                subList.append(newLi);
                addFacetListenersNested(newLi);
            } else {
                console.log(`Failed to create li for bucket ${facet.param}-${bucket.value}`);
            }
        }
    }
}

function createHierarchicalMarkup(facet, appliedFacetIds) {
    let markup = '';
    const inputType = facet.allowMultiselect ? 'checkbox' : 'radio';

    if (facet.showBack && facet.previousValue && facet.previousDisplayName) {
        const inputId = `${facet.param}-${facet.previousValue}`;
        markup += `<li><label class="filter-item back-filter">
<input id="${inputId}" name="${facet.param}" value="${facet.previousValue}" type="${inputType}" class="checkbox-input filter-checkbox">
<span class="filter-text-wrap">
    <span class="filter-text">Back to ${facet.previousDisplayName}</span>
</span>
</label>`;
    }

    for (let i = 0; i < facet.buckets.length; i++) {
        const bucket = facet.buckets[i];
        const lastBucket = (i === facet.buckets.length - 1);
        const nextBucketDeeper = (!lastBucket && facet.buckets[i + 1].hierarchyDepth > bucket.hierarchyDepth);
        const nextBucketShallower = (!lastBucket && facet.buckets[i + 1].hierarchyDepth < bucket.hierarchyDepth);
        const labelExtraClass = nextBucketDeeper ? ' mm-btn--next mm-listitem__btn' : '';
        const inputId = `${facet.param}-${bucket.value}`, checked = appliedFacetIds[inputId] ? ' checked' : '';
        const dataDirectUrl = bucket.directUrl ? ` data-direct-url="${bucket.directUrl}"` : '';
        const formattedCount = new Intl.NumberFormat().format(bucket.count);
        markup += `<li><label class="filter-item${labelExtraClass}">
<input id="${inputId}" name="${facet.param}" value="${bucket.value}" type="${inputType}" class="checkbox-input filter-checkbox"${checked}${dataDirectUrl}>
<span class="filter-text-wrap">
    <span class="filter-text">${bucket.displayName}</span>
    <span aria-label="${bucket.count} results" class="filter-text-item-count item-count">${formattedCount}</span>
</span>
</label>`;
        if (nextBucketDeeper) {
            markup += `<ol id="ol${inputId}">`;
        } else if (nextBucketShallower) {
            markup += '</li>'; // close current
            for (let j = facet.buckets[i + 1].hierarchyDepth; j < bucket.hierarchyDepth; j++) {
                markup += '</ol></li>';
            }
        } else {
            markup += '</li>'; // close current
        }
        if (lastBucket && bucket.hierarchyDepth != 1) {
            for (let j = 2; j <= bucket.hierarchyDepth; j++) {
                markup += '</ol></li>';
            }
        }
    }

    return markup;
}

function createHierarchicalFacetHeader(facet, appliedFacetIds) {
    let headerInnerHtml = `<span>${facet.displayName}</span>
<ol id="${facet.param}" class="${facet.fullHierarchy ? 'full-hierarchy' : ''} ${facet.displayMode == 'swatch' ? 'swatch-facets' : ''}">
</ol>
`;
    let newHeader = document.createElement('li');
    newHeader.classList.add('facet-header');
    newHeader.innerHTML = headerInnerHtml;
    let bucketList = newHeader.querySelector('ol');
    bucketList.innerHTML = createHierarchicalMarkup(facet, appliedFacetIds);

    return newHeader;
}

function createFacetHeader(facet, appliedFacetIds) {
    let headerInnerHtml = `<span>${facet.displayName}</span>
<ol id="${facet.param}" class="${facet.fullHierarchy ? 'full-hierarchy' : ''} ${facet.displayMode == 'swatch' ? 'swatch-facets' : ''}">
</ol>
`;
    let newHeader = document.createElement('li');
    newHeader.classList.add('facet-header');
    newHeader.innerHTML = headerInnerHtml;
    let bucketList = newHeader.querySelector('ol');
    for (let i = 0; i < facet.buckets.length; i++) {
        let bucket = facet.buckets[i];
        let bucketLi = createBucketLI(facet, bucket, appliedFacetIds);
        if (bucketLi) {
            bucketList.append(bucketLi);
        }
    }
    return newHeader;
}

/* Create a new bucket element.
* */
function createBucketLI(facet, bucket, appliedFacetIds) {
    let liInnerHtml = '', liClass = '';
    const inputType = facet.allowMultiselect ? 'checkbox' : 'radio';
    const inputId = `${facet.param}-${bucket.value}`;
    const inputChecked = !!appliedFacetIds[inputId];
    let formattedCount = new Intl.NumberFormat().format(bucket.count);
    let dataDirectUrl = bucket.directUrl ? `data-direct-url="${bucket.directUrl}"` : '';
    if (facet.displayMode === 'swatch') {
        liClass = 'swatch-facet';
        liInnerHtml = `
<input id="${inputId}" name="${facet.param}" value="${bucket.value}" type="${inputType}" class="filter-checkbox" ${inputChecked ? 'checked' : ''} ${dataDirectUrl} >
<label for="${inputId}" class="swatch ${bucket.displayModeData.attributeCssClass}"><i class="icon icon-check"></i></label>
<label for="${inputId}" class="swatch-label-text">
    ${bucket.displayName}
    (<span aria-label="${bucket.count} results" class="item-count">${formattedCount}</span>)
</label>
`;
    } else {
        let extraLiClasses = '';
        liInnerHtml = `
<label class="filter-item ${extraLiClasses}">
    <input id="${inputId}" name="${facet.param}" value="${bucket.value}" type="${inputType}" class="checkbox-input filter-checkbox" ${inputChecked ? 'checked' : ''} ${dataDirectUrl}>
    <span class="filter-text-wrap">
        <span class="filter-text">
            ${bucket.displayName}
        </span>
        <span aria-label="${bucket.count} results" class="filter-text-item-count item-count">${formattedCount}</span>
    </span>
</label>
`;
    }

    if (liInnerHtml) {
        /* using outerHTML does NOT work if the element has not been added to the DOM */
        let newLi = document.createElement('li');
        if (liClass) {
            newLi.classList.add(liClass);
        }
        newLi.innerHTML = liInnerHtml;
        return newLi;
    }

    return null;
}

function initShowResults() {
    document.querySelector('.show-results-button').addEventListener('click', api.close);
}

function initSortSelect(context) {
    context = context || document;
    context.querySelectorAll('#sortForm select').forEach((el) => {
        el.addEventListener('change', (evt) => {
            evt.stopPropagation();
            let {searchUrl, direct} = createSearchUrl(),
                fragUrl = createSearchFragmentsUrl(searchUrl);
            let newState = structuredClone(history.state);
            newState.fragUrl = fragUrl;
            newState.searchUrl = searchUrl;
            newState.direct = direct;
            newState.sort = evt.target.value;
            if (newState.direct || history.state.direct) {
                window.location = newState.searchUrl;
            } else {
                updateDocumentForFilters(newState, true);
            }
        });
    });
}

function initClearAll(context) {
    context = context || document;
    context.querySelectorAll('.clear-all-button').forEach((elem) => {
        elem.addEventListener('click', () => {
            // retain hidden facets - only remove those with inputs
            let newAppliedFacetIds = Object.assign({}, history.state.appliedFacetIds);
            document.querySelectorAll('.filter-checkbox:checked').forEach((elem) => {
                // do not delete a navPath filter - send them to the category page instead
                if (elem.getAttribute('name') !== 'navPath') {
                    elem.checked = false;
                    delete newAppliedFacetIds[elem.id];
                }
            });
            let {searchUrl, direct} = createSearchUrl(true),
                fragUrl = createSearchFragmentsUrl(searchUrl);
            let newState = structuredClone(history.state);
            newState.fragUrl = fragUrl;
            newState.searchUrl = searchUrl;
            newState.direct = direct;
            newState.appliedFacetIds = newAppliedFacetIds;
            if (newState.direct || history.state.direct) {
                window.location = newState.searchUrl;
            } else {
                updateDocumentForFilters(newState, true);
            }
        });
        if (document.querySelectorAll('.filter-checkbox:checked').length) {
            elem.removeAttribute('disabled');
        } else {
            elem.setAttribute("disabled", "disabled");
        }
    });
}

function openHierarchyForSelected(elem) {
    if (!elem) {
        console.log('openHierarchyForSelected: null elem!');
        return;
    }
    let panels = elem.querySelectorAll('.mm-panel');
    if (panels.length) {
        let checkedInput = elem.querySelector('input:checked');
        if (checkedInput) {
            // open all panels leading to selected input
            let containingPanel = checkedInput.closest('.mm-panel');
            if (containingPanel) {
                api.openPanel(containingPanel);
            }
        }
        // For panels that do not contain input:checked as a descendant - close unless is direct submenu of checked
        // option, in which case open
        panels.forEach((panel) => {
            let closePanel = !panel.querySelector('input:checked');
            if (closePanel) {
                const prev = panel.previousElementSibling;
                if (prev && prev.matches('label') && prev.control.checked) {
                    closePanel = false;
                    api.openPanel(panel);
                }
            }
            if (closePanel) {
                api.closePanel(panel);
            }
        });
    }
}

function initializeHierarchyMenuListeners() {
    document.querySelectorAll('.full-hierarchy').forEach((elem) => {
        elem.addEventListener('click', () => {
            openHierarchyForSelected(elem);
        });
    });
}

function initializeFilterMenu() {
    window.soldb = window.soldb || {};
    const menu = new Mmenu("#all-filters", {
        theme: 'white',
        slidingSubmenus: false,
        keyboardNavigation: {
            enable: "default",
            enhance: true
        },
        navbar: {
            title: 'Filters'
        },
        navbars: [
            {
                "position": "top",
                "content": [
                    "prev",
                    "title",
                    "close"
                ]
            },
            {
                position: "bottom",
                content: [
                    '<button class="clear-filter-button clear-all-button"><span aria-label="Clear all selected filters" class="button-label">Clear All</span></button>',
                    '<button class="btn-main-cta show-results-button"><span aria-label="Apply selected filters" class="button-label">Show <span id="show-results-count">' + window.soldb.totalResultCount + '</span> Items</span></button>'
                ]
            }
        ],
        offCanvas: {
            position: window.innerWidth <= 668 ? "bottom" : "left-front"
        }
    }, {
        offCanvas: {
            page: {
                selector: '.page-wrap'
            }
        }
    });
    api = menu.API;
    window.soldb.searchFilterMenuAPI = api;
    setInitialState(); /* needs to be before initClearAll which uses history.state */
    initClearAll();
    initSortSelect();
    initFilterBar();
    initShowResults();
    initFilterMenu();
    initializeHierarchyMenuListeners();
    document.getElementById('filter-menu-form').addEventListener('submit', (evt) => {
        evt.preventDefault();
    });
}

/*
    Bundle including common code plus support for showcases and search listings.
 */

runWhenDOMContentLoaded(() => {
    initializeCommon();
    initializeShowcases();
    initializeFilterMenu();
});
