PageSourceSearch

https://adastrathelabel.com/s/files/1/0433/9600/6051/t/8/assets/theme_v-157905205154440682571644516655.js

js adastrathelabel.com collected 2026-10-05 03:15:33 UTC 211,769 bytes, 7,601 lines download raw bytes

1window.theme = window.theme || {};
2
3/* ================ SLATE ================ */
4window.theme = window.theme || {};
5
6theme.Sections = function Sections() {
7  this.constructors = {};
8  this.instances = [];
9
10  $(document)
11    .on('shopify:section:load', this._onSectionLoad.bind(this))
12    .on('shopify:section:unload', this._onSectionUnload.bind(this))
13    .on('shopify:section:select', this._onSelect.bind(this))
14    .on('shopify:section:deselect', this._onDeselect.bind(this))
15    .on('shopify:block:select', this._onBlockSelect.bind(this))
16    .on('shopify:block:deselect', this._onBlockDeselect.bind(this));
17};
18
19theme.Sections.prototype = _.assignIn({}, theme.Sections.prototype, {
20  _createInstance: function(container, constructor) {
21    var $container = $(container);
22    var id = $container.attr('data-section-id');
23    var type = $container.attr('data-section-type');
24
25    constructor = constructor || this.constructors[type];
26
27    if (_.isUndefined(constructor)) {
28      return;
29    }
30
31    var instance = _.assignIn(new constructor(container), {
32      id: id,
33      type: type,
34      container: container
35    });
36
37    this.instances.push(instance);
38  },
39
40  _onSectionLoad: function(evt) {
41    var container = $('[data-section-id]', evt.target)[0];
42    if (container) {
43      this._createInstance(container);
44    }
45  },
46
47  _onSectionUnload: function(evt) {
48    this.instances = _.filter(this.instances, function(instance) {
49      var isEventInstance = instance.id === evt.detail.sectionId;
50
51      if (isEventInstance) {
52        if (_.isFunction(instance.onUnload)) {
53          instance.onUnload(evt);
54        }
55      }
56
57      return !isEventInstance;
58    });
59  },
60
61  _onSelect: function(evt) {
62    // eslint-disable-next-line no-shadow
63    var instance = _.find(this.instances, function(instance) {
64      return instance.id === evt.detail.sectionId;
65    });
66
67    if (!_.isUndefined(instance) && _.isFunction(instance.onSelect)) {
68      instance.onSelect(evt);
69    }
70  },
71
72  _onDeselect: function(evt) {
73    // eslint-disable-next-line no-shadow
74    var instance = _.find(this.instances, function(instance) {
75      return instance.id === evt.detail.sectionId;
76    });
77
78    if (!_.isUndefined(instance) && _.isFunction(instance.onDeselect)) {
79      instance.onDeselect(evt);
80    }
81  },
82
83  _onBlockSelect: function(evt) {
84    // eslint-disable-next-line no-shadow
85    var instance = _.find(this.instances, function(instance) {
86      return instance.id === evt.detail.sectionId;
87    });
88
89    if (!_.isUndefined(instance) && _.isFunction(instance.onBlockSelect)) {
90      instance.onBlockSelect(evt);
91    }
92  },
93
94  _onBlockDeselect: function(evt) {
95    // eslint-disable-next-line no-shadow
96    var instance = _.find(this.instances, function(instance) {
97      return instance.id === evt.detail.sectionId;
98    });
99
100    if (!_.isUndefined(instance) && _.isFunction(instance.onBlockDeselect)) {
101      instance.onBlockDeselect(evt);
102    }
103  },
104
105  register: function(type, constructor) {
106    this.constructors[type] = constructor;
107
108    $('[data-section-type=' + type + ']').each(
109      function(index, container) {
110        this._createInstance(container, constructor);
111      }.bind(this)
112    );
113  }
114});
115
116window.slate = window.slate || {};
117
118/**
119 * Slate utilities
120 * -----------------------------------------------------------------------------
121 * A collection of useful utilities to help build your theme
122 *
123 *
124 * @namespace utils
125 */
126
127slate.utils = {
128  /**
129   * Get the query params in a Url
130   * Ex
131   * https://mysite.com/search?q=noodles&b
132   * getParameterByName('q') = "noodles"
133   * getParameterByName('b') = "" (empty value)
134   * getParameterByName('test') = null (absent)
135   */
136  getParameterByName: function(name, url) {
137    if (!url) url = window.location.href;
138    name = name.replace(/[[\]]/g, '\\$&');
139    var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
140      results = regex.exec(url);
141    if (!results) return null;
142    if (!results[2]) return '';
143    return decodeURIComponent(results[2].replace(/\+/g, ' '));
144  },
145
146  resizeSelects: function($selects) {
147    $selects.each(function() {
148      var $this = $(this);
149      var arrowWidth = 10;
150      // create test element
151      var text = $this.find('option:selected').text();
152      var $test = $('<span>').html(text);
153
154      // add to body, get width, and get out
155      $test.appendTo('body');
156      var width = $test.width();
157      $test.remove();
158
159      // set select width
160      $this.width(width + arrowWidth);
161    });
162  },
163
164  keyboardKeys: {
165    TAB: 9,
166    ENTER: 13,
167    ESCAPE: 27,
168    LEFTARROW: 37,
169    RIGHTARROW: 39
170  }
171};
172
173window.slate = window.slate || {};
174
175/**
176 * iFrames
177 * -----------------------------------------------------------------------------
178 * Wrap videos in div to force responsive layout.
179 *
180 * @namespace iframes
181 */
182
183slate.rte = {
184  /**
185   * Wrap tables in a container div to make them scrollable when needed
186   *
187   * @param {object} options - Options to be used
188   * @param {jquery} options.$tables - jquery object(s) of the table(s) to wrap
189   * @param {string} options.tableWrapperClass - table wrapper class name
190   */
191  wrapTable: function(options) {
192    options.$tables.wrap(
193      '<div class="' + options.tableWrapperClass + '"></div>'
194    );
195  },
196
197  /**
198   * Wrap iframes in a container div to make them responsive
199   *
200   * @param {object} options - Options to be used
201   * @param {jquery} options.$iframes - jquery object(s) of the iframe(s) to wrap
202   * @param {string} options.iframeWrapperClass - class name used on the wrapping div
203   */
204  wrapIframe: function(options) {
205    options.$iframes.each(function() {
206      // Add wrapper to make video responsive
207      $(this).wrap('<div class="' + options.iframeWrapperClass + '"></div>');
208
209      // Re-set the src attribute on each iframe after page load
210      // for Chrome's "incorrect iFrame content on 'back'" bug.
211      // https://code.google.com/p/chromium/issues/detail?id=395791
212      // Need to specifically target video and admin bar
213      this.src = this.src;
214    });
215  }
216};
217
218window.slate = window.slate || {};
219
220/**
221 * A11y Helpers
222 * -----------------------------------------------------------------------------
223 * A collection of useful functions that help make your theme more accessible
224 * to users with visual impairments.
225 *
226 *
227 * @namespace a11y
228 */
229
230slate.a11y = {
231  /**
232   * For use when focus shifts to a container rather than a link
233   * eg for In-page links, after scroll, focus shifts to content area so that
234   * next `tab` is where user expects if focusing a link, just $link.focus();
235   *
236   * @param {JQuery} $element - The element to be acted upon
237   */
238  pageLinkFocus: function($element) {
239    var focusClass = 'js-focus-hidden';
240
241    $element
242      .first()
243      .attr('tabIndex', '-1')
244      .focus()
245      .addClass(focusClass)
246      .one('blur', callback);
247
248    function callback() {
249      $element
250        .first()
251        .removeClass(focusClass)
252        .removeAttr('tabindex');
253    }
254  },
255
256  /**
257   * If there's a hash in the url, focus the appropriate element
258   */
259  focusHash: function() {
260    var hash = window.location.hash;
261
262    // is there a hash in the url? is it an element on the page?
263    if (hash && document.getElementById(hash.slice(1))) {
264      this.pageLinkFocus($(hash));
265    }
266  },
267
268  /**
269   * When an in-page (url w/hash) link is clicked, focus the appropriate element
270   */
271  bindInPageLinks: function() {
272    $('a[href*=#]').on(
273      'click',
274      function(evt) {
275        this.pageLinkFocus($(evt.currentTarget.hash));
276      }.bind(this)
277    );
278  },
279
280  /**
281   * Traps the focus in a particular container
282   *
283   * @param {object} options - Options to be used
284   * @param {jQuery} options.$container - Container to trap focus within
285   * @param {jQuery}
285 options.$elementToFocus - Element to be focused when focus leaves container
286   * @param {string} options.namespace - Namespace used for new focus event handler
287   */
288  trapFocus: function(options) {
289    var eventsName = {
290      focusin: options.namespace ? 'focusin.' + options.namespace : 'focusin',
291      focusout: options.namespace
292        ? 'focusout.' + options.namespace
293        : 'focusout',
294      keydown: options.namespace
295        ? 'keydown.' + options.namespace
296        : 'keydown.handleFocus'
297    };
298
299    /**
300     * Get every possible visible focusable element
301     */
302    var $focusableElements = options.$container.find(
303      $(
304        'button, [href], input, select, textarea, [tabindex]:not([tabindex^="-"])'
305      ).filter(':visible')
306    );
307    var firstFocusable = $focusableElements[0];
308    var lastFocusable = $focusableElements[$focusableElements.length - 1];
309
310    if (!options.$elementToFocus) {
311      options.$elementToFocus = options.$container;
312    }
313
314    function _manageFocus(evt) {
315      if (evt.keyCode !== slate.utils.keyboardKeys.TAB) return;
316
317      /**
318       * On the last focusable element and tab forward,
319       * focus the first element.
320       */
321      if (evt.target === lastFocusable && !evt.shiftKey) {
322        evt.preventDefault();
323        firstFocusable.focus();
324      }
325      /**
326       * On the first focusable element and tab backward,
327       * focus the last element.
328       */
329      if (evt.target === firstFocusable && evt.shiftKey) {
330        evt.preventDefault();
331        lastFocusable.focus();
332      }
333    }
334
335    options.$container.attr('tabindex', '-1');
336    options.$elementToFocus.focus();
337
338    $(document).off('focusin');
339
340    $(document).on(eventsName.focusout, function() {
341      $(document).off(eventsName.keydown);
342    });
343
344    $(document).on(eventsName.focusin, function(evt) {
345      if (evt.target !== lastFocusable && evt.target !== firstFocusable) return;
346
347      $(document).on(eventsName.keydown, function(evt) {
348        _manageFocus(evt);
349      });
350    });
351  },
352
353  /**
354   * Removes the trap of focus in a particular container
355   *
356   * @param {object} options - Options to be used
357   * @param {jQuery} options.$container - Container to trap focus within
358   * @param {string} options.namespace - Namespace used for new focus event handler
359   */
360  removeTrapFocus: function(options) {
361    var eventName = options.namespace
362      ? 'focusin.' + options.namespace
363      : 'focusin';
364
365    if (options.$container && options.$container.length) {
366      options.$container.removeAttr('tabindex');
367    }
368
369    $(document).off(eventName);
370  },
371
372  /**
373   * Add aria-describedby attribute to external and new window links
374   *
375   * @param {object} options - Options to be used
376   * @param {object} options.messages - Custom messages to be used
377   * @param {jQuery} options.$links - Specific links to be targeted
378   */
379  accessibleLinks: function(options) {
380    var body = document.querySelector('body');
381
382    var idSelectors = {
383      newWindow: 'a11y-new-window-message',
384      external: 'a11y-external-message',
385      newWindowExternal: 'a11y-new-window-external-message'
386    };
387
388    if (options.$links === undefined || !options.$links.jquery) {
389      options.$links = $('a[href]:not([aria-describedby])');
390    }
391
392    function generateHTML(customMessages) {
393      if (typeof customMessages !== 'object') {
394        customMessages = {};
395      }
396
397      var messages = $.extend(
398        {
399          newWindow: 'Opens in a new window.',
400          external: 'Opens external website.',
401          newWindowExternal: 'Opens external website in a new window.'
402        },
403        customMessages
404      );
405
406      var container = document.createElement('ul');
407      var htmlMessages = '';
408
409      for (var message in messages) {
410        htmlMessages +=
411          '<li id=' + idSelectors[message] + '>' + messages[message] + '</li>';
412      }
413
414      container.setAttribute('hidden', true);
415      container.innerHTML = htmlMessages;
416
417      body.appendChild(container);
418    }
419
420    function _externalSite($link) {
421      var hostname = window.location.hostname;
422
423      return $link[0].hostname !== hostname;
424    }
425
426    $.each(options.$links, function() {
427      var $link = $(this);
428      var target = $link.attr('target');
429      var rel = $link.attr('rel');
430      var isExternal = _externalSite($link);
431      var isTargetBlank = target === '_blank';
432
433      if (isExternal) {
434        $link.attr('aria-describedby', idSelectors.external);
435      }
436      if (isTargetBlank) {
437        if (rel === undefined || rel.indexOf('noopener') === -1) {
438          $link.attr('rel', function(i, val) {
439            var relValue = val === undefined ? '' : val + ' ';
440            return relValue + 'noopener';
441          });
442        }
443        $link.attr('aria-describedby', idSelectors.newWindow);
444      }
445      if (isExternal && isTargetBlank) {
446        $link.attr('aria-describedby', idSelectors.newWindowExternal);
447      }
448    });
449
450    generateHTML(options.messages);
451  }
452};
453
454/**
455 * Image Helper Functions
456 * -----------------------------------------------------------------------------
457 * A collection of functions that help with basic image operations.
458 *
459 */
460
461theme.Images = (function() {
462  /**
463   * Preloads an image in memory and uses the browsers cache to store it until needed.
464   *
465   * @param {Array} images - A list of image urls
466   * @param {String} size - A shopify image size attribute
467   */
468
469  function preload(images, size) {
470    if (typeof images === 'string') {
471      images = [images];
472    }
473
474    for (var i = 0; i < images.length; i++) {
475      var image = images[i];
476      this.loadImage(this.getSizedImageUrl(image, size));
477    }
478  }
479
480  /**
481   * Loads and caches an image in the browsers cache.
482   * @param {string} path - An image url
483   */
484  function loadImage(path) {
485    new Image().src = path;
486  }
487
488  /**
489   * Swaps the src of an image for another OR returns the imageURL to the callback function
490   * @param image
491   * @param element
492   * @param callback
493   */
494  function switchImage(image, element, callback) {
495    var size = this.imageSize(element.src);
496    var imageUrl = this.getSizedImageUrl(image.src, size);
497
498    if (callback) {
499      callback(imageUrl, image, element); // eslint-disable-line callback-return
500    } else {
501      element.src = imageUrl;
502    }
503  }
504
505  /**
506   * +++ Useful
507   * Find the Shopify image attribute size
508   *
509   * @param {string} src
510   * @returns {null}
511   */
512  function imageSize(src) {
513    var match = src.match(
514      /.+_((?:pico|icon|thumb|small|compact|medium|large|grande)|\d{1,4}x\d{0,4}|x\d{1,4})[_\\.@]/
515    );
516
517    if (match !== null) {
518      if (match[2] !== undefined) {
519        return match[1] + match[2];
520      } else {
521        return match[1];
522      }
523    } else {
524      return null;
525    }
526  }
527
528  /**
529   * +++ Useful
530   * Adds a Shopify size attribute to a URL
531   *
532   * @param src
533   * @param size
534   * @returns {*}
535   */
536  function getSizedImageUrl(src, size) {
537    if (size === null) {
538      return src;
539    }
540
541    if (size === 'master') {
542      return this.removeProtocol(src);
543    }
544
545    var match = src.match(
546      /\.(jpg|jpeg|gif|png|bmp|bitmap|tiff|tif)(\?v=\d+)?$/i
547    );
548
549    if (match !== null) {
550      var prefix = src.split(match[0]);
551      var suffix = match[0];
552
553      return this.removeProtocol(prefix[0] + '_' + size + suffix);
554    }
555
556    return null;
557  }
558
559  function removeProtocol(path) {
560    return path.replace(/http(s)?:/, '');
561  }
562
563  return {
564    preload: preload,
565    loadImage: loadImage,
566    switchImage: switchImage,
567    imageSize: imageSize,
568    getSizedImageUrl: getSizedImageUrl,
569    removeProtocol: removeProtocol
570  };
571})();
572
573/**
574 * Currency Helpers
575 * -----------------------------------------------------------------------------
576 * A collection of useful functions that help with currency formatting
577 *
578 * Current contents
579 * - formatMoney - Takes an amount in cents and returns it as a formatted dollar value.
580 *
581 * Alternatives
582 * - Accounting.js - http://openexchangerates.github.io/accounting.js/
583 *
584 */
585
586theme.Currency = (function() {
587  var moneyFormat = '${{amount}}'; // eslint-disable-line camelcase
588
589  function formatMoney(cents, format) {
590    if (typeof cents === 'string') {
591      cents = cents.replace('.', '');
592    }
593    var value = '';
594    var placeholderRegex = /\{\{\s*(\w+)\s*\}\}/;
595    var formatString = format || moneyFormat;
596
597    function formatWithDelimiters(number, precision, thousands, decimal) {
598      thousands = thousands || ',';
599      decimal = decimal || '.';
600
601      if (isNaN(number) || number === null) {
602        return 0;
603      }
604
605      number = (number / 100.0).toFixed(precision);
606
607      var parts = number.split('.');
608      var dollarsAmount = parts[0].replace(
609        /(\d)(?=(\d\d\d)+(?!\d))/g,
610        '$1' + thousands
611      );
612      var centsAmount = parts[1] ? decimal + parts[1] : '';
613
614      return dollarsAmount + centsAmount;
615    }
616
617    switch (formatString.match(placeholderRegex)[1]) {
618      case 'amount':
619        value = formatWithDelimiters(cents, 2);
620        break;
621      case 'amount_no_decimals':
622        value = formatWithDelimiters(cents, 0);
623        break;
624      case 'amount_with_comma_separator':
625        value = formatWithDelimiters(cents, 2, '.', ',');
626        break;
627      case 'amount_no_decimals_with_comma_separator':
628        value = formatWithDelimiters(cents, 0, '.', ',');
629        break;
630      case 'amount_no_decimals_with_space_separator':
631        value = formatWithDelimiters(cents, 0, ' ');
632        break;
633      case 'amount_with_apostrophe_separator':
634        value = formatWithDelimiters(cents, 2, "'");
635        break;
636    }
637
638    return formatString.replace(placeholderRegex, value);
639  }
640
641  return {
642    formatMoney: formatMoney
643  };
644})();
645
646/**
647 * Variant Selection scripts
648 * ------------------------------------------------------------------------------
649 *
650 * Handles change events from the variant inputs in any `cart/add` forms that may
651 * exist.  Also updates the master select and triggers updates when the variants
652 * price or image changes.
653 *
654 * @namespace variants
655 */
656
657slate.Variants = (function() {
658  /**
659   * Variant constructor
660   *
661   * @param {object} options - Settings from `product.js`
662   */
663  function Variants(options) {
664    this.$container = options.$container;
665    this.product = options.product;
666    this.singleOptionSelector = options.singleOptionSelector;
667    this.originalSelectorId = options.originalSelectorId;
668    this.enableHistoryState = options.enableHistoryState;
669    this.currentVariant = this._getVariantFromOptions();
670
671    $(this.singleOptionSelector, this.$container).on(
672      'change',
673      this._onSelectChange.bind(this)
674    );
675  }
676
677  Variants.prototype = _.assignIn({}, Variants.prototype, {
678    /**
679     * Get the currently selected options from add-to-cart form. Works with all
680     * form input elements.
681     *
682     * @return {array} options - Values of currently selected variants
683     */
684    _getCurrentOptions: function() {
685      var currentOptions = _.map(
686        $(this.singleOptionSelector, this.$container),
687        function(element) {
688          var $element = $(element);
689          var type = $element.attr('type');
690          var currentOption = {};
691
692          if (type === 'radio' || type === 'checkbox') {
693            if ($element[0].checked) {
694              currentOption.value = $element.val();
695              currentOption.index = $element.data('index');
696
697              return currentOption;
698            } else {
699              return false;
700            }
701          } else {
702            currentOption.value = $element.val();
703            currentOption.index = $element.data('index');
704
705            return currentOption;
706          }
707        }
708      );
709
710      // remove any unchecked input values if using radio buttons or checkboxes
711      currentOptions = _.compact(currentOptions);
712
713      return currentOptions;
714    },
715
716    /**
717     * Find variant based on selected values.
718     *
719     * @param  {array} selectedValues - Values of variant inputs
720     * @return {object || undefined} found - Variant object from product.variants
721     */
722    _getVariantFromOptions: function() {
723      var selectedValues = this._getCurrentOptions();
724      var variants = this.product.variants;
725
726      var found = _.find(variants, function(variant) {
727        return selectedValues.every(function(values) {
728          return _.isEqual(variant[values.index], values.value);
729        });
730      });
731
732      return found;
733    },
734
735    /**
736     * Event handler for when a variant input changes.
737     */
738    _onSelectChange: function() {
739      var variant = this._getVariantFromOptions();
740
741      this.$container.trigger({
742        type: 'variantChange',
743        variant: variant
744      });
745
746      if (!variant) {
747        return;
748      }
749
750      this._updateMasterSelect(variant);
751      this._updateImages(variant);
752      this._updatePrice(variant);
753      this._updateSKU(variant);
754      this.currentVariant = variant;
755
756      if (this.enableHistoryState) {
757        this._updateHistoryState(variant);
758      }
759    },
760
761    /**
762     * Trigger event when variant image changes
763     *
764     * @param  {object} variant - Currently selected variant
765     * @return {event}  variantImageChange
766     */
767    _updateImages: function(variant) {
768      var variantImage = variant.featured_image || {};
769      var currentVariantImage = this.currentVariant.featured_image || {};
770
771      if (
772        !variant.featured_image ||
773        variantImage.src === currentVariantImage.src
774      ) {
775        return;
776      }
777
778      this.$container.trigger({
779        type: 'variantImageChange',
780        variant: variant
781      });
782    },
783
784    /**
785     * Trigger event when variant price changes.
786     *
787     * @param  {object} variant - Currently selected variant
788     * @return {event} variantPriceChange
789     */
790    _updatePrice: function(variant) {
791      if (
792        variant.price === this.currentVariant.price &&
793        variant.compare_at_price === this.currentVariant.compare_at_price
794      ) {
795        return;
796      }
797
798      this.$container.trigger({
799        type: 'variantPriceChange',
800        variant: variant
801      });
802    },
803
804    /**
805     * Trigger event when variant sku changes.
806     *
807     * @param  {object} variant - Currently selected variant
808     * @return {event} variantSKUChange
809     */
810    _updateSKU: function(variant) {
811      if (variant.sku === this.currentVariant.sku) {
812        return;
813      }
814
815      this.$container.trigger({
816        type: 'variantSKUChange',
817        variant: variant
818      });
819    },
820
821    /**
822     * Update history state for product deeplinking
823     *
824     * @param  {variant} variant - Currently selected variant
825     * @return {k}         [description]
826     */
827    _updateHistoryState: function(variant) {
828      if (!history.replaceState || !variant) {
829        return;
830      }
831
832      var newurl =
833        window.location.protocol +
834        '//' +
835        window.location.host +
836        window.location.pathname +
837        '?variant=' +
838        variant.id;
839      window.history.propertyIsEnumerable({ path: newurl }, '', newurl);
840    },
841
842    /**
843     * Update hidden master select of variant change
844     *
845     * @param  {variant} variant - Currently selected variant
846     */
847    _updateMasterSelect: function(variant) {
848      $(this.originalSelectorId, this.$container).val(variant.id);
849    }
850  });
851
852  return Variants;
853})();
854
855this.Shopify = this.Shopify || {};
856this.Shopify.theme = this.Shopify.theme || {};
857this.Shopify.theme.PredictiveSearch = (function() {
858  'use strict';
859
860  function validateQuery(query) {
861    var error;
862
863    if (query === null || query === undefined) {
864      error = new TypeError("'query' is missing");
865      error.type = 'argument';
866      throw error;
867    }
868
869    if (typeof query !== 'string') {
870      error = new TypeError("'query' is not a string");
871      error.type = 'argument';
872      throw error;
873    }
874  }
875
876  function GenericError() {
877    var error = Error.call(this);
878
879    error.name = 'Server error';
880    error.message = 'Something went wrong on the server';
881    error.status = 500;
882
883    return error;
884  }
885
886  function NotFoundError(status) {
887    var error = Error.call(this);
888
889    error.name = 'Not found';
890    error.message = 'Not found';
891    error.status = status;
892
893    return error;
894  }
895
896  function ServerError() {
897    var error = Error.call(this);
898
899    error.name = 'Server error';
900    error.message = 'Something went wrong on the server';
901    error.status = 500;
902
903    return error;
904  }
905
906  function ContentTypeError(status) {
907    var error = Error.call(this);
908
909    error.name = 'Content-Type error';
910    error.message = 'Content-Type was not provided or is of wrong type';
911    error.status = status;
912
913    return error;
914  }
915
916  function JsonParseError(status) {
917    var error = Error.call(this);
918
919    error.name = 'JSON parse error';
920    error.message = 'JSON syntax error';
921    error.status = status;
922
923    return error;
924  }
925
926  function ThrottledError(status, name, message, retryAfter) {
927    var error = Error.call(this);
928
929    error.name = name;
930    error.message = message;
931    error.status = status;
932    error.retryAfter = retryAfter;
933
934    return error;
935  }
936
937  function InvalidParameterError(status, name, message) {
938    var error = Error.call(this);
939
940    error.name = name;
941    error.message = message;
942    error.status = status;
943
944    return error;
945  }
946
947  function ExpectationFailedError(status, name, message) {
948    var error = Error.call(this);
949
950    error.name = name;
951    error.message = message;
952    error.status = status;
953
954    return error;
955  }
956
957  function request(configParams, query, onSuccess, onError) {
958    var xhr = new XMLHttpRequest();
959
960    xhr.onreadystatechange = function() {
961      if (xhr.readyState !== XMLHttpRequest.DONE) {
962        return;
963      }
964
965      var contentType = xhr.getResponseHeader('Content-Type');
966
967      if (xhr.status >= 500) {
968        onError(new ServerError());
969
970        return;
971      }
972
973      if (xhr.status === 404) {
974        onError(new NotFoundError(xhr.status));
975
976        return;
977      }
978
979      if (
980        typeof contentType !== 'string' ||
981        contentType.toLowerCase().match('application/json') === null
982      ) {
983        onError(new ContentTypeError(xhr.status));
984
985        return;
986      }
987
988      if (xhr.status === 417) {
989        try {
990          var invalidParameterJson = JSON.parse(xhr.responseText);
991
992          onError(
993            new InvalidParameterError(
994              xhr.status,
995              invalidParameterJson.message,
996              invalidParameterJson.description
997            )
998          );
999        } catch (error) {
1000          onError(new JsonParseError(xhr.status));
1001        }
1002
1003        return;
1004      }
1005
1006      if (xhr.status === 422) {
1007        try {
1008          var expectationFailedJson = JSON.parse(xhr.responseText);
1009
1010          onError(
1011            new ExpectationFailedError(
1012              xhr.status,
1013              expectationFailedJson.message,
1014              expectationFailedJson.description
1015            )
1016          );
1017        } catch (error) {
1018          onError(new JsonParseError(xhr.status));
1019        }
1020
1021        return;
1022      }
1023
1024      if (xhr.status === 429) {
1025        try {
1026          var throttledJson = JSON.parse(xhr.responseText);
1027
1028          onError(
1029            new ThrottledError(
1030              xhr.status,
1031              throttledJson.message,
1032              throttledJson.description,
1033              xhr.getResponseHeader('Retry-After')
1034            )
1035          );
1036        } catch (error) {
1037          onError(new JsonParseError(xhr.status));
1038        }
1039
1040        return;
1041      }
1042
1043      if (xhr.status === 200) {
1044        try {
1045          var res = JSON.parse(xhr.responseText);
1046          res.query = query;
1047          onSuccess(res);
1048        } catch (error) {
1049          onError(new JsonParseError(xhr.status));
1050        }
1051
1052        return;
1053      }
1054
1055      try {
1056        var genericErrorJson = JSON.parse(xhr.responseText);
1057        onError(
1058          new GenericError(
1059            xhr.status,
1060            genericErrorJson.message,
1061            genericErrorJson.description
1062          )
1063        );
1064      } catch (error) {
1065        onError(new JsonParseError(xhr.status));
1066      }
1067
1068      return;
1069    };
1070
1071    xhr.open(
1072      'get',
1073      '/search/suggest.json?q=' + encodeURIComponent(query) + '&' + configParams
1074    );
1075
1076    xhr.setRequestHeader('Content-Type', 'application/json');
1077
1078    xhr.send();
1079  }
1080
1081  function Cache(config) {
1082    this._store = {};
1083    this._keys = [];
1084    if (config && config.bucketSize) {
1085      this.bucketSize = config.bucketSize;
1086    } else {
1087      this.bucketSize = 20;
1088    }
1089  }
1090
1091  Cache.prototype.set = function(key, value) {
1092    if (this.count() >= this.bucketSize) {
1093      var deleteKey = this._keys.splice(0, 1);
1094      this.delete(deleteKey);
1095    }
1096
1097    this._keys.push(key);
1098    this._store[key] = value;
1099
1100    return this._store;
1101  };
1102
1103  Cache.prototype.get = function(key) {
1104    return this._store[key];
1105  };
1106
1107  Cache.prototype.has = function(key) {
1108    return Boolean(this._store[key]);
1109  };
1110
1111  Cache.prototype.count = function() {
1112    return Object.keys(this._store).length;
1113  };
1114
1115  Cache.prototype.delete = function(key) {
1116    var exists = Boolean(this._store[key]);
1117    delete this._store[key];
1118    return exists && !this._store[key];
1119  };
1120
1121  function Dispatcher() {
1122    this.events = {};
1123  }
1124
1125  Dispatcher.prototype.on = function(eventName, callback) {
1126    var event = this.events[eventName];
1127    if (!event) {
1128      event = new DispatcherEvent(eventName);
1129      this.events[eventName] = event;
1130    }
1131    event.registerCallback(callback);
1132  };
1133
1134  Dispatcher.prototype.off = function(eventName, callback) {
1135    var event = this.events[eventName];
1136    if (event && event.callbacks.indexOf(callback) > -1) {
1137      event.unregisterCallback(callback);
1138      if (event.callbacks.length === 0) {
1139        delete this.events[eventName];
1140      }
1141    }
1142  };
1143
1144  Dispatcher.prototype.dispatch = function(eventName, payload) {
1145    var event = this.events[eventName];
1146    if (event) {
1147      event.fire(payload);
1148    }
1149  };
1150
1151  function DispatcherEvent(eventName) {
1152    this.eventName = eventName;
1153    this.callbacks = [];
1154  }
1155
1156  DispatcherEvent.prototype.registerCallback = function(callback) {
1157    this.callbacks.push(callback);
1158  };
1159
1160  DispatcherEvent.prototype.unregisterCallback = function(callback) {
1161    var index = this.callbacks.indexOf(callback);
1162    if (index > -1) {
1163      this.callbacks.splice(index, 1);
1164    }
1165  };
1166
1167  DispatcherEvent.prototype.fire = function(payload) {
1168    var callbacks = this.callbacks.slice(0);
1169    callbacks.forEach(function(callback) {
1170      callback(payload);
1171    });
1172  };
1173
1174  function debounce(func, wait) {
1175    var timeout = null;
1176    return function() {
1177      var context = this;
1178      var args = arguments;
1179      clearTimeout(timeout);
1180      timeout = setTimeout(function() {
1181        timeout = null;
1182        func.apply(context, args);
1183      }, wait || 0);
1184    };
1185  }
1186
1187  function objectToQueryParams(obj, parentKey) {
1188    var output = '';
1189    parentKey = parentKey || null;
1190
1191    Object.keys(obj).forEach(function(key) {
1192      var outputKey = key + '=';
1193      if (parentKey) {
1194        outputKey = parentKey + '[' + key + ']';
1195      }
1196
1197      switch (trueTypeOf(obj[key])) {
1198        case 'object':
1199          output += objectToQueryParams(obj[key], parentKey ? outputKey : key);
1200          break;
1201        case 'array':
1202          output += outputKey + '=' + obj[key].join(',') + '&';
1203          break;
1204        default:
1205          if (parentKey) {
1206            outputKey += '=';
1207          }
1208          output += outputKey + encodeURIComponent(obj[key]) + '&';
1209          break;
1210      }
1211    });
1212
1213    return output;
1214  }
1215
1216  function trueTypeOf(obj) {
1217    return Object.prototype.toString
1218      .call(obj)
1219      .slice(8, -1)
1220      .toLowerCase();
1221  }
1222
1223  var DEBOUNCE_RATE = 10;
1224  var requestDebounced = debounce(request, DEBOUNCE_RATE);
1225
1226  function PredictiveSearch(config) {
1227    if (!config) {
1228      throw new TypeError('No config object was specified');
1229    }
1230
1231    this._retryAfter = null;
1232    this._currentQuery = null;
1233
1234    this.dispatcher = new Dispatcher();
1235    this.cache = new Cache({ bucketSize: 40 });
1236    this.configParams = objectToQueryParams(config);
1237  }
1238
1239  PredictiveSearch.TYPES = {
1240    PRODUCT: 'product',
1241    PAGE: 'page',
1242    ARTICLE: 'article'
1243  };
1244
1245  PredictiveSearch.FIELDS = {
1246    AUTHOR: 'author',
1247    BODY: 'body',
1248    PRODUCT_TYPE: 'product_type',
1249    TAG: 'tag',
1250    TITLE: 'title',
1251    VARIANTS_BARCODE: 'variants.barcode',
1252    VARIANTS_SKU: 'variants.sku',
1253    VARIANTS_TITLE: 'variants.title',
1254    VENDOR: 'vendor'
1255  };
1256
1257  PredictiveSearch.UNAVAILABLE_PRODUCTS = {
1258    SHOW: 'show',
1259    HIDE: 'hide',
1260    LAST: 'last'
1261  };
1262
1263  PredictiveSearch.prototype.query = function query(query) {
1264    try {
1265      validateQuery(query);
1266    } catch (error) {
1267      this.dispatcher.dispatch('error', error);
1268      return;
1269    }
1270
1271    if (query === '') {
1272      return this;
1273    }
1274
1275    this._currentQuery = normalizeQuery(query);
1276    var cacheResult = this.cache.get(this._currentQuery);
1277    if (cacheResult) {
1278      this.dispatcher.dispatch('success', cacheResult);
1279      return this;
1280    }
1281
1282    requestDebounced(
1283      this.configParams,
1284      query,
1285      function(result) {
1286        this.cache.set(normalizeQuery(result.query), result);
1287        if (normalizeQuery(result.query) === this._currentQuery) {
1288          this._retryAfter = null;
1289          this.dispatcher.dispatch('success', result);
1290        }
1291      }.bind(this),
1292      function(error) {
1293        if (error.retryAfter) {
1294          this._retryAfter = error.retryAfter;
1295        }
1296        this.dispatcher.dispatch('error', error);
1297      }.bind(this)
1298    );
1299
1300    return this;
1301  };
1302
1303  PredictiveSearch.prototype.on = function on(eventName, callback) {
1304    this.dispatcher.on(eventName, callback);
1305
1306    return this;
1307  };
1308
1309  PredictiveSearch.prototype.off = function on(eventName, callback) {
1310    this.dispatcher.off(eventName, callback);
1311
1312    return this;
1313  };
1314
1315  function normalizeQuery(query) {
1316    if (typeof query !== 'string') {
1317      return null;
1318    }
1319
1320    return query
1321      .trim()
1322      .replace(' ', '-')
1323      .toLowerCase();
1324  }
1325
1326  return PredictiveSearch;
1327})();
1328
1329this.Shopify = this.Shopify || {};
1330this.Shopify.theme = this.Shopify.theme || {};
1331this.Shopify.theme.PredictiveSearchComponent = (function(PredictiveSearch) {
1332  'use strict';
1333
1334  PredictiveSearch =
1335    PredictiveSearch && PredictiveSearch.hasOwnProperty('default')
1336      ? PredictiveSearch['default']
1337      : PredictiveSearch;
1338
1339  var DEFAULT_PREDICTIVE_SEARCH_API_CONFIG = {
1340    resources: {
1341      type: [PredictiveSearch.TYPES.PRODUCT],
1342      options: {
1343        unavailable_products: PredictiveSearch.UNAVAILABLE_PRODUCTS.LAST,
1344        fields: [
1345          PredictiveSearch.FIELDS.TITLE,
1346          PredictiveSearch.FIELDS.VENDOR,
1347          PredictiveSearch.FIELDS.PRODUCT_TYPE,
1348          PredictiveSearch.FIELDS.VARIANTS_TITLE
1349        ]
1350      }
1351    }
1352  };
1353
1354  function PredictiveSearchComponent(config) {
1355    // validate config
1356    if (
1357      !config ||
1358      !config.selectors ||
1359      !config.selectors.input ||
1360      !isString(config.selectors.input) ||
1361      !config.selectors.result ||
1362      !isString(config.selectors.result) ||
1363      !config.resultTemplateFct ||
1364      !isFunction(config.resultTemplateFct) ||
1365      !config.numberOfResultsTemplateFct ||
1366      !isFunction(config.numberOfResultsTemplateFct) ||
1367      !config.loadingResultsMessageTemplateFct ||
1368      !isFunction(config.loadingResultsMessageTemplateFct)
1369    ) {
1370      var error = new TypeError(
1371        'PredictiveSearchComponent config is not valid'
1372      );
1373      error.type = 'argument';
1374      throw error;
1375    }
1376
1377    // Find nodes
1378    this.nodes = findNodes(config.selectors);
1379
1380    // Validate nodes
1381    if (!isValidNodes(this.nodes)) {
1382      // eslint-disable-next-line no-console
1383      console.warn('Could not find valid nodes');
1384      return;
1385    }
1386
1387    // Store the keyword that was used for the search
1388    this._searchKeyword = '';
1389
1390    // Assign result template
1391    this.resultTemplateFct = config.resultTemplateFct;
1392
1393    // Assign number of results template
1394    this.numberOfResultsTemplateFct = config.numberOfResultsTemplateFct;
1395
1396    // Assign loading state template function
1397    this.loadingResultsMessageTemplateFct =
1398      config.loadingResultsMessageTemplateFct;
1399
1400    // Assign number of search results
1401    this.numberOfResults = config.numberOfResults || 4;
1402
1403    // Set classes
1404    this.classes = {
1405      visibleVariant: config.visibleVariant
1406        ? config.visibleVariant
1407        : 'predictive-search-wrapper--visible',
1408      itemSelected: config.itemSelectedClass
1409        ? config.itemSelectedClass
1410        : 'predictive-search-item--selected',
1411      clearButtonVisible: config.clearButtonVisibleClass
1412        ? config.clearButtonVisibleClass
1413        : 'predictive-search__clear-button--visible'
1414    };
1415
1416    this.selectors = {
1417      searchResult: config.searchResult
1418        ? config.searchResult
1419        : '[data-search-result]'
1420    };
1421
1422    // Assign callbacks
1423    this.callbacks = assignCallbacks(config);
1424
1425    // Add input attributes
1426    addInputAttributes(this.nodes.input);
1427
1428    // Add input event listeners
1429    this._addInputEventListeners();
1430
1431    // Add body listener
1432    this._addBodyEventListener();
1433
1434    // Add accessibility announcer
1435    this._addAccessibilityAnnouncer();
1436
1437    // Display the reset button if the input is not empty
1438    this._toggleClearButtonVisibility();
1439
1440    // Instantiate Predictive Search API
1441    this.predictiveSearch = new PredictiveSearch(
1442      config.PredictiveSearchAPIConfig
1443        ? config.PredictiveSearchAPIConfig
1444        : DEFAULT_PREDICTIVE_SEARCH_API_CONFIG
1445    );
1446
1447    // Add predictive search success event listener
1448    this.predictiveSearch.on(
1449      'success',
1450      this._handlePredictiveSearchSuccess.bind(this)
1451    );
1452
1453    // Add predictive search error event listener
1454    this.predictiveSearch.on(
1455      'error',
1456      this._handlePredictiveSearchError.bind(this)
1457    );
1458  }
1459
1460  /**
1461   * Private methods
1462   */
1463  function findNodes(selectors) {
1464    return {
1465      input: document.querySelector(selectors.input),
1466      reset: document.querySelector(selectors.reset),
1467      result: document.querySelector(selectors.result)
1468    };
1469  }
1470
1471  function isValidNodes(nodes) {
1472    if (
1473      !nodes ||
1474      !nodes.input ||
1475      !nodes.result ||
1476      nodes.input.tagName !== 'INPUT'
1477    ) {
1478      return false;
1479    }
1480
1481    return true;
1482  }
1483
1484  function assignCallbacks(config) {
1485    return {
1486      onBodyMousedown: config.onBodyMousedown,
1487      onBeforeOpen: config.onBeforeOpen,
1488      onOpen: config.onOpen,
1489      onBeforeClose: config.onBeforeClose,
1490      onClose: config.onClose,
1491      onInputFocus: config.onInputFocus,
1492      onInputKeyup: config.onInputKeyup,
1493      onInputBlur: config.onInputBlur,
1494      onInputReset: config.onInputReset,
1495      onBeforeDestroy: config.onBeforeDestroy,
1496      onDestroy: config.onDestroy
1497    };
1498  }
1499
1500  function addInputAttributes(input) {
1501    input.setAttribute('autocorrect', 'off');
1502    input.setAttribute('autocomplete', 'off');
1503    input.setAttribute('autocapitalize', 'off');
1504    input.setAttribute('spellcheck', 'false');
1505  }
1506
1507  function removeInputAttributes(input) {
1508    input.removeAttribute('autocorrect', 'off');
1509    input.removeAttribute('autocomplete', 'off');
1510    input.removeAttribute('autocapitalize', 'off');
1511    input.removeAttribute('spellcheck', 'false');
1512  }
1513
1514  /**
1515   * Public variables
1516   */
1517  PredictiveSearchComponent.prototype.isResultVisible = false;
1518  PredictiveSearchComponent.prototype.results = {};
1519
1520  /**
1521   * "Private" variables
1522   */
1523  PredictiveSearchComponent.prototype._latencyTimer = null;
1524  PredictiveSearchComponent.prototype._resultNodeClicked = false;
1525
1526  /**
1527   * "Private" instance methods
1528   */
1529  PredictiveSearchComponent.prototype._addInputEventListeners = function() {
1530    var input = this.nodes.input;
1531    var reset = this.nodes.reset;
1532
1533    if (!input) {
1534      return;
1535    }
1536
1537    this._handleInputFocus = this._handleInputFocus.bind(this);
1538    this._handleInputBlur = this._handleInputBlur.bind(this);
1539    this._handleInputKeyup = this._handleInputKeyup.bind(this);
1540    this._handleInputKeydown = this._handleInputKeydown.bind(this);
1541
1542    input.addEventListener('focus', this._handleInputFocus);
1543    input.addEventListener('blur', this._handleInputBlur);
1544    input.addEventListener('keyup', this._handleInputKeyup);
1545    input.addEventListener('keydown', this._handleInputKeydown);
1546
1547    if (reset) {
1548      this._handleInputReset = this._handleInputReset.bind(this);
1549      reset.addEventListener('click', this._handleInputReset);
1550    }
1551  };
1552
1553  PredictiveSearchComponent.prototype._removeInputEventListeners = function() {
1554    var input = this.nodes.input;
1555
1556    input.removeEventListener('focus', this._handleInputFocus);
1557    input.removeEventListener('blur', this._handleInputBlur);
1558    input.removeEventListener('keyup', this._handleInputKeyup);
1559    input.removeEventListener('keydown', this._handleInputKeydown);
1560  };
1561
1562  PredictiveSearchComponent.prototype._addBodyEventListener = function() {
1563    this._handleBodyMousedown = this._handleBodyMousedown.bind(this);
1564
1565    document
1566      .querySelector('body')
1567      .addEventListener('mousedown', this._handleBodyMousedown);
1568  };
1569
1570  PredictiveSearchComponent.prototype._removeBodyEventListener = function() {
1571    document
1572      .querySelector('body')
1573      .removeEventListener('mousedown', this._handleBodyMousedown);
1574  };
1575
1576  PredictiveSearchComponent.prototype._removeClearButtonEventListener = function() {
1577    var reset = this.nodes.reset;
1578
1579    if (!reset) {
1580      return;
1581    }
1582
1583    reset.removeEventListener('click', this._handleInputReset);
1584  };
1585
1586  /**
1587   * Event handlers
1588   */
1589  PredictiveSearchComponent.prototype._handleBodyMousedown = function(evt) {
1590    if (this.isResultVisible && this.nodes !== null) {
1591      if (
1592        evt.target.isEqualNode(this.nodes.input) ||
1593        this.nodes.input.contains(evt.target) ||
1594        evt.target.isEqualNode(this.nodes.result) ||
1595        this.nodes.result.contains(evt.target)
1596      ) {
1597        this._resultNodeClicked = true;
1598      } else {
1599        if (isFunction(this.callbacks.onBodyMousedown)) {
1600          var returnedValue = this.callbacks.onBodyMousedown(this.nodes);
1601          if (isBoolean(returnedValue) && returnedValue) {
1602            this.close();
1603          }
1604        } else {
1605          this.close();
1606        }
1607      }
1608    }
1609  };
1610
1611  PredictiveSearchComponent.prototype._handleInputFocus = function(evt) {
1612    if (isFunction(this.callbacks.onInputFocus)) {
1613      var returnedValue = this.callbacks.onInputFocus(this.nodes);
1614      if (isBoolean(returnedValue) && !returnedValue) {
1615        return false;
1616      }
1617    }
1618
1619    if (evt.target.value.length > 0) {
1620      this._search();
1621    }
1622
1623    return true;
1624  };
1625
1626  PredictiveSearchComponent.prototype._handleInputBlur = function() {
1627    // This has to be done async, to wait for the focus to be on the next
1628    // element and avoid closing the results.
1629    // Example: Going from the input to the reset button.
1630    setTimeout(
1631      function() {
1632        if (isFunction(this.callbacks.onInputBlur)) {
1633          var returnedValue = this.callbacks.onInputBlur(this.nodes);
1634          if (isBoolean(returnedValue) && !returnedValue) {
1635            return false;
1636          }
1637        }
1638
1639        if (document.activeElement.isEqualNode(this.nodes.reset)) {
1640          return false;
1641        }
1642
1643        if (this._resultNodeClicked) {
1644          this._resultNodeClicked = false;
1645          return false;
1646        }
1647
1648        this.close();
1649      }.bind(this)
1650    );
1651
1652    return true;
1653  };
1654
1655  PredictiveSearchComponent.prototype._addAccessibilityAnnouncer = function() {
1656    this._accessibilityAnnouncerDiv = window.document.createElement('div');
1657
1658    this._accessibilityAnnouncerDiv.setAttribute(
1659      'style',
1660      'position: absolute !important; overflow: hidden; clip: rect(0 0 0 0); height: 1px; width: 1px; margin: -1px; padding: 0; border: 0;'
1661    );
1662
1663    this._accessibilityAnnouncerDiv.setAttribute('data-search-announcer', '');
1664    this._accessibilityAnnouncerDiv.setAttribute('aria-live', 'polite');
1665    this._accessibilityAnnouncerDiv.setAttribute('aria-atomic', 'true');
1666
1667    this.nodes.result.parentElement.appendChild(
1668      this._accessibilityAnnouncerDiv
1669    );
1670  };
1671
1672  PredictiveSearchComponent.prototype._removeAccessibilityAnnouncer = function() {
1673    this.nodes.result.parentElement.removeChild(
1674      this._accessibilityAnnouncerDiv
1675    );
1676  };
1677
1678  PredictiveSearchComponent.prototype._updateAccessibilityAttributesAfterSelectingElement = function(
1679    previousSelectedElement,
1680    currentSelectedElement
1681  ) {
1682    // Update the active descendant on the search input
1683    this.nodes.input.setAttribute(
1684      'aria-activedescendant',
1685      currentSelectedElement.id
1686    );
1687
1688    // Unmark the previousSelected elemented as selected
1689    if (previousSelectedElement) {
1690      previousSelectedElement.removeAttribute('aria-selected');
1691    }
1692
1693    // Mark the element as selected
1694    currentSelectedElement.setAttribute('aria-selected', true);
1695  };
1696
1697  PredictiveSearchComponent.prototype._clearAriaActiveDescendant = function() {
1698    this.nodes.input.setAttribute('aria-activedescendant', '');
1699  };
1700
1701  PredictiveSearchComponent.prototype._announceNumberOfResultsFound = function(
1702    results
1703  ) {
1704    var currentAnnouncedMessage = this._accessibilityAnnouncerDiv.innerHTML;
1705    var newMessage = this.numberOfResultsTemplateFct(results);
1706
1707    // If the messages are the same, they won't get announced
1708    // add white space so it gets announced
1709    if (currentAnnouncedMessage === newMessage) {
1710      newMessage = newMessage + '&nbsp;';
1711    }
1712
1713    this._accessibilityAnnouncerDiv.innerHTML = newMessage;
1714  };
1715
1716  PredictiveSearchComponent.prototype._announceLoadingState = function() {
1717    this._accessibilityAnnouncerDiv.innerHTML = this.loadingResultsMessageTemplateFct();
1718  };
1719
1720  PredictiveSearchComponent.prototype._handleInputKeyup = function(evt) {
1721    var UP_ARROW_KEY_CODE = 38;
1722    var DOWN_ARROW_KEY_CODE = 40;
1723    var RETURN_KEY_CODE = 13;
1724    var ESCAPE_KEY_CODE = 27;
1725
1726    if (isFunction(this.callbacks.onInputKeyup)) {
1727      var returnedValue = this.callbacks.onInputKeyup(this.nodes);
1728      if (isBoolean(returnedValue) && !returnedValue) {
1729        return false;
1730      }
1731    }
1732
1733    this._toggleClearButtonVisibility();
1734
1735    if (this.isResultVisible && this.nodes !== null) {
1736      if (evt.keyCode === UP_ARROW_KEY_CODE) {
1737        this._navigateOption(evt, 'UP');
1738        return true;
1739      }
1740
1741      if (evt.keyCode === DOWN_ARROW_KEY_CODE) {
1742        this._navigateOption(evt, 'DOWN');
1743        return true;
1744      }
1745
1746      if (evt.keyCode === RETURN_KEY_CODE) {
1747        this._selectOption();
1748        return true;
1749      }
1750
1751      if (evt.keyCode === ESCAPE_KEY_CODE) {
1752        this.close();
1753      }
1754    }
1755
1756    if (evt.target.value.length <= 0) {
1757      this.close();
1758      this._setKeyword('');
1759    } else if (evt.target.value.length > 0) {
1760      this._search();
1761    }
1762
1763    return true;
1764  };
1765
1766  PredictiveSearchComponent.prototype._handleInputKeydown = function(evt) {
1767    var RETURN_KEY_CODE = 13;
1768    var UP_ARROW_KEY_CODE = 38;
1769    var DOWN_ARROW_KEY_CODE = 40;
1770
1771    // Prevent the form default submission if there is a selected option
1772    if (evt.keyCode === RETURN_KEY_CODE && this._getSelectedOption() !== null) {
1773      evt.preventDefault();
1774    }
1775
1776    // Prevent the cursor from moving in the input when using the up and down arrow keys
1777    if (
1778      evt.keyCode === UP_ARROW_KEY_CODE ||
1779      evt.keyCode === DOWN_ARROW_KEY_CODE
1780    ) {
1781      evt.preventDefault();
1782    }
1783  };
1784
1785  PredictiveSearchComponent.prototype._handleInputReset = function(evt) {
1786    evt.preventDefault();
1787
1788    if (isFunction(this.callbacks.onInputReset)) {
1789      var returnedValue = this.callbacks.onInputReset(this.nodes);
1790      if (isBoolean(returnedValue) && !returnedValue) {
1791        return false;
1792      }
1793    }
1794
1795    this.nodes.input.value = '';
1796    this.nodes.input.focus();
1797    this._toggleClearButtonVisibility();
1798    this.close();
1799
1800    return true;
1801  };
1802
1803  PredictiveSearchComponent.prototype._navigateOption = function(
1804    evt,
1805    direction
1806  ) {
1807    var currentOption = this._getSelectedOption();
1808
1809    if (!currentOption) {
1810      var firstOption = this.nodes.result.querySelector(
1811        this.selectors.searchResult
1812      );
1813      firstOption.classList.add(this.classes.itemSelected);
1814      this._updateAccessibilityAttributesAfterSelectingElement(
1815        null,
1816        firstOption
1817      );
1818    } else {
1819      if (direction === 'DOWN') {
1820        var nextOption = currentOption.nextElementSibling;
1821        if (nextOption) {
1822          currentOption.classList.remove(this.classes.itemSelected);
1823          nextOption.classList.add(this.classes.itemSelected);
1824          this._updateAccessibilityAttributesAfterSelectingElement(
1825            currentOption,
1826            nextOption
1827          );
1828        }
1829      } else {
1830        var previousOption = currentOption.previousElementSibling;
1831        if (previousOption) {
1832          currentOption.classList.remove(this.classes.itemSelected);
1833          previousOption.classList.add(this.classes.itemSelected);
1834          this._updateAccessibilityAttributesAfterSelectingElement(
1835            currentOption,
1836            previousOption
1837          );
1838        }
1839      }
1840    }
1841  };
1842
1843  PredictiveSearchComponent.prototype._getSelectedOption = function() {
1844    return this.nodes.result.querySelector('.' + this.classes.itemSelected);
1845  };
1846
1847  PredictiveSearchComponent.prototype._selectOption = function() {
1848    var selectedOption = this._getSelectedOption();
1849
1850    if (selectedOption) {
1851      selectedOption.querySelector('a, button').click();
1852    }
1853  };
1854
1855  PredictiveSearchComponent.prototype._search = function() {
1856    var newSearchKeyword = this.nodes.input.value;
1857
1858    if (this._searchKeyword === newSearchKeyword) {
1859      return;
1860    }
1861
1862    clearTimeout(this._latencyTimer);
1863    this._latencyTimer = setTimeout(
1864      function() {
1865        this.results.isLoading = true;
1866
1867        // Annonuce that we're loading the results
1868        this._announceLoadingState();
1869
1870        this.nodes.result.classList.add(this.classes.visibleVariant);
1871        // NOTE: We could benifit in using DOMPurify.
1872        // https://github.com/cure53/DOMPurify
1873        this.nodes.result.innerHTML = this.resultTemplateFct(this.results);
1874      }.bind(this),
1875      500
1876    );
1877
1878    this.predictiveSearch.query(newSearchKeyword);
1879    this._setKeyword(newSearchKeyword);
1880  };
1881
1882  PredictiveSearchComponent.prototype._handlePredictiveSearchSuccess = function(
1883    json
1884  ) {
1885    clearTimeout(this._latencyTimer);
1886    this.results = json.resources.results;
1887
1888    this.results.isLoading = false;
1889    this.results.products = this.results.products.slice(
1890      0,
1891      this.numberOfResults
1892    );
1893    this.results.canLoadMore =
1894      this.numberOfResults <= this.results.products.length;
1895    this.results.searchQuery = this.nodes.input.value;
1896
1897    if (this.results.products.length > 0 || this.results.searchQuery) {
1898      this.nodes.result.innerHTML = this.resultTemplateFct(this.results);
1899      this._announceNumberOfResultsFound(this.results);
1900      this.open();
1901    } else {
1902      this.nodes.result.innerHTML = '';
1903
1904      this._closeOnNoResults();
1905    }
1906  };
1907
1908  PredictiveSearchComponent.prototype._handlePredictiveSearchError = function() {
1909    clearTimeout(this._latencyTimer);
1910    this.nodes.result.innerHTML = '';
1911
1912    this._closeOnNoResults();
1913  };
1914
1915  PredictiveSearchComponent.prototype._closeOnNoResults = function() {
1916    if (this.nodes) {
1917      this.nodes.result.classList.remove(this.classes.visibleVariant);
1918    }
1919
1920    this.isResultVisible = false;
1921  };
1922
1923  PredictiveSearchComponent.prototype._setKeyword = function(keyword) {
1924    this._searchKeyword = keyword;
1925  };
1926
1927  PredictiveSearchComponent.prototype._toggleClearButtonVisibility = function() {
1928    if (!this.nodes.reset) {
1929      return;
1930    }
1931
1932    if (this.nodes.input.value.length > 0) {
1933      this.nodes.reset.classList.add(this.classes.clearButtonVisible);
1934    } else {
1935      this.nodes.reset.classList.remove(this.classes.clearButtonVisible);
1936    }
1937  };
1938
1939  /**
1940   * Public methods
1941   */
1942  PredictiveSearchComponent.prototype.open = function() {
1943    if (this.isResultVisible) {
1944      return;
1945    }
1946
1947    if (isFunction(this.callbacks.onBeforeOpen)) {
1948      var returnedValue = this.callbacks.onBeforeOpen(this.nodes);
1949      if (isBoolean(returnedValue) && !returnedValue) {
1950        return false;
1951      }
1952    }
1953
1954    this.nodes.result.classList.add(this.classes.visibleVariant);
1955    this.nodes.input.setAttribute('aria-expanded', true);
1956    this.isResultVisible = true;
1957
1958    if (isFunction(this.callbacks.onOpen)) {
1959      return this.callbacks.onOpen(this.nodes) || true;
1960    }
1961
1962    return true;
1963  };
1964
1965  PredictiveSearchComponent.prototype.close = function() {
1966    if (!this.isResultVisible) {
1967      return true;
1968    }
1969
1970    if (isFunction(this.callbacks.onBeforeClose)) {
1971      var returnedValue = this.callbacks.onBeforeClose(this.nodes);
1972      if (isBoolean(returnedValue) && !returnedValue) {
1973        return false;
1974      }
1975    }
1976
1977    if (this.nodes) {
1978      this.nodes.result.classList.remove(this.classes.visibleVariant);
1979    }
1980
1981    this.nodes.input.setAttribute('aria-expanded', false);
1982    this._clearAriaActiveDescendant();
1983    this._setKeyword('');
1984
1985    if (isFunction(this.callbacks.onClose)) {
1986      this.callbacks.onClose(this.nodes);
1987    }
1988
1989    this.isResultVisible = false;
1990    this.results = {};
1991
1992    return true;
1993  };
1994
1995  PredictiveSearchComponent.prototype.destroy = function() {
1996    this.close();
1997
1998    if (isFunction(this.callbacks.onBeforeDestroy)) {
1999      var returnedValue = this.callbacks.onBeforeDestroy(this.nodes);
2000      if (isBoolean(returnedValue) && !returnedValue) {
2001        return false;
2002      }
2003    }
2004
2005    this.nodes.result.classList.remove(this.classes.visibleVariant);
2006    removeInputAttributes(this.nodes.input);
2007    this._removeInputEventListeners();
2008    this._removeBodyEventListener();
2009    this._removeAccessibilityAnnouncer();
2010    this._removeClearButtonEventListener();
2011
2012    if (isFunction(this.callbacks.onDestroy)) {
2013      this.callbacks.onDestroy(this.nodes);
2014    }
2015
2016    return true;
2017  };
2018
2019  PredictiveSearchComponent.prototype.clearAndClose = function() {
2020    this.nodes.input.value = '';
2021    this.close();
2022  };
2023
2024  /**
2025   * Utilities
2026   */
2027  function getTypeOf(value) {
2028    return Object.prototype.toString.call(value);
2029  }
2030
2031  function isString(value) {
2032    return getTypeOf(value) === '[object String]';
2033  }
2034
2035  function isBoolean(value) {
2036    return getTypeOf(value) === '[object Boolean]';
2037  }
2038
2039  function isFunction(value) {
2040    return getTypeOf(value) === '[object Function]';
2041  }
2042
2043  return PredictiveSearchComponent;
2044})(Shopify.theme.PredictiveSearch);
2045
2046
2047/* ================ GLOBAL ================ */
2048/*============================================================================
2049  Drawer modules
2050==============================================================================*/
2051theme.Drawers = (function() {
2052  function Drawer(id, position, options) {
2053    var DEFAULT_OPEN_CLASS = 'js-drawer-open';
2054    var DEFAULT_CLOSE_CLASS = 'js-drawer-close';
2055
2056    var defaults = {
2057      selectors: {
2058        openVariant: '.' + DEFAULT_OPEN_CLASS + '-' + position,
2059        close: '.' + DEFAULT_CLOSE_CLASS
2060      },
2061      classes: {
2062        open: DEFAULT_OPEN_CLASS,
2063        openVariant: DEFAULT_OPEN_CLASS + '-' + position
2064      },
2065      withPredictiveSearch: false
2066    };
2067
2068    this.nodes = {
2069      $parent: $('html').add('body'),
2070      $page: $('#PageContainer')
2071    };
2072
2073    this.config = $.extend(defaults, options);
2074    this.position = position;
2075    this.$drawer = $('#' + id);
2076
2077    if (!this.$drawer.length) {
2078      return false;
2079    }
2080
2081    this.drawerIsOpen = false;
2082    this.init();
2083  }
2084
2085  Drawer.prototype.init = function() {
2086    $(this.config.selectors.openVariant).on('click', $.proxy(this.open, this));
2087    this.$drawer.on(
2088      'click',
2089      this.config.selectors.close,
2090      $.proxy(this.close, this)
2091    );
2092  };
2093
2094  Drawer.prototype.open = function(evt) {
2095    // Keep track if drawer was opened from a click, or called by another function
2096    var externalCall = false;
2097
2098    // Prevent following href if link is clicked
2099    if (evt) {
2100      evt.preventDefault();
2101    } else {
2102      externalCall = true;
2103    }
2104
2105    // Without this, the drawer opens, the click event bubbles up to nodes.$page
2106    // which closes the drawer.
2107    if (evt && evt.stopPropagation) {
2108      evt.stopPropagation();
2109      // save the source of the click, we'll focus to this on close
2110      this.$activeSource = $(evt.currentTarget);
2111    }
2112
2113    if (this.drawerIsOpen && !externalCall) {
2114      return this.close();
2115    }
2116
2117    // Add is-transitioning class to moved elements on open so drawer can have
2118    // transition for close animation
2119    if (!this.config.withPredictiveSearch) {
2120      this.$drawer.prepareTransition();
2121    }
2122
2123    this.nodes.$parent.addClass(
2124      this.config.classes.open + ' ' + this.config.classes.openVariant
2125    );
2126    this.drawerIsOpen = true;
2127
2128    // Run function when draw opens if set
2129    if (
2130      this.config.onDrawerOpen &&
2131      typeof this.config.onDrawerOpen === 'function'
2132    ) {
2133      if (!externalCall) {
2134        this.config.onDrawerOpen();
2135      }
2136    }
2137
2138    if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
2139      this.$activeSource.attr('aria-expanded', 'true');
2140    }
2141
2142    // Set focus on drawer
2143    var trapFocusConfig = {
2144      $container: this.$drawer,
2145      namespace: 'drawer_focus'
2146    };
2147    if (this.config.$elementToFocusOnOpen) {
2148      trapFocusConfig.$elementToFocus = this.config.$elementToFocusOnOpen;
2149    }
2150
2151    slate.a11y.trapFocus(trapFocusConfig);
2152
2153    this.bindEvents();
2154
2155    return this;
2156  };
2157
2158  Drawer.prototype.close = function() {
2159    if (!this.drawerIsOpen) {
2160      // don't close a closed drawer
2161      return;
2162    }
2163
2164    // deselect any focused form elements
2165    $(document.activeElement).trigger('blur');
2166
2167    // Ensure closing transition is applied to moved elements, like the nav
2168    if (!this.config.withPredictiveSearch) {
2169      this.$drawer.prepareTransition();
2170    }
2171
2172    this.nodes.$parent.removeClass(
2173      this.config.classes.open + ' ' + this.config.classes.openVariant
2174    );
2175
2176    if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
2177      this.$activeSource.attr('aria-expanded', 'false');
2178    }
2179
2180    this.drawerIsOpen = false;
2181
2182    // Remove focus on drawer
2183    slate.a11y.removeTrapFocus({
2184      $container: this.$drawer,
2185      namespace: 'drawer_focus'
2186    });
2187
2188    this.unbindEvents();
2189
2190    // Run function when draw closes if set
2191    if (
2192      this.config.onDrawerClose &&
2193      typeof this.config.onDrawerClose === 'function'
2194    ) {
2195      this.config.onDrawerClose();
2196    }
2197  };
2198
2199  Drawer.prototype.bindEvents = function() {
2200    this.nodes.$parent.on(
2201      'keyup.drawer',
2202      $.proxy(function(evt) {
2203        // close on 'esc' keypress
2204        if (evt.keyCode === 27) {
2205          this.close();
2206          return false;
2207        } else {
2208          return true;
2209        }
2210      }, this)
2211    );
2212
2213    // Lock scrolling on mobile
2214    this.nodes.$page.on('touchmove.drawer', function() {
2215      return false;
2216    });
2217
2218    this.nodes.$page.on(
2219      'click.drawer',
2220      $.proxy(function() {
2221        this.close();
2222        return false;
2223      }, this)
2224    );
2225  };
2226
2227  Drawer.prototype.unbindEvents = function() {
2228    this.nodes.$page.off('.drawer');
2229    this.nodes.$parent.off('.drawer');
2230  };
2231
2232  return Drawer;
2233})();
2234
2235theme.Helpers = (function() {
2236  var touchDevice = false;
2237
2238  function setTouch() {
2239    touchDevice = true;
2240  }
2241
2242  function isTouch() {
2243    return touchDevice;
2244  }
2245  return {
2246    setTouch: setTouch,
2247    isTouch: isTouch
2248  };
2249})();
2250
2251theme.LibraryLoader = (function() {
2252  var types = {
2253    link: 'link',
2254    script: 'script'
2255  };
2256
2257  var status = {
2258    requested: 'requested',
2259    loaded: 'loaded'
2260  };
2261
2262  var cloudCdn = 'https://cdn.shopify.com/shopifycloud/';
2263
2264  var libraries = {
2265    youtubeSdk: {
2266      tagId: 'youtube-sdk',
2267      src: 'https://www.youtube.com/iframe_api',
2268      type: types.script
2269    },
2270    plyrShopifyStyles: {
2271      tagId: 'plyr-shopify-styles',
2272      src: cloudCdn + 'shopify-plyr/v1.0/shopify-plyr.css',
2273      type: types.link
2274    },
2275    modelViewerUiStyles: {
2276      tagId: 'shopify-model-viewer-ui-styles',
2277      src: cloudCdn + 'model-viewer-ui/assets/v1.0/model-viewer-ui.css',
2278      type: types.link
2279    }
2280  };
2281
2282  function load(libraryName, callback) {
2283    var library = libraries[libraryName];
2284
2285    if (!library) return;
2286    if (library.status === status.requested) return;
2287
2288    callback = callback || function() {};
2289    if (library.status === status.loaded) {
2290      callback();
2291      return;
2292    }
2293
2294    library.status = status.requested;
2295
2296    var tag;
2297
2298    switch (library.type) {
2299      case types.script:
2300        tag = createScriptTag(library, callback);
2301        break;
2302      case types.link:
2303        tag = createLinkTag(library, callback);
2304        break;
2305    }
2306
2307    tag.id = library.tagId;
2308    library.element = tag;
2309
2310    var firstScriptTag = document.getElementsByTagName(library.type)[0];
2311    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
2312  }
2313
2314  function createScriptTag(library, callback) {
2315    var tag = document.createElement('script');
2316    tag.src = library.src;
2317    tag.addEventListener('load', function() {
2318      library.status = status.loaded;
2319      callback();
2320    });
2321    return tag;
2322  }
2323
2324  function createLinkTag(library, callback) {
2325    var tag = document.createElement('link');
2326    tag.href = library.src;
2327    tag.rel = 'stylesheet';
2328    tag.type = 'text/css';
2329    tag.addEventListener('load', function() {
2330      library.status = status.loaded;
2331      callback();
2332    });
2333    return tag;
2334  }
2335
2336  return {
2337    load: load
2338  };
2339})();
2340
2341
2342/* ================ MODULES ================ */
2343window.theme = window.theme || {};
2344
2345theme.Header = (function() {
2346  var selectors = {
2347    body: 'body',
2348    navigation: '#AccessibleNav',
2349    siteNavHasDropdown: '[data-has-dropdowns]',
2350    siteNavChildLinks: '.site-nav__child-link',
2351    siteNavActiveDropdown: '.site-nav--active-dropdown',
2352    siteNavHasCenteredDropdown: '.site-nav--has-centered-dropdown',
2353    siteNavCenteredDropdown: '.site-nav__dropdown--centered',
2354    siteNavLinkMain: '.site-nav__link--main',
2355    siteNavChildLink: '.site-nav__link--last',
2356    siteNavDropdown: '.site-nav__dropdown',
2357    siteHeader: '.site-header'
2358  };
2359
2360  var config = {
2361    activeClass: 'site-nav--active-dropdown',
2362    childLinkClass: 'site-nav__child-link',
2363    rightDropdownClass: 'site-nav__dropdown--right',
2364    leftDropdownClass: 'site-nav__dropdown--left'
2365  };
2366
2367  var cache = {};
2368
2369  function init() {
2370    cacheSelectors();
2371    styleDropdowns($(selectors.siteNavHasDropdown));
2372    positionFullWidthDropdowns();
2373
2374    cache.$parents.on('click.siteNav', function() {
2375      var $el = $(this);
2376      $el.hasClass(config.activeClass) ? hideDropdown($el) : showDropdown($el);
2377    });
2378
2379    // check when we're leaving a dropdown and close the active dropdown
2380    $(selectors.siteNavChildLink).on('focusout.siteNav', function() {
2381      setTimeout(function() {
2382        if (
2383          $(document.activeElement).hasClass(config.childLinkClass) ||
2384          !cache.$activeDropdown.length
2385        ) {
2386          return;
2387        }
2388
2389        hideDropdown(cache.$activeDropdown);
2390      });
2391    });
2392
2393    // close dropdowns when on top level nav
2394    cache.$topLevel.on('focus.siteNav', function() {
2395      if (cache.$activeDropdown.length) {
2396        hideDropdown(cache.$activeDropdown);
2397      }
2398    });
2399
2400    cache.$subMenuLinks.on('click.siteNav', function(evt) {
2401      // Prevent click on body from firing instead of link
2402      evt.stopImmediatePropagation();
2403    });
2404
2405    $(window).resize(
2406      $.debounce(50, function() {
2407        styleDropdowns($(selectors.siteNavHasDropdown));
2408        positionFullWidthDropdowns();
2409      })
2410    );
2411  }
2412
2413  function cacheSelectors() {
2414    cache = {
2415      $nav: $(selectors.navigation),
2416      $topLevel: $(selectors.siteNavLinkMain),
2417      $parents: $(selectors.navigation).find(selectors.siteNavHasDropdown),
2418      $subMenuLinks: $(selectors.siteNavChildLinks),
2419      $activeDropdown: $(selectors.siteNavActiveDropdown),
2420      $siteHeader: $(selectors.siteHeader)
2421    };
2422  }
2423
2424  function showDropdown($el) {
2425    $el.addClass(config.activeClass);
2426
2427    // close open dropdowns
2428    if (cache.$activeDropdown.length) {
2429      hideDropdown(cache.$activeDropdown);
2430    }
2431
2432    cache.$activeDropdown = $el;
2433
2434    // set expanded on open dropdown
2435    $el.find(selectors.siteNavLinkMain).attr('aria-expanded', 'true');
2436
2437    setTimeout(function() {
2438      $(window).on('keyup.siteNav', function(evt) {
2439        if (evt.keyCode === 27) {
2440          hideDropdown($el);
2441        }
2442      });
2443
2444      $(selectors.body).on('click.siteNav', function() {
2445        hideDropdown($el);
2446      });
2447    }, 250);
2448  }
2449
2450  function hideDropdown($el) {
2451    // remove aria on open dropdown
2452    $el.find(selectors.siteNavLinkMain).attr('aria-expanded', 'false');
2453    $el.removeClass(config.activeClass);
2454
2455    // reset active dropdown
2456    cache.$activeDropdown = $(selectors.siteNavActiveDropdown);
2457
2458    $(selectors.body).off('click.siteNav');
2459    $(window).off('keyup.siteNav');
2460  }
2461
2462  function styleDropdowns($dropdownListItems) {
2463    $dropdownListItems.each(function() {
2464      var $dropdownLi = $(this).find(selectors.siteNavDropdown);
2465      if (!$dropdownLi.length) {
2466        return;
2467      }
2468      var isRightOfLogo =
2469        Math.ceil($(this).offset().left) >
2470        Math.floor(cache.$siteHeader.outerWidth()) / 2
2471          ? true
2472          : false;
2473      if (isRightOfLogo) {
2474        $dropdownLi
2475          .removeClass(config.leftDropdownClass)
2476          .addClass(config.rightDropdownClass);
2477      } else {
2478        $dropdownLi
2479          .removeClass(config.rightDropdownClass)
2480          .addClass(config.leftDropdownClass);
2481      }
2482    });
2483  }
2484
2485  function positionFullWidthDropdowns() {
2486    var $listWithCenteredDropdown = $(selectors.siteNavHasCenteredDropdown);
2487
2488    $listWithCenteredDropdown.each(function() {
2489      var $hasCenteredDropdown = $(this);
2490      var $fullWidthDropdown = $hasCenteredDropdown.find(
2491        selectors.siteNavCenteredDropdown
2492      );
2493
2494      var fullWidthDropdownOffset = $hasCenteredDropdown.position().top + 41;
2495      $fullWidthDropdown.css('top', fullWidthDropdownOffset);
2496    });
2497  }
2498
2499  function unload() {
2500    $(window).off('.siteNav');
2501    cache.$parents.off('.siteNav');
2502    cache.$subMenuLinks.off('.siteNav');
2503    cache.$topLevel.off('.siteNav');
2504    $(selectors.siteNavChildLink).off('.siteNav');
2505    $(selectors.body).off('.siteNav');
2506  }
2507
2508  return {
2509    init: init,
2510    unload: unload
2511  };
2512})();
2513
2514window.theme = window.theme || {};
2515
2516theme.MobileNav = (function() {
2517  var classes = {
2518    mobileNavOpenIcon: 'mobile-nav--open',
2519    mobileNavCloseIcon: 'mobile-nav--close',
2520    navLinkWrapper: 'mobile-nav__item',
2521    navLink: 'mobile-nav__link',
2522    subNavLink: 'mobile-nav__sublist-link',
2523    return: 'mobile-nav__return-btn',
2524    subNavActive: 'is-active',
2525    subNavClosing: 'is-closing',
2526    navOpen: 'js-menu--is-open',
2527    subNavShowing: 'sub-nav--is-open',
2528    thirdNavShowing: 'third-nav--is-open',
2529    subNavToggleBtn: 'js-toggle-submenu'
2530  };
2531  var cache = {};
2532  var isTransitioning;
2533  var $activeSubNav;
2534  var $activeTrigger;
2535  var menuLevel = 1;
2536  // Breakpoints from src/stylesheets/global/variables.scss.liquid
2537  var mediaQuerySmall = 'screen and (max-width: 749px)';
2538
2539  function init() {
2540    cacheSelectors();
2541
2542    cache.$mobileNavToggle.on('click', toggleMobileNav);
2543    cache.$subNavToggleBtn.on('click.subNav', toggleSubNav);
2544
2545    // Close mobile nav when unmatching mobile breakpoint
2546    enquire.register(mediaQuerySmall, {
2547      unmatch: function() {
2548        if (cache.$mobileNavContainer.hasClass(classes.navOpen)) {
2549          closeMobileNav();
2550        }
2551      }
2552    });
2553  }
2554
2555  function toggleMobileNav() {
2556    if (cache.$mobileNavToggle.hasClass(classes.mobileNavCloseIcon)) {
2557      closeMobileNav();
2558    } else {
2559      openMobileNav();
2560    }
2561  }
2562
2563  function cacheSelectors() {
2564    cache = {
2565      $pageContainer: $('#PageContainer'),
2566      $siteHeader: $('.site-header'),
2567      $mobileNavToggle: $('.js-mobile-nav-toggle'),
2568      $mobileNavContainer: $('.mobile-nav-wrapper'),
2569      $mobileNav: $('#MobileNav'),
2570      $sectionHeader: $('#shopify-section-header'),
2571      $subNavToggleBtn: $('.' + classes.subNavToggleBtn)
2572    };
2573  }
2574
2575  function openMobileNav() {
2576    var translateHeaderHeight = cache.$siteHeader.outerHeight();
2577
2578    cache.$mobileNavContainer.prepareTransition().addClass(classes.navOpen);
2579
2580    cache.$mobileNavContainer.css({
2581      transform: 'translateY(' + translateHeaderHeight + 'px)'
2582    });
2583
2584    cache.$pageContainer.css({
2585      transform:
2586        'translate3d(0, ' + cache.$mobileNavContainer[0].scrollHeight + 'px, 0)'
2587    });
2588
2589    slate.a11y.trapFocus({
2590      $container: cache.$sectionHeader,
2591      $elementToFocus: cache.$mobileNavToggle,
2592      namespace: 'navFocus'
2593    });
2594
2595    cache.$mobileNavToggle
2596      .addClass(classes.mobileNavCloseIcon)
2597      .removeClass(classes.mobileNavOpenIcon)
2598      .attr('aria-expanded', true);
2599
2600    // close on escape
2601    $(window).on('keyup.mobileNav', function(evt) {
2602      if (evt.which === 27) {
2603        closeMobileNav();
2604      }
2605    });
2606  }
2607
2608  function closeMobileNav() {
2609    cache.$mobileNavContainer.prepareTransition().removeClass(classes.navOpen);
2610
2611    cache.$mobileNavContainer.css({
2612      transform: 'translateY(-100%)'
2613    });
2614
2615    cache.$pageContainer.removeAttr('style');
2616
2617    slate.a11y.trapFocus({
2618      $container: $('html'),
2619      $elementToFocus: $('body')
2620    });
2621
2622    cache.$mobileNavContainer.one(
2623      'TransitionEnd.navToggle webkitTransitionEnd.navToggle transitionend.navToggle oTransitionEnd.navToggle',
2624      function() {
2625        slate.a11y.removeTrapFocus({
2626          $container: cache.$mobileNav,
2627          namespace: 'navFocus'
2628        });
2629      }
2630    );
2631
2632    cache.$mobileNavToggle
2633      .addClass(classes.mobileNavOpenIcon)
2634      .removeClass(classes.mobileNavCloseIcon)
2635      .attr('aria-expanded', false)
2636      .focus();
2637
2638    $(window).off('keyup.mobileNav');
2639
2640    scrollTo(0, 0);
2641  }
2642
2643  function toggleSubNav(evt) {
2644    if (isTransitioning) {
2645      return;
2646    }
2647
2648    var $toggleBtn = $(evt.currentTarget);
2649    var isReturn = $toggleBtn.hasClass(classes.return);
2650    isTransitioning = true;
2651
2652    if (isReturn) {
2653      // Close all subnavs by removing active class on buttons
2654      $(
2655        '.' + classes.subNavToggleBtn + '[data-level="' + (menuLevel - 1) + '"]'
2656      ).removeClass(classes.subNavActive);
2657
2658      if ($activeTrigger && $activeTrigger.length) {
2659        $activeTrigger.removeClass(classes.subNavActive);
2660      }
2661    } else {
2662      $toggleBtn.addClass(classes.subNavActive);
2663    }
2664
2665    $activeTrigger = $toggleBtn;
2666
2667    goToSubnav($toggleBtn.data('target'));
2668  }
2669
2670  function goToSubnav(target) {
2671    /*eslint-disable shopify/jquery-dollar-sign-reference */
2672
2673    var $targetMenu = target
2674      ? $('.mobile-nav__dropdown[data-parent="' + target + '"]')
2675      : cache.$mobileNav;
2676
2677    menuLevel = $targetMenu.data('level') ? $targetMenu.data('level') : 1;
2678
2679    if ($activeSubNav && $activeSubNav.length) {
2680      $activeSubNav.prepareTransition().addClass(classes.subNavClosing);
2681    }
2682
2683    $activeSubNav = $targetMenu;
2684
2685    /*eslint-enable shopify/jquery-dollar-sign-reference */
2686
2687    var translateMenuHeight = $targetMenu.outerHeight();
2688
2689    var openNavClass =
2690      menuLevel > 2 ? classes.thirdNavShowing : classes.subNavShowing;
2691
2692    cache.$mobileNavContainer
2693      .css('height', translateMenuHeight)
2694      .removeClass(classes.thirdNavShowing)
2695      .addClass(openNavClass);
2696
2697    if (!target) {
2698      // Show top level nav
2699      cache.$mobileNavContainer
2700        .removeClass(classes.thirdNavShowing)
2701        .removeClass(classes.subNavShowing);
2702    }
2703
2704    /* if going back to first subnav, focus is on whole header */
2705    var $container = menuLevel === 1 ? cache.$sectionHeader : $targetMenu;
2706
2707    var $menuTitle = $targetMenu.find('[data-menu-title=' + menuLevel + ']');
2708    var $elementToFocus = $menuTitle ? $menuTitle : $targetMenu;
2709
2710    // Focusing an item in the subnav early forces element into view and breaks the animation.
2711    cache.$mobileNavContainer.one(
2712      'TransitionEnd.subnavToggle webkitTransitionEnd.subnavToggle transitionend.subnavToggle oTransitionEnd.subnavToggle',
2713      function() {
2714        slate.a11y.trapFocus({
2715          $container: $container,
2716          $elementToFocus: $elementToFocus,
2717          namespace: 'subNavFocus'
2718        });
2719
2720        cache.$mobileNavContainer.off('.subnavToggle');
2721        isTransitioning = false;
2722      }
2723    );
2724
2725    // Match height of subnav
2726    cache.$pageContainer.css({
2727      transform: 'translateY(' + translateMenuHeight + 'px)'
2728    });
2729
2730    $activeSubNav.removeClass(classes.subNavClosing);
2731  }
2732
2733  return {
2734    init: init,
2735    closeMobileNav: closeMobileNav
2736  };
2737})(jQuery);
2738
2739(function() {
2740  var selectors = {
2741    backButton: '.return-link'
2742  };
2743
2744  var $backButton = $(selectors.backButton);
2745
2746  if (!document.referrer || !$backButton.length || !window.history.length) {
2747    return;
2748  }
2749
2750  $backButton.one('click', function(evt) {
2751    evt.preventDefault();
2752
2753    var referrerDomain = urlDomain(document.referrer);
2754    var shopDomain = urlDomain(window.location.href);
2755
2756    if (shopDomain === referrerDomain) {
2757      history.back();
2758    }
2759
2760    return false;
2761  });
2762
2763  function urlDomain(url) {
2764    var anchor = document.createElement('a');
2765    anchor.ref = url;
2766
2767    return anchor.hostname;
2768  }
2769})();
2770
2771theme.Slideshow = (function() {
2772  this.$slideshow = null;
2773  var classes = {
2774    slideshow: 'slideshow',
2775    slickActiveMobile: 'slick-active-mobile',
2776    controlsHover: 'slideshow__controls--hover',
2777    isPaused: 'is-paused'
2778  };
2779
2780  var selectors = {
2781    section: '.shopify-section',
2782    wrapper: '#SlideshowWrapper-',
2783    slides: '.slideshow__slide',
2784    textWrapperMobile: '.slideshow__text-wrap--mobile',
2785    textContentMobile: '.slideshow__text-content--mobile',
2786    controls: '.slideshow__controls',
2787    pauseButton: '.slideshow__pause',
2788    dots: '.slick-dots',
2789    arrows: '.slideshow__arrows',
2790    arrowsMobile: '.slideshow__arrows--mobile',
2791    arrowLeft: '.slideshow__arrow-left',
2792    arrowRight: '.slideshow__arrow-right'
2793  };
2794
2795  function slideshow(el, sectionId) {
2796    var $slideshow = (this.$slideshow = $(el));
2797    this.adaptHeight = this.$slideshow.data('adapt-height');
2798    this.$wrapper = this.$slideshow.closest(selectors.wrapper + sectionId);
2799    this.$section = this.$wrapper.closest(selectors.section);
2800    this.$controls = this.$wrapper.find(selectors.controls);
2801    this.$arrows = this.$section.find(selectors.arrows);
2802    this.$arrowsMobile = this.$section.find(selectors.arrowsMobile);
2803    this.$pause = this.$controls.find(selectors.pauseButton);
2804    this.$textWrapperMobile = this.$section.find(selectors.textWrapperMobile);
2805    this.autorotate = this.$slideshow.data('autorotate');
2806    var autoplaySpeed = this.$slideshow.data('speed');
2807    var loadSlideA11yString = this.$slideshow.data('slide-nav-a11y');
2808
2809    this.settings = {
2810      accessibility: true,
2811      arrows: false,
2812      dots: true,
2813      fade: true,
2814      draggable: true,
2815      touchThreshold: 20,
2816      autoplay: this.autorotate,
2817      autoplaySpeed: autoplaySpeed,
2818      // eslint-disable-next-line shopify/jquery-dollar-sign-reference
2819      appendDots: this.$arrows,
2820      customPaging: function(slick, index) {
2821        return (
2822          '<a href="' +
2823          selectors.wrapper +
2824          sectionId +
2825          '" aria-label="' +
2826          loadSlideA11yString.replace('[slide_number]', index + 1) +
2827          '" data-slide-number="' +
2828          index +
2829          '"></a>'
2830        );
2831      }
2832    };
2833
2834    this.$slideshow.on('beforeChange', beforeChange.bind(this));
2835    this.$slideshow.on('init', slideshowA11ySetup.bind(this));
2836
2837    // Add class to style mobile dots & show the correct text content for the
2838    // first slide on mobile when the slideshow initialises
2839    this.$slideshow.on(
2840      'init',
2841      function() {
2842        this.$mobileDots
2843          .find('li:first-of-type')
2844          .addClass(classes.slickActiveMobile);
2845        this.showMobileText(0);
2846      }.bind(this)
2847    );
2848
2849    // Stop the autorotate when you scroll past the mobile controls, resume when
2850    // they are scrolled back into view
2851    if (this.autorotate) {
2852      $(document).scroll(
2853        $.debounce(
2854          250,
2855          function() {
2856            if (
2857              this.$arrowsMobile.offset().top +
2858                this.$arrowsMobile.outerHeight() <
2859              window.pageYOffset
2860            ) {
2861              $slideshow.slick('slickPause');
2862            }
2862 else if (!this.$pause.hasClass(classes.isPaused)) {
2863              $slideshow.slick('slickPlay');
2864            }
2865          }.bind(this)
2866        )
2867      );
2868    }
2869
2870    if (this.adaptHeight) {
2871      this.setSlideshowHeight();
2872      $(window).resize($.debounce(50, this.setSlideshowHeight.bind(this)));
2873    }
2874
2875    this.$slideshow.slick(this.settings);
2876
2877    // This can't be called when the slick 'init' event fires due to how slick
2878    // adds a11y features.
2879    slideshowPostInitA11ySetup.bind(this)();
2880
2881    this.$arrows.find(selectors.arrowLeft).on('click', function() {
2882      $slideshow.slick('slickPrev');
2883    });
2884    this.$arrows.find(selectors.arrowRight).on('click', function() {
2885      $slideshow.slick('slickNext');
2886    });
2887
2888    this.$pause.on('click', this.togglePause.bind(this));
2889  }
2890
2891  function slideshowA11ySetup(event, obj) {
2892    var $slider = obj.$slider;
2893    var $list = obj.$list;
2894    this.$dots = this.$section.find(selectors.dots);
2895    this.$mobileDots = this.$dots.eq(1);
2896
2897    // Remove default Slick aria-live attr until slider is focused
2898    $list.removeAttr('aria-live');
2899
2900    this.$wrapper.on('keyup', keyboardNavigation.bind(this));
2901    this.$controls.on('keyup', keyboardNavigation.bind(this));
2902    this.$textWrapperMobile.on('keyup', keyboardNavigation.bind(this));
2903
2904    // When an element in the slider is focused
2905    // pause slideshow and set aria-live.
2906    this.$wrapper
2907      .on(
2908        'focusin',
2909        function(evt) {
2910          if (!this.$wrapper.has(evt.target).length) {
2911            return;
2912          }
2913
2914          $list.attr('aria-live', 'polite');
2915          if (this.autorotate) {
2916            $slider.slick('slickPause');
2917          }
2918        }.bind(this)
2919      )
2920      .on(
2921        'focusout',
2922        function(evt) {
2923          if (!this.$wrapper.has(evt.target).length) {
2924            return;
2925          }
2926
2927          $list.removeAttr('aria-live');
2928          if (this.autorotate) {
2929            // Only resume playing if the user hasn't paused using the pause
2930            // button
2931            if (!this.$pause.is('.is-paused')) {
2932              $slider.slick('slickPlay');
2933            }
2934          }
2935        }.bind(this)
2936      );
2937
2938    // Add arrow key support when focused
2939    if (this.$dots) {
2940      this.$dots
2941        .find('a')
2942        .each(function() {
2943          var $dot = $(this);
2944          $dot.on('click keyup', function(evt) {
2945            if (
2946              evt.type === 'keyup' &&
2947              evt.which !== slate.utils.keyboardKeys.ENTER
2948            )
2949              return;
2950
2951            evt.preventDefault();
2952
2953            var slideNumber = $(evt.target).data('slide-number');
2954
2955            $slider.attr('tabindex', -1).slick('slickGoTo', slideNumber);
2956
2957            if (evt.type === 'keyup') {
2958              $slider.focus();
2959            }
2960          });
2961        })
2962        .eq(0)
2963        .attr('aria-current', 'true');
2964    }
2965
2966    this.$controls
2967      .on('focusin', highlightControls.bind(this))
2968      .on('focusout', unhighlightControls.bind(this));
2969  }
2970
2971  function slideshowPostInitA11ySetup() {
2972    var $slides = this.$slideshow.find(selectors.slides);
2973
2974    $slides.removeAttr('role').removeAttr('aria-labelledby');
2975    this.$dots
2976      .removeAttr('role')
2977      .find('li')
2978      .removeAttr('role')
2979      .removeAttr('aria-selected')
2980      .each(function() {
2981        var $dot = $(this);
2982        var ariaControls = $dot.attr('aria-controls');
2983        $dot
2984          .removeAttr('aria-controls')
2985          .find('a')
2986          .attr('aria-controls', ariaControls);
2987      });
2988  }
2989
2990  function beforeChange(event, slick, currentSlide, nextSlide) {
2991    var $dotLinks = this.$dots.find('a');
2992    var $mobileDotLinks = this.$mobileDots.find('li');
2993
2994    $dotLinks
2995      .removeAttr('aria-current')
2996      .eq(nextSlide)
2997      .attr('aria-current', 'true');
2998
2999    $mobileDotLinks
3000      .removeClass(classes.slickActiveMobile)
3001      .eq(nextSlide)
3002      .addClass(classes.slickActiveMobile);
3003    this.showMobileText(nextSlide);
3004  }
3005
3006  function keyboardNavigation() {
3007    if (event.keyCode === slate.utils.keyboardKeys.LEFTARROW) {
3008      this.$slideshow.slick('slickPrev');
3009    }
3010    if (event.keyCode === slate.utils.keyboardKeys.RIGHTARROW) {
3011      this.$slideshow.slick('slickNext');
3012    }
3013  }
3014
3015  function highlightControls() {
3016    this.$controls.addClass(classes.controlsHover);
3017  }
3018
3019  function unhighlightControls() {
3020    this.$controls.removeClass(classes.controlsHover);
3021  }
3022
3023  slideshow.prototype.togglePause = function() {
3024    var slideshowSelector = getSlideshowId(this.$pause);
3025    if (this.$pause.hasClass(classes.isPaused)) {
3026      this.$pause.removeClass(classes.isPaused).attr('aria-pressed', 'false');
3027      if (this.autorotate) {
3028        $(slideshowSelector).slick('slickPlay');
3029      }
3030    } else {
3031      this.$pause.addClass(classes.isPaused).attr('aria-pressed', 'true');
3032      if (this.autorotate) {
3033        $(slideshowSelector).slick('slickPause');
3034      }
3035    }
3036  };
3037
3038  slideshow.prototype.setSlideshowHeight = function() {
3039    var minAspectRatio = this.$slideshow.data('min-aspect-ratio');
3040    this.$slideshow.height($(document).width() / minAspectRatio);
3041  };
3042
3043  slideshow.prototype.showMobileText = function(slideIndex) {
3044    var $allTextContent = this.$textWrapperMobile.find(
3045      selectors.textContentMobile
3046    );
3047    var currentTextContentSelector =
3048      selectors.textContentMobile + '-' + slideIndex;
3049    var $currentTextContent = this.$textWrapperMobile.find(
3050      currentTextContentSelector
3051    );
3052    if (
3053      !$currentTextContent.length &&
3054      this.$slideshow.find(selectors.slides).length === 1
3055    ) {
3056      this.$textWrapperMobile.hide();
3057    } else {
3058      this.$textWrapperMobile.show();
3059    }
3060    $allTextContent.hide();
3061    $currentTextContent.show();
3062  };
3063
3064  function getSlideshowId($el) {
3065    return '#Slideshow-' + $el.data('id');
3066  }
3067
3068  return slideshow;
3069})();
3070
3071theme.Video = (function() {
3072  var autoplayCheckComplete = false;
3073  var playOnClickChecked = false;
3074  var playOnClick = false;
3075  var youtubeLoaded = false;
3076  var videos = {};
3077  var videoPlayers = [];
3078  var videoOptions = {
3079    ratio: 16 / 9,
3080    scrollAnimationDuration: 400,
3081    playerVars: {
3082      // eslint-disable-next-line camelcase
3083      iv_load_policy: 3,
3084      modestbranding: 1,
3085      autoplay: 0,
3086      controls: 0,
3087      wmode: 'opaque',
3088      branding: 0,
3089      autohide: 0,
3090      rel: 0
3091    },
3092    events: {
3093      onReady: onPlayerReady,
3094      onStateChange: onPlayerChange
3095    }
3096  };
3097  var classes = {
3098    playing: 'video-is-playing',
3099    paused: 'video-is-paused',
3100    loading: 'video-is-loading',
3101    loaded: 'video-is-loaded',
3102    backgroundVideoWrapper: 'video-background-wrapper',
3103    videoWithImage: 'video--image_with_play',
3104    backgroundVideo: 'video--background',
3105    userPaused: 'is-paused',
3106    supportsAutoplay: 'autoplay',
3107    supportsNoAutoplay: 'no-autoplay',
3108    wrapperMinHeight: 'video-section-wrapper--min-height'
3109  };
3110
3111  var selectors = {
3112    section: '.video-section',
3113    videoWrapper: '.video-section-wrapper',
3114    playVideoBtn: '.video-control__play',
3115    closeVideoBtn: '.video-control__close-wrapper',
3116    pauseVideoBtn: '.video__pause',
3117    pauseVideoStop: '.video__pause-stop',
3118    pauseVideoResume: '.video__pause-resume',
3119    fallbackText: '.icon__fallback-text'
3120  };
3121
3122  /**
3123   * Public functions
3124   */
3125  function init($video) {
3126    if (!$video.length) {
3127      return;
3128    }
3129
3130    videos[$video.attr('id')] = {
3131      id: $video.attr('id'),
3132      videoId: $video.data('id'),
3133      type: $video.data('type'),
3134      status:
3135        $video.data('type') === 'image_with_play' ? 'closed' : 'background', // closed, open, background
3136      $video: $video,
3137      $videoWrapper: $video.closest(selectors.videoWrapper),
3138      $section: $video.closest(selectors.section),
3139      controls: $video.data('type') === 'background' ? 0 : 1
3140    };
3141
3142    if (!youtubeLoaded) {
3143      // This code loads the IFrame Player API code asynchronously.
3144      var tag = document.createElement('script');
3145      tag.src = 'https://www.youtube.com/iframe_api';
3146      var firstScriptTag = document.getElementsByTagName('script')[0];
3147      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
3148    }
3149
3150    playOnClickCheck();
3151  }
3152
3153  function customPlayVideo(playerId) {
3154    // Make sure we have carried out the playOnClick check first
3155    if (!playOnClickChecked && !playOnClick) {
3156      return;
3157    }
3158
3159    if (playerId && typeof videoPlayers[playerId].playVideo === 'function') {
3160      privatePlayVideo(playerId);
3161    }
3162  }
3163
3164  function pauseVideo(playerId) {
3165    if (
3166      videoPlayers[playerId] &&
3167      typeof videoPlayers[playerId].pauseVideo === 'function'
3168    ) {
3169      videoPlayers[playerId].pauseVideo();
3170    }
3171  }
3172
3173  function loadVideos() {
3174    for (var key in videos) {
3175      if (videos.hasOwnProperty(key)) {
3176        createPlayer(key);
3177      }
3178    }
3179
3180    initEvents();
3181    youtubeLoaded = true;
3182  }
3183
3184  function editorLoadVideo(key) {
3185    if (!youtubeLoaded) {
3186      return;
3187    }
3188    createPlayer(key);
3189
3190    initEvents();
3191  }
3192
3193  /**
3194   * Private functions
3195   */
3196
3197  function privatePlayVideo(id, clicked) {
3198    var videoData = videos[id];
3199    var player = videoPlayers[id];
3200    var $videoWrapper = videoData.$videoWrapper;
3201
3202    if (playOnClick) {
3203      // playOnClick means we are probably on mobile (no autoplay).
3204      // setAsPlaying will show the iframe, requiring another click
3205      // to play the video.
3206      setAsPlaying(videoData);
3207    } else if (clicked || autoplayCheckComplete) {
3208      // Play if autoplay is available or clicked to play
3209      $videoWrapper.removeClass(classes.loading);
3210      setAsPlaying(videoData);
3211      player.playVideo();
3212      return;
3213    } else {
3214      player.playVideo();
3215    }
3216  }
3217
3218  function setAutoplaySupport(supported) {
3219    var supportClass = supported
3220      ? classes.supportsAutoplay
3221      : classes.supportsNoAutoplay;
3222    $(document.documentElement)
3223      .removeClass(classes.supportsAutoplay)
3224      .removeClass(classes.supportsNoAutoplay)
3225      .addClass(supportClass);
3226
3227    if (!supported) {
3228      playOnClick = true;
3229    }
3230
3231    autoplayCheckComplete = true;
3232  }
3233
3234  function playOnClickCheck() {
3235    // Bail early for a few instances:
3236    // - small screen
3237    // - device sniff mobile browser
3238
3239    if (playOnClickChecked) {
3240      return;
3241    }
3242
3243    if (isMobile()) {
3244      playOnClick = true;
3245    }
3246
3247    if (playOnClick) {
3248      // No need to also do the autoplay check
3249      setAutoplaySupport(false);
3250    }
3251
3252    playOnClickChecked = true;
3253  }
3254
3255  // The API will call this function when each video player is ready
3256  function onPlayerReady(evt) {
3257    evt.target.setPlaybackQuality('hd1080');
3258    var videoData = getVideoOptions(evt);
3259    var videoTitle = evt.target.getVideoData().title;
3260    playOnClickCheck();
3261
3262    // Prevent tabbing through YouTube player controls until visible
3263    $('#' + videoData.id).attr('tabindex', '-1');
3264
3265    sizeBackgroundVideos();
3266    setButtonLabels(videoData.$videoWrapper, videoTitle);
3267
3268    // Customize based on options from the video ID
3269    if (videoData.type === 'background') {
3270      evt.target.mute();
3271      privatePlayVideo(videoData.id);
3272    }
3273
3274    videoData.$videoWrapper.addClass(classes.loaded);
3275  }
3276
3277  function onPlayerChange(evt) {
3278    var videoData = getVideoOptions(evt);
3279    if (
3280      videoData.status === 'background' &&
3281      !isMobile() &&
3282      !autoplayCheckComplete &&
3283      (evt.data === YT.PlayerState.PLAYING ||
3284        evt.data === YT.PlayerState.BUFFERING)
3285    ) {
3286      setAutoplaySupport(true);
3287      autoplayCheckComplete = true;
3288      videoData.$videoWrapper.removeClass(classes.loading);
3289    }
3290    switch (evt.data) {
3291      case YT.PlayerState.ENDED:
3292        setAsFinished(videoData);
3293        break;
3294      case YT.PlayerState.PAUSED:
3295        // Seeking on a YouTube video also fires a PAUSED state change,
3296        // checking the state after a delay prevents us pausing the video when
3297        // the user is seeking instead of pausing
3298        setTimeout(function() {
3299          if (evt.target.getPlayerState() === YT.PlayerState.PAUSED) {
3300            setAsPaused(videoData);
3301          }
3302        }, 200);
3303        break;
3304    }
3305  }
3306
3307  function setAsFinished(videoData) {
3308    switch (videoData.type) {
3309      case 'background':
3310        videoPlayers[videoData.id].seekTo(0);
3311        break;
3312      case 'image_with_play':
3313        closeVideo(videoData.id);
3314        toggleExpandVideo(videoData.id, false);
3315        break;
3316    }
3317  }
3318
3319  function setAsPlaying(videoData) {
3320    var $videoWrapper = videoData.$videoWrapper;
3321    var $pauseButton = $videoWrapper.find(selectors.pauseVideoBtn);
3322
3323    $videoWrapper.removeClass(classes.loading);
3324
3325    if ($pauseButton.hasClass(classes.userPaused)) {
3326      $pauseButton.removeClass(classes.userPaused);
3327    }
3328
3329    // Do not change element visibility if it is a background video
3330    if (videoData.status === 'background') {
3331      return;
3332    }
3333
3334    $('#' + videoData.id).attr('tabindex', '0');
3335
3336    if (videoData.type === 'image_with_play') {
3337      $videoWrapper.removeClass(classes.paused).addClass(classes.playing);
3338    }
3339
3340    // Update focus to the close button so we stay within the video wrapper,
3341    // allowing time for the scroll animation
3342    setTimeout(function() {
3343      $videoWrapper.find(selectors.closeVideoBtn).focus();
3344    }, videoOptions.scrollAnimationDuration);
3345  }
3346
3347  function setAsPaused(videoData) {
3348    var $videoWrapper = videoData.$videoWrapper;
3349
3350    // YT's events fire after our click event. This status flag ensures
3351    // we don't interact with a closed or background video.
3352    if (videoData.type === 'image_with_play') {
3353      if (videoData.status === 'closed') {
3354        $videoWrapper.removeClass(classe
3354s.paused);
3355      } else {
3356        $videoWrapper.addClass(classes.paused);
3357      }
3358    }
3359
3360    $videoWrapper.removeClass(classes.playing);
3361  }
3362
3363  function closeVideo(playerId) {
3364    var videoData = videos[playerId];
3365    var $videoWrapper = videoData.$videoWrapper;
3366    var classesToRemove = [classes.paused, classes.playing].join(' ');
3367
3368    if (isMobile()) {
3369      $videoWrapper.removeAttr('style');
3370    }
3371
3372    $('#' + videoData.id).attr('tabindex', '-1');
3373
3374    videoData.status = 'closed';
3375
3376    switch (videoData.type) {
3377      case 'image_with_play':
3378        videoPlayers[playerId].stopVideo();
3379        setAsPaused(videoData); // in case the video is already paused
3380        break;
3381      case 'background':
3382        videoPlayers[playerId].mute();
3383        setBackgroundVideo(playerId);
3384        break;
3385    }
3386
3387    $videoWrapper.removeClass(classesToRemove);
3388  }
3389
3390  function getVideoOptions(evt) {
3391    var id = evt.target.getIframe().id;
3392    return videos[id];
3393  }
3394
3395  function toggleExpandVideo(playerId, expand) {
3396    var video = videos[playerId];
3397    var elementTop = video.$videoWrapper.offset().top;
3398    var $playButton = video.$videoWrapper.find(selectors.playVideoBtn);
3399    var offset = 0;
3400    var newHeight = 0;
3401
3402    if (isMobile()) {
3403      video.$videoWrapper.parent().toggleClass('page-width', !expand);
3404    }
3405
3406    if (expand) {
3407      if (isMobile()) {
3408        newHeight = $(window).width() / videoOptions.ratio;
3409      } else {
3410        newHeight = video.$videoWrapper.width() / videoOptions.ratio;
3411      }
3412      offset = ($(window).height() - newHeight) / 2;
3413
3414      video.$videoWrapper
3415        .removeClass(classes.wrapperMinHeight)
3416        .animate({ height: newHeight }, 600);
3417
3418      // Animate doesn't work in mobile editor, so we don't use it
3419      if (!(isMobile() && Shopify.designMode)) {
3420        $('html, body').animate(
3421          {
3422            scrollTop: elementTop - offset
3423          },
3424          videoOptions.scrollAnimationDuration
3425        );
3426      }
3427    } else {
3428      if (isMobile()) {
3429        newHeight = video.$videoWrapper.data('mobile-height');
3430      } else {
3431        newHeight = video.$videoWrapper.data('desktop-height');
3432      }
3433
3434      video.$videoWrapper
3435        .height(video.$videoWrapper.width() / videoOptions.ratio)
3436        .animate({ height: newHeight }, 600);
3437      setTimeout(function() {
3438        video.$videoWrapper.addClass(classes.wrapperMinHeight);
3439      }, 600);
3440      $playButton.focus();
3441    }
3442  }
3443
3444  function togglePause(playerId) {
3445    var $pauseButton = videos[playerId].$videoWrapper.find(
3446      selectors.pauseVideoBtn
3447    );
3448    var paused = $pauseButton.hasClass(classes.userPaused);
3449    if (paused) {
3450      $pauseButton.removeClass(classes.userPaused);
3451      customPlayVideo(playerId);
3452    } else {
3453      $pauseButton.addClass(classes.userPaused);
3454      pauseVideo(playerId);
3455    }
3456    $pauseButton.attr('aria-pressed', !paused);
3457  }
3458
3459  function startVideoOnClick(playerId) {
3460    var video = videos[playerId];
3461
3462    // add loading class to wrapper
3463    video.$videoWrapper.addClass(classes.loading);
3464
3465    // Explicity set the video wrapper height (needed for height transition)
3466    video.$videoWrapper.attr(
3467      'style',
3468      'height: ' + video.$videoWrapper.height() + 'px'
3469    );
3470
3471    video.status = 'open';
3472
3473    switch (video.type) {
3474      case 'image_with_play':
3475        privatePlayVideo(playerId, true);
3476        break;
3477      case 'background':
3478        unsetBackgroundVideo(playerId, video);
3479        videoPlayers[playerId].unMute();
3480        privatePlayVideo(playerId, true);
3481        break;
3482    }
3483
3484    toggleExpandVideo(playerId, true);
3485
3486    // esc to close video player
3487    $(document).on('keydown.videoPlayer', function(evt) {
3488      var playerId = $(document.activeElement).data('controls');
3489      if (evt.keyCode !== slate.utils.keyboardKeys.ESCAPE || !playerId) {
3490        return;
3491      }
3492
3493      closeVideo(playerId);
3494      toggleExpandVideo(playerId, false);
3495    });
3496  }
3497
3498  function sizeBackgroundVideos() {
3499    $('.' + classes.backgroundVideo).each(function(index, el) {
3500      sizeBackgroundVideo($(el));
3501    });
3502  }
3503
3504  function sizeBackgroundVideo($videoPlayer) {
3505    if (!youtubeLoaded) {
3506      return;
3507    }
3508
3509    if (isMobile()) {
3510      $videoPlayer.removeAttr('style');
3511    } else {
3512      var $videoWrapper = $videoPlayer.closest(selectors.videoWrapper);
3513      var videoWidth = $videoWrapper.width();
3514      var playerWidth = $videoPlayer.width();
3515      var desktopHeight = $videoWrapper.data('desktop-height');
3516
3517      // when screen aspect ratio differs from video, video must center and underlay one dimension
3518      if (videoWidth / videoOptions.ratio < desktopHeight) {
3519        playerWidth = Math.ceil(desktopHeight * videoOptions.ratio); // get new player width
3520        $videoPlayer
3521          .width(playerWidth)
3522          .height(desktopHeight)
3523          .css({
3524            left: (videoWidth - playerWidth) / 2,
3525            top: 0
3526          }); // player width is greater, offset left; reset top
3527      } else {
3528        // new video width < window width (gap to right)
3529        desktopHeight = Math.ceil(videoWidth / videoOptions.ratio); // get new player height
3530        $videoPlayer
3531          .width(videoWidth)
3532          .height(desktopHeight)
3533          .css({
3534            left: 0,
3535            top: (desktopHeight - desktopHeight) / 2
3536          }); // player height is greater, offset top; reset left
3537      }
3538
3539      $videoPlayer.prepareTransition();
3540      $videoWrapper.addClass(classes.loaded);
3541    }
3542  }
3543
3544  function unsetBackgroundVideo(playerId) {
3545    // Switch the background video to a chrome-only player once played
3546    $('#' + playerId)
3547      .removeClass(classes.backgroundVideo)
3548      .addClass(classes.videoWithImage);
3549
3550    setTimeout(function() {
3551      $('#' + playerId).removeAttr('style');
3552    }, 600);
3553
3554    videos[playerId].$videoWrapper
3555      .removeClass(classes.backgroundVideoWrapper)
3556      .addClass(classes.playing);
3557
3558    videos[playerId].status = 'open';
3559  }
3560
3561  function setBackgroundVideo(playerId) {
3562    $('#' + playerId)
3563      .removeClass(classes.videoWithImage)
3564      .addClass(classes.backgroundVideo);
3565
3566    videos[playerId].$videoWrapper.addClass(classes.backgroundVideoWrapper);
3567
3568    videos[playerId].status = 'background';
3569    sizeBackgroundVideo($('#' + playerId));
3570  }
3571
3572  function isMobile() {
3573    return $(window).width() < 750 || window.mobileCheck();
3574  }
3575
3576  function initEvents() {
3577    $(document).on('click.videoPlayer', selectors.playVideoBtn, function(evt) {
3578      var playerId = $(evt.currentTarget).data('controls');
3579
3580      startVideoOnClick(playerId);
3581    });
3582
3583    $(document).on('click.videoPlayer', selectors.closeVideoBtn, function(evt) {
3584      var playerId = $(evt.currentTarget).data('controls');
3585
3586      $(evt.currentTarget).blur();
3587      closeVideo(playerId);
3588      toggleExpandVideo(playerId, false);
3589    });
3590
3591    $(document).on('click.videoPlayer', selectors.pauseVideoBtn, function(evt) {
3592      var playerId = $(evt.currentTarget).data('controls');
3593      togglePause(playerId);
3594    });
3595
3596    // Listen to resize to keep a background-size:cover-like layout
3597    $(window).on(
3598      'resize.videoPlayer',
3599      $.debounce(200, function() {
3600        if (!youtubeLoaded) return;
3601        var key;
3602        var fullscreen = window.innerHeight === screen.height;
3603
3604        sizeBackgroundVideos();
3605
3606        if (isMobile()) {
3607          for (key in videos) {
3608            if (videos.hasOwnProperty(key)) {
3609              if (videos[key].$videoWrapper.hasClass(classes.playing)) {
3610                if (!fullscreen) {
3611                  pauseVideo(key);
3612                  setAsPaused(videos[key]);
3613                }
3614              }
3615              videos[key].$videoWrapper.height(
3616                $(document).width() / videoOptions.ratio
3617              );
3618            }
3619          }
3620          setAutoplaySupport(false);
3621        } else {
3622          setAutoplaySupport(true);
3623          for (key in videos) {
3624            if (
3625              videos[key].$videoWrapper.find('.' + classes.videoWithImage)
3626                .length
3627            ) {
3628              continue;
3629            }
3630            videoPlayers[key].playVideo();
3631            setAsPlaying(videos[key]);
3632          }
3633        }
3634      })
3635    );
3636
3637    $(window).on(
3638      'scroll.videoPlayer',
3639      $.debounce(50, function() {
3640        if (!youtubeLoaded) return;
3641
3642        for (var key in videos) {
3643          if (videos.hasOwnProperty(key)) {
3644            var $videoWrapper = videos[key].$videoWrapper;
3645
3646            // Close the video if more than 75% of it is scrolled out of view
3647            if (
3648              $videoWrapper.hasClass(classes.playing) &&
3649              ($videoWrapper.offset().top + $videoWrapper.height() * 0.75 <
3650                $(window).scrollTop() ||
3651                $videoWrapper.offset().top + $videoWrapper.height() * 0.25 >
3652                  $(window).scrollTop() + $(window).height())
3653            ) {
3654              closeVideo(key);
3655              toggleExpandVideo(key, false);
3656            }
3657          }
3658        }
3659      })
3660    );
3661  }
3662
3663  function createPlayer(key) {
3664    var args = $.extend({}, videoOptions, videos[key]);
3665    args.playerVars.controls = args.controls;
3666    videoPlayers[key] = new YT.Player(key, args);
3667  }
3668
3669  function removeEvents() {
3670    $(document).off('.videoPlayer');
3671    $(window).off('.videoPlayer');
3672  }
3673
3674  function setButtonLabels($videoWrapper, title) {
3675    var $playButtons = $videoWrapper.find(selectors.playVideoBtn);
3676    var $closeButton = $videoWrapper.find(selectors.closeVideoBtn);
3677    var $pauseButton = $videoWrapper.find(selectors.pauseVideoBtn);
3678    var $closeButtonText = $closeButton.find(selectors.fallbackText);
3679    var $pauseButtonStopText = $pauseButton
3680      .find(selectors.pauseVideoStop)
3681      .find(selectors.fallbackText);
3682    var $pauseButtonResumeText = $pauseButton
3683      .find(selectors.pauseVideoResume)
3684      .find(selectors.fallbackText);
3685
3686    // Insert the video title retrieved from YouTube into the instructional text
3687    // for each button
3688    $playButtons.each(function() {
3689      var $playButton = $(this);
3690      var $playButtonText = $playButton.find(selectors.fallbackText);
3691
3692      $playButtonText.text(
3693        $playButtonText.text().replace('[video_title]', title)
3694      );
3695    });
3696    $closeButtonText.text(
3697      $closeButtonText.text().replace('[video_title]', title)
3698    );
3699    $pauseButtonStopText.text(
3700      $pauseButtonStopText.text().replace('[video_title]', title)
3701    );
3702    $pauseButtonResumeText.text(
3703      $pauseButtonResumeText.text().replace('[video_title]', title)
3704    );
3705  }
3706
3707  return {
3708    init: init,
3709    editorLoadVideo: editorLoadVideo,
3710    loadVideos: loadVideos,
3711    playVideo: customPlayVideo,
3712    pauseVideo: pauseVideo,
3713    removeEvents: removeEvents
3714  };
3715})();
3716
3717theme.ProductVideo = (function() {
3718  var videos = {};
3719
3720  var hosts = {
3721    html5: 'html5',
3722    youtube: 'youtube'
3723  };
3724
3725  var selectors = {
3726    productMediaWrapper: '[data-product-single-media-wrapper]'
3727  };
3728
3729  var attributes = {
3730    enableVideoLooping: 'enable-video-looping',
3731    videoId: 'video-id'
3732  };
3733
3734  function init(videoContainer, sectionId) {
3735    if (!videoContainer.length) {
3736      return;
3737    }
3738
3739    var videoElement = videoContainer.find('iframe, video')[0];
3740    var mediaId = videoContainer.data('mediaId');
3741
3742    if (!videoElement) {
3743      return;
3744    }
3745
3746    videos[mediaId] = {
3747      mediaId: mediaId,
3748      sectionId: sectionId,
3749      host: hostFromVideoElement(videoElement),
3750      container: videoContainer,
3751      element: videoElement,
3752      ready: function() {
3753        createPlayer(this);
3754      }
3755    };
3756
3757    var video = videos[mediaId];
3758
3759    switch (video.host) {
3760      case hosts.html5:
3761        window.Shopify.loadFeatures([
3762          {
3763            name: 'video-ui',
3764            version: '1.0',
3765            onLoad: setupPlyrVideos
3766          }
3767        ]);
3768        theme.LibraryLoader.load('plyrShopifyStyles');
3769        break;
3770      case hosts.youtube:
3771        theme.LibraryLoader.load('youtubeSdk', setupYouTubeVideos);
3772        break;
3773    }
3774  }
3775
3776  function setupPlyrVideos(errors) {
3777    if (errors) {
3778      fallbackToNativeVideo();
3779      return;
3780    }
3781
3782    loadVideos(hosts.html5);
3783  }
3784
3785  function setupYouTubeVideos() {
3786    if (!window.YT.Player) return;
3787
3788    loadVideos(hosts.youtube);
3789  }
3790
3791  function createPlayer(video) {
3792    if (video.player) {
3793      return;
3794    }
3795
3796    var productMediaWrapper = video.container.closest(
3797      selectors.productMediaWrapper
3798    );
3799    var enableLooping = productMediaWrapper.data(attributes.enableVideoLooping);
3800
3801    switch (video.host) {
3802      case hosts.html5:
3803        // eslint-disable-next-line no-undef
3804        video.player = new Shopify.Plyr(video.element, {
3805          loop: { active: enableLooping }
3806        });
3807        break;
3808      case hosts.youtube:
3809        var videoId = productMediaWrapper.data(attributes.videoId);
3810
3811        video.player = new YT.Player(video.element, {
3812          videoId: videoId,
3813          events: {
3814            onStateChange: function(event) {
3815              if (event.data === 0 && enableLooping) event.target.seekTo(0);
3816            }
3817          }
3818        });
3819        break;
3820    }
3821
3822    productMediaWrapper.on('mediaHidden xrLaunch', function() {
3823      if (!video.player) return;
3824
3825      if (video.host === hosts.html5) {
3826        video.player.pause();
3827      }
3828
3829      if (video.host === hosts.youtube && video.player.pauseVideo) {
3830        video.player.pauseVideo();
3831      }
3832    });
3833
3834    productMediaWrapper.on('mediaVisible', function() {
3835      if (theme.Helpers.isTouch()) return;
3836      if (!video.player) return;
3837
3838      if (video.host === hosts.html5) {
3839        video.player.play();
3840      }
3841
3842      if (video.host === hosts.youtube && video.player.playVideo) {
3843        video.player.playVideo();
3844      }
3845    });
3846  }
3847
3848  function hostFromVideoElement(video) {
3849    if (video.tagName === 'VIDEO') {
3850      return hosts.html5;
3851    }
3852
3853    if (video.tagName === 'IFRAME') {
3854      if (
3855        /^(https?:\/\/)?(www\.)?(youtube\.com|youtube-nocookie\.com|youtu\.?be)\/.+$/.test(
3856          video.src
3857        )
3858      ) {
3859        return hosts.youtube;
3860      }
3861    }
3862    return null;
3863  }
3864
3865  function loadVideos(host) {
3866    for (var key in videos) {
3867      if (videos.hasOwnProperty(key)) {
3868        var video = videos[key];
3869        if (video.host === host) {
3870          video.ready();
3871        }
3872      }
3873    }
3874  }
3875
3876  function fallbackToNativeVideo() {
3877    for (var key in videos) {
3878      if (videos.hasOwnProperty(key)) {
3879        var video = videos[key];
3880
3881        if (video.nativeVideo) continue;
3882
3883        if (video.host === hosts.html5) {
3884          video.element.setAttribute('controls', 'controls');
3885          video.nativeVideo = true;
3886        }
3887      }
3888    }
3889  }
3890
3891  function removeSectionVideos(sectionId) {
3892    for (var key in videos) {
3893      if (videos.hasOwnProperty(key)) {
3894        var video = videos[key];
3895
3896        if (video.sectionId === sectionId) {
3897          if (video.player) video.player.destroy();
3898          delete videos[key];
3899        }
3900      }
3901    }
3902  }
3903
3904  return {
3905    init: init,
3906    hosts: hosts,
3907    loadVideos: loadVideos,
3908    removeSectionVideos: removeSectionVideos
3909  };
3910})();
3911
3912theme.ProductModel = (function() {
3913  var modelJsonSections = {};
3914  var models = {};
3915  var xrButtons = {};
3916
3917  var selectors = {
3918    mediaGroup: '[data-product-single-media-group]',
3919    xrButton: '[data-shopify-xr]'
3920  };
3921
3922  function init(modelViewerContainers, sectionId) {
3923    modelJsonSections[sectionId] = {
3924      loaded: false
3925    };
3926
3927    modelViewerContainers.each(function(index) {
3928      var $modelViewerContainer = $(this);
3929      var mediaId = $modelViewerContainer.data('media-id');
3930      var $modelViewerElement = $(
3931        $modelViewerContainer.find('model-viewer')[0]
3932      );
3933      var modelId = $modelViewerElement.data('model-id');
3934
3935      if (index === 0) {
3936        var $xrButton = $modelViewerContainer
3937          .closest(selectors.mediaGroup)
3938          .find(selectors.xrButton);
3939        xrButtons[sectionId] = {
3940          $element: $xrButton,
3941          defaultId: modelId
3942        };
3943      }
3944
3945      models[mediaId] = {
3946        modelId: modelId,
3947        sectionId: sectionId,
3948        $container: $modelViewerContainer,
3949        $element: $modelViewerElement
3950      };
3951    });
3952
3953    window.Shopify.loadFeatures([
3954      {
3955        name: 'shopify-xr',
3956        version: '1.0',
3957        onLoad: setupShopifyXr
3958      },
3959      {
3960        name: 'model-viewer-ui',
3961        version: '1.0',
3962        onLoad: setupModelViewerUi
3963      }
3964    ]);
3965    theme.LibraryLoader.load('modelViewerUiStyles');
3966  }
3967
3968  function setupShopifyXr(errors) {
3969    if (errors) return;
3970
3971    if (!window.ShopifyXR) {
3972      document.addEventListener('shopify_xr_initialized', function() {
3973        setupShopifyXr();
3974      });
3975      return;
3976    }
3977
3978    for (var sectionId in modelJsonSections) {
3979      if (modelJsonSections.hasOwnProperty(sectionId)) {
3980        var modelSection = modelJsonSections[sectionId];
3981
3982        if (modelSection.loaded) continue;
3983        var $modelJson = $('#ModelJson-' + sectionId);
3984
3985        window.ShopifyXR.addModels(JSON.parse($modelJson.html()));
3986        modelSection.loaded = true;
3987      }
3988    }
3989    window.ShopifyXR.setupXRElements();
3990  }
3991
3992  function setupModelViewerUi(errors) {
3993    if (errors) return;
3994
3995    for (var key in models) {
3996      if (models.hasOwnProperty(key)) {
3997        var model = models[key];
3998        if (!model.modelViewerUi) {
3999          model.modelViewerUi = new Shopify.ModelViewerUI(model.$element);
4000        }
4001        setupModelViewerListeners(model);
4002      }
4003    }
4004  }
4005
4006  function setupModelViewerListeners(model) {
4007    var xrButton = xrButtons[model.sectionId];
4008
4009    model.$container.on('mediaVisible', function() {
4010      xrButton.$element.attr('data-shopify-model3d-id', model.modelId);
4011      if (theme.Helpers.isTouch()) return;
4012      model.modelViewerUi.play();
4013    });
4014
4015    model.$container
4016      .on('mediaHidden', function() {
4017        xrButton.$element.attr('data-shopify-model3d-id', xrButton.defaultId);
4018        model.modelViewerUi.pause();
4019      })
4020      .on('xrLaunch', function() {
4021        model.modelViewerUi.pause();
4022      });
4023  }
4024
4025  function removeSectionModels(sectionId) {
4026    for (var key in models) {
4027      if (models.hasOwnProperty(key)) {
4028        var model = models[key];
4029        if (model.sectionId === sectionId) {
4030          models[key].modelViewerUi.destroy();
4031          delete models[key];
4032        }
4033      }
4034    }
4035    delete modelJsonSections[sectionId];
4036  }
4037
4038  return {
4039    init: init,
4040    removeSectionModels: removeSectionModels
4041  };
4042})();
4043
4044window.theme = window.theme || {};
4045
4046theme.FormStatus = (function() {
4047  var selectors = {
4048    statusMessage: '[data-form-status]'
4049  };
4050
4051  function init() {
4052    this.$statusMessage = $(selectors.statusMessage);
4053
4054    if (!this.$statusMessage) return;
4055
4056    this.$statusMessage.attr('tabindex', -1).focus();
4057
4058    this.$statusMessage.on('blur', handleBlur.bind(this));
4059  }
4060
4061  function handleBlur() {
4062    this.$statusMessage.removeAttr('tabindex');
4063  }
4064
4065  return {
4066    init: init
4067  };
4068})();
4069
4070theme.Hero = (function() {
4071  var classes = {
4072    indexSectionFlush: 'index-section--flush'
4073  };
4074
4075  var selectors = {
4076    heroFixedWidthContent: '.hero-fixed-width__content',
4077    heroFixedWidthImage: '.hero-fixed-width__image'
4078  };
4079
4080  function hero(el, sectionId) {
4081    this.$hero = $(el);
4082    this.layout = this.$hero.data('layout');
4083    var $parentSection = $('#shopify-section-' + sectionId);
4084    var $heroContent = $parentSection.find(selectors.heroFixedWidthContent);
4085    var $heroImage = $parentSection.find(selectors.heroFixedWidthImage);
4086
4087    if (this.layout !== 'fixed_width') {
4088      return;
4089    }
4090
4091    $parentSection.removeClass(classes.indexSectionFlush);
4092    heroFixedHeight();
4093    $(window).resize(
4094      $.debounce(50, function() {
4095        heroFixedHeight();
4096      })
4097    );
4098
4099    function heroFixedHeight() {
4100      var contentHeight = $heroContent.height() + 50;
4101      var imageHeight = $heroImage.height();
4102
4103      if (contentHeight > imageHeight) {
4104        $heroImage.css('min-height', contentHeight);
4105      }
4106    }
4107  }
4108
4109  return hero;
4110})();
4111
4112// prettier-ignore
4113window.theme = window.theme || {};
4114
4115theme.SearchResultsTemplate = (function() {
4116  function renderResults(products, isLoading, searchQuery) {
4117    return [
4118      '<div class="predictive-search">',
4119      renderHeader(products, isLoading),
4120      renderProducts(products, searchQuery),
4121      '</div>'
4122    ].join('');
4123  }
4124
4125  function renderHeader(products, isLoading) {
4126    if (products.length === 0) {
4127      return '';
4128    }
4129
4130    return [
4131      '<div class="predictive-search-title">',
4132      '<h3 id="predictive-search" class="predictive-search-title__content">' +
4133        theme.strings.products +
4134        '</h3>',
4135      '<span class="predictive-search-title__loading-spinner">' +
4136        (isLoading
4137          ? '<span class= "icon-predictive-search-spinner" ></span >'
4138          : '') +
4139        '</span>',
4140      '</div>'
4141    ].join('');
4142  }
4143
4144  function loadingState() {
4145    return [
4146      '<div class="predictive-search">',
4147      '<div class="predictive-search-loading">',
4148      '<span class="visually-hidden">' + theme.strings.loading + '</span>',
4149      '<span class="predictive-search-loading__icon">',
4150      '<span class="icon-predictive-search-spinner"></span>',
4151      '</span>',
4152      '</div>',
4153      '</div>'
4154    ].join('');
4155  }
4156
4157  function renderViewAll(searchQuery) {
4158    return [
4159      '<button type="submit" class="predictive-search-view-all__button" tabindex="-1">',
4160      theme.strings.searchFor +
4161        '<span class="predictive-search-view-all__query"> &ldquo;' +
4162        _htmlEscape(searchQuery) +
4163        '&rdquo;</span>',
4164      '</button>'
4165    ].join('');
4166  }
4167
4168  function renderProducts(products, searchQuery) {
4169    var resultsCount = products.length;
4170
4171    return [
4172      '<ul id="predictive-search-results" class="predictive-search__list" role="listbox" aria-labelledby="predictive-search">',
4173      products
4174        .map(function(product, index) {
4175          return renderProduct(normalizeProduct(product), index, resultsCount);
4176        })
4177        .join(''),
4178      '<li id="search-all" class="predictive-search-view-all" role="option" data-search-result>' +
4179        renderViewAll(searchQuery) +
4180        '</li>',
4181      '</ul>'
4182    ].join('');
4183  }
4184
4185  function renderProduct(product, index, resultsCount) {
4186    return [
4187      '<li id="search-result-' +
4188        index +
4189        '" class="predictive-search-item" role="option" data-search-result>',
4190      '<a class="predictive-search-item__link" href="' +
4191        product.url +
4192        '" tabindex="-1">',
4193      '<div class="predictive-search__column predictive-search__column--image" data-image-with-placeholder-wrapper>',
4194      renderProductImage(product),
4195      '</div>',
4196      '<div class="predictive-search__column predictive-search__column--content ' +
4197        (getDetailsCount() ? '' : 'predictive-search__column--center') +
4198        '">',
4199      '<span class="predictive-search-item__title">',
4200      '<span class="predictive-search-item__title-text">' +
4201        product.title +
4202        '</span>',
4203      '</span>' + (getDetailsCount() ? renderProductDetails(product) : ''),
4204      '<span class="visually-hidden">, </span>',
4205      '<span class="visually-hidden">' +
4206        getNumberOfResultsString(index + 1, resultsCount) +
4207        '</span>',
4208      '</div>',
4209      '</a>',
4210      '</li>'
4211    ].join('');
4212  }
4213
4214  function renderProductImage(product) {
4215    if (product.image === null) {
4216      return '';
4217    }
4218
4219    return [
4220      '<div class="placeholder-background placeholder-background--animation" data-image-placeholder aria-hidden="true"></div>',
4221      '<img class="predictive-search-item__image lazyload" src="' +
4222        product.image.url +
4223        '" data-src="' +
4224        product.image.url +
4225        '" data-image alt="' +
4226        product.image.alt +
4227        '" />'
4228    ].join('');
4229  }
4230
4231  function renderProductDetails(product) {
4232    return [
4233      '<dl class="predictive-search-item__details price' +
4234        (product.isOnSale ? ' price--on-sale' : '') +
4235        (!product.available ? ' price--sold-out' : '') +
4236        (!product.isPriceVaries && product.isCompareVaries
4237          ? ' price--compare-price-hidden'
4238          : '') +
4239        '">',
4240      '<div class="predictive-search-item__detail">',
4241      renderVendor(product),
4242      '</div>',
4243      '<div class="predictive-search-item__detail predictive-search-item__detail--inline">' +
4244        renderProductPrice(product),
4245      '</div>',
4246      '</dl>'
4247    ].join('');
4248  }
4249  function renderProductPrice(product) {
4250    if (!theme.settings.predictiveSearchShowPrice) {
4251      return '';
4252    }
4253
4254    var accessibilityAnnounceComma = '<span class="visually-hidden">, </span>';
4255
4256    var priceMarkup =
4257      '<div class="price__regular">' + renderPrice(product) + '</div>';
4258
4259    var salePriceMarkup =
4260      '<div class="price__sale">' + renderSalePrice(product) + '</div>';
4261
4262    return (
4263      accessibilityAnnounceComma +
4264      '<div class="price__pricing-group">' +
4265      (product.isOnSale ? salePriceMarkup : priceMarkup) +
4266      '</div>'
4267    );
4268  }
4269
4270  function renderSalePrice(product) {
4271    return [
4272      '<dt>',
4273      '<span class="visually-hidden">' + theme.strings.salePrice + '</span>',
4274      '</dt>',
4275      '<dd>',
4276      '<span class="predictive-search-item__price predictive-search-item__price--sale">' +
4277        (product.isPriceVaries
4278          ? theme.strings.fromLowestPrice.replace('[price]', product.price)
4279          : product.price) +
4280        '</span>',
4281      '</dd>',
4282      '<div class="price__compare">' + renderCompareAtPrice(product) + '</div>'
4283    ].join('');
4284  }
4285
4286  function renderCompareAtPrice(product) {
4287    return [
4288      '<dt>',
4289      '<span class="visually-hidden">' +
4290        theme.strings.regularPrice +
4291        '</span> ',
4292      '</dt>',
4293      '<dd>',
4294      '<span class="predictive-search-item__price predictive-search-item__price--compare">' +
4295        product.compareAtPrice +
4296        '</span>',
4297      '</dd>'
4298    ].join('');
4299  }
4300
4301  function renderPrice(product) {
4302    return [
4303      '<dt>',
4304      '<span class="visually-hidden">' + theme.strings.regularPrice + '</span>',
4305      '</dt>',
4306      '<dd>',
4307      '<span class="predictive-search-item__price">' +
4308        (product.isPriceVaries
4309          ? theme.strings.fromLowestPrice.replace('[price]', product.price)
4310          : product.price) +
4311        '</span>',
4312      '</dd>'
4313    ].join('');
4314  }
4315
4316  function renderVendor(product) {
4317    if (!theme.settings.predictiveSearchShowVendor || product.vendor === '') {
4318      return '';
4319    }
4320
4321    return [
4322      '<dt>',
4323      '<span class="visually-hidden">' + theme.strings.vendor + '</span>',
4324      '</dt>',
4325      '<dd class="predictive-search-item__vendor">' + product.vendor + '</dd>'
4326    ].join('');
4327  }
4328
4329  function normalizeProduct(product) {
4330    var productOrVariant =
4331      product.variants.length > 0 ? product.variants[0] : product;
4332
4333    return {
4334      url: productOrVariant.url,
4335      image: getProductImage(product),
4336      title: product.title,
4337      vendor: product.vendor || '',
4338      price: theme.Currency.formatMoney(product.price_min, theme.moneyFormat),
4339      compareAtPrice: theme.Currency.formatMoney(
4340        product.compare_at_price_min,
4341        theme.moneyFormat
4342      ),
4343      available: product.available,
4344      isOnSale: isOnSale(product),
4345      isPriceVaries: isPriceVaries(product),
4346      isCompareVaries: isCompareVaries(product)
4347    };
4348  }
4349
4350  function getProductImage(product) {
4351    var image;
4352    var featuredImage;
4353
4354    if (product.variants.length > 0 && product.variants[0].image !== null) {
4355      featuredImage = product.variants[0].featured_image;
4356    } else if (product.image) {
4357      featuredImage = product.featured_image;
4358    } else {
4359      image = null;
4360    }
4361
4362    if (image !== null) {
4363      image = {
4364        url: theme.Images.getSizedImageUrl(featuredImage.url, '100x'),
4365        alt: featuredImage.alt
4366      };
4367    }
4368
4369    return image;
4370  }
4371
4372  function isOnSale(product) {
4373    return (
4374      product.compare_at_price_min !== null &&
4375      parseInt(product.compare_at_price_min, 10) >
4376        parseInt(product.price_min, 10)
4377    );
4378  }
4379
4380  function isPriceVaries(product) {
4381    return product.price_max !== product.price_min;
4382  }
4383
4384  function isCompareVaries(product) {
4385    return product.compare_at_price_max !== product.compare_at_price_min;
4386  }
4387
4388  // Returns the number of optional product details to be shown,
4389  // values of the detailsList need to be boolean.
4390  function getDetailsCount() {
4391    var detailsList = [
4392      theme.settings.predictiveSearchShowPrice,
4393      theme.settings.predictiveSearchShowVendor
4394    ];
4395
4396    var detailsCount = detailsList.reduce(function(acc, detail) {
4397      return acc + (detail ? 1 : 0);
4398    }, 0);
4399
4400    return detailsCount;
4401  }
4402
4403  function getNumberOfResultsString(resultNumber, resultsCount) {
4404    return theme.strings.number_of_results
4405      .replace('[result_number]', resultNumber)
4406      .replace('[results_count]', resultsCount);
4407  }
4408
4409  function _htmlEscape(input) {
4410    return input
4411      .replace(/&/g, '&amp;')
4412      .replace(/</g, '&lt;')
4413      .replace(/>/g, '&gt;')
4414      .replace(/"/g, '&quot;')
4415      .replace(/'/g, '&#39;');
4416  }
4417
4418  return function(data) {
4419    var products = data.products || [];
4420    var isLoading = data.isLoading;
4421    var searchQuery = data.searchQuery || '';
4422
4423    if (isLoading && products.length === 0) {
4424      return loadingState();
4425    }
4426
4427    return renderResults(products, isLoading, searchQuery);
4428  };
4429})();
4430
4431window.theme = window.theme || {};
4432
4433(function() {
4434  // (a11y) This function will be used by the Predictive Search Component
4435  // to announce the number of search results
4436  function numberOfResultsTemplateFct(data) {
4437    if (data.products.length === 1) {
4438      return theme.strings.one_result_found;
4439    } else {
4440      return theme.strings.number_of_results_found.replace(
4441        '[results_count]',
4442        data.products.length
4443      );
4444    }
4445  }
4446
4447  // (a11y) This function will be used by the Predictive Search Component
4448  // to announce that it's loading results
4449  function loadingResultsMessageTemplateFct() {
4450    return theme.strings.loading;
4451  }
4452
4453  function isPredictiveSearchSupported() {
4454    var shopifyFeatures = JSON.parse($('#shopify-features').text());
4455
4456    return shopifyFeatures.predictiveSearch;
4457  }
4458
4459  function isPredictiveSearchEnabled() {
4460    return window.theme.settings.predictiveSearchEnabled;
4461  }
4462
4463  function canInitializePredictiveSearch() {
4464    return isPredictiveSearchSupported() && isPredictiveSearchEnabled();
4465  }
4466
4467  // listen for search submits and validate query
4468  function validateSearchHandler(searchEl, submitEl) {
4469    submitEl.addEventListener(
4470      'click',
4471      validateSearchInput.bind(this, searchEl)
4472    );
4473  }
4474
4475  // if there is nothing in the search field, prevent submit
4476  function validateSearchInput(searchEl, evt) {
4477    var isInputValueEmpty = searchEl.value.trim().length === 0;
4478    if (!isInputValueEmpty) {
4479      return;
4480    }
4481
4482    if (typeof evt !== 'undefined') {
4483      evt.preventDefault();
4484    }
4485
4486    searchEl.focus();
4487  }
4488
4489  window.theme.SearchPage = (function() {
4490    var selectors = {
4491      searchReset: '[data-search-page-predictive-search-clear]',
4492      searchInput: '[data-search-page-predictive-search-input]',
4493      searchSubmit: '[data-search-page-predictive-search-submit]',
4494      searchResults: '[data-predictive-search-mount="default"]'
4495    };
4496
4497    var componentInstance;
4498    var searchInput = document.querySelector(selectors.searchInput);
4499    var searchSubmit = document.querySelector(selectors.searchSubmit);
4500
4501    function init(config) {
4502      componentInstance = new window.Shopify.theme.PredictiveSearchComponent({
4503        selectors: {
4504          input: selectors.searchInput,
4505          reset: selectors.searchReset,
4506          result: selectors.searchResults
4507        },
4508        resultTemplateFct: window.theme.SearchResultsTemplate,
4509        numberOfResultsTemplateFct: numberOfResultsTemplateFct,
4510        loadingResultsMessageTemplateFct: loadingResultsMessageTemplateFct,
4511        onOpen: function(nodes) {
4512          if (config.isTabletAndUp) {
4513            return;
4514          }
4515
4516          var searchInputBoundingRect = $(
4517            selectors.searchInput
4518          )[0].getBoundingClientRect();
4519          var bodyHeight = $('body').height();
4520          var offset = 50;
4521          var resultsMaxHeight =
4522            bodyHeight - searchInputBoundingRect.bottom - offset;
4523          $(nodes.result).css({
4524            maxHeight: resultsMaxHeight
4525          });
4526        },
4527        onBeforeDestroy: function(nodes) {
4528          // If the viewport width changes from mobile to tablet
4529          // reset the top position of the results
4530          $(nodes.result).css({
4531            maxHeight: ''
4532          });
4533        }
4534      });
4535
4536      validateSearchHandler(searchInput, searchSubmit);
4537    }
4538
4539    function unload() {
4540      if (!componentInstance) {
4541        return;
4542      }
4543      componentInstance.destroy();
4544      componentInstance = null;
4545    }
4546
4547    return {
4548      init: init,
4549      unload: unload
4550    };
4551  })();
4552
4553  window.theme.SearchHeader = (function() {
4554    var selectors = {
4555      searchInput: '[data-predictive-search-drawer-input]',
4556      searchResults: '[data-predictive-search-mount="drawer"]',
4557      searchFormContainer: '[data-search-form-container]',
4558      searchSubmit: '[data-search-form-submit]'
4559    };
4560
4561    var componentInstance;
4562    var searchInput = document.querySelector(selectors.searchInput);
4563    var searchSubmit = document.querySelector(selectors.searchSubmit);
4564
4565    function init(config) {
4566      componentInstance = new window.Shopify.theme.PredictiveSearchComponent({
4567        selectors: {
4568          input: selectors.searchInput,
4569          result: selectors.searchResults
4570        },
4571        resultTemplateFct: window.theme.SearchResultsTemplate,
4572        numberOfResultsTemplateFct: numberOfResultsTemplateFct,
4573        numberOfResults: config.numberOfResults,
4574        loadingResultsMessageTemplateFct: loadingResultsMessageTemplateFct,
4575        onInputBlur: function() {
4576          return false;
4577        },
4578        onOpen: function(nodes) {
4579          var searchInputBoundingRect = $(
4580            searchInput
4581          )[0].getBoundingClientRect();
4582
4583          // For tablet screens and up, stop the scroll area from extending past
4584          // the bottom of the screen because we're locking the body scroll
4585          var maxHeight =
4586            window.innerHeight -
4587            searchInputBoundingRect.bottom -
4588            (config.isTabletAndUp ? 20 : 0);
4589
4590          $(nodes.result).css({
4591            top: config.isTabletAndUp ? '' : searchInputBoundingRect.bottom,
4592            maxHeight: maxHeight
4593          });
4594        },
4595        onClose: function(nodes) {
4596          $(nodes.result).css({
4597            maxHeight: ''
4598          });
4599        },
4600        onBeforeDestroy: function(nodes) {
4601          // If the viewport width changes from mobile to tablet
4602          // reset the top position of the results
4603          $(nodes.result).css({
4604            top: ''
4605          });
4606        }
4607      });
4608
4609      validateSearchHandler(searchInput, searchSubmit);
4610    }
4611
4612    function unload() {
4613      if (!componentInstance) {
4614        return;
4615      }
4616
4617      componentInstance.destroy();
4618      componentInstance = null;
4619    }
4620
4621    function clearAndClose() {
4622      if (!componentInstance) {
4623        return;
4624      }
4625
4626      componentInstance.clearAndClose();
4627    }
4628
4629    return {
4630      init: init,
4631      unload: unload,
4632      clearAndClose: clearAndClose
4633    };
4634  })();
4635
4636  window.theme.Search = (function() {
4637    var classes = {
4638      searchTemplate: 'template-search'
4639    };
4640    var selectors = {
4641      siteHeader: '.site-header'
4642    };
4643    var mediaQueryList = {
4644      mobile: window.matchMedia('(max-width: 749px)'),
4645      tabletAndUp: window.matchMedia('(min-width: 750px)')
4646    };
4647
4648    function init() {
4649      if (!$(selectors.siteHeader).length) {
4650        return;
4651      }
4652
4653      if (!canInitializePredictiveSearch()) {
4654        return;
4655      }
4656
4657      Object.keys(mediaQueryList).forEach(function(device) {
4658        mediaQueryList[device].addListener(initSearchAccordingToViewport);
4659      });
4660
4661      initSearchAccordingToViewport();
4662    }
4663
4664    function initSearchAccordingToViewport() {
4665      theme.SearchDrawer.close();
4666      theme.SearchHeader.unload();
4667      theme.SearchPage.unload();
4668
4669      if (mediaQueryList.mobile.matches) {
4670        theme.SearchHeader.init({
4671          numberOfResults: 4,
4672          isTabletAndUp: false
4673        });
4674
4675        if (isSearchPage()) {
4676          theme.SearchPage.init({ isTabletAndUp: false });
4677        }
4678      } else {
4679        // Tablet and up
4680        theme.SearchHeader.init({
4681          numberOfResults: 4,
4682          isTabletAndUp: true
4683        });
4684
4685        if (isSearchPage()) {
4686          theme.SearchPage.init({ isTabletAndUp: true });
4687        }
4688      }
4689    }
4690
4691    function isSearchPage() {
4692      return $('body').hasClass(classes.searchTemplate);
4693    }
4694
4695    function unload() {
4696      theme.SearchHeader.unload();
4697      theme.SearchPage.unload();
4698    }
4699
4700    return {
4701      init: init,
4702      unload: unload
4703    };
4704  })();
4705})();
4706
4707window.theme = window.theme || {};
4708
4709theme.SearchDrawer = (function() {
4710  var selectors = {
4711    headerSection: '[data-header-section]',
4712    drawer: '[data-predictive-search-drawer]',
4713    drawerOpenButton: '[data-predictive-search-open-drawer]',
4714    headerSearchInput: '[data-predictive-search-drawer-input]',
4715    predictiveSearchWrapper: '[data-predictive-search-mount="drawer"]'
4716  };
4717
4718  var drawerInstance;
4719
4720  function init() {
4721    setAccessibilityProps();
4722
4723    drawerInstance = new theme.Drawers('SearchDrawer', 'top', {
4724      onDrawerOpen: function() {
4725        setHeight();
4726        theme.MobileNav.closeMobileNav();
4727        lockBodyScroll();
4728      },
4729      onDrawerClose: function() {
4730        theme.SearchHeader.clearAndClose();
4731        $(selectors.drawerOpenButton).focus();
4732        unlockBodyScroll();
4733      },
4734      withPredictiveSearch: true,
4735      $elementToFocusOnOpen: $(selectors.headerSearchInput)
4736    });
4737  }
4738
4739  function setAccessibilityProps() {
4740    $(selectors.drawerOpenButton)
4741      .attr('aria-controls', 'SearchDrawer')
4742      .attr('aria-expanded', 'false')
4743      .attr('aria-haspopup', 'dialog');
4744  }
4745
4746  function setHeight() {
4747    $(selectors.drawer).css({
4748      height: $(selectors.headerSection).outerHeight()
4749    });
4750  }
4751
4752  function close() {
4753    drawerInstance.close();
4754  }
4755
4756  function lockBodyScroll() {
4757    // Disable scroll except on the predictive search container.
4758    window.bodyScrollLock.disableBodyScroll(
4759      document.querySelector(selectors.predictiveSearchWrapper),
4760      {
4761        allowTouchMove: function(element) {
4762          return (
4763            // If the touch event is in an element under the predictive search
4764            // we allow don't prevent default.
4765            $(element).parents(selectors.predictiveSearchWrapper).length === 1
4766          );
4767        }
4768      }
4769    );
4770  }
4771
4772  function unlockBodyScroll() {
4773    window.bodyScrollLock.clearAllBodyScrollLocks();
4774  }
4775
4776  return {
4777    init: init,
4778    close: close
4779  };
4780})();
4781
4782theme.Disclosure = (function() {
4783  var selectors = {
4784    disclosureList: '[data-disclosure-list]',
4785    disclosureToggle: '[data-disclosure-toggle]',
4786    disclosureInput: '[data-disclosure-input]',
4787    disclosureOptions: '[data-disclosure-option]'
4788  };
4789
4790  var classes = {
4791    listVisible: 'disclosure-list--visible'
4792  };
4793
4794  function Disclosure($disclosure) {
4795    this.$container = $disclosure;
4796    this.cache = {};
4797    this._cacheSelectors();
4798    this._connectOptions();
4799    this._connectToggle();
4800    this._onFocusOut();
4801  }
4802
4803  Disclosure.prototype = _.assignIn({}, Disclosure.prototype, {
4804    _cacheSelectors: function() {
4805      this.cache = {
4806        $disclosureList: this.$container.find(selectors.disclosureList),
4807        $disclosureToggle: this.$container.find(selectors.disclosureToggle),
4808        $disclosureInput: this.$container.find(selectors.disclosureInput),
4809        $disclosureOptions: this.$container.find(selectors.disclosureOptions)
4810      };
4811    },
4812
4813    _connectToggle: function() {
4814      this.cache.$disclosureToggle.on(
4815        'click',
4816        function(evt) {
4817          var ariaExpanded =
4818            $(evt.currentTarget).attr('aria-expanded') === 'true';
4819          $(evt.currentTarget).attr('aria-expanded', !ariaExpanded);
4820
4821          this.cache.$disclosureList.toggleClass(classes.listVisible);
4822        }.bind(this)
4823      );
4824    },
4825
4826    _connectOptions: function() {
4827      this.cache.$disclosureOptions.on(
4828        'click',
4829        function(evt) {
4830          this._submitForm($(evt.currentTarget).data('value'));
4831        }.bind(this)
4832      );
4833    },
4834
4835    _onFocusOut: function() {
4836      this.cache.$disclosureToggle.on(
4837        'focusout',
4838        function(evt) {
4839          var disclosureLostFocus =
4840            this.$container.has(evt.relatedTarget).length === 0;
4841
4842          if (disclosureLostFocus) {
4843            this._hideList();
4844          }
4845        }.bind(this)
4846      );
4847
4848      this.cache.$disclosureList.on(
4849        'focusout',
4850        function(evt) {
4851          var childInFocus =
4852            $(evt.currentTarget).has(evt.relatedTarget).length > 0;
4853          var isVisible = this.cache.$disclosureList.hasClass(
4854            classes.listVisible
4855          );
4856
4857          if (isVisible && !childInFocus) {
4858            this._hideList();
4859          }
4860        }.bind(this)
4861      );
4862
4863      this.$container.on(
4864        'keyup',
4865        function(evt) {
4866          if (evt.which !== slate.utils.keyboardKeys.ESCAPE) return;
4867          this._hideList();
4868          this.cache.$disclosureToggle.focus();
4869        }.bind(this)
4870      );
4871
4872      $('body').on(
4873        'click',
4874        function(evt) {
4875          var isOption = this.$container.has(evt.target).length > 0;
4876          var isVisible = this.cache.$disclosureList.hasClass(
4877            classes.listVisible
4878          );
4879
4880          if (isVisible && !isOption) {
4881            this._hideList();
4882          }
4883        }.bind(this)
4884      );
4885    },
4886
4887    _submitForm: function(value) {
4888      this.cache.$disclosureInput.val(value);
4889      this.$container.parents('form').submit();
4890    },
4891
4892    _hideList: function() {
4893      this.cache.$disclosureList.removeClass(classes.listVisible);
4894      this.cache.$disclosureToggle.attr('aria-expanded', false);
4895    },
4896
4897    unload: function() {
4898      this.cache.$disclosureOptions.off();
4899      this.cache.$disclosureToggle.off();
4900      this.cache.$disclosureList.off();
4901      this.$container.off();
4902    }
4903  });
4904
4905  return Disclosure;
4906})();
4907
4908
4909/* ================ TEMPLATES ================ */
4910(function() {
4911  var $filterBy = $('#BlogTagFilter');
4912
4913  if (!$filterBy.length) {
4914    return;
4915  }
4916
4917  slate.utils.resizeSelects($filterBy);
4918
4919  $filterBy.on('change', function() {
4920    location.href = $(this).val();
4921  });
4922})();
4923
4924window.theme = theme || {};
4925
4926theme.customerTemplates = (function() {
4927  var selectors = {
4928    RecoverHeading: '#RecoverHeading',
4929    RecoverEmail: '#RecoverEmail',
4930    LoginHeading: '#LoginHeading'
4931  };
4932
4933  function initEventListeners() {
4934    this.$RecoverHeading = $(selectors.RecoverHeading);
4935    this.$RecoverEmail = $(selectors.RecoverEmail);
4936    this.$LoginHeading = $(selectors.LoginHeading);
4937
4938    // Show reset password form
4939    $('#RecoverPassword').on(
4940      'click',
4941      function(evt) {
4942        evt.preventDefault();
4943        showRecoverPasswordForm();
4944        this.$RecoverHeading.attr('tabindex', '-1').focus();
4945      }.bind(this)
4946    );
4947
4948    // Hide reset password form
4949    $('#HideRecoverPasswordLink').on(
4950      'click',
4951      function(evt) {
4952        evt.preventDefault();
4953        hideRecoverPasswordForm();
4954        this.$LoginHeading.attr('tabindex', '-1').focus();
4955      }.bind(this)
4956    );
4957
4958    this.$RecoverHeading.on('blur', function() {
4959      $(this).removeAttr('tabindex');
4960    });
4961
4962    this.$LoginHeading.on('blur', function() {
4963      $(this).removeAttr('tabindex');
4964    });
4965  }
4966
4967  /**
4968   *
4969   *  Show/Hide recover password form
4970   *
4971   */
4972
4973  function showRecoverPasswordForm() {
4974    $('#RecoverPasswordForm').removeClass('hide');
4975    $('#CustomerLoginForm').addClass('hide');
4976
4977    if (this.$RecoverEmail.attr('aria-invalid') === 'true') {
4978      this.$RecoverEmail.focus();
4979    }
4980  }
4981
4982  function hideRecoverPasswordForm() {
4983    $('#RecoverPasswordForm').addClass('hide');
4984    $('#CustomerLoginForm').removeClass('hide');
4985  }
4986
4987  /**
4988   *
4989   *  Show reset password success message
4990   *
4991   */
4992  function resetPasswordSuccess() {
4993    var $formState = $('.reset-password-success');
4994
4995    // check if reset password form was successfully submited.
4996    if (!$formState.length) {
4997      return;
4998    }
4999
5000    // show success message
5001    $('#ResetSuccess')
5002      .removeClass('hide')
5003      .focus();
5004  }
5005
5006  /**
5007   *
5008   *  Show/hide customer address forms
5009   *
5010   */
5011  function customerAddressForm() {
5012    var $newAddressForm = $('#AddressNewForm');
5013    var $newAddressFormButton = $('#AddressNewButton');
5014
5015    if (!$newAddressForm.length) {
5016      return;
5017    }
5018
5019    // Initialize observers on address selectors, defined in shopify_common.js
5020    if (Shopify) {
5021      // eslint-disable-next-line no-new
5022      new Shopify.CountryProvinceSelector(
5023        'AddressCountryNew',
5024        'AddressProvinceNew',
5025        {
5026          hideElement: 'AddressProvinceContainerNew'
5027        }
5028      );
5029    }
5030
5031    // Initialize each edit form's country/province selector
5032    $('.address-country-option').each(function() {
5033      var formId = $(this).data('form-id');
5034      var countrySelector = 'AddressCountry_' + formId;
5035      var provinceSelector = 'AddressProvince_' + formId;
5036      var containerSelector = 'AddressProvinceContainer_' + formId;
5037
5038      // eslint-disable-next-line no-new
5039      new Shopify.CountryProvinceSelector(countrySelector, provinceSelector, {
5040        hideElement: containerSelector
5041      });
5042    });
5043
5044    // Toggle new/edit address forms
5045    $('.address-new-toggle').on('click', function() {
5046      var isExpanded = $newAddressFormButton.attr('aria-expanded') === 'true';
5047
5048      $newAddressForm.toggleClass('hide');
5049      $newAddressFormButton.attr('aria-expanded', !isExpanded).focus();
5050    });
5051
5052    $('.address-edit-toggle').on('click', function() {
5053      var formId = $(this).data('form-id');
5054      var $editButton = $('#EditFormButton_' + formId);
5055      var $editAddress = $('#EditAddress_' + formId);
5056      var isExpanded = $editButton.attr('aria-expanded') === 'true';
5057
5058      $editAddress.toggleClass('hide');
5059      $editButton.attr('aria-expanded', !isExpanded).focus();
5060    });
5061
5062    $('.address-delete').on('click', function() {
5063      var $el = $(this);
5064      var target = $el.data('target');
5065      var confirmMessage = $el.data('confirm-message');
5066
5067      // eslint-disable-next-line no-alert
5068      if (
5069        confirm(
5070          confirmMessage || 'Are you sure you wish to delete this address?'
5071        )
5072      ) {
5073        Shopify.postLink(target, {
5074          parameters: { _method: 'delete' }
5075        });
5076      }
5077    });
5078  }
5079
5080  /**
5081   *
5082   *  Check URL for reset password hash
5083   *
5084   */
5085  function checkUrlHash() {
5086    var hash = window.location.hash;
5087
5088    // Allow deep linking to recover password form
5089    if (hash === '#recover') {
5090      showRecoverPasswordForm.bind(this)();
5091    }
5092  }
5093
5094  return {
5095    init: function() {
5096      initEventListeners();
5097      checkUrlHash();
5098      resetPasswordSuccess();
5099      customerAddressForm();
5100    }
5101  };
5102})();
5103
5104
5105/*================ SECTIONS ================*/
5106window.theme = window.theme || {};
5107
5108theme.Cart = (function() {
5109  var selectors = {
5110    cartCount: '[data-cart-count]',
5111    cartCountBubble: '[data-cart-count-bubble]',
5112    cartDiscount: '[data-cart-discount]',
5113    cartDiscountTitle: '[data-cart-discount-title]',
5114    cartDiscountAmount: '[data-cart-discount-amount]',
5115    cartDiscountWrapper: '[data-cart-discount-wrapper]',
5116    cartErrorMessage: '[data-cart-error-message]',
5117    cartErrorMessageWrapper: '[data-cart-error-message-wrapper]',
5118    cartItem: '[data-cart-item]',
5119    cartItemDetails: '[data-cart-item-details]',
5120    cartItemDiscount: '[data-cart-item-discount]',
5121    cartItemDiscountedPriceGroup: '[data-cart-item-discounted-price-group]',
5122    cartItemDiscountTitle: '[data-cart-item-discount-title]',
5123    cartItemDiscountAmount: '[data-cart-item-discount-amount]',
5124    cartItemDiscountList: '[data-cart-item-discount-list]',
5125    cartItemFinalPrice: '[data-cart-item-final-price]',
5126    cartItemImage: '[data-cart-item-image]',
5127    cartItemLinePrice: '[data-cart-item-line-price]',
5128    cartItemOriginalPrice: '[data-cart-item-original-price]',
5129    cartItemPrice: '[data-cart-item-price]',
5130    cartItemPriceList: '[data-cart-item-price-list]',
5131    cartItemProperty: '[data-cart-item-property]',
5132    cartItemPropertyName: '[data-cart-item-property-name]',
5133    cartItemPropertyValue: '[data-cart-item-property-value]',
5134    cartItemRegularPriceGroup: '[data-cart-item-regular-price-group]',
5135    cartItemRegularPrice: '[data-cart-item-regular-price]',
5136    cartItemTitle: '[data-cart-item-title]',
5137    cartItemOption: '[data-cart-item-option]',
5138    cartLineItems: '[data-cart-line-items]',
5139    cartNote: '[data-cart-notes]',
5140    cartQuantityErrorMessage: '[data-cart-quantity-error-message]',
5141    cartQuantityErrorMessageWrapper:
5142      '[data-cart-quantity-error-message-wrapper]',
5143    cartRemove: '[data-cart-remove]',
5144    cartStatus: '[data-cart-status]',
5145    cartSubtotal: '[data-cart-subtotal]',
5146    cartTableCell: '[data-cart-table-cell]',
5147    cartWrapper: '[data-cart-wrapper]',
5148    emptyPageContent: '[data-empty-page-content]',
5149    quantityInput: '[data-quantity-input]',
5150    quantityInputMobile: '[data-quantity-input-mobile]',
5151    quantityInputDesktop: '[data-quantity-input-desktop]',
5152    quantityLabelMobile: '[data-quantity-label-mobile]',
5153    quantityLabelDesktop: '[data-quantity-label-desktop]',
5154    inputQty: '[data-quantity-input]',
5155    thumbnails: '.cart__image',
5156    unitPrice: '[data-unit-price]',
5157    unitPriceBaseUnit: '[data-unit-price-base-unit]',
5158    unitPriceGroup: '[data-unit-price-group]'
5159  };
5160
5161  var classes = {
5162    cartNoCookies: 'cart--no-cookies',
5163    cartRemovedProduct: 'cart__removed-product',
5164    hide: 'hide',
5165    inputError: 'input--error'
5166  };
5167
5168  var attributes = {
5169    cartItemIndex: 'data-cart-item-index',
5170    cartItemKey: 'data-cart-item-key',
5171    cartItemQuantity: 'data-cart-item-quantity',
5172    cartItemTitle: 'data-cart-item-title',
5173    cartItemUrl: 'data-cart-item-url',
5174    quantityItem: 'data-quantity-item'
5175  };
5176
5177  theme.breakpoints = theme.breakpoints || {};
5178
5179  if (
5180    isNaN(theme.breakpoints.medium) ||
5181    theme.breakpoints.medium === undefined
5182  ) {
5183    theme.breakpoints.medium = 750;
5184  }
5185
5186  var mediumUpQuery = '(min-width: ' + theme.breakpoints.medium + 'px)';
5187
5188  function Cart(container) {
5189    this.$container = $(container);
5190    this.$thumbnails = $(selectors.thumbnails, this.$container);
5191    this.ajaxEnabled = this.$container.data('ajax-enabled');
5192
5193    if (!this.cookiesEnabled()) {
5194      this.$container.addClass(classes.cartNoCookies);
5195    }
5196
5197    this.$thumbnails.css('cursor', 'pointer');
5198    this.$container.on(
5199      'click',
5200      selectors.thumbnails,
5201      this._handleThumbnailClick
5202    );
5203
5204    this.$container.on(
5205      'change',
5206      selectors.inputQty,
5207      $.debounce(500, this._handleInputQty.bind(this))
5208    );
5209
5210    this.mql = window.matchMedia(mediumUpQuery);
5211    this.mql.addListener(this.setQuantityFormControllers.bind(this));
5212    this.setQuantityFormControllers();
5213
5214    if (this.ajaxEnabled) {
5215      /**
5216       * Because the entire cart is recreated when a cart item is updated,
5217       * we cannot cache the elements in the cart. Instead, we add the event
5218       * listeners on the cart's container to allow us to retain the event
5219       * listeners after rebuilding the cart when an item is updated.
5220       */
5221
5222      this.$container.on(
5223        'change',
5224        selectors.cartNote,
5225        this._onNoteChange.bind(this)
5226      );
5227
5228      this.$container.on(
5229        'click',
5230        selectors.cartRemove,
5231        this._onRemoveItem.bind(this)
5232      );
5233
5234      this._setupCartTemplates();
5235    }
5236  }
5237
5238  Cart.prototype = _.assignIn({}
5238, Cart.prototype, {
5239    _setupCartTemplates: function() {
5240      this.$itemTemplate = $(selectors.cartItem, this.$container)
5241        .first()
5242        .clone();
5243      this.$itemDiscountTemplate = $(
5244        selectors.cartItemDiscount,
5245        this.$itemTemplate
5246      ).clone();
5247      this.$itemOptionTemplate = $(
5248        selectors.cartItemOption,
5249        this.$itemTemplate
5250      ).clone();
5251      this.$itemPropertyTemplate = $(
5252        selectors.cartItemProperty,
5253        this.$itemTemplate
5254      ).clone();
5255      this.$itemPriceListTemplate = $(
5256        selectors.cartItemPriceList,
5257        this.$itemTemplate
5258      ).clone();
5259      this.$itemLinePriceTemplate = $(
5260        selectors.cartItemLinePrice,
5261        this.$itemTemplate
5262      ).clone();
5263      this.$cartDiscountTemplate = $(
5264        selectors.cartDiscount,
5265        this.$container
5266      ).clone();
5267    },
5268
5269    _handleInputQty: function(evt) {
5270      var $input = $(evt.target);
5271      var itemIndex = $input.data('quantity-item');
5272      var $itemElement = $input.closest(selectors.cartItem);
5273      var $itemQtyInputs = $('[data-quantity-item=' + itemIndex + ']');
5274      var value = parseInt($input.val());
5275      var isValidValue = !(value < 0 || isNaN(value));
5276      $itemQtyInputs.val(value);
5277
5278      this._hideCartError();
5279      this._hideQuantityErrorMessage();
5280
5281      if (!isValidValue) {
5282        this._showQuantityErrorMessages($itemElement);
5283        return;
5284      }
5285
5286      if (isValidValue && this.ajaxEnabled) {
5287        this._updateItemQuantity(
5288          itemIndex,
5289          $itemElement,
5290          $itemQtyInputs,
5291          value
5292        );
5293      }
5294    },
5295
5296    _updateItemQuantity: function(
5297      itemIndex,
5298      $itemElement,
5299      $itemQtyInputs,
5300      value
5301    ) {
5302      var key = $itemElement.attr(attributes.cartItemKey);
5303      var index = $itemElement.attr(attributes.cartItemIndex);
5304
5305      var params = {
5306        url: '/cart/change.js',
5307        data: { quantity: value, line: index },
5308        dataType: 'json'
5309      };
5310
5311      $.post(params)
5312        .done(
5313          function(state) {
5314            if (state.item_count === 0) {
5315              this._emptyCart();
5316            } else {
5317              this._createCart(state);
5318
5319              if (value === 0) {
5320                this._showRemoveMessage($itemElement.clone());
5321              } else {
5322                var $lineItem = $('[data-cart-item-key="' + key + '"]');
5323                var item = this.getItem(key, state);
5324
5325                $(selectors.quantityInput, $lineItem).focus();
5326                this._updateLiveRegion(item);
5327              }
5328            }
5329
5330            this._setCartCountBubble(state.item_count);
5331          }.bind(this)
5332        )
5333        .fail(
5334          function() {
5335            this._showCartError($itemQtyInputs);
5336          }.bind(this)
5337        );
5338    },
5339
5340    getItem: function(key, state) {
5341      return state.items.find(function(item) {
5342        return item.key === key;
5343      });
5344    },
5345
5346    _liveRegionText: function(item) {
5347      // Dummy content for live region
5348      var liveRegionText =
5349        theme.strings.update +
5350        ': [QuantityLabel]: [Quantity], [Regular] [$$] [DiscountedPrice] [$]. [PriceInformation]';
5351
5352      // Update Quantity
5353      liveRegionText = liveRegionText
5354        .replace('[QuantityLabel]', theme.strings.quantity)
5355        .replace('[Quantity]', item.quantity);
5356
5357      // Update pricing information
5358      var regularLabel = '';
5359      var regularPrice = theme.Currency.formatMoney(
5360        item.original_line_price,
5361        theme.moneyFormat
5362      );
5363      var discountLabel = '';
5364      var discountPrice = '';
5365      var discountInformation = '';
5366
5367      if (item.original_line_price > item.final_line_price) {
5368        regularLabel = theme.strings.regularTotal;
5369
5370        discountLabel = theme.strings.discountedTotal;
5371        discountPrice = theme.Currency.formatMoney(
5372          item.final_line_price,
5373          theme.moneyFormat
5374        );
5375
5376        discountInformation = theme.strings.priceColumn;
5377      }
5378
5379      liveRegionText = liveRegionText
5380        .replace('[Regular]', regularLabel)
5381        .replace('[$$]', regularPrice)
5382        .replace('[DiscountedPrice]', discountLabel)
5383        .replace('[$]', discountPrice)
5384        .replace('[PriceInformation]', discountInformation)
5385        .trim();
5386
5387      return liveRegionText;
5388    },
5389
5390    _updateLiveRegion: function(item) {
5391      var $liveRegion = $(selectors.cartStatus);
5392      $liveRegion.html(this._liveRegionText(item)).attr('aria-hidden', false);
5393
5394      // hide content from accessibility tree after announcement
5395      setTimeout(function() {
5396        $liveRegion.attr('aria-hidden', true);
5397      }, 1000);
5398    },
5399
5400    _createCart: function(state) {
5401      var cartDiscountList = this._createCartDiscountList(state);
5402
5403      $(selectors.cartLineItems, this.$container).html(
5404        this._createLineItemList(state)
5405      );
5406
5407      this.setQuantityFormControllers();
5408
5409      $(selectors.cartNote, this.$container).val(state.note);
5410
5411      if (cartDiscountList.length === 0) {
5412        $(selectors.cartDiscountWrapper, this.$container)
5413          .html('')
5414          .addClass(classes.hide);
5415      } else {
5416        $(selectors.cartDiscountWrapper, this.$container)
5417          .html(cartDiscountList)
5418          .removeClass(classes.hide);
5419      }
5420
5421      $(selectors.cartSubtotal, this.$container).html(
5422        theme.Currency.formatMoney(
5423          state.total_price,
5424          theme.moneyFormatWithCurrency
5425        )
5426      );
5427    },
5428
5429    _createCartDiscountList: function(cart) {
5430      return $.map(
5431        cart.cart_level_discount_applications,
5432        function(discount) {
5433          var $discount = this.$cartDiscountTemplate.clone();
5434          $discount.find(selectors.cartDiscountTitle).text(discount.title);
5435          $discount
5436            .find(selectors.cartDiscountAmount)
5437            .html(
5438              theme.Currency.formatMoney(
5439                discount.total_allocated_amount,
5440                theme.moneyFormat
5441              )
5442            );
5443          return $discount[0];
5444        }.bind(this)
5445      );
5446    },
5447
5448    _createLineItemList: function(state) {
5449      return $.map(
5450        state.items,
5451        function(item, index) {
5452          var $item = this.$itemTemplate.clone();
5453          var $itemPriceList = this.$itemPriceListTemplate.clone();
5454
5455          this._setLineItemAttributes($item, item, index);
5456          this._setLineItemImage($item, item.featured_image);
5457
5458          $(selectors.cartItemTitle, $item)
5459            .text(item.product_title)
5460            .attr('href', item.url);
5461
5462          var productDetailsList = this._createProductDetailsList(
5463            item.product_has_only_default_variant,
5464            item.options_with_values,
5465            item.properties
5466          );
5467          this._setProductDetailsList($item, productDetailsList);
5468
5469          this._setItemRemove($item, item.title);
5470
5471          $itemPriceList.html(
5472            this._createItemPrice(
5473              item.original_price,
5474              item.final_price,
5475              this.$itemPriceListTemplate
5476            )
5477          );
5478
5479          if (item.unit_price_measurement) {
5480            $itemPriceList.append(
5481              this._createUnitPrice(
5482                item.unit_price,
5483                item.unit_price_measurement,
5484                this.$itemPriceListTemplate
5485              )
5486            );
5487          }
5488
5489          this._setItemPrice($item, $itemPriceList);
5490
5491          var itemDiscountList = this._createItemDiscountList(item);
5492          this._setItemDiscountList($item, itemDiscountList);
5493
5494          this._setQuantityInputs($item, item, index);
5495
5496          var itemLinePrice = this._createItemPrice(
5497            item.original_line_price,
5498            item.final_line_price,
5499            this.$itemLinePriceTemplate
5500          );
5501          this._setItemLinePrice($item, itemLinePrice);
5502
5503          return $item[0];
5504        }.bind(this)
5505      );
5506    },
5507
5508    _setLineItemAttributes: function($item, item, index) {
5509      $item
5510        .attr(attributes.cartItemKey, item.key)
5511        .attr(attributes.cartItemUrl, item.url)
5512        .attr(attributes.cartItemTitle, item.title)
5513        .attr(attributes.cartItemIndex, index + 1)
5514        .attr(attributes.cartItemQuantity, item.quantity);
5515    },
5516
5517    _setLineItemImage: function($item, featuredImage) {
5518      var $image = $(selectors.cartItemImage, $item);
5519
5520      var sizedImageUrl =
5521        featuredImage.url !== null
5522          ? theme.Images.getSizedImageUrl(featuredImage.url, 'x190')
5523          : null;
5524
5525      if (sizedImageUrl) {
5526        $image
5527          .attr('alt', featuredImage.alt)
5528          .attr('src', sizedImageUrl)
5529          .removeClass(classes.hide);
5530      } else {
5531        $image.remove();
5532      }
5533    },
5534
5535    _setProductDetailsList: function($item, productDetailsList) {
5536      var $itemDetails = $(selectors.cartItemDetails, $item);
5537
5538      if (productDetailsList.length === 0) {
5539        $itemDetails.addClass(classes.hide).text('');
5540      } else {
5541        $itemDetails.removeClass(classes.hide).html(productDetailsList);
5542      }
5543    },
5544
5545    _setItemPrice: function($item, price) {
5546      $(selectors.cartItemPrice, $item).html(price);
5547    },
5548
5549    _setItemDiscountList: function($item, discountList) {
5550      var $itemDiscountList = $(selectors.cartItemDiscountList, $item);
5551
5552      if (discountList.length === 0) {
5553        $itemDiscountList.html('').addClass(classes.hide);
5554      } else {
5555        $itemDiscountList.html(discountList).removeClass(classes.hide);
5556      }
5557    },
5558
5559    _setItemRemove: function($item, title) {
5560      $(selectors.cartRemove, $item).attr(
5561        'aria-label',
5562        theme.strings.removeLabel.replace('[product]', title)
5563      );
5564    },
5565
5566    _setQuantityInputs: function($item, item, index) {
5567      $(selectors.quantityInputMobile, $item)
5568        .attr('id', 'updates_' + item.key)
5569        .attr(attributes.quantityItem, index + 1)
5570        .val(item.quantity);
5571
5572      $(selectors.quantityInputDesktop, $item)
5573        .attr('id', 'updates_large_' + item.key)
5574        .attr(attributes.quantityItem, index + 1)
5575        .val(item.quantity);
5576
5577      $(selectors.quantityLabelMobile, $item).attr(
5578        'for',
5579        'updates_' + item.key
5580      );
5581
5582      $(selectors.quantityLabelDesktop, $item).attr(
5583        'for',
5584        'updates_large_' + item.key
5585      );
5586    },
5587
5588    setQuantityFormControllers: function() {
5589      if (this.mql.matches) {
5590        $(selectors.quantityInputDesktop).attr('name', 'updates[]');
5591        $(selectors.quantityInputMobile).removeAttr('name');
5592      } else {
5593        $(selectors.quantityInputMobile).attr('name', 'updates[]');
5594        $(selectors.quantityInputDesktop).removeAttr('name');
5595      }
5596    },
5597
5598    _setItemLinePrice: function($item, price) {
5599      $(selectors.cartItemLinePrice, $item).html(price);
5600    },
5601
5602    _createProductDetailsList: function(
5603      product_has_only_default_variant,
5604      options,
5605      properties
5606    ) {
5607      var optionsPropertiesHTML = [];
5608
5609      if (!product_has_only_default_variant) {
5610        optionsPropertiesHTML = optionsPropertiesHTML.concat(
5611          this._getOptionList(options)
5612        );
5613      }
5614
5615      if (properties !== null && Object.keys(properties).length !== 0) {
5616        optionsPropertiesHTML = optionsPropertiesHTML.concat(
5617          this._getPropertyList(properties)
5618        );
5619      }
5620
5621      return optionsPropertiesHTML;
5622    },
5623
5624    _getOptionList: function(options) {
5625      return $.map(
5626        options,
5627        function(option) {
5628          var $optionElement = this.$itemOptionTemplate.clone();
5629
5630          $optionElement
5631            .text(option.name + ': ' + option.value)
5632            .removeClass(classes.hide);
5633
5634          return $optionElement[0];
5635        }.bind(this)
5636      );
5637    },
5638
5639    _getPropertyList: function(properties) {
5640      var propertiesArray =
5641        properties !== null ? Object.entries(properties) : [];
5642
5643      return $.map(
5644        propertiesArray,
5645        function(property) {
5646          var $propertyElement = this.$itemPropertyTemplate.clone();
5647
5648          // Line item properties prefixed with an underscore are not to be displayed
5649          if (property[0].charAt(0) === '_') return;
5650
5651          // if the property value has a length of 0 (empty), don't display it
5652          if (property[1].length === 0) return;
5653
5654          $propertyElement
5655            .find(selectors.cartItemPropertyName)
5656            .text(property[0]);
5657
5658          if (property[0].indexOf('/uploads/') === -1) {
5659            $propertyElement
5660              .find(selectors.cartItemPropertyValue)
5661              .text(': ' + property[1]);
5662          } else {
5663            $propertyElement
5664              .find(selectors.cartItemPropertyValue)
5665              .html(
5666                ': <a href="' +
5667                  property[1] +
5668                  '"> ' +
5669                  property[1].split('/').pop() +
5670                  '</a>'
5671              );
5672          }
5673
5674          $propertyElement.removeClass(classes.hide);
5675
5676          return $propertyElement[0];
5677        }.bind(this)
5678      );
5679    },
5680
5681    _createItemPrice: function(original_price, final_price, $priceTemplate) {
5682      if (original_price !== final_price) {
5683        var $discountedPrice = $(
5684          selectors.cartItemDiscountedPriceGroup,
5685          $priceTemplate
5686        ).clone();
5687
5688        $(selectors.cartItemOriginalPrice, $discountedPrice).html(
5689          theme.Currency.formatMoney(original_price, theme.moneyFormat)
5690        );
5691        $(selectors.cartItemFinalPrice, $discountedPrice).html(
5692          theme.Currency.formatMoney(final_price, theme.moneyFormat)
5693        );
5694        $discountedPrice.removeClass(classes.hide);
5695
5696        return $discountedPrice[0];
5697      } else {
5698        var $regularPrice = $(
5699          selectors.cartItemRegularPriceGroup,
5700          $priceTemplate
5701        ).clone();
5702
5703        $(selectors.cartItemRegularPrice, $regularPrice).html(
5704          theme.Currency.formatMoney(original_price, theme.moneyFormat)
5705        );
5706
5707        $regularPrice.removeClass(classes.hide);
5708
5709        return $regularPrice[0];
5710      }
5711    },
5712
5713    _createUnitPrice: function(
5714      unitPrice,
5715      unitPriceMeasurement,
5716      $itemPriceGroup
5717    ) {
5718      var $unitPriceGroup = $(
5719        selectors.unitPriceGroup,
5720        $itemPriceGroup
5721      ).clone();
5722
5723      var unitPriceBaseUnit =
5724        (unitPriceMeasurement.reference_value !== 1
5725          ? unitPriceMeasurement.reference_value
5726          : '') + unitPriceMeasurement.reference_unit;
5727
5728      $(selectors.unitPriceBaseUnit, $unitPriceGroup).text(unitPriceBaseUnit);
5729      $(selectors.unitPrice, $unitPriceGroup).html(
5730        theme.Currency.formatMoney(unitPrice, theme.moneyFormat)
5731      );
5732
5733      $unitPriceGroup.removeClass(classes.hide);
5734
5735      return $unitPriceGroup[0];
5736    },
5737
5738    _createItemDiscountList: function(item) {
5739      return $.map(
5740        item.line_level_discount_allocations,
5741        function(discount) {
5742          var $discount = this.$itemDiscountTemplate.clone();
5743          $discount
5744            .find(selectors.cartItemDiscountTitle)
5745            .text(discount.discount_application.title);
5746          $discount
5747            .find(selectors.cartItemDiscountAmount)
5748            .html(
5749              theme.Currency.formatMoney(discount.amount, theme.moneyFormat)
5750            );
5751          return $discount[0];
5752        }.bind(this)
5753      );
5754    },
5755
5756    _showQuantityErrorMessages: function(itemElement) {
5757      $(selectors.cartQuantityErrorMessage, itemElement).text(
5758        theme.strings.quantityMinimumMessage
5759      );
5760
5761      $(selectors.cartQuantityErrorMessageWrapper, itemElement).removeClass(
5762        classes.hide
5763      );
5764
5765      $(selectors.inputQty, itemElement)
5766        .addClass(classes.inputError)
5767        .focus();
5768    },
5769
5770    _hideQuantityErrorMessage: function() {
5771      var $errorMessages = $(
5772        selectors.cartQuantityErrorMessageWrapper
5773      ).addClass(classes.hide);
5774
5775      $(selectors.cartQuantityErrorMessage, $errorMessages).text('');
5776
5777      $(selectors.inputQty, this.$container).removeClass(classes.inputError);
5778    },
5779
5780    _handleThumbnailClick: function(evt) {
5781      var url = $(evt.target)
5782        .closest(selectors.cartItem)
5783        .data('cart-item-url');
5784
5785      window.location.href = url;
5786    },
5787
5788    _onNoteChange: function(evt) {
5789      var note = evt.currentTarget.value;
5790      this._hideCartError();
5791      this._hideQuantityErrorMessage();
5792
5793      var params = {
5794        url: '/cart/update.js',
5795        data: { note: note },
5796        dataType: 'json'
5797      };
5798
5799      $.post(params).fail(
5800        function() {
5801          this._showCartError(evt.currentTarget);
5802        }.bind(this)
5803      );
5804    },
5805
5806    _showCartError: function(elementToFocus) {
5807      $(selectors.cartErrorMessage).text(theme.strings.cartError);
5808
5809      $(selectors.cartErrorMessageWrapper).removeClass(classes.hide);
5810
5811      elementToFocus.focus();
5812    },
5813
5814    _hideCartError: function() {
5815      $(selectors.cartErrorMessageWrapper).addClass(classes.hide);
5816      $(selectors.cartErrorMessage).text('');
5817    },
5818
5819    _onRemoveItem: function(evt) {
5820      evt.preventDefault();
5821      var $remove = $(evt.target);
5822      var $lineItem = $remove.closest(selectors.cartItem);
5823      var index = $lineItem.attr(attributes.cartItemIndex);
5824      this._hideCartError();
5825
5826      var params = {
5827        url: '/cart/change.js',
5828        data: { quantity: 0, line: index },
5829        dataType: 'json'
5830      };
5831
5832      $.post(params)
5833        .done(
5834          function(state) {
5835            if (state.item_count === 0) {
5836              this._emptyCart();
5837            } else {
5838              this._createCart(state);
5839              this._showRemoveMessage($lineItem.clone());
5840            }
5841
5842            this._setCartCountBubble(state.item_count);
5843          }.bind(this)
5844        )
5845        .fail(
5846          function() {
5847            this._showCartError(null);
5848          }.bind(this)
5849        );
5850    },
5851
5852    _showRemoveMessage: function(lineItem) {
5853      var index = lineItem.data('cart-item-index');
5854      var removeMessage = this._getRemoveMessage(lineItem);
5855      var $lineItemAtIndex;
5856
5857      if (index - 1 === 0) {
5858        $lineItemAtIndex = $('[data-cart-item-index="1"]', this.$container);
5859        $(removeMessage).insertBefore($lineItemAtIndex);
5860      } else {
5861        $lineItemAtIndex = $(
5862          '[data-cart-item-index="' + (index - 1) + '"]',
5863          this.$container
5864        );
5865        removeMessage.insertAfter($lineItemAtIndex);
5866      }
5867      removeMessage.focus();
5868    },
5869
5870    _getRemoveMessage: function(lineItem) {
5871      var formattedMessage = this._formatRemoveMessage(lineItem);
5872
5873      var $tableCell = $(selectors.cartTableCell, lineItem).clone();
5874      $tableCell
5875        .removeClass()
5876        .addClass(classes.cartRemovedProduct)
5877        .attr('colspan', '4')
5878        .html(formattedMessage);
5879
5880      lineItem
5881        .attr('role', 'alert')
5882        .html($tableCell)
5883        .attr('tabindex', '-1');
5884
5885      return lineItem;
5886    },
5887
5888    _formatRemoveMessage: function(lineItem) {
5889      var quantity = lineItem.data('cart-item-quantity');
5890      var url = lineItem.attr(attributes.cartItemUrl);
5891      var title = lineItem.attr(attributes.cartItemTitle);
5892
5893      return theme.strings.removedItemMessage
5894        .replace('[quantity]', quantity)
5895        .replace(
5896          '[link]',
5897          '<a ' +
5898            'href="' +
5899            url +
5900            '" class="text-link text-link--accent">' +
5901            title +
5902            '</a>'
5903        );
5904    },
5905
5906    _setCartCountBubble: function(quantity) {
5907      this.$cartCountBubble =
5908        this.$cartCountBubble || $(selectors.cartCountBubble);
5909      this.$cartCount = this.$cartCount || $(selectors.cartCount);
5910
5911      if (quantity > 0) {
5912        this.$cartCountBubble.removeClass(classes.hide);
5913        this.$cartCount.html(quantity);
5914      } else {
5915        this.$cartCountBubble.addClass(classes.hide);
5916        this.$cartCount.html('');
5917      }
5918    },
5919
5920    _emptyCart: function() {
5921      this.$emptyPageContent =
5922        this.$emptyPageContent ||
5923        $(selectors.emptyPageContent, this.$container);
5924      this.$cartWrapper =
5925        this.$cartWrapper || $(selectors.cartWrapper, this.$container);
5926
5927      this.$emptyPageContent.removeClass(classes.hide);
5928      this.$cartWrapper.addClass(classes.hide);
5929    },
5930
5931    cookiesEnabled: function() {
5932      var cookieEnabled = navigator.cookieEnabled;
5933
5934      if (!cookieEnabled) {
5935        document.cookie = 'testcookie';
5936        cookieEnabled = document.cookie.indexOf('testcookie') !== -1;
5937      }
5938      return cookieEnabled;
5939    }
5940  });
5941
5942  return Cart;
5943})();
5944
5945window.theme = window.theme || {};
5946
5947theme.Filters = (function() {
5948  var settings = {
5949    // Breakpoints from src/stylesheets/global/variables.scss.liquid
5950    mediaQueryMediumUp: 'screen and (min-width: 750px)'
5951  };
5952
5953  var selectors = {
5954    mainContent: '#MainContent',
5955    filterSelection: '#FilterTags',
5956    sortSelection: '#SortBy'
5957  };
5958
5959  var data = {
5960    sortBy: 'data-default-sortby'
5961  };
5962
5963  function Filters(container) {
5964    var $container = (this.$container = $(container));
5965
5966    this.$filterSelect = $(selectors.filterSelection, $container);
5967    this.$sortSelect = $(selectors.sortSelection, $container);
5968    this.$selects = $(selectors.filterSelection, $container).add(
5969      $(selectors.sortSelection, $container)
5970    );
5971
5972    this.defaultSort = this._getDefaultSortValue();
5973    this.$selects.removeClass('hidden');
5974
5975    this.$filterSelect.on('change', this._onFilterChange.bind(this));
5976    this.$sortSelect.on('change', this._onSortChange.bind(this));
5977    this._initBreakpoints();
5978    this._initParams();
5979  }
5980
5981  Filters.prototype = _.assignIn({}, Filters.prototype, {
5982    _initBreakpoints: function() {
5983      var self = this;
5984
5985      enquire.register(settings.mediaQueryMediumUp, {
5986        match: function() {
5987          slate.utils.resizeSelects(self.$selects);
5988        }
5989      });
5990    },
5991
5992    _initParams: function() {
5993      self.queryParams = {};
5994      if (location.search.length) {
5995        var aKeyValue;
5996        var aCouples = location.search.substr(1).split('&');
5997        for (var i = 0; i < aCouples.length; i++) {
5998          aKeyValue = aCouples[i].split('=');
5999          if (aKeyValue.length > 1) {
6000            self.queryParams[
6001              decodeURIComponent(aKeyValue[0])
6002            ] = decodeURIComponent(aKeyValue[1]);
6003          }
6004        }
6005      }
6006    },
6007
6008    _onSortChange: function() {
6009      self.queryParams.sort_by = this._getSortValue();
6010
6011      if (self.queryParams.page) {
6012        delete self.queryParams.page;
6013      }
6014      window.location.search = decodeURIComponent($.param(self.queryParams));
6015    },
6016
6017    _onFilterChange: function() {
6018      document.location.href = this._getFilterValue();
6019    },
6020
6021    _getFilterValue: function() {
6022      return this.$filterSelect.val();
6023    },
6024
6025    _getSortValue: function() {
6026      return this.$sortSelect.val() || this.defaultSort;
6027    },
6028
6029    _getDefaultSortValue: function() {
6030      return this.$sortSelect.attr(data.sortBy);
6031    },
6032
6033    onUnload: function() {
6034      this.$filterSelect.off('change', this._onFilterChange);
6035      this.$sortSelect.off('change', this._onSortChange);
6036    }
6037  });
6038
6039  return Filters;
6040})();
6041
6042window.theme = window.theme || {};
6043
6044theme.HeaderSection = (function() {
6045  function Header() {
6046    theme.Header.init();
6047    theme.MobileNav.init();
6048    theme.SearchDrawer.init();
6049    theme.Search.init();
6050  }
6051
6052  Header.prototype = _.assignIn({}, Header.prototype, {
6053    onUnload: function() {
6054      theme.Header.unload();
6055      theme.Search.unload();
6056    }
6057  });
6058
6059  return Header;
6060})();
6061
6062theme.Maps = (function() {
6063  var config = {
6064    zoom: 14
6065  };
6066  var apiStatus = null;
6067  var mapsToLoad = [];
6068
6069  var errors = {
6070    addressNoResults: theme.strings.addressNoResults,
6071    addressQueryLimit: theme.strings.addressQueryLimit,
6072    addressError: theme.strings.addressError,
6073    authError: theme.strings.authError
6074  };
6075
6076  var selectors = {
6077    section: '[data-section-type="map"]',
6078    map: '[data-map]',
6079    mapOverlay: '[data-map-overlay]'
6080  };
6081
6082  var classes = {
6083    mapError: 'map-section--load-error',
6084    errorMsg: 'map-section__error errors text-center'
6085  };
6086
6087  // Global function called by Google on auth errors.
6088  // Show an auto error message on all map instances.
6089  // eslint-disable-next-line camelcase, no-unused-vars
6090  window.gm_authFailure = function() {
6091    if (!Shopify.designMode) {
6092      return;
6093    }
6094
6095    $(selectors.section).addClass(classes.mapError);
6096    $(selectors.map).remove();
6097    $(selectors.mapOverlay).after(
6098      '<div class="' +
6099        classes.errorMsg +
6100        '">' +
6101        theme.strings.authError +
6102        '</div>'
6103    );
6104  };
6105
6106  function Map(container) {
6107    this.$container = $(container);
6108    this.$map = this.$container.find(selectors.map);
6109    this.key = this.$map.data('api-key');
6110
6111    if (typeof this.key === 'undefined') {
6112      return;
6113    }
6114
6115    if (apiStatus === 'loaded') {
6116      this.createMap();
6117    } else {
6118      mapsToLoad.push(this);
6119
6120      if (apiStatus !== 'loading') {
6121        apiStatus = 'loading';
6122        if (typeof window.google === 'undefined') {
6123          $.getScript(
6124            'https://maps.googleapis.com/maps/api/js?key=' + this.key
6125          ).then(function() {
6126            apiStatus = 'loaded';
6127            initAllMaps();
6128          });
6129        }
6130      }
6131    }
6132  }
6133
6134  function initAllMaps() {
6135    // API has loaded, load all Map instances in queue
6136    $.each(mapsToLoad, function(index, instance) {
6137      instance.createMap();
6138    });
6139  }
6140
6141  function geolocate($map) {
6142    var deferred = $.Deferred();
6143    var geocoder = new google.maps.Geocoder();
6144    var address = $map.data('address-setting');
6145
6146    geocoder.geocode({ address: address }, function(results, status) {
6147      if (status !== google.maps.GeocoderStatus.OK) {
6148        deferred.reject(status);
6149      }
6150
6151      deferred.resolve(results);
6152    });
6153
6154    return deferred;
6155  }
6156
6157  Map.prototype = _.assignIn({}, Map.prototype, {
6158    createMap: function() {
6159      var $map = this.$map;
6160
6161      return geolocate($map)
6162        .then(
6163          function(results) {
6164            var mapOptions = {
6165              zoom: config.zoom,
6166              center: results[0].geometry.location,
6167              draggable: false,
6168              clickableIcons: false,
6169              scrollwheel: false,
6170              disableDoubleClickZoom: true,
6171              disableDefaultUI: true
6172            };
6173
6174            var map = (this.map = new google.maps.Map($map[0], mapOptions));
6175            var center = (this.center = map.getCenter());
6176
6177            //eslint-disable-next-line no-unused-vars
6178            var marker = new google.maps.Marker({
6179              map: map,
6180              position: map.getCenter()
6181            });
6182
6183            google.maps.event.addDomListener(
6184              window,
6185              'resize',
6186              $.debounce(250, function() {
6187                google.maps.event.trigger(map, 'resize');
6188                map.setCenter(center);
6189                $map.removeAttr('style');
6190              })
6191            );
6192          }.bind(this)
6193        )
6194        .fail(function() {
6195          var errorMessage;
6196
6197          switch (status) {
6198            case 'ZERO_RESULTS':
6199              errorMessage = errors.addressNoResults;
6200              break;
6201            case 'OVER_QUERY_LIMIT':
6202              errorMessage = errors.addressQueryLimit;
6203              break;
6204            case 'REQUEST_DENIED':
6205              errorMessage = errors.authError;
6206              break;
6207            default:
6208              errorMessage = errors.addressError;
6209              break;
6210          }
6211
6212          // Show errors only to merchant in the editor.
6213          if (Shopify.designMode) {
6214            $map
6215              .parent()
6216              .addClass(classes.mapError)
6217              .html(
6218                '<div class="' +
6219                  classes.errorMsg +
6220                  '">' +
6221                  errorMessage +
6222                  '</div>'
6223              );
6224          }
6225        });
6226    },
6227
6228    onUnload: function() {
6229      if (this.$map.length === 0) {
6230        return;
6231      }
6232      google.maps.event.clearListeners(this.map, 'resize');
6233    }
6234  });
6235
6236  return Map;
6237})();
6238
6239/* eslint-disable no-new */
6240theme.Product = (function() {
6241  function Product(container) {
6242    var $container = (this.$container = $(container));
6243    var sectionId = $container.attr('data-section-id');
6244    this.ajaxEnabled = $container.data('ajax-enabled');
6245
6246    this.settings = {
6247      // Breakpoints from src/stylesheets/global/variables.scss.liquid
6248      mediaQueryMediumUp: 'screen and (min-width: 750px)',
6249      mediaQuerySmall: 'screen and (max-width: 749px)',
6250      bpSmall: false,
6251      enableHistoryState: $container.data('enable-history-state') || false,
6252      namespace: '.slideshow-' + sectionId,
6253      sectionId: sectionId,
6254      sliderActive: false,
6255      zoomEnabled: false
6256    };
6257
6258    this.selectors = {
6259      addToCart: '[data-add-to-cart]',
6260      addToCartText: '[data-add-to-cart-text]',
6261      cartCount: '[data-cart-count]',
6262      cartCountBubble: '[data-cart-count-bubble]',
6263      cartPopup: '[data-cart-popup]',
6264      cartPopupCartQuantity: '[data-cart-popup-cart-quantity]',
6265      cartPopupClose: '[data-cart-popup-close]',
6266      cartPopupDismiss: '[data-cart-popup-dismiss]',
6267      cartPopupImage: '[data-cart-popup-image]',
6268      cartPopupImageWrapper: '[data-cart-popup-image-wrapper]',
6269      cartPopupImagePlaceholder: '[data-cart-popup-image-placeholder]',
6270      cartPopupPlaceholderSize: '[data-placeholder-size]',
6271      cartPopupProductDetails: '[data-cart-popup-product-details]',
6272      cartPopupQuantity: '[data-cart-popup-quantity]',
6273      cartPopupQuantityLabel: '[data-cart-popup-quantity-label]',
6274      cartPopupTitle: '[data-cart-popup-title]',
6275      cartPopupWrapper: '[data-cart-popup-wrapper]',
6276      loader: '[data-loader]',
6277      loaderStatus: '[data-loader-status]',
6278      quantity: '[data-quantity-input]',
6279      SKU: '.variant-sku',
6280      productStatus: '[data-product-status]',
6281      originalSelectorId: '#ProductSelect-' + sectionId,
6282      productForm: '[data-product-form]',
6283      errorMessage: '[data-error-message]',
6284      errorMessageWrapper: '[data-error-message-wrapper]',
6285      imageZoomWrapper: '[data-image-zoom-wrapper]',
6286      productMediaWrapper: '[data-product-single-media-wrapper]',
6287      productThumbImages: '.product-single__thumbnail--' + sectionId,
6288      productThumbs: '.product-single__thumbnails-' + sectionId,
6289      productThumbListItem: '.product-single__thumbnails-item',
6290      productThumbsWrapper: '.thumbnails-wrapper',
6291      saleLabel: '.product-price__sale-label-' + sectionId,
6292      singleOptionSelector: '.single-option-selector-' + sectionId,
6293      shopifyPaymentButton: '.shopify-payment-button',
6294      productMediaTypeVideo: '[data-product-media-type-video]',
6295      productMediaTypeModel: '[data-product-media-type-model]',
6296      priceContainer: '[data-price]',
6297      regularPrice: '[data-regular-price]',
6298      salePrice: '[data-sale-price]',
6299      unitPrice: '[data-unit-price]',
6300      unitPriceBaseUnit: '[data-unit-price-base-unit]',
6301      productPolicies: '[data-product-policies]'
6302    };
6303
6304    this.classes = {
6305      cartPopupWrapperHidden: 'cart-popup-wrapper--hidden',
6306      hidden: 'hide',
6307      visibilityHidden: 'visibility-hidden',
6308      inputError: 'input--error',
6309      jsZoomEnabled: 'js-zoom-enabled',
6310      productOnSale: 'price--on-sale',
6311      productUnitAvailable: 'price--unit-available',
6312      productUnavailable: 'price--unavailable',
6313      productSoldOut: 'price--sold-out',
6314      cartImage: 'cart-popup-item__image',
6315      productFormErrorMessageWrapperHidden:
6316        'product-form__error-message-wrapper--hidden',
6317      activeClass: 'active-thumb',
6318      variantSoldOut: 'product-form--variant-sold-out'
6319    };
6320
6321    this.$quantityInput = $(this.selectors.quantity, $container);
6322    this.$errorMessageWrapper = $(
6323      this.selectors.errorMessageWrapper,
6324      $container
6325    );
6326    this.$addToCart = $(this.selectors.addToCart, $container);
6327    this.$addToCartText = $(this.selectors.addToCartText, this.$addToCart);
6328    this.$shopifyPaymentButton = $(
6329      this.selectors.shopifyPaymentButton,
6330      $container
6331    );
6332    this.$productPolicies = $(this.selectors.productPolicies, $container);
6333
6334    this.$loader = $(this.selectors.loader, this.$addToCart);
6335    this.$loaderStatus = $(this.selectors.loaderStatus, $container);
6336
6337    // Stop parsing if we don't have the product json script tag when loading
6338    // section in the Theme Editor
6339    if (!$('#ProductJson-' + sectionId).html()) {
6340      return;
6341    }
6342
6343    this.productSingleObject = JSON.parse(
6344      document.getElementById('ProductJson-' + sectionId).innerHTML
6345    );
6346
6347    this.settings.zoomEnabled = $(this.selectors.imageZoomWrapper).hasClass(
6348      this.classes.jsZoomEnabled
6349    );
6350
6351    this._initBreakpoints();
6352    this._stringOverrides();
6353    this._initVariants();
6354    this._initMediaSwitch();
6355    this._initAddToCart();
6356    this._setActiveThumbnail();
6357    this._initProductVideo();
6358    this._initModelViewerLibraries();
6359    this._initShopifyXrLaunch();
6360  }
6361
6362  Product.prototype = _.assignIn({}, Product.prototype, {
6363    _stringOverrides: function() {
6364      theme.productStrings = theme.productStrings || {};
6365      $.extend(theme.strings, theme.productStrings);
6366    },
6367
6368    _initBreakpoints: function() {
6369      var self = this;
6370
6371      enquire.register(this.settings.mediaQuerySmall, {
6372        match: function() {
6373          // initialize thumbnail slider on mobile if more than four thumbnails
6374          if ($(self.selectors.productThumbImages).length > 4) {
6375            self._initThumbnailSlider();
6376          }
6377
6378          // destroy image zooming if enabled
6379          if (self.settings.zoomEnabled) {
6380            $(self.selectors.imageZoomWrapper).each(function() {
6381              _destroyZoom(this);
6382            });
6383          }
6384
6385          self.settings.bpSmall = true;
6386        },
6387        unmatch: function() {
6388          if (self.settings.sliderActive) {
6389            self._destroyThumbnailSlider();
6390          }
6391
6392          self.settings.bpSmall = false;
6393        }
6394      });
6395
6396      enquire.register(this.settings.mediaQueryMediumUp, {
6397        match: function() {
6398          if (self.settings.zoomEnabled) {
6399            $(self.selectors.imageZoomWrapper).each(function() {
6400              _enableZoom(this);
6401            });
6402          }
6403        }
6404      });
6405    },
6406
6407    _initVariants: function() {
6408      var options = {
6409        $container: this.$container,
6410        enableHistoryState:
6411          this.$container.data('enable-history-state') || false,
6412        singleOptionSelector: this.selectors.singleOptionSelector,
6413        originalSelectorId: this.selectors.originalSelectorId,
6414        product: this.productSingleObject
6415      };
6416
6417      this.variants = new slate.Variants(options);
6418
6419      this.$container.on(
6420        'variantChange' + this.settings.namespace,
6421        this._updateAvailability.bind(this)
6422      );
6423      this.$container.on(
6424        'variantImageChange' + this.settings.namespace,
6425        this._updateMedia.bind(this)
6426      );
6427      this.$container.on(
6428        'variantPriceChange' + this.settings.namespace,
6429        this._updatePrice.bind(this)
6430      );
6431      this.$container.on(
6432        'variantSKUChange' + this.settings.namespace,
6433        this._updateSKU.bind(this)
6434      );
6435    },
6436
6437    _initMediaSwitch: function() {
6438      if (!$(this.selectors.productThumbImages).length) {
6439        return;
6440      }
6441
6442      var self = this;
6443
6444      $(this.selectors.productThumbImages)
6445        .on('click', function(evt) {
6446          evt.preventDefault();
6447          var $el = $(this);
6448
6449          var mediaId = $el.data('thumbnail-id');
6450
6451          self._switchMedia(mediaId);
6452          self._setActiveThumbnail(mediaId);
6453        })
6454        .on('keyup', self._handleMediaFocus.bind(self));
6455    },
6456
6457    _initAddToCart: function() {
6458      $(this.selectors.productForm, this.$container).on(
6459        'submit',
6460        function(evt) {
6461          if (this.$addToCart.is('[aria-disabled]')) {
6462            evt.preventDefault();
6463            return;
6464          }
6465
6466          if (!this.ajaxEnabled) return;
6467
6468          evt.preventDefault();
6469
6470          this.$previouslyFocusedElement = $(':focus');
6471
6472          var isInvalidQuantity = this.$quantityInput.val() <= 0;
6473
6474          if (isInvalidQuantity) {
6475            this._showErrorMessage(theme.strings.quantityMinimumMessage);
6476            return;
6477          }
6478
6479          if (!isInvalidQuantity && this.ajaxEnabled) {
6480            // disable the addToCart and dynamic checkout button while
6481            // request/cart popup is loading and handle loading state
6482            this._handleButtonLoadingState(true);
6483            var $data = $(this.selectors.productForm, this.$container);
6484            this._addItemToCart($data);
6485            return;
6486          }
6487        }.bind(this)
6488      );
6489    },
6490
6491    _initProductVideo: function() {
6492      var sectionId = this.settings.sectionId;
6493
6494      $(this.selectors.productMediaTypeVideo, this.$container).each(function() {
6495        var $el = $(this);
6496        theme.ProductVideo.init($el, sectionId);
6497      });
6498    },
6499
6500    _initModelViewerLibraries: function() {
6501      var $modelViewerElements = $(
6502        this.selectors.productMediaTypeModel,
6503        this.$container
6504      );
6505      if ($modelViewerElements.length < 1) return;
6506      theme.ProductModel.init($modelViewerElements, this.settings.sectionId);
6507    },
6508
6509    _initShopifyXrLaunch: function() {
6510      var self = this;
6511      $(document).on('shopify_xr_launch', function() {
6512        var $currentMedia = $(
6513          self.selectors.productMediaWrapper +
6514            ':not(.' +
6515            self.classes.hidden +
6516            ')',
6517          self.$container
6518        );
6519        $currentMedia.trigger('xrLaunch');
6520      });
6521    },
6522
6523    _addItemToCart: function(data) {
6524      var params = {
6525        url: '/cart/add.js',
6526        data: $(data).serialize(),
6527        dataType: 'json'
6528      };
6529
6530      $.post(params)
6531        .done(
6532          function(item) {
6533            this._hideErrorMessage();
6534            this._setupCartPopup(item);
6535          }.bind(this)
6536        )
6537        .fail(
6538          function(response) {
6539            this.$previouslyFocusedElement.focus();
6540            var errorMessage = response.responseJSON
6541              ? response.responseJSON.description
6542              : theme.strings.cartError;
6543            this._showErrorMessage(errorMessage);
6544            this._handleButtonLoadingState(false);
6545          }.bind(this)
6546        );
6547    },
6548
6549    _handleButtonLoadingState: function(isLoading) {
6550      if (isLoading) {
6551        this.$addToCart.attr('aria-disabled', true);
6552        this.$addToCartText.addClass(this.classes.hidden);
6553        this.$loader.removeClass(this.classes.hidden);
6554        this.$shopifyPaymentButton.attr('disabled', true);
6555        this.$loaderStatus.attr('aria-hidden', false);
6556      } else {
6557        this.$addToCart.removeAttr('aria-disabled');
6558        this.$addToCartText.removeClass(this.classes.hidden);
6559        this.$loader.addClass(this.classes.hidden);
6560        this.$shopifyPaymentButton.removeAttr('disabled');
6561        this.$loaderStatus.attr('aria-hidden', true);
6562      }
6563    },
6564
6565    _showErrorMessage: function(errorMessage) {
6566      $(this.selectors.errorMessage, this.$container).html(errorMessage);
6567
6568      if (this.$quantityInput.length !== 0) {
6569        this.$quantityInput.addClass(this.classes.inputError);
6570      }
6571
6572      this.$errorMessageWrapper
6573        .removeClass(this.classes.productFormErrorMessageWrapperHidden)
6574        .attr('aria-hidden', true)
6575        .removeAttr('aria-hidden');
6576    },
6577
6578    _hideErrorMessage: function() {
6579      this.$errorMessageWrapper.addClass(
6580        this.classes.productFormErrorMessageWrapperHidden
6581      );
6582
6583      if (this.$quantityInput.length !== 0) {
6584        this.$quantityInput.removeClass(this.classes.inputError);
6585      }
6586    },
6587
6588    _setupCartPopup: function(item) {
6589      this.$cartPopup = this.$cartPopup || $(this.selectors.cartPopup);
6590      this.$cartPopupWrapper =
6591        this.$cartPopupWrapper || $(this.selectors.cartPopupWrapper);
6592      this.$cartPopupTitle =
6593        this.$cartPopupTitle || $(this.selectors.cartPopupTitle);
6594      this.$cartPopupQuantity =
6595        this.$cartPopupQuantity || $(this.selectors.cartPopupQuantity);
6596      this.$cartPopupQuantityLabel =
6597        this.$cartPopupQuantityLabel ||
6598        $(this.selectors.cartPopupQuantityLabel);
6599      this.$cartPopupClose =
6600        this.$cartPopupClose || $(this.selectors.cartPopupClose);
6601      this.$cartPopupDismiss =
6602        this.$cartPopupDismiss || $(this.selectors.cartPopupDismiss);
6603      this.$cartPopupImagePlaceholder =
6604        this.$cartPopupImagePlaceholder ||
6605        $(this.selectors.cartPopupImagePlaceholder);
6606
6607      this._setupCartPopupEventListeners();
6608
6609      this._updateCartPopupContent(item);
6610    },
6611
6612    _updateCartPopupContent: function(item) {
6613      var quantity = this.$quantityInput.length ? this.$quantityInput.val() : 1;
6614
6615      this.$cartPopupTitle.text(item.product_title);
6616      this.$cartPopupQuantity.text(quantity);
6617      this.$cartPopupQuantityLabel.text(
6618        theme.strings.quantityLabel.replace('[count]', quantity)
6619      );
6620
6621      this._setCartPopupPlaceholder(
6622        item.featured_image.url,
6623        item.featured_image.aspect_ratio
6624      );
6625      this._setCartPopupImage(item.featured_image.url, item.featured_image.alt);
6626      this._setCartPopupProductDetails(
6627        item.product_has_only_default_variant,
6628        item.options_with_values,
6629        item.properties
6630      );
6631
6632      $.getJSON('/cart.js').then(
6633        function(cart) {
6634          this._setCartQuantity(cart.item_count);
6635          this._setCartCountBubble(cart.item_count);
6636          this._showCartPopup();
6637        }.bind(this)
6638      );
6639    },
6640
6641    _setupCartPopupEventListeners: function() {
6642      this.$cartPopupWrapper.on(
6643        'keyup',
6644        function(event) {
6645          if (event.keyCode === slate.utils.keyboardKeys.ESCAPE) {
6646            this._hideCartPopup(event);
6647          }
6648        }.bind(this)
6649      );
6650
6651      this.$cartPopupClose.on('click', this._hideCartPopup.bind(this));
6652      this.$cartPopupDismiss.on('click', this._hideCartPopup.bind(this));
6653      $('body').on('click', this._onBodyClick.bind(this));
6654    },
6655
6656    _setCartPopupPlaceholder: function(imageUrl, imageAspectRatio) {
6657      this.$cartPopupImageWrapper =
6658        this.$cartPopupImageWrapper || $(this.selectors.cartPopupImageWrapper);
6659
6660      if (imageUrl === null) {
6661        this.$cartPopupImageWrapper.addClass(this.classes.hidden);
6662        return;
6663      }
6664
6665      var $placeholder = $(this.selectors.cartPopupPlaceholderSize);
6666      var maxWidth = 95 * imageAspectRatio;
6667      var heightRatio = 100 / imageAspectRatio;
6668
6669      this.$cartPopupImagePlaceholder.css('max-width', maxWidth);
6670
6671      $placeholder.css('padding-top', heightRatio + '%');
6672    },
6673
6674    _setCartPopupImage: function(imageUrl, imageAlt) {
6675      if (imageUrl === null) return;
6676
6677      this.$cartPopupImageWrapper.removeClass(this.classes.hidden);
6678      var sizedImageUrl = theme.Images.getSizedImageUrl(imageUrl, '200x');
6679      var image = document.createElement('img');
6680      image.src = sizedImageUrl;
6681      image.alt = imageAlt;
6682      image.classList.add(this.classes.cartImage);
6683      image.dataset.cartPopupImage = '';
6684
6685      image.onload = function() {
6686        this.$cartPopupImagePlaceholder.addClass(this.classes.hidden);
6687        this.$cartPopupImageWrapper.append(image);
6688      }.bind(this);
6689    },
6690
6691    _setCartPopupProductDetails: function(
6692      product_has_only_default_variant,
6693      options,
6694      properties
6695    ) {
6696      this.$cartPopupProductDetails =
6697        this.$cartPopupProductDetails ||
6698        $(this.selectors.cartPopupProductDetails);
6699      var variantPropertiesHTML = '';
6700
6701      if (!product_has_only_default_variant) {
6702        variantPropertiesHTML =
6703          variantPropertiesHTML + this._getVariantOptionList(options);
6704      }
6705
6706      if (properties !== null && Object.keys(properties).length !== 0) {
6707        variantPropertiesHTML =
6708          variantPropertiesHTML + this._getPropertyList(properties);
6709      }
6710
6711      if (variantPropertiesHTML.length === 0) {
6712        this.$cartPopupProductDetails.html('');
6713        this.$cartPopupProductDetails.attr('hidden', '');
6714      } else {
6715        this.$cartPopupProductDetails.html(variantPropertiesHTML);
6716        this.$cartPopupProductDetails.removeAttr('hidden');
6717      }
6718    },
6719
6720    _getVariantOptionList: function(variantOptions) {
6721      var variantOptionListHTML = '';
6722
6723      variantOptions.forEach(function(variantOption) {
6724        variantOptionListHTML =
6725          variantOptionListHTML +
6726          '<li class="product-details__item product-details__item--variant-option">' +
6727          variantOption.name +
6728          ': ' +
6729          variantOption.value +
6730          '</li>';
6731      });
6732
6733      return variantOptionListHTML;
6734    },
6735
6736    _getPropertyList: function(properties) {
6737      var propertyListHTML = '';
6738      var propertiesArray = Object.entries(properties);
6739
6740      propertiesArray.forEach(function(property) {
6741        // Line item properties prefixed with an underscore are not to be displayed
6742        if (property[0].charAt(0) === '_') return;
6743
6744        // if the property value has a length of 0 (empty), don't display it
6745        if (property[1].length === 0) return;
6746
6747        propertyListHTML =
6748          propertyListHTML +
6749          '<li class="product-details__item product-details__item--property">' +
6750          '<span class="product-details__property-label">' +
6751          property[0] +
6752          ': </span>' +
6753          property[1];
6754        ': ' + '</li>';
6755      });
6756
6757      return propertyListHTML;
6758    },
6759
6760    _setCartQuantity: function(quantity) {
6761      this.$cartPopupCartQuantity =
6762        this.$cartPopupCartQuantity || $(this.selectors.cartPopupCartQuantity);
6763      var ariaLabel;
6764
6765      if (quantity === 1) {
6766        ariaLabel = theme.strings.oneCartCount;
6767      } else if (quantity > 1) {
6768        ariaLabel = theme.strings.otherCartCount.replace('[count]', quantity);
6769      }
6770
6771      this.$cartPopupCartQuantity.text(quantity).attr('aria-label', ariaLabel);
6772    },
6773
6774    _setCartCountBubble: function(quantity) {
6775      this.$cartCountBubble =
6776        this.$cartCountBubble || $(this.selectors.cartCountBubble);
6777      this.$cartCount = this.$cartCount || $(this.selectors.cartCount);
6778
6779      this.$cartCountBubble.removeClass(this.classes.hidden);
6780      this.$cartCount.text(quantity);
6781    },
6782
6783    _showCartPopup: function() {
6784      this.$cartPopupWrapper
6785        .prepareTransition()
6786        .removeClass(this.classes.cartPopupWrapperHidden);
6787      this._handleButtonLoadingState(false);
6788
6789      slate.a11y.trapFocus({
6790        $container: this.$cartPopupWrapper,
6791        $elementToFocus: this.$cartPopup,
6792        namespace: 'cartPopupFocus'
6793      });
6794    },
6795
6796    _hideCartPopup: function(event) {
6797      var setFocus = event.detail === 0 ? true : false;
6798      this.$cartPopupWrapper
6799        .prepareTransition()
6800        .addClass(this.classes.cartPopupWrapperHidden);
6801
6802      $(this.selectors.cartPopupImage).remove();
6803      this.$cartPopupImagePlaceholder.removeClass(this.classes.hidden);
6804
6805      slate.a11y.removeTrapFocus({
6806        $container: this.$cartPopupWrapper,
6807        namespace: 'cartPopupFocus'
6808      });
6809
6810      if (setFocus) this.$previouslyFocusedElement[0].focus();
6811
6812      this.$cartPopupWrapper.off('keyup');
6813      this.$cartPopupClose.off('click');
6814      this.$cartPopupDismiss.off('click');
6815      $('body').off('click');
6816    },
6817
6818    _onBodyClick: function(event) {
6819      var $target = $(event.target);
6820
6821      if (
6822        $target[0] !== this.$cartPopupWrapper[0] &&
6823        !$target.parents(this.selectors.cartPopup).length
6824      ) {
6825        this._hideCartPopup(event);
6826      }
6827    },
6828
6829    _setActiveThumbnail: function(mediaId) {
6830      // If there is no element passed, find it by the current product image
6831      if (typeof mediaId === 'undefined') {
6832        mediaId = $(
6833          this.selectors.productMediaWrapper + ':not(.hide)',
6834          this.$container
6835        ).data('media-id');
6836      }
6837
6838      var $thumbnailWrappers = $(
6839        this.selectors.productThumbListItem + ':not(.slick-cloned)',
6840        this.$container
6841      );
6842
6843      var $activeThumbnail = $thumbnailWrappers.find(
6844        this.selectors.productThumbImages +
6845          "[data-thumbnail-id='" +
6846          mediaId +
6847          "']"
6848      );
6849
6850      $(this.selectors.productThumbImages)
6851        .removeClass(this.classes.activeClass)
6852        .removeAttr('aria-current');
6853
6854      $activeThumbnail.addClass(this.classes.activeClass);
6855      $activeThumbnail.attr('aria-current', true);
6856
6857      if (!$thumbnailWrappers.hasClass('slick-slide')) {
6858        return;
6859      }
6860
6861      var slideIndex = $activeThumbnail.parent().data('slick-index');
6862
6863      $(this.selectors.productThumbs).slick('slickGoTo', slideIndex, true);
6864    },
6865
6866    _switchMedia: function(mediaId) {
6867      var $currentMedia = $(
6868        this.selectors.productMediaWrapper +
6869          ':not(.' +
6870          this.classes.hidden +
6871          ')',
6872        this.$container
6873      );
6874
6875      var $newMedia = $(
6876        this.selectors.productMediaWrapper +
6877          "[data-media-id='" +
6878          mediaId +
6879          "']",
6880        this.$container
6881      );
6882
6883      var $otherMedia = $(
6884        this.selectors.productMediaWrapper +
6885          ":not([data-media-id='" +
6886          mediaId +
6887          "'])",
6888        this.$container
6889      );
6890
6891      $currentMedia.trigger('mediaHidden');
6892      $newMedia.removeClass(this.classes.hidden).trigger('mediaVisible');
6893      $otherMedia.addClass(this.classes.hidden);
6894    },
6895
6896    _handleMediaFocus: function(evt) {
6897      if (evt.keyCode !== slate.utils.keyboardKeys.ENTER) return;
6898
6899      var mediaId = $(evt.currentTarget).data('thumbnail-id');
6900
6901      $(
6902        this.selectors.productMediaWrapper +
6903          "[data-media-id='" +
6904          mediaId +
6905          "']",
6906        this.$container
6907      ).focus();
6908    },
6909
6910    _initThumbnailSlider: function() {
6911      var options = {
6912        slidesToShow: 3,
6913        slidesToScroll: 2,
6914        infinite: false,
6915        prevArrow: '.thumbnails-slider__prev--' + this.settings.sectionId,
6916        nextArrow: '.thumbnails-slider__next--' + this.settings.sectionId
6917      };
6918
6919      $(this.selectors.productThumbs).slick(options);
6920
6921      // Accessibility concerns not yet fixed in Slick Slider
6922      $(this.selectors.productThumbsWrapper, this.$container)
6923        .find('.slick-list')
6924        .removeAttr('aria-live');
6925      $(this.selectors.productThumbsWrapper, this.$container)
6926        .find('.slick-disabled')
6927        .removeAttr('aria-disabled');
6928
6929      this.settings.sliderActive = true;
6930    },
6931
6932    _destroyThumbnailSlider: function() {
6933      $(this.selectors.productThumbs).slick('unslick');
6934      this.settings.sliderActive = false;
6935
6936      // Accessibility concerns not yet fixed in Slick Slider
6937      $(this.selectors.productThumbsWrapper, this.$container)
6938        .find('[tabindex="-1"]')
6939        .removeAttr('tabindex');
6940    },
6941
6942    _liveRegionText: function(variant) {
6943      // Dummy content for live region
6944      var liveRegionText =
6945        '[Availability] [Regular] [$$] [Sale] [$]. [UnitPrice] [$$$]';
6946
6947      if (!variant) {
6948        liveRegionText = theme.strings.unavailable;
6949        return liveRegionText;
6950      }
6951
6952      // Update availability
6953      var availability = variant.available ? '' : theme.strings.soldOut + ',';
6954      liveRegionText = liveRegionText.replace('[Availability]', availability);
6955
6956      // Update pricing information
6957      var regularLabel = '';
6958      var regularPrice = theme.Currency.formatMoney(
6959        variant.price,
6960        theme.moneyFormat
6961      );
6962      var saleLabel = '';
6963      var salePrice = '';
6964      var unitLabel = '';
6965      var unitPrice = '';
6966
6967      if (variant.compare_at_price > variant.price) {
6968        regularLabel = theme.strings.regularPrice;
6969        regularPrice =
6970          theme.Currency.formatMoney(
6971            variant.compare_at_price,
6972            theme.moneyFormat
6973          ) + ',';
6974        saleLabel = theme.strings.sale;
6975        salePrice = theme.Currency.formatMoney(
6976          variant.price,
6977          theme.moneyFormat
6978        );
6979      }
6980
6981      if (variant.unit_price) {
6982        unitLabel = theme.strings.unitPrice;
6983        unitPrice =
6984          theme.Currency.formatMoney(variant.unit_price, theme.moneyFormat) +
6985          ' ' +
6986          theme.strings.unitPriceSeparator +
6987          ' ' +
6988          this._getBaseUnit(variant);
6989      }
6990
6991      liveRegionText = liveRegionText
6992        .replace('[Regular]', regularLabel)
6993        .replace('[$$]', regularPrice)
6994        .replace('[Sale]', saleLabel)
6995        .replace('[$]', salePrice)
6996        .replace('[UnitPrice]', unitLabel)
6997        .replace('[$$$]', unitPrice)
6998        .trim();
6999
7000      return liveRegionText;
7001    },
7002
7003    _updateLiveRegion: function(evt) {
7004      var variant = evt.variant;
7005      var liveRegion = this.container.querySelector(
7006        this.selectors.productStatus
7007      );
7008      liveRegion.innerHTML = this._liveRegionText(variant);
7009      liveRegion.setAttribute('aria-hidden', false);
7010
7011      // hide content from accessibility tree after announcement
7012      setTimeout(function() {
7013        liveRegion.setAttribute('aria-hidden', true);
7014      }, 1000);
7015    },
7016
7017    _updateAddToCart: function(evt) {
7018      var variant = evt.variant;
7019
7020      if (variant) {
7021        if (variant.available) {
7022          this.$addToCart
7023            .removeAttr('aria-disabled')
7024            .attr('aria-label', theme.strings.addToCart);
7025          $(this.selectors.addToCartText, this.$container).text(
7026            theme.strings.addToCart
7027          );
7028          $(this.selectors.productForm, this.container).removeClass(
7029            this.classes.variantSoldOut
7030          );
7031        } else {
7032          // Variant is sold out, disable submit button and change the text.
7033          this.$addToCart
7034            .attr('aria-disabled', true)
7035            .attr('aria-label', theme.strings.soldOut);
7036          $(this.selectors.addToCartText, this.$container).text(
7037            theme.strings.soldOut
7038          );
7039          $(this.selectors.productForm, this.container).addClass(
7040            this.classes.variantSoldOut
7041          );
7042        }
7043      } else {
7044        // The variant doesn't exist, disable submit button and change the text.
7045        this.$addToCart
7046          .attr('aria-disabled', true)
7047          .attr('aria-label', theme.strings.unavailable);
7048        $(this.selectors.addToCartText, this.$container).text(
7049          theme.strings.unavailable
7050        );
7051        $(this.selectors.productForm, this.container).addClass(
7052          this.classes.variantSoldOut
7053        );
7054      }
7055    },
7056
7057    _updateAvailability: function(evt) {
7058      // remove error message if one is showing
7059      this._hideErrorMessage();
7060
7061      // update form submit
7062      this._updateAddToCart(evt);
7063      // update live region
7064      this._updateLiveRegion(evt);
7065
7066      this._updatePrice(evt);
7067    },
7068
7069    _updateMedia: function(evt) {
7070      var variant = evt.variant;
7071      var mediaId = variant.featured_media.id;
7072      var sectionMediaId = this.settings.sectionId + '-' + mediaId;
7073
7074      this._switchMedia(sectionMediaId);
7075      this._setActiveThumbnail(sectionMediaId);
7076    },
7077
7078    _updatePrice: function(evt) {
7079      var variant = evt.variant;
7080
7081      var $priceContainer = $(this.selectors.priceContainer, this.$container);
7082      var $regularPrice = $(this.selectors.regularPrice, $priceContainer);
7083      var $salePrice = $(this.selectors.salePrice, $priceContainer);
7084      var $unitPrice = $(this.selectors.unitPrice, $priceContainer);
7085      var $unitPriceBaseUnit = $(
7086        this.selectors.unitPriceBaseUnit,
7087        $priceContainer
7088      );
7089
7090      // Reset product price state
7091      $priceContainer
7092        .removeClass(this.classes.productUnavailable)
7093        .removeClass(this.classes.productOnSale)
7094        .removeClass(this.classes.productUnitAvailable)
7095        .removeClass(this.classes.productSoldOut)
7096        .removeAttr('aria-hidden');
7097
7098      this.$productPolicies.removeClass(this.classes.visibilityHidden);
7099
7100      // Unavailable
7101      if (!variant) {
7102        $priceContainer
7103          .addClass(this.classes.productUnavailable)
7104          .attr('aria-hidden', true);
7105
7106        this.$productPolicies.addClass(this.classes.visibilityHidden);
7107        return;
7108      }
7109
7110      // Sold out
7111      if (!variant.available) {
7112        $priceContainer.addClass(this.classes.productSoldOut);
7113      }
7114
7115      // On sale
7116      if (variant.compare_at_price > variant.price) {
7117        $regularPrice.html(
7118          theme.Currency.formatMoney(
7119            variant.compare_at_price,
7120            theme.moneyFormat
7121          )
7122        );
7123        $salePrice.html(
7124          theme.Currency.formatMoney(variant.price, theme.moneyFormat)
7125        );
7126        $priceContainer.addClass(this.classes.productOnSale);
7127      } else {
7128        // Regular price
7129        $regularPrice.html(
7130          theme.Currency.formatMoney(variant.price, theme.moneyFormat)
7131        );
7132      }
7133
7134      // Unit price
7135      if (variant.unit_price) {
7136        $unitPrice.html(
7137          theme.Currency.formatMoney(variant.unit_price, theme.moneyFormat)
7138        );
7139        $unitPriceBaseUnit.html(this._getBaseUnit(variant));
7140        $priceContainer.addClass(this.classes.productUnitAvailable);
7141      }
7142    },
7143
7144    _getBaseUnit: function(variant) {
7145      return variant.unit_price_measurement.reference_value === 1
7146        ? variant.unit_price_measurement.reference_unit
7147        : variant.unit_price_measurement.reference_value +
7148            variant.unit_price_measurement.reference_unit;
7149    },
7150
7151    _updateSKU: function(evt) {
7152      var variant = evt.variant;
7153
7154      // Update the sku
7155      $(this.selectors.SKU).html(variant.sku);
7156    },
7157
7158    onUnload: function() {
7159      this.$container.off(this.settings.namespace);
7160      theme.ProductVideo.removeSectionVideos(this.settings.sectionId);
7161      theme.ProductModel.removeSectionModels(this.settings.sectionId);
7162    }
7163  });
7164
7165  function _enableZoom(el) {
7166    var zoomUrl = $(el).data('zoom');
7167    $(el).zoom({
7168      url: zoomUrl
7169    });
7170  }
7171
7172  function _destroyZoom(el) {
7173    $(el).trigger('zoom.destroy');
7174  }
7175
7176  return Product;
7177})();
7178
7179theme.ProductRecommendations = (function() {
7180  function ProductRecommendations(container) {
7181    this.$container = $(container);
7182
7183    var baseUrl = this.$container.data('baseUrl');
7184    var productId = this.$container.data('productId');
7185    var recommendationsSectionUrl =
7186      baseUrl +
7187      '?section_id=product-recommendations&product_id=' +
7188      productId +
7189      '&limit=4';
7190
7191    $.get(recommendationsSectionUrl).then(
7192      function(section) {
7193        var recommendationsMarkup = $(section).html();
7194        if (recommendationsMarkup.trim() !== '') {
7195          this.$container.html(recommendationsMarkup);
7196        }
7197      }.bind(this)
7198    );
7199  }
7200
7201  return ProductRecommendations;
7202})();
7203
7204theme.Quotes = (function() {
7205  var config = {
7206    mediaQuerySmall: 'screen and (max-width: 749px)',
7207    mediaQueryMediumUp: 'screen and (min-width: 750px)',
7208    slideCount: 0
7209  };
7210  var defaults = {
7211    accessibility: true,
7212    arrows: false,
7213    dots: true,
7214    autoplay: false,
7215    touchThreshold: 20,
7216    slidesToShow: 3,
7217    slidesToScroll: 3
7218  };
7219
7220  function Quotes(container) {
7221    var $container = (this.$container = $(container));
7222    var sectionId = $container.attr('data-section-id');
7223    var wrapper = (this.wrapper = '.quotes-wrapper');
7224    var slider = (this.slider = '#Quotes-' + sectionId);
7225    var $slider = $(slider, wrapper);
7226
7227    var sliderActive = false;
7228    var mobileOptions = $.extend({}, defaults, {
7229      slidesToShow: 1,
7230      slidesToScroll: 1,
7231      adaptiveHeight: true
7232    });
7233
7234    config.slideCount = $slider.data('count');
7235
7236    // Override slidesToShow/Scroll if there are not enough blocks
7237    if (config.slideCount < defaults.slidesToShow) {
7238      defaults.slidesToShow = config.slideCount;
7239      defaults.slidesToScroll = config.slideCount;
7240    }
7241
7242    $slider.on('init', this.a11y.bind(this));
7243
7244    enquire.register(config.mediaQuerySmall, {
7245      match: function() {
7246        initSlider($slider, mobileOptions);
7247      }
7248    });
7249
7250    enquire.register(config.mediaQueryMediumUp, {
7251      match: function() {
7252        initSlider($slider, defaults);
7253      }
7254    });
7255
7256    function initSlider(sliderObj, args) {
7257      if (sliderActive) {
7258        sliderObj.slick('unslick');
7259        sliderActive = false;
7260      }
7261
7262      sliderObj.slick(args);
7263      sliderActive = true;
7264    }
7265  }
7266
7267  Quotes.prototype = _.assignIn({}, Quotes.prototype, {
7268    onUnload: function() {
7269      enquire.unregister(config.mediaQuerySmall);
7270      enquire.unregister(config.mediaQueryMediumUp);
7271
7272      $(this.slider, this.wrapper).slick('unslick');
7273    },
7274
7275    onBlockSelect: function(evt) {
7276      // Ignore the cloned version
7277      var $slide = $(
7278        '.quotes-slide--' + evt.detail.blockId + ':not(.slick-cloned)'
7279      );
7280      var slideIndex = $slide.data('slick-index');
7281
7282      // Go to selected slide, pause autoplay
7283      $(this.slider, this.wrapper).slick('slickGoTo', slideIndex);
7284    },
7285
7286    a11y: function(event, obj) {
7287      var $list = obj.$list;
7288      var $wrapper = $(this.wrapper, this.$container);
7289
7290      // Remove default Slick aria-live attr until slider is focused
7291      $list.removeAttr('aria-live');
7292
7293      // When an element in the slider is focused set aria-live
7294      $wrapper.on('focusin', function(evt) {
7295        if ($wrapper.has(evt.target).length) {
7296          $list.attr('aria-live', 'polite');
7297        }
7298      });
7299
7300      // Remove aria-live
7301      $wrapper.on('focusout', function(evt) {
7302        if ($wrapper.has(evt.target).length) {
7303          $list.removeAttr('aria-live');
7304        }
7305      });
7306    }
7307  });
7308
7309  return Quotes;
7310})();
7311
7312theme.slideshows = {};
7313
7314theme.SlideshowSection = (function() {
7315  function SlideshowSection(container) {
7316    var $container = (this.$container = $(container));
7317    var sectionId = $container.attr('data-section-id');
7318    var slideshow = (this.slideshow = '#Slideshow-' + sectionId);
7319
7320    theme.slideshows[slideshow] = new theme.Slideshow(slideshow, sectionId);
7321  }
7322
7323  return SlideshowSection;
7324})();
7325
7326theme.SlideshowSection.prototype = _.assignIn(
7327  {},
7328  theme.SlideshowSection.prototype,
7329  {
7330    onUnload: function() {
7331      delete theme.slideshows[this.slideshow];
7332    },
7333
7334    onBlockSelect: function(evt) {
7335      var $slideshow = $(this.slideshow);
7336      var adaptHeight = $slideshow.data('adapt-height');
7337
7338      if (adaptHeight) {
7339        theme.slideshows[this.slideshow].setSlideshowHeight();
7340      }
7341
7342      // Ignore the cloned version
7343      var $slide = $(
7344        '.slideshow__slide--' + evt.detail.blockId + ':not(.slick-cloned)'
7345      );
7346      var slideIndex = $slide.data('slick-index');
7347
7348      // Go to selected slide, pause auto-rotate
7349      $slideshow.slick('slickGoTo', slideIndex).slick('slickPause');
7350    },
7351
7352    onBlockDeselect: function() {
7353      // Resume auto-rotate
7354      $(this.slideshow).slick('slickPlay');
7355    }
7356  }
7357);
7358
7359theme.slideshows = {};
7360
7361theme.VideoSection = (function() {
7362  function VideoSection(container) {
7363    var $container = (this.$container = $(container));
7364
7365    $('.video', $container).each(function() {
7366      var $el = $(this);
7367      theme.Video.init($el);
7368      theme.Video.editorLoadVideo($el.attr('id'));
7369    });
7370  }
7371
7372  return VideoSection;
7373})();
7374
7375theme.VideoSection.prototype = _.assignIn({}, theme.VideoSection.prototype, {
7376  onUnload: function() {
7377    theme.Video.removeEvents();
7378  }
7379});
7380
7381theme.heros = {};
7382
7383theme.HeroSection = (function() {
7384  function HeroSection(container) {
7385    var $container = (this.$container = $(container));
7386    var sectionId = $container.attr('data-section-id');
7387    var hero = '#Hero-' + sectionId;
7388    theme.heros[hero] = new theme.Hero(hero, sectionId);
7389  }
7390
7391  return HeroSection;
7392})();
7393
7394window.theme = window.theme || {};
7395
7396var selectors = {
7397  disclosureLocale: '[data-disclosure-locale]',
7398  disclosureCurrency: '[data-disclosure-currency]'
7399};
7400
7401theme.FooterSection = (function() {
7402  function Footer(container) {
7403    this.$container = $(container);
7404    this.cache = {};
7405    this.cacheSelectors();
7406
7407    if (this.cache.$localeDisclosure.length) {
7408      this.localeDisclosure = new theme.Disclosure(
7409        this.cache.$localeDisclosure
7410      );
7411    }
7412
7413    if (this.cache.$currencyDisclosure.length) {
7414      this.currencyDisclosure = new theme.Disclosure(
7415        this.cache.$currencyDisclosure
7416      );
7417    }
7418  }
7419
7420  Footer.prototype = _.assignIn({}, Footer.prototype, {
7421    cacheSelectors: function() {
7422      this.cache = {
7423        $localeDisclosure: this.$container.find(selectors.disclosureLocale),
7424        $currencyDisclosure: this.$container.find(selectors.disclosureCurrency)
7425      };
7426    },
7427
7428    onUnload: function() {
7429      if (this.cache.$localeDisclosure.length) {
7430        this.localeDisclosure.unload();
7431      }
7432
7433      if (this.cache.$currencyDisclosure.length) {
7434        this.currencyDisclosure.unload();
7435      }
7436    }
7437  });
7438
7439  return Footer;
7440})();
7441
7442
7443$(document).ready(function() {
7444  var sections = new theme.Sections();
7445
7446  sections.register('cart-template', theme.Cart);
7447  sections.register('product', theme.Product);
7448  sections.register('collection-template', theme.Filters);
7449  sections.register('product-template', theme.Product);
7450  sections.register('header-section', theme.HeaderSection);
7451  sections.register('map', theme.Maps);
7452  sections.register('slideshow-section', theme.SlideshowSection);
7453  sections.register('video-section', theme.VideoSection);
7454  sections.register('quotes', theme.Quotes);
7455  sections.register('hero-section', theme.HeroSection);
7456  sections.register('product-recommendations', theme.ProductRecommendations);
7457  sections.register('footer-section', theme.FooterSection);
7458});
7459
7460theme.init = function() {
7461  theme.customerTemplates.init();
7462
7463  // Theme-specific selectors to make tables scrollable
7464  var tableSelectors = '.rte table,' + '.custom__item-inner--html table';
7465
7466  slate.rte.wrapTable({
7467    $tables: $(tableSelectors),
7468    tableWrapperClass: 'scrollable-wrapper'
7469  });
7470
7471  // Theme-specific selectors to make iframes responsive
7472  var iframeSelectors =
7473    '.rte iframe[src*="youtube.com/embed"],' +
7474    '.rte iframe[src*="player.vimeo"],' +
7475    '.custom__item-inner--html iframe[src*="youtube.com/embed"],' +
7476    '.custom__item-inner--html iframe[src*="player.vimeo"]';
7477
7478  slate.rte.wrapIframe({
7479    $iframes: $(iframeSelectors),
7480    iframeWrapperClass: 'video-wrapper'
7481  });
7482
7483  // Common a11y fixes
7484  slate.a11y.pageLinkFocus($(window.location.hash));
7485
7486  $('.in-page-link').on('click', function(evt) {
7487    slate.a11y.pageLinkFocus($(evt.currentTarget.hash));
7488  });
7489
7490  $('a[href="#"]').on('click', function(evt) {
7491    evt.preventDefault();
7492  });
7493
7494  slate.a11y.accessibleLinks({
7495    messages: {
7496      newWindow: theme.strings.newWindow,
7497      external: theme.strings.external,
7498      newWindowExternal: theme.strings.newWindowExternal
7499    },
7500    $links: $('a[href]:not([aria-describedby], .product-single__thumbnail)')
7501  });
7502
7503  theme.FormStatus.init();
7504
7505  var selectors = {
7506    image: '[data-image]',
7507    imagePlaceholder: '[data-image-placeholder]',
7508    imageWithPlaceholderWrapper: '[data-image-with-placeholder-wrapper]',
7509    lazyloaded: '.lazyloaded'
7510  };
7511
7512  var classes = {
7513    hidden: 'hide'
7514  };
7515
7516  $(document).on('lazyloaded', function(e) {
7517    var $target = $(e.target);
7518
7519    if ($target.data('bgset')) {
7520      var $image = $target.find(selectors.lazyloaded);
7521      if ($image.length) {
7522        var alt = $target.data('alt');
7523        var src = $image.data('src') ? $image.data('src') : $target.data('bg');
7524
7525        $image.attr('alt', alt ? alt : '');
7526        $image.attr('src', src ? src : '');
7527      }
7528    }
7529
7530    if (!$target.is(selectors.image)) {
7531      return;
7532    }
7533
7534    $target
7535      .closest(selectors.imageWithPlaceholderWrapper)
7536      .find(selectors.imagePlaceholder)
7537      .addClass(classes.hidden);
7538  });
7539
7540  // When the theme loads, lazysizes might load images before the "lazyloaded"
7541  // event listener has been attached. When this happens, the following function
7542  // hides the loading placeholders.
7543  function onLoadHideLazysizesAnimation() {
7544    $(selectors.image + '.lazyloaded')
7545      .closest(selectors.imageWithPlaceholderWrapper)
7546      .find(selectors.imagePlaceholder)
7547      .addClass(classes.hidden);
7548  }
7549
7550  onLoadHideLazysizesAnimation();
7551  $(document).one('touchstart', function() {
7552    theme.Helpers.setTouch();
7553  });
7554};
7555
7556// Youtube API callback
7557// eslint-disable-next-line no-unused-vars
7558function onYouTubeIframeAPIReady() {
7559  theme.Video.loadVideos();
7560  theme.ProductVideo.loadVideos(theme.ProductVideo.hosts.youtube);
7561}
7562
7563$(theme.init);
7564
7565// Info Tabs on Product page
7566
7567 $(document).ready(function() {
7568    $('ul.tabs').each(function(){
7569      var active, content, links = $(this).find('a');
7570      active = links.first().addClass('active');
7571      content = $(active.attr('href'));
7572      links.not(':first').each(function () {
7573        $($(this).attr('href')).hide();
7574      });
7575      $(this).find('a').click(function(e){
7576        active.removeClass('active');
7577        content.hide();
7578        active = $(this);
7579        content = $($(this).attr('href'));
7580        active.addClass('active');
7581        content.show();
7582        return false;
7583      });
7584    });
7585  });
7586
7587// Custom size variants
7588// GR - 12 Jan'22
7589
7590$('.custom-size-button').on('click', function() {
7591	// add selected state
7592    if ( $(this).hasClass('selected') ) {
7593        $(this).removeClass('selected');
7594    } else {
7595        $('.custom-size-button').removeClass('selected');
7596        $(this).addClass('selected');    
7597    }
7598  // grab the value attribute
7599  let value = $(this).attr('data-value');
7600  $('.custom-size-selector').val(value).trigger('change');
7601})

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.