PageSourceSearch

https://www.buyartjewels.com/s/files/1/0257/4784/9313/t/2/assets/theme-v-11401045510204408572.js

js buyartjewels.com collected 2026-10-04 03:56:34 UTC 158,576 bytes, 5,285 lines download raw bytes

1window.theme = window.theme || {};
2window.slate = window.slate || {};
3
4window.lazySizesConfig = window.lazySizesConfig || {};
5lazySizesConfig.expFactor = 4;
6
7(function($){
8  var $ = jQuery = $;
9
10  slate.utils = {
11    /**
12     * _.defaultTo from lodash
13     * Checks `value` to determine whether a default value should be returned in
14     * its place. The `defaultValue` is returned if `value` is `NaN`, `null`,
15     * or `undefined`.
16     * Source: https://github.com/lodash/lodash/blob/master/defaultTo.js
17     *
18     * @param {*} value - Value to check
19     * @param {*} defaultValue - Default value
20     * @returns {*} - Returns the resolved value
21     */
22    defaultTo: function(value, defaultValue) {
23      return (value == null || value !== value) ? defaultValue : value
24    }
25  };
26  
27  slate.a11y = {
28  
29    /**
30     * Traps the focus in a particular container
31     *
32     * @param {object} options - Options to be used
33     * @param {jQuery} options.$container - Container to trap focus within
34     * @param {jQuery} options.$elementToFocus - Element to be focused when focus leaves container
35     * @param {string} options.namespace - Namespace used for new focus event handler
36     */
37    trapFocus: function(options) {
38      var eventName = options.namespace
39        ? 'focusin.' + options.namespace
40        : 'focusin';
41  
42      if (!options.$elementToFocus) {
43        options.$elementToFocus = options.$container;
44      }
45  
46      options.$container.attr('tabindex', '-1');
47      options.$elementToFocus.focus();
48  
49      $(document).off('focusin');
50  
51      $(document).on(eventName, function(evt) {
52        if (options.$container[0] !== evt.target && !options.$container.has(evt.target).length) {
53          options.$container.focus();
54        }
55      });
56    },
57  
58    /**
59     * Removes the trap of focus in a particular container
60     *
61     * @param {object} options - Options to be used
62     * @param {jQuery} options.$container - Container to trap focus within
63     * @param {string} options.namespace - Namespace used for new focus event handler
64     */
65    removeTrapFocus: function(options) {
66      var eventName = options.namespace
67        ? 'focusin.' + options.namespace
68        : 'focusin';
69  
70      if (options.$container && options.$container.length) {
71        options.$container.removeAttr('tabindex');
72      }
73  
74      $(document).off(eventName);
75    },
76  
77  
78    // Not from Slate, but fit in the a11y category
79    lockMobileScrolling: function(namespace, $element) {
80      if ($element) {
81        var $el = $element;
82      } else {
83        var $el = $(document.documentElement).add('body');
84      }
85      $el.on('touchmove' + namespace, function () {
86        return false;
87      });
88    },
89  
90    unlockMobileScrolling: function(namespace, $element) {
91      if ($element) {
92        var $el = $element;
93      } else {
94        var $el = $(document.documentElement).add('body');
95      }
96      $el.off(namespace);
97    }
98  };
99  
100  theme.Sections = function Sections() {
101    this.constructors = {};
102    this.instances = [];
103  
104    $(document)
105      .on('shopify:section:load', this._onSectionLoad.bind(this))
106      .on('shopify:section:unload', this._onSectionUnload.bind(this))
107      .on('shopify:section:select', this._onSelect.bind(this))
108      .on('shopify:section:deselect', this._onDeselect.bind(this))
109      .on('shopify:block:select', this._onBlockSelect.bind(this))
110      .on('shopify:block:deselect', this._onBlockDeselect.bind(this));
111  };
112  
113  theme.Sections.prototype = $.extend({}, theme.Sections.prototype, {
114    createInstance: function(container, constructor, customScope) {
115      var $container = $(container);
116      var id = $container.attr('data-section-id');
117      var type = $container.attr('data-section-type');
118  
119      constructor = constructor || this.constructors[type];
120  
121      if (typeof constructor === 'undefined') {
122        return;
123      }
124  
125      // If custom scope passed, check to see if instance
126      // is already initialized so we don't double up
127      if (customScope) {
128        var instanceExists = this._findInstance(id);
129        if (instanceExists) {
130          return;
131        }
132      }
133  
134      var instance = $.extend(new constructor(container), {
135        id: id,
136        type: type,
137        container: container
138      });
139  
140      this.instances.push(instance);
141    },
142  
143    _onSectionLoad: function(evt, subSection, subSectionId) {
144      if (AOS) {
145        AOS.refreshHard();
146      }
147      var container = subSection ? subSection : $('[data-section-id]', evt.target)[0];
148  
149      if (!container) {
150        return;
151      }
152  
153      this.createInstance(container);
154  
155      var instance = subSection ? subSectionId : this._findInstance(evt.detail.sectionId);
156  
157      if (!subSection) {
158        this._loadSubSections();
159      }
160  
161      // Run JS only in case of the section being selected in the editor
162      // before merchant clicks "Add"
163      if (instance && typeof instance.onLoad === 'function') {
164        instance.onLoad(evt);
165      }
166    },
167  
168    _loadSubSections: function() {
169      if (AOS) {
170        AOS.refreshHard();
171      }
172      $('[data-subsection]').each(function(evt, el) {
173        this._onSectionLoad(null, el, $(el).data('section-id'));
174      }.bind(this));
175    },
176  
177    _onSectionUnload: function(evt) {
178      var instance = this._removeInstance(evt.detail.sectionId);
179      if (instance && typeof instance.onUnload === 'function') {
180        instance.onUnload(evt);
181      }
182    },
183  
184    _onSelect: function(evt) {
185      var instance = this._findInstance(evt.detail.sectionId);
186  
187      if (instance && typeof instance.onSelect === 'function') {
188        instance.onSelect(evt);
189      }
190    },
191  
192    _onDeselect: function(evt) {
193      var instance = this._findInstance(evt.detail.sectionId);
194  
195      if (instance && typeof instance.onDeselect === 'function') {
196        instance.onDeselect(evt);
197      }
198    },
199  
200    _onBlockSelect: function(evt) {
201      var instance = this._findInstance(evt.detail.sectionId);
202  
203      if (instance && typeof instance.onBlockSelect === 'function') {
204        instance.onBlockSelect(evt);
205      }
206    },
207  
208    _onBlockDeselect: function(evt) {
209      var instance = this._findInstance(evt.detail.sectionId);
210  
211      if (instance && typeof instance.onBlockDeselect === 'function') {
212        instance.onBlockDeselect(evt);
213      }
214    },
215  
216    _findInstance: function(id) {
217      for (var i = 0; i < this.instances.length; i++) {
218        if (this.instances[i].id === id) {
219          return this.instances[i];
220        }
221      }
222    },
223  
224    _removeInstance: function(id) {
225      var i = this.instances.length;
226      var instance;
227  
228      while(i--) {
229        if (this.instances[i].id === id) {
230          instance = this.instances[i];
231          this.instances.splice(i, 1);
232          break;
233        }
234      }
235  
236      return instance;
237    },
238  
239    register: function(type, constructor, $scope) {
240      var afterLoad = false;
241      this.constructors[type] = constructor;
242      var $sections = $('[data-section-type=' + type + ']');
243  
244      if ($scope) {
245        $sections = $('[data-section-type=' + type + ']', $scope);
246      }
247  
248      $sections.each(function(index, container) {
249        this.createInstance(container, constructor, $scope);
250      }.bind(this));
251    }
252  });
253  
254  
255  /**
256   * Currency Helpers
257   * -----------------------------------------------------------------------------
258   * A collection of useful functions that help with currency formatting
259   *
260   * Current contents
261   * - formatMoney - Takes an amount in cents and returns it as a formatted dollar value.
262   *
263   * Alternatives
264   * - Accounting.js - http://openexchangerates.github.io/accounting.js/
265   *
266   */
267  
268  theme.Currency = (function() {
269    var moneyFormat = '$';
270  
271    function formatMoney(cents, format) {
272      if (typeof cents === 'string') {
273        cents = cents.replace('.', '');
274      }
275      var value = '';
276      var placeholderRegex = /\{\{\s*(\w+)\s*\}\}/;
277      var formatString = (format || moneyFormat);
278  
279      function formatWithDelimiters(number, precision, thousands, decimal) {
280        precision = slate.utils.defaultTo(precision, 2);
281        thousands = slate.utils.defaultTo(thousands, ',');
282        decimal = slate.utils.defaultTo(decimal, '.');
283  
284        if (isNaN(number) || number == null) {
285          return 0;
286        }
287  
288        number = (number / 100.0).toFixed(precision);
289  
290        var parts = number.split('.');
291        var dollarsAmount = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + thousands);
292        var centsAmount = parts[1] ? (decimal + parts[1]) : '';
293  
294        return dollarsAmount + centsAmount;
295      }
296  
297      switch (formatString.match(placeholderRegex)[1]) {
298        case 'amount':
299          value = formatWithDelimiters(cents, 2);
300          break;
301        case 'amount_no_decimals':
302          value = formatWithDelimiters(cents, 0);
303          break;
304        case 'amount_with_comma_separator':
305          value = formatWithDelimiters(cents, 2, '.', ',');
306          break;
307        case 'amount_no_decimals_with_comma_separator':
308          value = formatWithDelimiters(cents, 0, '.', ',');
309          break;
310        case 'amount_no_decimals_with_space_separator':
311          value = formatWithDelimiters(cents, 0, ' ');
312          break;
313      }
314  
315      return formatString.replace(placeholderRegex, value);
316    }
317  
318    return {
319      formatMoney: formatMoney
320    }
321  })();
322  
323  
324  /**
325   * Image Helper Functions
326   * -----------------------------------------------------------------------------
327   * A collection of functions that help with basic image operations.
328   *
329   */
330  
331  theme.Images = (function() {
332  
333    /**
334     * Find the Shopify image attribute size
335     *
336     * @param {string} src
337     * @returns {null}
338     */
339    function imageSize(src) {
340      if (!src) {
341        return '620x'; // default based on theme
342      }
343  
344      var match = src.match(/.+_((?:pico|icon|thumb|small|compact|medium|large|grande)|\d{1,4}x\d{0,4}|x\d{1,4})[_\.@]/);
345  
346      if (match !== null) {
347        return match[1];
348      } else {
349        return null;
350      }
351    }
352  
353    /**
354     * Adds a Shopify size attribute to a URL
355     *
356     * @param src
357     * @param size
358     * @returns {*}
359     */
360    function getSizedImageUrl(src, size) {
361      if (size == null) {
362        return src;
363      }
364  
365      if (size === 'master') {
366        return this.removeProtocol(src);
367      }
368  
369      var match = src.match(/\.(jpg|jpeg|gif|png|bmp|bitmap|tiff|tif)(\?v=\d+)?$/i);
370  
371      if (match != null) {
372        var prefix = src.split(match[0]);
373        var suffix = match[0];
374  
375        return this.removeProtocol(prefix[0] + '_' + size + suffix);
376      }
377  
378      return null;
379    }
380  
381    function removeProtocol(path) {
382      return path.replace(/http(s)?:/, '');
383    }
384  
385    return {
386      imageSize: imageSize,
387      getSizedImageUrl: getSizedImageUrl,
388      removeProtocol: removeProtocol
389    };
390  })();
391  
392  slate.Variants = (function() {
393  
394    function Variants(options) {
395      this.$container = options.$container;
396      this.variants = options.variants;
397      this.singleOptionSelector = options.singleOptionSelector;
398      this.originalSelectorId = options.originalSelectorId;
399      this.enableHistoryState = options.enableHistoryState;
400      this.currentVariant = this._getVariantFromOptions();
401  
402      $(this.singleOptionSelector, this.$container).on('change', this._onSelectChange.bind(this));
403    }
404  
405    Variants.prototype = $.extend({}, Variants.prototype, {
406  
407      _getCurrentOptions: function() {
408        var currentOptions = $.map($(this.singleOptionSelector, this.$container), function(element) {
409          var $element = $(element);
410          var type = $element.attr('type');
411          var currentOption = {};
412  
413          if (type === 'radio' || type === 'checkbox') {
414            if ($element[0].checked) {
415              currentOption.value = $element.val();
416              currentOption.index = $element.data('index');
417  
418              return currentOption;
419            } else {
420              return false;
421            }
422          } else {
423            currentOption.value = $element.val();
424            currentOption.index = $element.data('index');
425  
426            return currentOption;
427          }
428        });
429  
430        // remove any unchecked input values if using radio buttons or checkboxes
431        currentOptions = this._compact(currentOptions);
432  
433        return currentOptions;
434      },
435  
436      _getVariantFromOptions: function() {
437        var selectedValues = this._getCurrentOptions();
438        var variants = this.variants;
439        var found = false;
440  
441        variants.forEach(function(variant) {
442          var match = true;
443          var options = variant.options;
444  
445          selectedValues.forEach(function(option) {
446            // console.log('try to match ' + option.value + ' with ' + variant[option.index]);
447  
448            if (match) {
449              match = (variant[option.index] === option.value);
450            }
451          });
452  
453          if (match) {
454            found = variant;
455          }
456        });
457  
458        return found || null;
459      },
460  
461      _onSelectChange: function() {
462        var variant = this._getVariantFromOptions();
463  
464        this.$container.trigger({
465          type: 'variantChange',
466          variant: variant
467        });
468  
469        if (!variant) {
470          return;
471        }
472  
473        this._updateMasterSelect(variant);
474        this._updateImages(variant);
475        this._updatePrice(variant);
476        this._updateSKU(variant);
477        this.currentVariant = variant;
478  
479        if (this.enableHistoryState) {
480          this._updateHistoryState(variant);
481        }
482      },
483  
484      _updateImages: function(variant) {
485        var variantImage = variant.featured_image || {};
486        var currentVariantImage = this.currentVariant.featured_image || {};
487  
488        if (!variant.featured_image || variantImage.src === currentVariantImage.src) {
489          return;
490        }
491  
492        this.$container.trigger({
493          type: 'variantImageChange',
494          variant: variant
495        });
496      },
497  
498      _updatePrice: function(variant) {
499        if (variant.price === this.currentVariant.price && variant.compare_at_price === this.currentVariant.compare_at_price) {
500          return;
501        }
502  
503        this.$container.trigger({
504          type: 'variantPriceChange',
505          variant: variant
506        });
507      },
508  
509      _updateSKU: function(variant) {
510        if (variant.sku === this.currentVariant.sku) {
511          return;
512        }
513  
514        this.$container.trigger({
515          type: 'variantSKUChange',
516          variant: variant
517        });
518      },
519  
520      _updateHistoryState: function(variant) {
521        if (!history.replaceState || !variant) {
522          return;
523        }
524  
525        var newurl = window.location.protocol + '//' + window.location.host + window.location.pathname + '?variant=' + variant.id;
526        window.history.propertyIsEnumerable({path: newurl}, '', newurl);
527      },
528  
529      _updateMasterSelect: function(variant) {
530        $(this.originalSelectorId, this.$container).val(variant.id);
531      },
532  
533      // _.compact from lodash
534      // https://github.com/lodash/lodash/blob/4d4e452ade1e78c7eb890968d851f837be37e429/compact.js
535      _compact: function(array) {
536        var index = -1,
537            length = array == null ? 0 : array.length,
538            resIndex = 0,
539            result = [];
540  
541        while (++index < length) {
542          var value = array[index];
543          if (value) {
544            result[resIndex++] = value;
545          }
546        }
547        return result;
548      }
549    });
550  
551    return Variants;
552  })();
553  
554  slate.rte = {
555    init: function() {
556      slate.rte.wrapTable();
557      slate.rte.wrapVideo();
558      slate.rte.imageLinks();
559    },
560  
561    wrapTable: function() {
562      $('.rte table').wrap('<div class="table-wrapper"></div>');
563    },
564  
565    wrapVideo: function() {
566      var $iframeVideo = $('.rte iframe[src*="youtube.com/embed"], .rte iframe[src*="player.vimeo"]');
567      var $iframeReset = $iframeVideo.add('iframe#admin_bar_iframe');
568  
569      $iframeVideo.each(function () {
570        // Add wrapper to make video responsive
571        if (!$(this).parents('.video-wrapper').length) {
572          $(this).wrap('<div class="video-wrapper"></div>');
573        }
574      });
575  
576      $iframeReset.each(function () {
577        // Re-set the src attribute on each iframe after page load
578        // for Chrome's "incorrect iFrame content on 'back'" bug.
579        // https://code.google.com/p/chromium/issues/detail?id=395791
580        // Need to specifically target video and admin bar
581        this.src = this.src;
582      });
583    },
584  
585    // Remove CSS that adds animated underline under image links
586    imageLinks: function() {
587      $('.rte a img').parent().addClass('rte__image');
588    }
589  };
590  
591
592  theme.Modals = (function() {
593    function Modal(id, name, options) {
594      var defaults = {
595        close: '.js-modal-close',
596        open: '.js-modal-open-' + name,
597        openClass: 'modal--is-active',
598        closingClass: 'modal--is-closing',
599        bodyOpenClass: 'modal-open',
600        bodyOpenSolidClass: 'modal-open--solid',
601        bodyClosingClass: 'modal-closing',
602        closeOffContentClick: true
603      };
604  
605      this.id = id;
606      this.$modal = $('#' + id);
607  
608      if (!this.$modal.length) {
609        return false;
610      }
611  
612      this.nodes = {
613        $parent: $('html').add('body'),
614        $modalContent: this.$modal.find('.modal__inner')
615      };
616  
617      this.config = $.extend(defaults, options);
618      this.modalIsOpen = false;
619      this.$focusOnOpen = this.config.focusOnOpen ? $(this.config.focusOnOpen) : this.$modal;
620      this.isSolid = this.config.solid;
621  
622      this.init();
623    }
624  
625    Modal.prototype.init = function() {
626      var $openBtn = $(this.config.open);
627  
628      // Add aria controls
629      $openBtn.attr('aria-expanded', 'false');
630  
631      $(this.config.open).on('click', this.open.bind(this));
632      this.$modal.find(this.config.close).on('click', this.close.bind(this));
633  
634      // Close modal if a drawer is opened
635      $('body').on('drawerOpen', function() {
636        this.close();
637      }.bind(this));
638    };
639  
640    Modal.prototype.open = function(evt) {
641      // Keep track if modal was opened from a click, or called by another function
642      var externalCall = false;
643  
644      // don't open an opened modal
645      if (this.modalIsOpen) {
646        return;
647      }
648  
649      // Prevent following href if link is clicked
650      if (evt) {
651        evt.preventDefault();
652      } else {
653        externalCall = true;
654      }
655  
656      // Without this, the modal opens, the click event bubbles up to $nodes.page
657      // which closes the modal.
658      if (evt && evt.stopPropagation) {
659        evt.stopPropagation();
660        // save the source of the click, we'll focus to this on close
661        this.$activeSource = $(evt.currentTarget);
662      }
663  
664      if (this.modalIsOpen && !externalCall) {
665        this.close();
666      }
667  
668      this.$modal
669        .prepareTransition()
670        .addClass(this.config.openClass);
671      this.nodes.$parent.addClass(this.config.bodyOpenClass);
672  
673      if (this.isSolid) {
674        this.nodes.$parent.addClass(this.config.bodyOpenSolidClass);
675      }
676  
677      this.modalIsOpen = true;
678  
679      slate.a11y.trapFocus({
680        $container: this.$modal,
681        $elementToFocus: this.$focusOnOpen,
682        namespace: 'modal_focus'
683      });
684  
685      if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
686        this.$activeSource.attr('aria-expanded', 'true');
687      }
688  
689      $('body').trigger('modalOpen.' + this.id);
690  
691      this.bindEvents();
692    };
693  
694    Modal.prototype.close = function() {
695      // don't close a closed modal
696      if (!this.modalIsOpen) {
697        return;
698      }
699  
700      // deselect any focused form elements
701      $(document.activeElement).trigger('blur');
702  
703      this.$modal
704        .prepareTransition()
705        .removeClass(this.config.openClass)
706        .addClass(this.config.closingClass);
707      this.nodes.$parent.removeClass(this.config.bodyOpenClass);
708      this.nodes.$parent.addClass(this.config.bodyClosingClass);
709      var o = this;
710      window.setTimeout(function() {
711        o.nodes.$parent.removeClass(o.config.bodyClosingClass);
712        o.$modal.removeClass(o.config.closingClass);
713      }, 500); // modal close css transition
714  
715      if (this.isSolid) {
716        this.nodes.$parent.removeClass(this.config.bodyOpenSolidClass);
717      }
718  
719      this.modalIsOpen = false;
720  
721      slate.a11y.removeTrapFocus({
722        $container: this.$modal,
723        namespace: 'modal_focus'
724      });
725  
726      if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
727        this.$activeSource.attr('aria-expanded', 'false').focus();
728      }
729  
730      $('body').trigger('modalClose.' + this.id);
731  
732      this.unbindEvents();
733    };
734  
735    Modal.prototype.bindEvents = function() {
736      // Pressing escape closes modal
737      this.nodes.$parent.on('keyup.modal', function(evt) {
738        if (evt.keyCode === 27) {
739          this.close();
740        }
741      }.bind(this));
742  
743      if (this.config.closeOffContentClick) {
744        // Clicking outside of the modal content also closes it
745        this.$modal.on('click.modal', this.close.bind(this));
746  
747        // Exception to above: clicking anywhere on the modal content will NOT close it
748        this.nodes.$modalContent.on('click.modal', function(evt) {
749          evt.stopImmediatePropagation();
750        });
751      }
752    };
753  
754    Modal.prototype.unbindEvents = function() {
755      this.nodes.$parent.off('.modal');
756  
757      if (this.config.closeOffContentClick) {
758        this.$modal.off('.modal');
759        this.nodes.$modalContent.off('.modal');
760      }
761    };
762  
763    return Modal;
764  })();
765  
766  theme.Drawers = (function() {
767    function Drawer(id, name) {
768      this.config = {
769        id: id,
770        close: '.js-drawer-close',
771        open: '.js-drawer-open-' + name,
772        openClass: 'js-drawer-open',
773        closingClass: 'js-drawer-closing',
774        activeDrawer: 'drawer--is-open',
775        namespace: '.drawer-' + name
776      };
777  
778      this.$nodes = {
779        parent: $(document.documentElement).add('body'),
780        page: $('#MainContent')
781      };
782  
783      this.$drawer = $('#' + id);
784  
785      if (!this.$drawer.length) {
786        return false;
787      }
788  
789      this.isOpen = false;
790      this.init();
791    };
792  
793    Drawer.prototype = $.extend({}, Drawer.prototype, {
794      init: function() {
795        var $openBtn = $(this.config.open);
796  
797        // Add aria controls
798        $openBtn.attr('aria-expanded', 'false');
799  
800        $openBtn.on('click', this.open.bind(this));
801        this.$drawer.find(this.config.close).on('click', this.close.bind(this));
802      },
803  
804      open: function(evt) {
805        if (evt) {
806          evt.preventDefault();
807        }
808  
809        if (this.isOpen) {
810          return;
811        }
812  
813        // Without this the drawer opens, the click event bubbles up to $nodes.page which closes the drawer.
814        if (evt && evt.stopPropagation) {
815          evt.stopPropagation();
816          // save the source of the click, we'll focus to this on close
817          this.$activeSource = $(evt.currentTarget);
818        }
819  
820        this.$drawer.prepareTransition().addClass(this.config.activeDrawer);
821  
822        this.$nodes.parent.addClass(this.config.openClass);
823        this.isOpen = true;
824  
825        slate.a11y.trapFocus({
826          $container: this.$drawer,
827          namespace: 'drawer_focus'
828        });
829  
830        $('body').trigger('drawerOpen.' + this.config.id);
831  
832        if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
833          this.$activeSource.attr('aria-expanded', 'true');
834        }
835  
836        this.bindEvents();
837      },
838  
839      close: function() {
840        if (!this.isOpen) {
841          return;
842        }
843  
844        // deselect any focused form elements
845        $(document.activeElement).trigger('blur');
846  
847        this.$drawer.prepareTransition().removeClass(this.config.activeDrawer);
848  
849        this.$nodes.parent.removeClass(this.config.openClass);
850        this.$nodes.parent.addClass(this.config.closingClass);
851        var o = this;
852        window.setTimeout(function() {
853          o.$nodes.parent.removeClass(o.config.closingClass);
854        }, 500);
855  
856        this.isOpen = false;
857  
858        slate.a11y.removeTrapFocus({
859          $container: this.$drawer,
860          namespace: 'drawer_focus'
861        });
862  
863        if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
864          this.$activeSource.attr('aria-expanded', 'false');
865        }
866  
867        this.unbindEvents();
868      },
869  
870      bindEvents: function() {
871        slate.a11y.lockMobileScrolling(this.config.namespace, this.$nodes.page);
872  
873        // Clicking out of drawer closes it
874        this.$nodes.page.on('click' + this.config.namespace, function () {
875          this.close();
876          return false;
877        }.bind(this));
878  
879        // Pressing escape closes drawer
880        this.$nodes.parent.on('keyup' + this.config.namespace, function(evt) {
881          if (evt.keyCode === 27) {
882            this.close();
883          }
884        }.bind(this));
885      },
886  
887      unbindEvents: function() {
888        slate.a11y.unlockMobileScrolling(this.config.namespace, this.$nodes.page);
889        this.$nodes.parent.off(this.config.namespace);
890        this.$nodes.page.off(this.config.namespace);
891      }
892    });
893  
894    return Drawer;
895  })();
896  
897  theme.cart = {
898    getCart: function() {
899      return $.getJSON('/cart.js');
900    },
901  
902    changeItem: function(key, qty) {
903      return this._updateCart({
904        type: 'POST',
905        url: '/cart/change.js',
906        data: 'quantity=' + qty + '&id=' + key,
907        dataType: 'json'
908      });
909    },
910  
911    addItemFromForm: function(data) {
912      return this._updateCart({
913        type: 'POST',
914        url: '/cart/add.js',
915        data: data,
916        dataType: 'json'
917      });
918    },
919  
920    _updateCart: function(params) {
921      return $.ajax(params)
922        .then(function(cart) {
923          return cart;
924        }.bind(this))
925    },
926  
927    updateNote: function(note) {
928      var params = {
929        type: 'POST',
930        url: '/cart/update.js',
931        data: 'note=' + theme.cart.attributeToString(note),
932        dataType: 'json',
933        success: function(cart) {},
934        error: function(XMLHttpRequest, textStatus) {}
935      };
936  
937      $.ajax(params);
938    },
939  
940    attributeToString: function(attribute) {
941      if ((typeof attribute) !== 'string') {
942        attribute += '';
943        if (attribute === 'undefined') {
944          attribute = '';
945        }
946      }
947      return $.trim(attribute);
948    }
949  }
950  
951  $(function() {
952    // Add a loading indicator on the cart checkout button (/cart and drawer)
953    $('body').on('click', '.cart__checkout', function() {
954      $(this).addClass('btn--loading');
955    });
956  
957    $('body').on('change', 'textarea[name="note"]', function() {
958      var newNote = $(this).val();
959      theme.cart.updateNote(newNote);
960    });
961  
962  
963    // Custom JS to prevent checkout without confirming terms and conditions
964    $('body').on('click', '.cart__checkout--ajax', function(evt) {
965      if ($('#CartAgree').is(':checked')) {
966      } else {
967        alert(theme.strings.cartTermsConfirmation);
968        $(this).removeClass('btn--loading');
969        return false;
970      }
971    });
972  
973    $('body').on('click', '.cart__checkout--page', function(evt) {
974      if ($('#CartPageAgree').is(':checked')) {
975      } else {
976        alert(theme.strings.cartTermsConfirmation);
977        $(this).removeClass('btn--loading');
978        return false;
979      }
980    });
981  });
982  
983  theme.QtySelector = (function() {
984    var classes = {
985      input: '.js-qty__num',
986      plus: '.js-qty__adjust--plus',
987      minus: '.js-qty__adjust--minus'
988    };
989  
990    function QtySelector($el, options) {
991      this.$wrapper = $el;
992      this.$input = $el.find(classes.input);
993      this.$plus = $el.find(classes.plus);
994      this.$minus = $el.find(classes.minus);
995      this.minValue = this.$input.attr('min') || 1;
996  
997      var defaults = {
998        namespace: null,
999        key: this.$input.data('id')
1000      };
1001  
1002      this.options = $.extend(defaults, options);
1003  
1004      this.initEventListeners();
1005    };
1006  
1007    QtySelector.prototype = $.extend({}, QtySelector.prototype, {
1008      initEventListeners: function() {
1009        this.$plus.on('click', function() {
1010          var qty = this.validateQty(this.$input.val());
1011          this.addQty(qty);
1012        }.bind(this));
1013  
1014        this.$minus.on('click', function() {
1015          var qty = this.validateQty(this.$input.val());
1016          this.subtractQty(qty);
1017        }.bind(this));
1018  
1019        this.$input.on('change', function() {
1020          var qty = this.validateQty(this.$input.val());
1021          this.changeQty(qty);
1022        }.bind(this));
1023      },
1024  
1025      addQty: function(number) {
1026        var qty = number + 1;
1027        this.changeQty(qty);
1028      },
1029  
1030      subtractQty: function(number) {
1031        var qty = number - 1;
1032        if (qty <= this.minValue) {
1033          qty = this.minValue;
1034        }
1035        this.changeQty(qty);
1036      },
1037  
1038      changeQty: function(qty) {
1039        this.$input.val(qty);
1040        $('body').trigger('qty' + this.options.namespace, [this.options.key, qty]);
1041      },
1042  
1043      validateQty: function(number) {
1044        if((parseFloat(number) == parseInt(number)) && !isNaN(number)) {
1045          // We have a valid number!
1046        } else {
1047          // Not a number. Default to 1.
1048          number = 1;
1049        }
1050        return parseInt(number);
1051      }
1052    });
1053  
1054    return QtySelector;
1055  })();
1056  
1057  theme.CartDrawer = (function() {
1058    var config = {
1059      namespace: '.ajaxcart'
1060    };
1061  
1062    var selectors = {
1063      drawer: '#CartDrawer',
1064      container: '#CartContainer',
1065      template: '#CartTemplate',
1066      fixedFooter: '.drawer__footer--fixed',
1067      fixedInnerContent: '.drawer__inner--has-fixed-footer',
1068      cartBubble: '.cart-link__bubble'
1069    };
1070  
1071    function CartDrawer() {
1072      this.status = {
1073        loaded: false,
1074        loading: false
1075      };
1076  
1077      this.drawer = new theme.Drawers('CartDrawer', 'cart');
1078  
1079      // Prep handlebars template
1080      var source = $(selectors.template).html();
1081      this.template = Handlebars.compile(source);
1082  
1083      // Build cart on page load so it's ready in the drawer
1084      theme.cart.getCart().then(this.buildCart.bind(this));
1085  
1086      this.initEventListeners();
1087    };
1088  
1089    CartDrawer.prototype = $.extend({}, CartDrawer.prototype, {
1090      initEventListeners: function() {
1091        $('body').on('updateCart' + config.namespace, this.initQtySelectors.bind(this));
1092        $('body').on('updateCart' + config.namespace, this.sizeFooter.bind(this));
1093        $('body').on('updateCart' + config.namespace, this.updateCartNotification.bind(this));
1094        $('body').on('drawerOpen.CartDrawer', this.sizeFooter.bind(this));
1095  
1096        $(window).on('resize' + config.namespace, $.debounce(150, this.sizeFooter.bind(this)));
1097  
1098        $('body').on('added.ajaxProduct', function() {
1099          theme.cart.getCart().then(function(cart) {
1100            this.buildCart(cart, true);
1101          }.bind(this));
1102        }.bind(this));
1103      },
1104  
1105      buildCart: function(cart, openDrawer) {
1106        this.loading(true);
1107        this.emptyCart();
1108  
1109        if (cart.item_count === 0) {
1110          $(selectors.container).append('<p class="appear-animation appear-delay-3">' + theme.strings.cartEmpty +'</p>');
1111        } else {
1112          var items = [];
1113          var item = {};
1114          var data = {};
1115          var animation_row = 1;
1116  
1117          $.each(cart.items, function(index, product) {
1118  
1119            var prodImg;
1120            if (product.image !== null) {
1121              prodImg = product.image.replace(/(\.[^.]*)$/, "_180x$1");
1122            } else {
1123              prodImg = 's/assets/admin/no-image-medium-cc9732cb976dd349a0df1d39816fbcc7.gif';
1124            }
1125  
1126            if (product.properties !== null) {
1127              $.each(product.properties, function(key, value) {
1128                if (key.charAt(0) === '_' || !value) {
1129                   delete product.properties[key];
1130                 }
1131              });
1132            }
1133  
1134            // If we have line-item discount, add formattedAmount to discount object
1135            var amount = 0;
1136            if (product.line_level_discount_allocations.length !== 0) {
1137              for (var discount in product.line_level_discount_allocations) {
1138                amount = product.line_level_discount_allocations[discount].amount;
1139  
1140                product.line_level_discount_allocations[discount].formattedAmount = theme.Currency.formatMoney(amount, theme.settings.moneyFormat);
1141              }
1142            }
1143  
1144            animation_row+=2;
1145  
1146            item = {
1147              key: product.key,
1148              url: product.url,
1149              img: prodImg,
1150              animationRow: animation_row,
1151              name: product.product_title,
1152              variation: product.variant_title,
1153              properties: product.properties,
1154              itemQty: product.quantity,
1155              price: theme.Currency.formatMoney(product.price, theme.settings.moneyFormat),
1156              discountedPrice: theme.Currency.formatMoney((product.price - (product.total_discount/product.quantity)), theme.settings.moneyFormat),
1157              discounts: product.line_level_discount_allocations,
1158              discountsApplied: product.line_level_discount_allocations.length === 0 ? false : true,
1159              vendor: product.vendor
1160            };
1161  
1162            items.push(item);
1163          });
1164  
1165          animation_row+=2;
1166  
1167          // If we have cart discount, add a formattedAmount to the discount object
1168          var cartAmount = 0;
1169          if (cart.cart_level_discount_applications.length !== 0) {
1170            for (var cartDiscount in cart.cart_level_discount_applications) {
1171              cartAmount = cart.cart_level_discount_applications[cartDiscount].total_allocated_amount;
1172  
1173              cart.cart_level_discount_applications[cartDiscount].formattedAmount = theme.Currency.formatMoney(cartAmount,theme.settings.moneyFormat);
1174            }
1175          }
1176  
1177          data = {
1178            items: items,
1179            note: cart.note,
1180            lastAnimationRow: animation_row,
1181            cartDiscounts: cart.cart_level_discount_applications,
1182            cartDiscountsApplied: cart.cart_level_discount_applications.length === 0 ? false : true,
1183            totalPrice: theme.Currency.formatMoney(cart.total_price, theme.settings.moneyFormat)
1184          };
1185  
1186          $(selectors.container).append(this.template(data));
1187        }
1188  
1189        this.status.loaded = true;
1190        this.loading(false);
1191  
1192        if (theme.settings.currenciesEnabled) {
1193          theme.currencySwitcher.ajaxrefresh();
1194        }
1195  
1196        $('body').trigger('updateCart' + config.namespace, cart);
1197  
1198        if (window.Shopify && Shopify.StorefrontExpressButtons) {
1199          Shopify.StorefrontExpressButtons.initialize();
1200  
1201          // Resize footer after arbitrary delay for buttons to load
1202          setTimeout(function() {
1203            this.sizeFooter();
1204          }.bind(this), 800);
1205        }
1206  
1207        // If specifically asked, open the cart drawer (only happens after product added from form)
1208        if (openDrawer === true) {
1209          this.drawer.open();
1210        }
1211      },
1212  
1213      initQtySelectors: function() {
1214        $(selectors.container).find('.js-qty__wrapper').each(function(index, el) {
1215          var selector = new theme.QtySelector($(el), {
1216            namespace: '.cart-drawer'
1217          });
1218        }.bind(this));
1219  
1220        $('body').on('qty.cart-drawer', this.updateItem.bind(this));
1221      },
1222  
1223      updateItem: function(evt, key, qty) {
1224        if (this.status.loading) {
1225          return;
1226        }
1227  
1228        this.loading(true);
1229  
1230        theme.cart.changeItem(key, qty)
1231          .then(function(cart) {
1232            this.updateSuccess(cart);
1233          }.bind(this))
1234          .catch(function(XMLHttpRequest) {
1235            this.updateError(XMLHttpRequest)
1236          }.bind(this))
1237          .always(function() {
1238            this.loading(false);
1239          }.bind(this));
1240      },
1241  
1242      loading: function(state) {
1243        this.status.loading = state;
1244  
1245        if (state) {
1246          $(selectors.container).addClass('is-loading');
1247        } else {
1248          $(selectors.container).removeClass('is-loading');
1249        }
1250      },
1251  
1252      emptyCart: function() {
1253        $(selectors.container).empty();
1254      },
1255  
1256      updateSuccess: function(cart) {
1257        this.buildCart(cart)
1258      },
1259  
1260      updateError: function(XMLHttpRequest) {
1261        if (XMLHttpRequest.responseJSON && XMLHttpRequest.responseJSON.description) {
1262          console.warn(XMLHttpRequest.responseJSON.description);
1263        }
1264      },
1265  
1266      // Update elements after cart is updated
1267      sizeFooter: function() {
1268        // Stop if our drawer doesn't have a fixed footer
1269        if (!$(selectors.drawer).hasClass('drawer--has-fixed-footer')) {
1270          return;
1271        }
1272  
1273        // Elements are reprinted regularly so selectors are not cached
1274        var $cartFooter = $(selectors.drawer).find(selectors.fixedFooter).removeAttr('style');
1275        var $cartInner = $(selectors.drawer).find(selectors.fixedInnerContent).removeAttr('style');
1276        var cartFooterHeight = $cartFooter.outerHeight();
1277  
1278        $cartInner.css('bottom', cartFooterHeight);
1279        $cartFooter.css('height', cartFooterHeight);
1280      },
1281  
1282      updateCartNotification: function(evt, cart) {
1283        if (cart.items.length > 0) {
1284          $(selectors.cartBubble).addClass('cart-link__bubble--visible');
1285        } else {
1286          $(selectors.cartBubble).removeClass('cart-link__bubble--visible');
1287        }
1288      }
1289    });
1290  
1291    return CartDrawer;
1292  })();
1293  
1294  theme.AjaxProduct = (function() {
1295    var status = {
1296      loading: false
1297    };
1298  
1299    function ProductForm($form) {
1300      this.$form = $form;
1301      this.$addToCart = this.$form.find('.add-to-cart');
1302  
1303      if (this.$form.length) {
1304        this.$form.on('submit', this.addItemFromForm.bind(this));
1305      }
1306    };
1307  
1308    ProductForm.prototype = $.extend({}, ProductForm.prototype, {
1309      addItemFromForm: function(evt, callback){
1310        evt.preventDefault();
1311  
1312        if (status.loading) {
1313          console.warn('already adding item to cart | bail');
1314          return;
1315        }
1316  
1317        // Loading indicator on add to cart button
1318        this.$addToCart.addClass('btn--loading');
1319  
1320        status.loading = true;
1321  
1322        var data = this.$form.serialize();
1323  
1324        theme.cart.addItemFromForm(data)
1325          .then(function(product) {
1326            this.success(product);
1327          }.bind(this))
1328          .catch(function(XMLHttpRequest) {
1329            this.error(XMLHttpRequest)
1330          }.bind(this))
1331          .always(function() {
1332            status.loading = false;
1333            this.$addToCart.removeClass('btn--loading');
1334          }.bind(this));
1335      },
1336  
1337      success: function(product) {
1338        this.$form.find('.errors').remove();
1339        $('body').trigger('added.ajaxProduct');
1340      },
1341  
1342      error: function(XMLHttpRequest) {
1343        this.$form.find('.errors').remove();
1344  
1345        if (XMLHttpRequest.responseJSON && XMLHttpRequest.responseJSON.description) {
1346          console.warn(XMLHttpRequest.responseJSON.description);
1347  
1348          this.$form.prepend('<div class="errors text-center">' + XMLHttpRequest.responseJSON.description + '</div>');
1349        }
1350      }
1351    });
1352  
1353    return ProductForm;
1354  })();
1355  
1356  theme.collapsibles = (function() {
1357  
1358    var selectors = {
1359      trigger: '.collapsible-trigger',
1360      module: '.collapsible-content',
1361      moduleInner: '.collapsible-content__inner'
1362    };
1363  
1364    var classes = {
1365      hide: 'hide',
1366      open: 'is-open',
1367      autoHeight: 'collapsible--auto-height'
1368    };
1369  
1370    var namespace = '.collapsible';
1371  
1372    var isTransitioning = false;
1373  
1374    function init() {
1375      $(selectors.trigger).each(function() {
1376        var $el = $(this);
1377        var state = $el.hasClass(classes.open);
1378        $el.attr('aria-expanded', state);
1379      });
1380  
1381      // Event listeners (hack for modals)
1382      $('body, .modal__inner')
1383        .off(namespace)
1384        .on('click' + namespace, selectors.trigger, function() {
1385        if (isTransitioning) {
1386          return;
1387        }
1388  
1389        isTransitioning = true;
1390  
1391        var $el = $(this);
1392        var isOpen = $el.hasClass(classes.open);
1393        var moduleId = $el.attr('aria-controls');
1394        var $module = $('#' + moduleId);
1395        var height = $module.find(selectors.moduleInner).outerHeight();
1396        var isAutoHeight = $el.hasClass(classes.autoHeight);
1397  
1398        // If isAutoHeight, set the height to 0 just after setting the actual height
1399        // so the closing animation works nicely
1400        if (isOpen && isAutoHeight) {
1401          setTimeout(function() {
1402            height = 0;
1403            setTransitionHeight($module, height, isOpen, isAutoHeight);
1404          }, 0);
1405        }
1406  
1407        if (isOpen && !isAutoHeight) {
1408          height = 0;
1409        }
1410  
1411        $el
1412          .attr('aria-expanded', !isOpen)
1413          .toggleClass(classes.open, !isOpen);
1414  
1415        setTransitionHeight($module, height, isOpen, isAutoHeight);
1416      });
1417    }
1418  
1419    function setTransitionHeight($module, height, isOpen, isAutoHeight) {
1420      $module
1421        .removeClass(classes.hide)
1422        .prepareTransition()
1423        .css('height', height)
1424        .toggleClass(classes.open, !isOpen);
1425  
1426      if (!isOpen && isAutoHeight) {
1427        var o = $module;
1428        window.setTimeout(function() {
1429          o.css('height','auto');
1430          isTransitioning = false;
1431        }, 500);
1432      } else {
1433        isTransitioning = false;
1434      }
1435    }
1436  
1437    return {
1438      init: init
1439    };
1440  })();
1441  
1442  theme.headerNav = (function() {
1443  
1444    var $parent = $(document.documentElement).add('body');
1445    var $page = $('#MainContent');
1446    var selectors = {
1447      wrapper: '.header-wrapper',
1448      siteHeader: '.site-header',
1449      searchBtn: '.js-search-header',
1450      closeSearch: '.js-search-header-close',
1451      searchContainer: '.site-header__search-container',
1452      logoContainer: '.site-header__logo',
1453      logo: '.site-header__logo img',
1454      navigation: '.site-navigation',
1455      navContainerWithLogo: '.header-item--logo',
1456      navItems: '.site-nav__item',
1457      navLinks: '.site-nav__link',
1458      navLinksWithDropdown: '.site-nav__link--has-dropdown',
1459      navDropdownLinks: '.site-nav__dropdown-link--second-level'
1460    };
1461  
1462    var classes = {
1463      hasDropdownClass: 'site-nav--has-dropdown',
1464      hasSubDropdownClass: 'site-nav__deep-dropdown-trigger',
1465      dropdownActive: 'is-focused'
1466    };
1467  
1468    var config = {
1469      namespace: '.siteNav',
1470      wrapperOverlayed: false,
1471      overlayedClass: 'is-light',
1472      overlayEnabledClass: 'header-wrapper--overlay',
1473      stickyEnabled: false,
1474      stickyActive: false,
1475      stickyClass: 'site-header--stuck',
1476      openTransitionClass: 'site-header--opening',
1477      lastScroll: 0
1478    };
1479  
1480    // Elements used in resize functions, defined in init
1481    var $window;
1482    var $navContainerWithLogo;
1483    var $logoContainer;
1484    var $nav;
1485    var $wrapper;
1486    var $siteHeader;
1487  
1488    function init() {
1489      $window = $(window);
1490      $navContainerWithLogo = $(selectors.navContainerWithLogo);
1491      $logoContainer = $(selectors.logoContainer);
1492      $nav = $(selectors.navigation);
1493      $wrapper = $(selectors.wrapper);
1494      $siteHeader = $(selectors.siteHeader);
1495  
1496      config.stickyEnabled = $siteHeader.data('sticky');
1497      if (config.stickyEnabled) {
1498        config.wrapperOverlayed = $wrapper.hasClass(config.overlayedClass);
1499        stickyHeader();
1500      }
1501  
1502      theme.settings.overlayHeader = $siteHeader.data('overlay');
1503      // Disable overlay header if on collection template with no collection image
1504      if (theme.settings.overlayHeader && Shopify.designMode) {
1505        if ($('body').hasClass('template-collection') && !$('.collection-hero').length) {
1506          this.disableOverlayHeader();
1507        }
1508      }
1509  
1510      accessibleDropdowns();
1511      searchDrawer();
1512  
1513      $window.on('load' + config.namespace, resizeLogo);
1514      $window.on('resize' + config.namespace, $.debounce(150, resizeLogo));
1515    }
1516  
1517    function unload() {
1518      $(window).off(config.namespace);
1519      $(selectors.searchBtn).off(config.namespace);
1520      $(selectors.closeSearch).off(config.namespace);
1521      $parent.off(config.namespace);
1522      $(selectors.navLinks).off(config.namespace);
1523      $(selectors.navDropdownLinks).off(config.namespace);
1524    }
1525  
1526    function searchDrawer() {
1527      $(selectors.searchBtn).on('click' + config.namespace, function(evt) {
1528        evt.preventDefault();
1529        openSearchDrawer();
1530      });
1531  
1532      $(selectors.closeSearch).on('click' + config.namespace, function() {
1533        closeSearchDrawer();
1534      });
1535    }
1536  
1537    function openSearchDrawer() {
1538      $(selectors.searchContainer).addClass('is-active');
1539      $parent.addClass('js-drawer-open js-drawer-open--search');
1540  
1541      slate.a11y.trapFocus({
1542        $container: $(selectors.searchContainer),
1543        namespace: 'header_search',
1544        $elementToFocus: $(selectors.searchContainer).find('input')
1545      });
1546  
1547      // If sticky is enabled, scroll to top on mobile when close to it
1548      // so you don't get an invisible search box
1549      if (theme.config.bpSmall && config.stickyEnabled && config.lastScroll < 300) {
1550        window.scrollTo(0,0);
1551      }
1552  
1553      // Bind events
1554      slate.a11y.lockMobileScrolling(config.namespace);
1555  
1556      // Clicking out of container closes it
1557      $page.on('click' + config.namespace, function () {
1558        closeSearchDrawer();
1559        return false;
1560      });
1561  
1562      $parent.on('keyup' + config.namespace, function(evt) {
1563        if (evt.keyCode === 27) {
1564          closeSearchDrawer();
1565        }
1566      });
1567    }
1568  
1569    function closeSearchDrawer() {
1570      // deselect any focused form elements
1571      $(document.activeElement).trigger('blur');
1572  
1573      $parent.removeClass('js-drawer-open js-drawer-open--search').off(config.namespace);
1574      $(selectors.searchContainer).removeClass('is-active');
1575  
1576      slate.a11y.removeTrapFocus({
1577        $container: $(selectors.searchContainer),
1578        namespace: 'header_search'
1579      });
1580  
1581      slate.a11y.unlockMobileScrolling(config.namespace);
1582      $page.off('click' + config.namespace);
1583      $parent.off('keyup' + config.namespace);
1584    }
1585  
1586    function resizeLogo() {
1587      // Using .each() as there can be a reversed logo too
1588      $(selectors.logo).each(function() {
1589        var $el = $(this),
1590            logoWidthOnScreen = $el.width(),
1591            containerWidth = $el.closest('.grid__item').width();
1592        // If image exceeds container, let's make it smaller
1593        if (logoWidthOnScreen > containerWidth) {
1594          $el.css('maxWidth', containerWidth);
1595        }
1596        else {
1597          $el.removeAttr('style');
1598        }
1599      });
1600    }
1601  
1602    function accessibleDropdowns() {
1603      var hasActiveDropdown = false;
1604      var hasActiveSubDropdown = false;
1605      var closeOnClickActive = false;
1606  
1607      // Touch devices open dropdown on first click, navigate to link on second
1608      if (theme.config.isTouch) {
1609        $(selectors.navLinksWithDropdown).on('touchend' + config.namespace, function(evt) {
1610          var $el = $(this);
1611          var $parentItem = $el.parent();
1612          if (!$parentItem.hasClass(classes.dropdownActive)) {
1613            evt.preventDefault();
1614            closeDropdowns();
1615            openFirstLevelDropdown($el);
1616          } else {
1617            window.location.replace($el.attr('href'));
1618          }
1619        });
1620  
1621        $(selectors.navDropdownLinks).on('touchend' + config.namespace, function(evt) {
1622          var $el = $(this);
1623          var $parentItem = $el.parent();
1624  
1625          // Open third level menu or go to link based on active state
1626          if ($parentItem.hasClass(classes.hasSubDropdownClass)) {
1627            if (!$parentItem.hasClass(classes.dropdownActive)) {
1628              evt.preventDefault();
1629              closeThirdLevelDropdown();
1630              openSecondLevelDropdown($el);
1631            } else {
1632              window.location.replace($el.attr('href'));
1633            }
1634          } else {
1635            // No third level nav, go to link
1636            window.location.replace($el.attr('href'));
1637          }
1638        });
1639      }
1640  
1641      // Open/hide top level dropdowns
1642      $(selectors.navLinks).on('focusin mouseover' + config.namespace, function() {
1643        if (hasActiveDropdown) {
1644          closeSecondLevelDropdown();
1645        }
1646  
1647        if (hasActiveSubDropdown) {
1648          closeThirdLevelDropdown();
1649        }
1650  
1651        openFirstLevelDropdown($(this));
1652      });
1653  
1654      // Force remove focus on sitenav links because focus sometimes gets stuck
1655      $(selectors.navLinks).on('mouseleave' + config.namespace, function() {
1656        closeDropdowns();
1657      });
1658  
1659      // Open/hide sub level dropdowns
1660      $(selectors.navDropdownLinks).on('focusin' + config.namespace, function() {
1661        closeThirdLevelDropdown();
1662        openSecondLevelDropdown($(this), true);
1663      });
1664  
1665      // Private dropdown methods
1666      function openFirstLevelDropdown($el) {
1667        var $parentItem = $el.parent();
1668        if ($parentItem.hasClass(classes.hasDropdownClass)) {
1669          $parentItem.addClass(classes.dropdownActive);
1670          hasActiveDropdown = true;
1671        }
1672  
1673        if (!theme.config.isTouch) {
1674          if (!closeOnClickActive) {
1675            var eventType = theme.config.isTouch ? 'touchend' : 'click';
1676            closeOnClickActive = true;
1677            $('body').on(eventType + config.namespace, function() {
1678              closeDropdowns();
1679              $('body').off(config.namespace);
1680              closeOnClickActive = false;
1681            });
1682          }
1683        }
1684      }
1685  
1686      function openSecondLevelDropdown($el, skipCheck) {
1687        var $parentItem = $el.parent();
1688        if ($parentItem.hasClass(classes.hasSubDropdownClass) || skipCheck) {
1689          $parentItem.addClass(classes.dropdownActive);
1690          hasActiveSubDropdown = true;
1691        }
1692      }
1693  
1694      function closeDropdowns() {
1695        closeSecondLevelDropdown();
1696        closeThirdLevelDropdown();
1697      }
1698  
1699      function closeSecondLevelDropdown() {
1700        $(selectors.navItems).removeClass(classes.dropdownActive);
1701      }
1702  
1703      function closeThirdLevelDropdown() {
1704        $(selectors.navDropdownLinks).parent().removeClass(classes.dropdownActive);
1705      }
1706    }
1707  
1708    function stickyHeader() {
1709      config.lastScroll = 0;
1710      $siteHeader.wrap('<div class="site-header-sticky"></div>');
1711  
1712      stickyHeaderHeight();
1713      $window.on('resize' + config.namespace, $.debounce(50, stickyHeaderHeight));
1714      $window.on('scroll' + config.namespace, $.throttle(15, stickyHeaderScroll));
1715  
1716      // This gets messed up in the editor, so here's a fix
1717      if (Shopify && Shopify.designMode) {
1718        setTimeout(function() {
1719          stickyHeaderHeight();
1720        }, 250);
1721      }
1722    }
1723  
1724    function stickyHeaderHeight() {
1725      var $stickyHeader = $('.site-header-sticky').css('height', $siteHeader.outerHeight(true));
1726    }
1727  
1728    function stickyHeaderScroll() {
1729      var scroll = $window.scrollTop();
1730      var threshold = 250;
1731  
1732      if (scroll > threshold) {
1733        if (config.stickyActive) {
1734          return;
1735        }
1736  
1737        config.stickyActive = true;
1738  
1739        $siteHeader.addClass(config.stickyClass);
1740        if (config.wrapperOverlayed) {
1741          $wrapper.removeClass(config.overlayedClass);
1742        }
1743  
1744        // Add open transition class after element is set to fixed
1745        // so CSS animation is applied correctly
1746        setTimeout(function() {
1747          $siteHeader.addClass(config.openTransitionClass);
1748        }, 100);
1749      } else {
1750        if (!config.stickyActive) {
1751          return;
1752        }
1753  
1754        config.stickyActive = false;
1755  
1756        $siteHeader.removeClass(config.openTransitionClass).removeClass(config.stickyClass);
1757        if (config.wrapperOverlayed) {
1758          $wrapper.addClass(config.overlayedClass);
1759        }
1760      }
1761  
1762      config.lastScroll = scroll;
1763    }
1764  
1765    // If the header setting to overlay the menu on the collection image
1766    // is enabled but the collection setting is disabled, we need to undo
1767    // the init of the sticky nav
1768    function disableOverlayHeader() {
1769      $(selectors.wrapper)
1770        .removeClass(config.overlayEnabledClass)
1771        .removeClass(config.overlayedClass);
1772      config.wrapperOverlayed = false;
1773    }
1774  
1775    return {
1776      init: init,
1777      disableOverlayHeader: disableOverlayHeader,
1778      unload: unload
1779    };
1780  })();
1781  
1782  theme.articleImages = (function() {
1783  
1784    var cache = {};
1785  
1786    function init() {
1787      cache.$rteImages = $('.rte--indented-images');
1788  
1789      if (!cache.$rteImages.length) {
1790        return;
1791      }
1792  
1793      $(window).on('load', setImages);
1794    }
1795  
1796    function setImages() {
1797      cache.$rteImages.find('img').each(function() {
1798        var $el = $(this);
1799        var attr = $el.attr('style');
1800  
1801        // Check if undefined or float: none
1802        if (!attr || attr == 'float: none;') {
1803          // Remove grid-breaking styles if image isn't wider than parent
1804          if ($el.width() < cache.$rteImages.width()) {
1805            $el.addClass('rte__no-indent');
1806          }
1807        }
1808      });
1809    }
1810  
1811    return {
1812      init: init
1813    };
1814  })();
1815  
1816  theme.Slideshow = (function() {
1817    this.$slideshow = null;
1818  
1819    var classes = {
1820      next: 'is-next',
1821      init: 'is-init',
1822      wrapper: 'slideshow-wrapper',
1823      slideshow: 'slideshow',
1824      currentSlide: 'slick-current',
1825      pauseButton: 'slideshow__pause',
1826      isPaused: 'is-paused'
1827    };
1828  
1829    function slideshow(el, args) {
1830      this.$slideshow = $(el);
1831      this.$wrapper = this.$slideshow.closest('.' + classes.wrapper);
1832      this.$pause = this.$wrapper.find('.' + classes.pauseButton);
1833  
1834      this.settings = {
1835        accessibility: true,
1836        arrows: args.arrows ? true : false,
1837        dots: args.dots ? true : false,
1838        draggable: true,
1839        touchThreshold: 5,
1840        pauseOnHover: false,
1841        autoplay: this.$slideshow.data('autoplay'),
1842        autoplaySpeed: this.$slideshow.data('speed')
1843      };
1844  
1845      this.$slideshow.on('init', this.init.bind(this));
1846  
1847      this.$slideshow.slick(this.settings);
1848  
1849      this.$pause.on('click', this._togglePause.bind(this));
1850    }
1851  
1852    slideshow.prototype = $.extend({}, slideshow.prototype, {
1853      init: function(event, obj) {
1854        this.$slideshowList = obj.$list;
1855        this.$slickDots = obj.$dots;
1856        this.$allSlides = obj.$slides;
1857        this.slideCount = obj.slideCount;
1858  
1859        this.$slideshow.addClass(classes.init);
1860        this._a11y();
1861        this._clonedLazyloading();
1862      },
1863      destroy: function() {
1864        this.$slideshow.slick('unslick');
1865      },
1866  
1867      // Playback
1868      _play: function() {
1869        this.$slideshow.slick('slickPause');
1870        $(classes.pauseButton).addClass('is-paused');
1871      },
1872      _pause: function() {
1873        this.$slideshow.slick('slickPlay');
1874        $(classes.pauseButton).removeClass('is-paused');
1875      },
1876      _togglePause: function() {
1877        var slideshowSelector = this._getSlideshowId(this.$pause);
1878        if (this.$pause.hasClass(classes.isPaused)) {
1879          this.$pause.removeClass(classes.isPaused);
1880          $(slideshowSelector).slick('slickPlay');
1881        } else {
1882          this.$pause.addClass(classes.isPaused);
1883          $(slideshowSelector).slick('slickPause');
1884        }
1885      },
1886  
1887      // Helpers
1888      _getSlideshowId: function($el) {
1889        return '#Slideshow-' + $el.data('id');
1890      },
1891      _activeSlide: function() {
1892        return this.$slideshow.find('.slick-active');
1893      },
1894      _currentSlide: function() {
1895        return this.$slideshow.find('.slick-current');
1896      },
1897      _nextSlide: function(index) {
1898        return this.$slideshow.find('.slideshow__slide[data-slick-index="' + index + '"]');
1899      },
1900  
1901      // a11y fixes
1902      _a11y: function() {
1903        var $list = this.$slideshowList;
1904        var autoplay = this.settings.autoplay;
1905  
1906        if (!$list) {
1907          return;
1908        }
1909  
1910        // Remove default Slick aria-live attr until slider is focused
1911        $list.removeAttr('aria-live');
1912  
1913        // When an element in the slider is focused
1914        // pause slideshow and set aria-live
1915        $(classes.wrapper).on('focusin', function(evt) {
1916          if (!$(classes.wrapper).has(evt.target).length) {
1917            return;
1918          }
1919  
1920          $list.attr('aria-live', 'polite');
1921          if (autoplay) {
1922            this._pause();
1923          }
1924        }.bind(this));
1925  
1926        // Resume autoplay
1927        $(classes.wrapper).on('focusout', function(evt) {
1928          if (!$(classes.wrapper).has(evt.target).length) {
1929            return;
1930          }
1931  
1932          $list.removeAttr('aria-live');
1933          if (autoplay) {
1934            this._play();
1935          }
1936        }.bind(this));
1937      },
1938  
1939      // Make sure lazyloading works on cloned slides
1940      _clonedLazyloading: function() {
1941        var $slideshow = this.$slideshow;
1942  
1943        $slideshow.find('.slick-slide').each(function(index, el) {
1944          var $slide = $(el);
1945          if ($slide.hasClass('slick-cloned')) {
1946            var slideId = $slide.data('id');
1947            var $slideImg = $slide.find('.hero__image').removeClass('lazyloading').addClass('lazyloaded');
1948  
1949            // Get inline style attribute from non-cloned slide with arbitrary timeout
1950            // so the image is loaded
1951            setTimeout(function() {
1952              var loadedImageStyle = $slideshow.find('.slideshow__slide--' + slideId + ':not(.slick-cloned) .hero__image').attr('style');
1953  
1954              if (loadedImageStyle) {
1955                $slideImg.attr('style', loadedImageStyle);
1956              }
1957  
1958            }, this.settings.autoplaySpeed / 1.5);
1959  
1960          }
1961        }.bind(this));
1962      }
1963    });
1964  
1965    return slideshow;
1966  })();
1967  
1968  theme.announcementBar = (function() {
1969    var selectors = {
1970      bar: '.announcement',
1971      text: '.announcement__text',
1972      closeBtn: '.announcement__close'
1973    };
1974  
1975    var classes = {
1976      opening: 'announcement--opening',
1977      closed: 'announcement--closed'
1978    };
1979  
1980    var config = {
1981      namespace: '.announcementBar'
1982    };
1983  
1984    function init() {
1985      if (!$(selectors.closeBtn).length) {
1986        return;
1987      }
1988  
1989      if (theme.config.hasSessionStorage && sessionStorage[announcementBarText()] !== 'hidden') {
1990        window.setTimeout(function() {
1991          announcementBarShow();
1992        }, 2000);
1993      }
1994  
1995      // Probably in the editor, so show it anyway
1996      if (!theme.config.hasSessionStorage) {
1997        window.setTimeout(function() {
1998          announcementBarShow();
1999        }, 2000);
2000      }
2001  
2002      $(selectors.closeBtn).on('click', function(evt) {
2003        evt.preventDefault();
2004        announcementBarClose();
2005      });
2006  
2007      $(window).on('resize' + config.namespace, $.debounce(150, announcementBarHeight));
2008    }
2009  
2010    function unload() {
2011      $(window).off(config.namespace);
2012    }
2013  
2014    function announcementBarShow() {
2015      $(selectors.bar).removeClass(classes.closed);
2016      announcementBarHeight();
2017    }
2018  
2019    function announcementBarHeight() {
2020      var barHeight = $(selectors.text).outerHeight(true);
2021      $(selectors.bar).addClass(classes.opening).css({'max-height': barHeight});
2022    }
2023  
2024    function announcementBarClose() {
2025      if (theme.config.hasSessionStorage) {
2026        sessionStorage.setItem(announcementBarText(), 'hidden');
2027      }
2028  
2029      $(selectors.bar).addClass(classes.closed);
2030    }
2031  
2032    function announcementBarText() {
2033      return $(selectors.text).data('text');
2034    }
2035  
2036    return {
2037      init: init,
2038      unload: unload
2039    };
2040  })();
2041  
2042  theme.currencySwitcher = (function() {
2043  
2044    var selectors = {
2045      dataDiv: '#CurrencyData',
2046      picker: '.currency-input'
2047    };
2048  
2049    var data = {};
2050  
2051    function init() {
2052      var $dataDiv = $(selectors.dataDiv);
2053  
2054      if (!$dataDiv.length) {
2055        return;
2056      }
2057  
2058      $primaryPicker = $('#CurrencyPicker-header');
2059      $drawerPicker = $('#CurrencyPicker-drawer');
2060  
2061      // Keep all currency pickers in sync
2062      $primaryPicker.on('change', function() {
2063        $drawerPicker.val($(this).val());
2064      });
2065  
2066      $drawerPicker.on('change', function() {
2067        $primaryPicker.val($(this).val());
2068      });
2069  
2070      data = {
2071        currency: $dataDiv.data('shop-currency'),
2072        default: $dataDiv.data('default-currency'),
2073        format: $dataDiv.data('format'),
2074        moneyFormat: $dataDiv.data('money-format'),
2075        moneyCurrencyFormat: $dataDiv.data('money-currency-format')
2076      };
2077  
2078      Currency.format = data.format;
2079  
2080      // Rely on the shop's currency format, not Shopify defaults (in case merchant changes it)
2081      Currency.money_format[data.currency] = data.moneyFormat;
2082      Currency.money_with_currency_format[data.currency] = data.moneyCurrencyFormat;
2083  
2084      // Fix for customer account page
2085      $('span.money span.money').each(function() {
2086        $(this).parents('span.money').removeClass('money');
2087      });
2088  
2089      // Save current price
2090      $('span.money').each(function() {
2091        $(this).attr('data-currency-' + data.currency, $(this).html());
2092      });
2093  
2094      checkCookie();
2095  
2096      $(selectors.picker).val(Currency.currentCurrency).on('change', refresh);
2097    }
2098  
2099    function refresh() {
2100      var newCurrency = $(selectors.picker).val();
2101      Currency.convertAll(Currency.currentCurrency, newCurrency);
2102    }
2103  
2104    function ajaxrefresh() {
2105      var newCurrency = $(selectors.picker).val();
2106      // Ajax cart always returns shop's currency, not what theme settings defines
2107      Currency.convertAll(data.currency, newCurrency);
2108    }
2109  
2110    function checkCookie() {
2111      var cookieCurrency = Currency.cookie.read();
2112  
2113      if (cookieCurrency == null) {
2114        if (data.currency !== data.default) {
2115          Currency.convertAll(data.currency, data.default);
2116        }
2117        else {
2118          Currency.currentCurrency = data.default;
2119        }
2120      } else if ($(selectors.picker).length && $(selectors.picker).find('option[value=' + cookieCurrency + ']').length === 0) {
2121        // If the cookie value does not correspond to any value in the currency dropdown
2122        Currency.currentCurrency = data.currency;
2123        Currency.cookie.write(data.currency);
2124      }
2125      else if (cookieCurrency === data.currency) {
2126        Currency.currentCurrency = data.currency;
2127      }
2128      else {
2129        Currency.convertAll(data.currency, cookieCurrency);
2130      }
2131    }
2132  
2133    return {
2134      init: init,
2135      refresh: refresh,
2136      ajaxrefresh: ajaxrefresh
2137    };
2138  })();
2139  
2140  theme.initQuickShop = function(reinit) {
2141    var ids = [];
2142    var $buttons = $('.quick-product__btn');
2143  
2144    $buttons.each(function() {
2145      var id = $(this).data('product-id');
2146      var modalId = 'QuickShopModal-' + id;
2147      var name = 'quick-modal-' + id;
2148  
2149      // If another identical modal exists, remove duplicates
2150      if (ids.indexOf(id) > -1) {
2151        $('.modal--quick-shop[data-product-id="'+ id +'"]').each(function(i) {
2152          if (i > 0) {
2153            $(this).remove();
2154          }
2155        });
2156        return;
2157      }
2158  
2159      new theme.Modals(modalId, name);
2160      ids.push(id);
2161    });
2162  };
2163  
2164  theme.videoModal = function() {
2165    var videoModalPlayer = null;
2166    var videoOptions = {
2167      width: 1280,
2168      height: 720,
2169      playerVars: {
2170        autohide: 0,
2171        autoplay: 1,
2172        branding: 0,
2173        cc_load_policy: 0,
2174        fs: 0,
2175        iv_load_policy: 3,
2176        modestbranding: 1,
2177        playsinline: 1,
2178        quality: 'hd720',
2179        rel: 0,
2180        showinfo: 0,
2181        wmode: 'opaque'
2182      }
2183    };
2184  
2185    var selectors = {
2186      triggers: 'a[href*="youtube.com/watch"], a[href*="youtu.be/"]'
2187    };
2188  
2189    if (!$(selectors.triggers).length) {
2190      return;
2191    }
2192  
2193    var modal = new theme.Modals('VideoModal', 'video-modal', {
2194      closeOffContentClick: true,
2195      solid: true
2196    });
2197  
2198    $(selectors.triggers).on('click', triggerYouTubeModal);
2199  
2200    function triggerYouTubeModal(evt) {
2201      evt.preventDefault();
2202      window.loadYouTube();
2203  
2204      if (theme.config.youTubeReady) {
2205        startVideoOnClick(evt);
2206      } else {
2207        $('body').on('youTubeReady', function() {
2208          startVideoOnClick(evt);
2209        });
2210      }
2211    }
2212  
2213    function startVideoOnClick(evt) {
2214      var $el = $(evt.currentTarget);
2215      var videoId = getYoutubeVideoId($el.attr('href'));
2216  
2217      var args = $.extend({}, videoOptions, {
2218        videoId: videoId
2219      });
2220  
2221      // Disable plays inline on mobile
2222      args.playerVars.playsinline = theme.config.bpSmall ? 0 : 1;
2223  
2224      var videoModalPlayer = new YT.Player('VideoHolder', args);
2225      modal.open();
2226  
2227      $('body').on('modalClose.VideoModal', function() {
2228        // Slight timeout so it is destroyed after the modal closes
2229        setTimeout(function() {
2230          videoModalPlayer.destroy();
2231        }, 500); // modal close css transition
2232      });
2233    }
2234  
2235    function getYoutubeVideoId(url) {
2236      var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/;
2237      var match = url.match(regExp);
2238      return (match&&match[7].length==11)? match[7] : false;
2239    }
2240  };
2241  
2242  
2243
2244  theme.customerTemplates = (function() {
2245  
2246    function initEventListeners() {
2247      // Show reset password form
2248      $('#RecoverPassword').on('click', function(evt) {
2249        evt.preventDefault();
2250        toggleRecoverPasswordForm();
2251      });
2252  
2253      // Hide reset password form
2254      $('#HideRecoverPasswordLink').on('click', function(evt) {
2255        evt.preventDefault();
2256        toggleRecoverPasswordForm();
2257      });
2258    }
2259  
2260    /**
2261     *
2262     *  Show/Hide recover password form
2263     *
2264     */
2265    function toggleRecoverPasswordForm() {
2266      $('#RecoverPasswordForm').toggleClass('hide');
2267      $('#CustomerLoginForm').toggleClass('hide');
2268    }
2269  
2270    /**
2271     *
2272     *  Show reset password success message
2273     *
2274     */
2275    function resetPasswordSuccess() {
2276      var $formState = $('.reset-password-success');
2277  
2278      // check if reset password form was successfully submitted
2279      if (!$formState.length) {
2280        return;
2281      }
2282  
2283      // show success message
2284      $('#ResetSuccess').removeClass('hide');
2285    }
2286  
2287    /**
2288     *
2289     *  Show/hide customer address forms
2290     *
2291     */
2292    function customerAddressForm() {
2293      var $newAddressForm = $('#AddressNewForm');
2294      var $addressForms = $('.js-address-form');
2295  
2296      if (!$newAddressForm.length || !$addressForms.length) {
2297        return;
2298      }
2299  
2300      if (Shopify) {
2301        $('.js-address-country').each(function() {
2302          var $container = $(this);
2303          var countryId = $container.data('country-id');
2304          var provinceId = $container.data('province-id');
2305          var provinceContainerId = $container.data('province-container-id');
2306  
2307          new Shopify.CountryProvinceSelector(
2308            countryId,
2309            provinceId,
2310            {
2311              hideElement: provinceContainerId
2312            }
2313          );
2314        });
2315      }
2316  
2317      // Toggle new/edit address forms
2318      $('.address-new-toggle').on('click', function() {
2319        $newAddressForm.toggleClass('hide');
2320      });
2321  
2322      $('.address-edit-toggle').on('click', function() {
2323        var formId = $(this).data('form-id');
2324        $('#EditAddress_' + formId).toggleClass('hide');
2325      });
2326  
2327      $('.address-delete').on('click', function() {
2328        var $el = $(this);
2329        var formId = $el.data('form-id');
2330        var confirmMessage = $el.data('confirm-message');
2331  
2332        if (confirm(confirmMessage || 'Are you sure you wish to delete this address?')) {
2333          Shopify.postLink('/account/addresses/' + formId, {parameters: {_method: 'delete'}});
2334        }
2335      });
2336    }
2337  
2338    /**
2339     *
2340     *  Check URL for reset password hash
2341     *
2342     */
2343    function checkUrlHash() {
2344      var hash = window.location.hash;
2345  
2346      // Allow deep linking to recover password form
2347      if (hash === '#recover') {
2348        toggleRecoverPasswordForm();
2349      }
2350    }
2351  
2352    return {
2353      init: function() {
2354        checkUrlHash();
2355        initEventListeners();
2356        resetPasswordSuccess();
2357        customerAddressForm();
2358      }
2359    };
2360  })();
2361  
2362
2363  theme.Product = (function() {
2364  
2365    var classes = {
2366      onSale: 'on-sale',
2367      disabled: 'disabled',
2368      isModal: 'is-modal',
2369      loading: 'loading',
2370      loaded: 'loaded',
2371      interactable: 'video-interactable'
2372    };
2373  
2374    var selectors = {
2375      productVideo: '.product__video',
2376      videoParent: '.product__video-wrapper',
2377      currentSlide: '.slick-current'
2378    };
2379  
2380    var youtubeReady;
2381    var videos = {};
2382    var youtubePlayers = [];
2383    var youtubeVideoOptions = {
2384      height: '480',
2385      width: '850',
2386      playerVars :{
2387        autohide: 0,
2388        autoplay: 1,
2389        branding: 0,
2390        cc_load_policy: 0,
2391        controls: 0,
2392        fs: 0,
2393        iv_load_policy: 3,
2394        modestbranding: 1,
2395        playsinline: 1,
2396        quality: 'hd720',
2397        rel: 0,
2398        showinfo: 0,
2399        wmode: 'opaque'
2400      },
2401      events: {
2402        onReady: onVideoPlayerReady,
2403        onStateChange: onVideoStateChange
2404      }
2405    };
2406  
2407    var vimeoReady;
2408    var vimeoPlayers = [];
2409    var vimeoVideoOptions = {
2410      byline: false,
2411      title: false,
2412      portrait: false,
2413      loop: true
2414    };
2415  
2416    function onVideoPlayerReady(evt) {
2417      var $player = $(evt.target.a);
2418      var playerId = $player.attr('id');
2419      youtubePlayers[playerId] = evt.target; // update stored player
2420      var player = youtubePlayers[playerId];
2421  
2422      setParentAsLoading($player);
2423  
2424      if (videos[playerId].style === 'muted') {
2425        youtubePlayers[playerId].mute().playVideo().pauseVideo();
2426      } else {
2427        setParentAsLoaded($player);
2428      }
2429  
2430      // If first slide or only photo, start video
2431      if ($player.closest(selectors.currentSlide).length || $player.data('image-count') === 1) {
2432        if (videos[playerId].style === 'muted') {
2433          youtubePlayers[playerId].playVideo();
2434          initCheckVisibility(playerId);
2435        }
2436      }
2437    }
2438  
2439    function initCheckVisibility(playerId) {
2440      // Add out of view pausing
2441      videoVisibilityCheck(playerId);
2442      $(window).on('scroll.' + playerId, {id: playerId}, $.throttle(150, videoVisibilityCheck));
2443    }
2444  
2445    function videoVisibilityCheck(id) {
2446      var playerId;
2447  
2448      if (typeof id === 'string') {
2449        playerId = id;
2450      } else {
2451        // Data comes in as part of the scroll event
2452        playerId = id.data.id;
2453      }
2454  
2455      if (theme.isElementVisible($('#' + playerId))) {
2456        if (videos[playerId] && videos[playerId].style === 'unmuted') {
2457          return;
2458        }
2459        playVisibleVideo(playerId);
2460      } else {
2461        pauseHiddenVideo(playerId);
2462      }
2463    }
2464  
2465    function playVisibleVideo(id) {
2466      if (youtubePlayers[id] && typeof youtubePlayers[id].playVideo === 'function') {
2467        youtubePlayers[id].playVideo();
2468      }
2469    }
2470  
2471    function pauseHiddenVideo(id) {
2472      if (youtubePlayers[id] && typeof youtubePlayers[id].pauseVideo === 'function') {
2473        youtubePlayers[id].pauseVideo();
2474      }
2475    }
2476  
2477    function onVideoStateChange(evt) {
2478      var $player = $(evt.target.a);
2479      var playerId = $player.attr('id');
2480      var player = youtubePlayers[playerId];
2481  
2482      switch (evt.data) {
2483        case -1: // unstarted
2484          // Handle low power state on iOS by checking if
2485          // video is reset to unplayed after attempting to buffer
2486          if (videos[playerId].attemptedToPlay) {
2487            setParentAsLoaded($player);
2488            setVideoToBeInteractedWith($player);
2489          }
2490          break;
2491        case 0: // ended
2492          player.playVideo();
2493          break;
2494        case 1: // playing
2495          setParentAsLoaded($player);
2496          break;
2497        case 3: // buffering
2498          videos[playerId].attemptedToPlay = true;
2499          break;
2500      }
2501    }
2502  
2503    function setParentAsLoading($el) {
2504      $el
2505        .closest(selectors.videoParent)
2506        .addClass(classes.loading);
2507    }
2508  
2509    function setParentAsLoaded($el) {
2510      $el
2511        .closest(selectors.videoParent)
2512        .removeClass(classes.loading)
2513        .addClass(classes.loaded);
2514    }
2515  
2516    function setVideoToBeInteractedWith($el) {
2517      $el
2518        .closest(selectors.videoParent)
2519        .addClass(classes.interactable);
2520    }
2521  
2522    function Product(container) {
2523      var $container = this.$container = $(container);
2524      var sectionId = this.sectionId = $container.attr('data-section-id');
2525  
2526      this.inModal = $container.closest('.modal').length;
2527      this.$modal;
2528  
2529      this.settings = {
2530        enableHistoryState: $container.data('enable-history-state') || false,
2531        namespace: '.product-' + sectionId,
2532        inventory: $container.data('inventory') || false,
2533        modalInit: false,
2534        slickMainInitialized: false,
2535        slickThumbInitialized: false,
2536        hasImages: true,
2537        hasMultipleImages: false,
2538        imageSize: '620x'
2539      };
2540  
2541      // Overwrite some settings when loaded in modal
2542      if (this.inModal) {
2543        this.settings.enableHistoryState = false;
2544        this.settings.namespace = '.product-' + sectionId + '-modal';
2545        this.$modal = $('#QuickShopModal-' + sectionId);
2546      }
2547  
2548      this.selectors = {
2549        variantsJson: 'VariantsJson-' + sectionId,
2550        currentVariantJson: 'CurrentVariantJson-' + sectionId,
2551  
2552        video: 'ProductVideo-' + sectionId,
2553        photoThumbs: '.product__thumb-' + sectionId,
2554        thumbSlider: '#ProductThumbs-' + sectionId,
2555        mainSlider: '#ProductPhotos-' + sectionId,
2556        imageContainer: '[data-product-images]',
2557        productImageMain: '.product-image-main--' + sectionId,
2558  
2559        priceWrapper: '.product__price-wrap-' + sectionId,
2560        price: '#ProductPrice-' + sectionId,
2561        comparePrice: '#ComparePrice-' + sectionId,
2562        priceA11y: '#PriceA11y-' + sectionId,
2563        comparePriceA11y: '#ComparePriceA11y-' + sectionId,
2564        sku: '#Sku-' + sectionId,
2565        inventory: '#ProductInventory-' + sectionId,
2566  
2567        addToCart: '#AddToCart-' + sectionId,
2568        addToCartText: '#AddToCartText-' + sectionId,
2569  
2570        originalSelectorId: '#ProductSelect-' + sectionId,
2571        singleOptionSelector: '.variant__input-' + sectionId,
2572        variantColorSwatch: '.variant__input--color-swatch-' + sectionId,
2573  
2574        modalFormHolder: '#ProductFormHolder-' + sectionId,
2575        formContainer: '#AddToCartForm-' + sectionId
2576      };
2577  
2578      this.$mainSlider = $(this.selectors.mainSlider);
2579      this.$thumbSlider = $(this.selectors.thumbSlider);
2580      this.$firstProductImage = this.$mainSlider.find('img').first();
2581  
2582      if (!this.$firstProductImage.length) {
2583        this.settings.hasImages = false;
2584      }
2585  
2586      this.init();
2587    }
2588  
2589    Product.prototype = $.extend({}, Product.prototype, {
2590      init: function() {
2591        if (this.inModal) {
2592          this.$container.addClass(classes.isModal);
2593          $('body')
2594            .off('modalOpen.QuickShopModal-' + this.sectionId)
2595            .off('modalClose.QuickShopModal-' + this.sectionId);
2596          $('body').on('modalOpen.QuickShopModal-' + this.sectionId, this.openModalProduct.bind(this));
2597          $('body').on('modalClose.QuickShopModal-' + this.sectionId, this.closeModalProduct.bind(this));
2598        }
2599  
2600        if (!this.inModal) {
2601          this.stringOverrides();
2602          this.formSetup();
2603          this.productSetup();
2604  
2605          this.checkIfVideos();
2606          this.createImageCarousels();
2607        }
2608      },
2609  
2610      formSetup: function() {
2611        // Determine how to handle variant availability selectors
2612        if (theme.settings.dynamicVariantsEnable) {
2613          if (theme.settings.dynamicVariantType === 'dropdown') {
2614            this.$variantSelectors = {
2615              all: $(this.selectors.formContainer).find('select'),
2616              selected: $(this.selectors.formContainer).find('select')
2617            }
2618          } else {
2619            this.$variantSelectors = {
2620              all: $(this.selectors.formContainer).find('input'),
2621              selected: $(this.selectors.formContainer).find('input:checked')
2622            }
2623          }
2624        }
2625  
2626        this.initQtySelector();
2627        this.initAjaxProductForm();
2628        this.initVariants();
2629      },
2630  
2631      productSetup: function() {
2632        this.setImageSizes();
2633        this.initImageSwitch();
2634        this.initImageZoom();
2635      },
2636  
2637      stringOverrides: function() {
2638        theme.productStrings = theme.productStrings || {};
2639        $.extend(theme.strings, theme.productStrings);
2640      },
2641  
2642      setImageSizes: function() {
2643        if (!this.settings.hasImages) {
2644          return;
2645        }
2646  
2647        // Get srcset image src, works on most modern browsers
2648        // otherwise defaults to settings.imageSize
2649        var currentImage = this.$firstProductImage[0].currentSrc;
2650  
2651        if (currentImage) {
2652          this.settings.imageSize = theme.Images.imageSize(currentImage);
2653        }
2654      },
2655  
2656      initVariants: function() {
2657        if (!document.getElementById(this.selectors.variantsJson)) {
2658          return;
2659        }
2660  
2661        this.variantsObject = JSON.parse(document.getElementById(this.selectors.variantsJson).innerHTML);
2662  
2663        var options = {
2664          $container: this.$container,
2665          enableHistoryState: this.settings.enableHistoryState,
2666          singleOptionSelector: this.selectors.singleOptionSelector,
2667          originalSelectorId: this.selectors.originalSelectorId,
2668          variants: this.variantsObject
2669        };
2670  
2671        if ($(this.selectors.variantColorSwatch).length) {
2672          $(this.selectors.variantColorSwatch).on('change', function(evt) {
2673            var $el = $(evt.currentTarget);
2674            var color = $el.data('color-name');
2675            var index = $el.data('color-index');
2676            this.updateColorName(color, index);
2677          }.bind(this));
2678        }
2679  
2680        this.variants = new slate.Variants(options);
2681        this.$container.on('variantChange' + this.settings.namespace, this.updateCartButton.bind(this));
2682        this.$container.on('variantImageChange' + this.settings.namespace, this.updateVariantImage.bind(this));
2683        this.$container.on('variantPriceChange' + this.settings.namespace, this.updatePrice.bind(this));
2684        if ($(this.selectors.sku).length) {
2685          this.$container.on('variantSKUChange' + this.settings.namespace, this.updateSku.bind(this));
2686        }
2687        if (this.settings.inventory) {
2688          this.$container.on('variantChange' + this.settings.namespace, this.updateInventory.bind(this));
2689        }
2690  
2691        // Update individual variant availability on each selection
2692        if (theme.settings.dynamicVariantsEnable && document.getElementById(this.selectors.currentVariantJson)) {
2693          this.currentVariantObject = JSON.parse(document.getElementById(this.selectors.currentVariantJson).innerHTML);
2694  
2695          this.$variantSelectors.all.on('change' + this.settings.namespace, this.updateV
2695ariantAvailability.bind(this));
2696  
2697          // Set default state based on current selected variant
2698          this.setCurrentVariantAvailability(this.currentVariantObject, true);
2699        }
2700      },
2701  
2702      // Variant change functions
2703      updateColorName: function(color, index) {
2704        // Updates on radio button change, not variant.js
2705        $('#VariantColorLabel-' + this.sectionId + '-' + index).text(color);
2706      },
2707  
2708      setCurrentVariantAvailability: function(variant) {
2709        var valuesToEnable = {
2710          option1: [],
2711          option2: [],
2712          option3: []
2713        };
2714  
2715        // Disable all options to start
2716        this.disableVariantGroup($(this.selectors.formContainer).find('.variant-input-wrap'));
2717  
2718        // Combine all available variants
2719        var availableVariants = this.variantsObject.filter(function(el) {
2720          if (variant.id === el.id) {
2721            return false;
2722          }
2723  
2724          // Option 1
2725          if (variant.option2 === el.option2 && variant.option3 === el.option3) {
2726            return true;
2727          }
2728  
2729          // Option 2
2730          if (variant.option1 === el.option1 && variant.option3 === el.option3) {
2731            return true;
2732          }
2733  
2734          // Option 3
2735          if (variant.option1 === el.option1 && variant.option2 === el.option2) {
2736            return true;
2737          }
2738        });
2739  
2740  
2741        // IE11 can't handle shortform of {variant} so extra step is needed
2742        var variantObject = {
2743          variant: variant
2744        };
2745  
2746        availableVariants = Object.assign({}, variantObject, availableVariants);
2747  
2748        // Loop through each available variant to gather variant values
2749        for (var property in availableVariants) {
2750          if (availableVariants.hasOwnProperty(property)) {
2751            var item = availableVariants[property];
2752            var option1 = item.option1;
2753            var option2 = item.option2;
2754            var option3 = item.option3;
2755  
2756            if (option1) {
2757              if (valuesToEnable.option1.indexOf(option1) === -1) {
2758                valuesToEnable.option1.push(option1);
2759              }
2760            }
2761            if (option2) {
2762              if (valuesToEnable.option2.indexOf(option2) === -1) {
2763                valuesToEnable.option2.push(option2);
2764              }
2765            }
2766            if (option3) {
2767              if (valuesToEnable.option3.indexOf(option3) === -1) {
2768                valuesToEnable.option3.push(option3);
2769              }
2770            }
2771          }
2772        }
2773  
2774        // Have values to enable, separated by option index
2775        if (valuesToEnable.option1.length) {
2776          this.enableVariantOptionByValue(valuesToEnable.option1, 'option1');
2777        }
2778        if (valuesToEnable.option2.length) {
2779          this.enableVariantOptionByValue(valuesToEnable.option2, 'option2');
2780        }
2781        if (valuesToEnable.option3.length) {
2782          this.enableVariantOptionByValue(valuesToEnable.option3, 'option3');
2783        }
2784      },
2785  
2786      updateVariantAvailability: function(evt, value, index) {
2787        if (value && index) {
2788          var newVal = value;
2789          var optionIndex = index;
2790        } else {
2791          var $el = $(evt.currentTarget);
2792          var newVal = $el.val() ? $el.val() : evt.currentTarget.value;
2793          var optionIndex = $el.data('index');
2794        }
2795  
2796        var variants = this.variantsObject.filter(function(el) {
2797          return el[optionIndex] === newVal;
2798        });
2799  
2800        // Disable all buttons/dropdown options that aren't the current index
2801        $(this.selectors.formContainer).find('.variant-input-wrap').each(function(index, el) {
2802          var $group = $(el);
2803          var currentOptionIndex = $group.data('index');
2804  
2805          if (currentOptionIndex !== optionIndex) {
2806            // Disable all options as a starting point
2807            this.disableVariantGroup($group);
2808  
2809            // Loop through legit available options and enable
2810            for (var i = 0; i < variants.length; i++) {
2811              this.enableVariantOption($group, variants[i][currentOptionIndex]);
2812            }
2813          }
2814        }.bind(this));
2815      },
2816  
2817      disableVariantGroup: function($group) {
2818        if (theme.settings.dynamicVariantType === 'dropdown') {
2819          $group.find('option').prop('disabled', true)
2820        } else {
2821          $group.find('input').prop('disabled', true);
2822          $group.find('label').toggleClass('disabled', true);
2823        }
2824      },
2825  
2826      enableVariantOptionByValue: function(array, index) {
2827        var $group = $(this.selectors.formContainer).find('.variant-input-wrap[data-index="'+ index +'"]');
2828  
2829        for (var i = 0; i < array.length; i++) {
2830          this.enableVariantOption($group, array[i]);
2831        }
2832      },
2833  
2834      enableVariantOption: function($group, value) {
2835        // Selecting by value so escape it
2836        value = value.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/@])/g,'\\$1');
2837  
2838        if (theme.settings.dynamicVariantType === 'dropdown') {
2839          $group.find('option[value="'+ value +'"]').prop('disabled', false);
2840        } else {
2841          var $buttonGroup = $group.find('.variant-input[data-value="'+ value +'"]');
2842          $buttonGroup.find('input').prop('disabled', false);
2843          $buttonGroup.find('label').toggleClass('disabled', false);
2844        }
2845      },
2846  
2847      updateCartButton: function(evt) {
2848        var variant = evt.variant;
2849  
2850        if (variant) {
2851          if (variant.available) {
2852            // Available, enable the submit button and change text
2853            $(this.selectors.addToCart).removeClass(classes.disabled).prop('disabled', false);
2854            $(this.selectors.addToCartText).html(theme.strings.addToCart);
2855          } else {
2856            // Sold out, disable the submit button and change text
2857            $(this.selectors.addToCart).addClass(classes.disabled).prop('disabled', true);
2858            $(this.selectors.addToCartText).html(theme.strings.soldOut);
2859          }
2860        } else {
2861          // The variant doesn't exist, disable submit button
2862          $(this.selectors.addToCart).addClass(classes.disabled).prop('disabled', true);
2863          $(this.selectors.addToCartText).html(theme.strings.unavailable);
2864        }
2865      },
2866  
2867      updatePrice: function(evt) {
2868        var variant = evt.variant;
2869  
2870        if (variant) {
2871          // Regular price
2872          $(this.selectors.price).html(theme.Currency.formatMoney(variant.price, theme.settings.moneyFormat)).show();
2873  
2874          // Sale price, if necessary
2875          if (variant.compare_at_price > variant.price) {
2876            $(this.selectors.comparePrice).html(theme.Currency.formatMoney(variant.compare_at_price, theme.settings.moneyFormat));
2877            $(this.selectors.priceWrapper).removeClass('hide');
2878            $(this.selectors.price).addClass(classes.onSale);
2879            $(this.selectors.comparePriceA11y).attr('aria-hidden', 'false');
2880            $(this.selectors.priceA11y).attr('aria-hidden', 'false');
2881          } else {
2882            $(this.selectors.priceWrapper).addClass('hide');
2883            $(this.selectors.price).removeClass(classes.onSale);
2884            $(this.selectors.comparePriceA11y).attr('aria-hidden', 'true');
2885            $(this.selectors.priceA11y).attr('aria-hidden', 'true');
2886          }
2887  
2888          if (theme.settings.currenciesEnabled) {
2889            theme.currencySwitcher.ajaxrefresh();
2890          }
2891        }
2892      },
2893  
2894      updateSku: function(evt) {
2895        var variant = evt.variant;
2896        var newSku = '';
2897  
2898        if (variant) {
2899          if (variant.sku) {
2900            newSku = variant.sku;
2901          }
2902  
2903          $(this.selectors.sku).html(newSku);
2904        }
2905      },
2906  
2907      updateInventory: function(evt) {
2908        var variant = evt.variant;
2909        var showInventory = false;
2910        var quantity = 0;
2911        var $inventoryLabel = $(this.selectors.inventory);
2912  
2913        if (variant && variant.inventory_management === 'shopify') {
2914          showInventory = true;
2915          quantity = window.inventories[this.sectionId][variant.id];
2916  
2917          if (quantity <= 0 || quantity > theme.settings.inventoryThreshold) {
2918            showInventory = false;
2919          }
2920        }
2921  
2922        if (!showInventory) {
2923          $inventoryLabel.addClass('hide');
2924        } else {
2925          $inventoryLabel
2926            .removeClass('hide')
2927            .text(theme.strings.stockLabel.replace('[count]', quantity));
2928        }
2929      },
2930  
2931      updateVariantImage: function(evt) {
2932        var variant = evt.variant;
2933        var sizedImgUrl = theme.Images.getSizedImageUrl(variant.featured_image.src, this.settings.imageSize);
2934  
2935        var $newImage = $('.product__thumb[data-id="' + variant.featured_image.id + '"]');
2936        var imageIndex = this._slideIndex($newImage.closest('.product__thumb-item'));
2937  
2938        // If there is no index, slider is not initalized
2939        if (typeof imageIndex === 'undefined') {
2940          return;
2941        }
2942  
2943        this.$mainSlider.slick('slickGoTo', imageIndex);
2944      },
2945  
2946      // Image/thumbnail toggling
2947      initImageSwitch: function() {
2948        if (!$(this.selectors.photoThumbs).length) {
2949          return;
2950        }
2951  
2952        var self = this;
2953  
2954        $(this.selectors.photoThumbs).on('click', function(evt) {
2955          evt.preventDefault();
2956        });
2957      },
2958  
2959      initImageZoom: function() {
2960        var $container = $(this.selectors.imageContainer, this.$container);
2961        var imageZoom = new theme.Photoswipe($container[0], this.sectionId);
2962      },
2963  
2964      checkIfVideos: function() {
2965        var $productVideos = this.$mainSlider.find(selectors.productVideo);
2966  
2967        // Stop if there are 0 videos
2968        if (!$productVideos.length) {
2969          return false;
2970        }
2971  
2972        var videoTypes = [];
2973  
2974        $productVideos.each(function() {
2975          var type = $(this).data('video-type');
2976  
2977          if (videoTypes.indexOf(type) < 0) {
2978            videoTypes.push(type);
2979          }
2980        });
2981  
2982        // Load YouTube API if not already loaded
2983        if (videoTypes.indexOf('youtube') > -1) {
2984          if (!theme.config.youTubeReady) {
2985            window.loadYouTube();
2986            $('body').on('youTubeReady' + this.settings.namespace, function() {
2987              this.loadYoutubeVideos($productVideos);
2988            }.bind(this));
2989          } else {
2990            this.loadYoutubeVideos($productVideos);
2991          }
2992        }
2993  
2994        // Load Vimeo API if not already loaded
2995        if (videoTypes.indexOf('vimeo') > -1) {
2996          if (!vimeoReady) {
2997            window.loadVimeo();
2998            $('body').on('vimeoReady' + this.settings.namespace, function() {
2999              this.loadVimeoVideos($productVideos);
3000            }.bind(this))
3001          } else {
3002            this.loadVimeoVideos($productVideos);
3003          }
3004        }
3005  
3006        // Add mp4 video players
3007        if (videoTypes.indexOf('mp4') > -1) {
3008          this.loadMp4Videos($productVideos);
3009        }
3010  
3011        return videoTypes;
3012      },
3013  
3014      loadMp4Videos: function($videos) {
3015        $videos.each(function() {
3016          var $el = $(this);
3017          if ($el.data('video-type') != 'mp4') {
3018            return;
3019          }
3020  
3021          var id = $el.attr('id');
3022          var videoId = $el.data('video-id');
3023  
3024          videos[this.id] = {
3025            type: 'mp4',
3026            divId: id,
3027            style: $el.data('video-style')
3028          };
3029        });
3030      },
3031  
3032      loadVimeoVideos: function($videos) {
3033        $videos.each(function() {
3034          var $el = $(this);
3035          if ($el.data('video-type') != 'vimeo') {
3036            return;
3037          }
3038  
3039          var id = $el.attr('id');
3040          var videoId = $el.data('video-id');
3041  
3042          videos[this.id] = {
3043            type: 'vimeo',
3044            divId: id,
3045            id: videoId,
3046            style: $el.data('video-style'),
3047            width: $el.data('video-width'),
3048            height: $el.data('video-height')
3049          };
3050        });
3051  
3052        // Create a new player for each Vimeo video
3053        for (var key in videos) {
3054          if (videos[key].type != 'vimeo') {
3055            continue;
3056          }
3057  
3058          var args = $.extend({}, vimeoVideoOptions, videos[key]);
3059          vimeoPlayers[key] = new Vimeo.Player(videos[key].divId, args);
3060        }
3061  
3062        vimeoReady = true;
3063      },
3064  
3065      autoplayVimeoVideo: function(id) {
3066        // Do not autoplay on mobile though
3067        if (!theme.config.bpSmall) {
3068          this.requestToPlayVimeoVideo(id);
3069        } else {
3070          // Set as loaded on mobile so you can see the image
3071          var $player = $('#' + id);
3072          setParentAsLoaded($player);
3073        }
3074      },
3075  
3076      requestToPlayVimeoVideo: function(id) {
3077        // The slider may initialize and attempt to play the video before
3078        // the API is even ready, because it sucks.
3079  
3080        var $player = $('#' + id);
3081        setParentAsLoading($player);
3082  
3083        if (!vimeoReady) {
3084          // Wait for the trigger, then play it
3085          $('body').on('vimeoReady' + this.settings.namespace, function() {
3086            this.playVimeoVideo(id);
3087          }.bind(this))
3088          return;
3089        }
3090  
3091        this.playVimeoVideo(id);
3092      },
3093  
3094      playVimeoVideo: function(id) {
3095        vimeoPlayers[id].play();
3096  
3097        if (videos[id].style === 'muted') {
3098          vimeoPlayers[id].setVolume(0);
3099        }
3100  
3101        var $player = $('#' + id);
3102        setParentAsLoaded($player);
3103      },
3104  
3105      stopVimeoVideo: function(id) {
3106        if (!theme.config.vimeoReady) {
3107          return;
3108        }
3109  
3110        if (id) {
3111          vimeoPlayers[id].pause();
3112        } else {
3113          for (key in vimeoPlayers) {
3114            if (typeof vimeoPlayers[key].pause === 'function') {
3115              vimeoPlayers[key].pause();
3116            }
3117          }
3118        }
3119      },
3120  
3121      loadYoutubeVideos: function($videos) {
3122        $videos.each(function() {
3123          var $el = $(this);
3124          if ($el.data('video-type') != 'youtube') {
3125            return;
3126          }
3127  
3128          var id = $el.attr('id');
3129          var videoId = $el.data('youtube-id');
3130  
3131          videos[this.id] = {
3132            type: 'youtube',
3133            id: id,
3134            videoId: videoId,
3135            style: $el.data('video-style'),
3136            width: $el.data('video-width'),
3137            height: $el.data('video-height'),
3138            attemptedToPlay: false
3139          };
3140        });
3141  
3142        // Create a player for each YouTube video
3143        for (var key in videos) {
3144          if (videos[key].type === 'youtube') {
3145            if (videos.hasOwnProperty(key)) {
3146              var args = $.extend({}, youtubeVideoOptions, videos[key]);
3147  
3148              if (args.style === 'muted') {
3149                // default youtubeVideoOptions, no need to change anything
3150              } else {
3151                args.playerVars.controls = 1;
3152                args.playerVars.autoplay = 0;
3153              }
3154  
3155              youtubePlayers[key] = new YT.Player(key, args);
3156            }
3157          }
3158        }
3159  
3160        youtubeReady = true;
3161      },
3162  
3163      requestToPlayYoutubeVideo: function(id, forcePlay) {
3164        if (!theme.config.youTubeReady) {
3165          return;
3166        }
3167  
3168        var $player = $('#' + id);
3169        setParentAsLoading($player);
3170  
3171        // If video is requested too soon, player might not be ready.
3172        // Set arbitrary timeout to request it again in a second
3173        if (typeof youtubePlayers[id].playVideo != 'function') {
3174          var o = this;
3175          setTimeout(function() {
3176            o.playYoutubeVideo(id, forcePlay);
3177          }, 1000);
3178          return;
3179        }
3180  
3181        this.playYoutubeVideo(id, forcePlay);
3182      },
3183  
3184      playYoutubeVideo: function (id, forcePlay) {
3185        var $player = $('#' + id);
3186        setParentAsLoaded($player);
3187        if (typeof youtubePlayers[id].playVideo === 'function') {
3188          youtubePlayers[id].playVideo();
3189        }
3190  
3191        // forcePlay is sent as true from beforeSlideChange so the visibility
3192        // check isn't fooled by the next slide positioning
3193        if (!forcePlay) {
3194          initCheckVisibility(id);
3195        }
3196      },
3197  
3198      stopYoutubeVideo: function(id) {
3199        if (!theme.config.youTubeReady) {
3200          return;
3201        }
3202  
3203        if (id && youtubePlayers[id]) {
3204          if (typeof youtubePlayers[id].pauseVideo === 'function') {
3205            youtubePlayers[id].pauseVideo();
3206          }
3207          $(window).off('scroll.' + id);
3208        } else {
3209          for (key in youtubePlayers) {
3210            if (typeof youtubePlayers[key].pauseVideo === 'function') {
3211              youtubePlayers[key].pauseVideo();
3212              $(window).off('scroll.' + key);
3213            }
3214          }
3215        }
3216      },
3217  
3218      playMp4Video: function(id) {
3219        var $player = $('#' + id);
3220        setParentAsLoaded($player);
3221  
3222        $player[0].play();
3223      },
3224  
3225      stopMp4Video: function(id) {
3226        if (id) {
3227          $('#' + id)[0].pause();
3228        } else {
3229          // loop through all mp4 videos to stop them
3230          for (var key in videos) {
3231            if (videos[key].type === 'mp4') {
3232              var player = $('#' + videos[key].divId)[0];
3233              if (typeof player.pause === 'function') {
3234                player.pause();
3235              }
3236            }
3237          }
3238        }
3239      },
3240  
3241      createImageCarousels: function() {
3242        if (!this.$thumbSlider.length || $(this.selectors.photoThumbs).length < 2) {
3243          // Single product image. Init video if it exists
3244          var $video = $(this.selectors.productImageMain).find(selectors.productVideo);
3245          if ($video.length) {
3246            this.initVideo($video);
3247          }
3248  
3249          return;
3250        }
3251  
3252        this.settings.hasMultipleImages = true;
3253  
3254        // Set starting slide (for both sliders)
3255        var $activeSlide = this.$mainSlider.find('.starting-slide');
3256        var startIndex = this._slideIndex($activeSlide);
3257  
3258        // Lame way to prevent duplicate event listeners
3259        this.$mainSlider.off('init');
3260        this.$mainSlider.off('beforeChange');
3261        this.$mainSlider.on('init', this.mainSlideInit.bind(this));
3262        this.$mainSlider.on('beforeChange', this.beforeSlideChange.bind(this));
3263        this.$thumbSlider.on('init', this.thumbSlideInit.bind(this));
3264  
3265        var thumbnailsVertical = this.$thumbSlider.data('position') === 'beside' ? true : false;
3266  
3267        this.$mainSlider.slick({
3268          adaptiveHeight: thumbnailsVertical ? false : true,
3269          asNavFor: this.selectors.thumbSlider,
3270          infinite: false,
3271          arrows: false,
3272          dots: false,
3273          initialSlide: startIndex
3274        });
3275  
3276        this.$thumbSlider.slick({
3277          asNavFor: this.selectors.mainSlider,
3278          slidesToShow: thumbnailsVertical ? 3 : 5,
3279          slidesToScroll: 1,
3280          arrows: false,
3281          dots: false,
3282          vertical: thumbnailsVertical,
3283          verticalSwiping: thumbnailsVertical,
3284          focusOnSelect: true,
3285          infinite: false,
3286          customHeightMatching: thumbnailsVertical,
3287          customSlideAdvancement: true,
3288          initialSlide: startIndex
3289        });
3290  
3291        // Too many thumbnails can cause the AOS calculations to be off
3292        // so refresh that when the slider is ready
3293        if (AOS) {
3294          AOS.refresh();
3295        }
3296      },
3297  
3298      destroyImageCarousels: function() {
3299        if (this.$mainSlider && this.settings.slickMainInitialized) {
3300          this.$mainSlider.slick('unslick');
3301          this.settings.slickMainInitialized = false;
3302        }
3303  
3304        if (this.$thumbSlider && this.settings.slickThumbInitialized) {
3305          this.$thumbSlider.slick('unslick');
3306          this.settings.slickThumbInitialized = false;
3307        }
3308  
3309        this.settings.slickMainInitialized = false;
3310        this.settings.slickThumbInitialized = false;
3311      },
3312  
3313      mainSlideInit: function(event, slick) {
3314        var $slider = slick.$slider;
3315        var $currentSlide = $slider.find(selectors.currentSlide);
3316        var $video = $currentSlide.find(selectors.productVideo);
3317  
3318        this.settings.slickMainInitialized = true;
3319  
3320        if (!$video.length) {
3321          return;
3322        }
3323  
3324        this.initVideo($video);
3325      },
3326  
3327      thumbSlideInit: function(event, slick) {
3328        this.settings.slickThumbInitialized = true;
3329      },
3330  
3331      initVideo: function($video) {
3332        var videoType = $video.data('video-type');
3333        var divId = $video.attr('id');
3334  
3335        if (videoType === 'mp4' && videos[divId].style === 'muted') {
3336          this.playMp4Video(divId);
3337        }
3338  
3339        if (videoType === 'youtube') {
3340          if (youtubeReady && videos[divId].style === 'muted') {
3341            this.requestToPlayYoutubeVideo(divId);
3342          }
3343        }
3344  
3345        if (videoType === 'vimeo') {
3346          if (vimeoReady) {
3347            this.playOrShowVimeo(divId);
3348          } else {
3349            $('body').on('vimeoReady' + this.settings.namespace, function() {
3350              this.playOrShowVimeo(divId);
3351            }.bind(this))
3352          }
3353        }
3354  
3355        // Hacky way to trigger resetting the slider layout in modals
3356        if (this.inModal) {
3357          this.resizeSlides();
3358        }
3359      },
3360  
3361      stopVideo: function(id, type) {
3362        if (!id) {
3363          this.stopYoutubeVideo();
3364          this.stopVimeoVideo();
3365          this.stopMp4Video();
3366        }
3367  
3368        if (type === 'youtube') {
3369          this.stopYoutubeVideo(id);
3370        }
3371  
3372        if (type === 'mp4') {
3373          this.stopMp4Video(id);
3374        }
3375  
3376        if (type === 'vimeo') {
3377          this.stopVimeoVideo(id);
3378        }
3379      },
3380  
3381      playOrShowVimeo: function(id) {
3382        if (videos[id] && videos[id].style === 'muted') {
3383          this.autoplayVimeoVideo(id);
3384        } else if (videos[id] && videos[id].style === 'unmuted') {
3385          setParentAsLoaded($('#' + id));
3386        }
3387      },
3388  
3389      getVideoType: function($video) {
3390        return $video.data('video-type');
3391      },
3392  
3393      getVideoId: function($video) {
3394        return $video.attr('id');
3395      },
3396  
3397      beforeSlideChange: function(event, slick, currentSlide, nextSlide) {
3398        var $slider = slick.$slider;
3399        var $currentSlide = $slider.find(selectors.currentSlide);
3400        var $prevVideo = $currentSlide.find('.product__video');
3401        var $nextSlide = $slider.find('.slick-slide[data-slick-index="' + nextSlide + '"]');
3402        var $nextVideo = $nextSlide.find('.product__video');
3403  
3404        // Pause any existing slide video
3405        if (currentSlide !== nextSlide && $prevVideo.length) {
3406          var prevVideoType = this.getVideoType($prevVideo);
3407          var prevVideoId = this.getVideoId($prevVideo);
3408  
3409          if (prevVideoId) {
3410            this.stopVideo(prevVideoId, prevVideoType);
3411          }
3412        }
3413  
3414        // Prep next slide video
3415        if ($nextVideo.length) {
3416          var nextVideoType = this.getVideoType($nextVideo);
3417          var nextVideoId = this.getVideoId($nextVideo);
3418  
3419          // Prep Vimeo with a backup in case the API isn't ready
3420          if (nextVideoId && nextVideoType === 'vimeo') {
3421            if (vimeoReady) {
3422              if (videos[nextVideoId] && videos[nextVideoId].style === 'muted') {
3423                this.autoplayVimeoVideo(nextVideoId);
3424              }
3425            } else {
3426              $('body').on('vimeoReady' + this.settings.namespace, function() {
3427                if (videos[nextVideoId] && videos[nextVideoId].style === 'muted') {
3428                  this.autoplayVimeoVideo(nextVideoId);
3429                }
3430              }.bind(this))
3431            }
3432          }
3433  
3434          // Prep YouTube with a backup in case API isn't ready
3435          if (nextVideoId && nextVideoType === 'youtube') {
3436            if (youtubeReady) {
3437              if (videos[nextVideoId] && videos[nextVideoId].style === 'muted') {
3438                this.requestToPlayYoutubeVideo(nextVideoId, true);
3439              }
3440            } else {
3441              $('body').on('youTubeReady' + this.settings.namespace, function() {
3442                if (videos[nextVideoId] && videos[nextVideoId].style === 'muted') {
3443                  this.requestToPlayYoutubeVideo(nextVideoId, true);
3444                }
3445              }.bind(this))
3446            }
3447          }
3448  
3449          // Autoplay muted MP4 videos
3450          if (nextVideoId && videos[nextVideoId] && videos[nextVideoId].style === 'muted') {
3451            if (nextVideoType === 'mp4') {
3452              this.playMp4Video(nextVideoId);
3453            }
3454          }
3455  
3456          // Set unmuted videos to loaded state
3457          if (nextVideoId && videos[nextVideoId] && videos[nextVideoId].style != 'muted') {
3458            setParentAsLoaded($('#' + nextVideoId));
3459          }
3460        }
3461      },
3462  
3463      resizeSlides: function() {
3464        if (!this.settings.hasMultipleImages) {
3465          return;
3466        }
3467  
3468        $(window).trigger('resize.slick');
3469        setTimeout(function() {
3470          if (this.$mainSlider && this.settings.slickMainInitialized) {
3471            this.$mainSlider.slick('setPosition');
3472          }
3473          if (this.$thumbSlider && this.settings.slickThumbInitialized) {
3474            this.$thumbSlider.slick('setPosition');
3475          }
3476        }.bind(this), 500); // same timing as modal open transition
3477      },
3478  
3479      _slideIndex: function($el) {
3480        return $el.data('index');
3481      },
3482  
3483      initQtySelector: function() {
3484        this.$container.find('.js-qty__wrapper').each(function() {
3485          new theme.QtySelector($(this), {
3486            namespace: '.product'
3487          });
3488        });
3489      },
3490  
3491      initAjaxProductForm: function() {
3492        if (theme.settings.cartType === 'drawer') {
3493          new theme.AjaxProduct($(this.selectors.formContainer));
3494        }
3495      },
3496  
3497      openModalProduct: function() {
3498        if (!this.settings.modalInit) {
3499          var $formHolder = $(this.selectors.modalFormHolder);
3500          var url = $formHolder.data('url');
3501          url = url + '?view=ajax';
3502  
3503          $formHolder.load(url + ' #AddToCartForm-' + this.sectionId, function() {
3504            this.formSetup();
3505            if (Shopify.PaymentButton) {
3506              Shopify.PaymentButton.init();
3507            }
3508          }.bind(this));
3509  
3510          this.productSetup();
3511          this.loadModalContent();
3512          this.createImageCarousels();
3513          this.settings.modalInit = true;
3514        }
3515  
3516        this.resizeSlides();
3517      },
3518  
3519      closeModalProduct: function() {
3520        this.stopVideo();
3521        $('body').off(this.settings.namespace);
3522        $(window).off(this.settings.namespace);
3523      },
3524  
3525      loadModalContent: function() {
3526        // Load videos if they exist
3527        var videoTypes = this.checkIfVideos();
3528  
3529        // Lazyload mp4 videos similar to images
3530        if (videoTypes && videoTypes.indexOf('mp4') > -1) {
3531          this.$modal.find('.product__video[data-video-type="mp4"]').each(function(i, video) {
3532            var $el = $(video);
3533            var src = $el.data('video-src');
3534            var source = document.createElement('source');
3535            source.setAttribute('src', src);
3536            $el.append(source);
3537          }.bind(this));
3538        }
3539      },
3540  
3541      onUnload: function() {
3542        this.$container.off(this.settings.namespace);
3543        $('body').off(this.settings.namespace);
3544        this.destroyImageCarousels();
3545      }
3546    });
3547  
3548    return Product;
3549  })();
3550  
3551  theme.Recommendations = (function() {
3552  
3553    function Recommendations(container) {
3554      var $container = this.$container = $(container);
3555      var sectionId = this.sectionId = $container.attr('data-section-id');
3556  
3557      this.selectors = {
3558        recommendations: '#Recommendations-' + sectionId,
3559        placeholder: '.product-recommendations-placeholder',
3560        sectionClass: ' .product-recommendations',
3561        productResults: '.grid-product'
3562      };
3563  
3564      this.init();
3565    }
3566  
3567    Recommendations.prototype = $.extend({}, Recommendations.prototype, {
3568      init: function() {
3569        var $section = $(this.selectors.recommendations);
3570  
3571        if (!$section.length || $section.data('enable') === false) {
3572          return;
3573        }
3574  
3575        var $placeholder = $section.find(this.selectors.placeholder);
3576        var id = $section.data('product-id');
3577        var limit = $section.data('limit');
3578  
3579        var url = '/recommendations/products?section_id=product-recommendations&limit='+ limit +'&product_id=' + id;
3580  
3581        $placeholder.load(url + this.selectors.sectionClass, function(data) {
3582          theme.reinitProductGridItem($section);
3583          this.updateVariantInventory($section);
3584  
3585          // If no results, hide the entire section
3586          if ($(data).find(this.selectors.sectionClass).find(this.selectors.productResults).length === 0) {
3587            $section.addClass('hide');
3588          }
3589        }.bind(this));
3590      },
3591  
3592      // Recommended products load via JS and don't add variant inventory to the
3593      // global variable that we later check. This function scrapes a data div
3594      // to get that info and manually add the values.
3595      updateVariantInventory: function($section) {
3596        window.inventories = window.inventories || {};
3597  
3598        $section.find('.product__inventory-data').each(function() {
3599          var $el = $(this);
3600          var sectionId = $el.data('section-id');
3601          window.inventories[sectionId] = {};
3602          $.each($el.data(), function(variant, qty) {
3603            window.inventories[sectionId][variant] = qty;
3604          })
3605        });
3606      }
3607    });
3608  
3609    return Recommendations;
3610  })();
3611  
3612  theme.Collection = (function() {
3613    var isAnimating = false;
3614  
3615    var classes = {
3616      tags: '.tags',
3617      activeTag: 'tag--active'
3618    };
3619  
3620    function Collection(container) {
3621      this.container = container;
3622      this.sectionId = $(container).attr('data-section-id');
3623      this.namespace = '.collection-' + this.sectionId;
3624  
3625      var $heroImageContainer = this.$heroContainer = $(container).find('.collection-hero');
3626      if ($heroImageContainer.length) {
3627        this.checkIfNeedReload();
3628        theme.loadImageSection($heroImageContainer);
3629      } else if (theme.settings.overlayHeader) {
3630        theme.headerNav.disableOverlayHeader();
3631      }
3632  
3633      // Ajax pagination
3634      $(window).on('popstate', function(state) {
3635        if (state) {
3636          this.getNewCollectionContent(location.href);
3637        }
3638      }.bind(this));
3639  
3640      this.init();
3641    }
3642  
3643    Collection.prototype = $.extend({}, Collection.prototype, {
3644      init: function() {
3645        // init is called on load and when tags are selected
3646        this.$container = $(this.container);
3647        this.sectionId = this.$container.attr('data-section-id');
3648  
3649        this.sortBy();
3650        this.sortTags();
3651        this.initTagAjax();
3652      },
3653  
3654      initTagAjax: function() {
3655        this.$container.on('click' + this.namespace, '.tags a', function(evt) {
3656          var $el = $(evt.currentTarget);
3657          if ($el.hasClass('no-ajax')) {
3658            return;
3659          }
3660  
3661          evt.preventDefault();
3662  
3663          if (isAnimating) {
3664            return;
3665          }
3666  
3667          isAnimating = true;
3668  
3669          var newUrl = $el.attr('href');
3670          $(classes.tags).find('.' + classes.activeTag).removeClass(classes.activeTag);
3671          $el.parent().addClass(classes.activeTag);
3672          history.pushState({}, '', newUrl);
3673          $('.grid-product').addClass('unload');
3674          this.getNewCollectionContent(newUrl);
3675        }.bind(this));
3676      },
3677  
3678      getNewCollectionContent: function(url) {
3679        url = url + '?view=ajax';
3680        $('#CollectionAjaxResult').load(url + ' #CollectionAjaxContent', function() {
3681          isAnimating = false;
3682          this.reInit();
3683        }.bind(this));
3684      },
3685  
3686      sortBy: function() {
3687        var $sortBy = $('#SortBy');
3688  
3689        if (!$sortBy.length) {
3690          return;
3691        }
3692  
3693        $sortBy.on('change', function() {
3694          location.href = '?sort_by=' + $(this).val();
3695        });
3696      },
3697  
3698      sortTags: function() {
3699        var $sortTags = $('#SortTags');
3700  
3701        if (!$sortTags.length) {
3702          return;
3703        }
3704  
3705        $sortTags.on('change', function() {
3706          location.href = $(this).val();
3707        });
3708      },
3709  
3710      reInit: function() {
3711        // Reload collection template (it can be anywhere in the sections.instance array)
3712        for (var i = 0; i < sections.instances.length; i++) {
3713          var instance = sections.instances[i];
3714          if (instance['type'] === 'collection-template') {
3715            instance.forceReload();
3716          }
3717        }
3718  
3719        theme.reinitProductGridItem();
3720      },
3721  
3722      forceReload: function() {
3723        this.onUnload();
3724        this.init();
3725      },
3726  
3727      // A liquid variable in the header needs a full page refresh
3728      // if the collection header hero image setting is enabled
3729      // and the header is set to sticky. Only necessary in the editor.
3730      checkIfNeedReload: function() {
3731        if (!Shopify.designMode) {
3732          return;
3733        }
3734  
3735        if (theme.settings.overlayHeader) {
3736          if (!$('.header-wrapper').hasClass('header-wrapper--overlay')) {
3737            location.reload();
3738          }
3739        }
3740      },
3741  
3742      onUnload: function() {
3743        $(window).off(this.namespace);
3744        this.$container.off(this.namespace);
3745      }
3746  
3747    });
3748  
3749    return Collection;
3750  })();
3751  
3752  theme.CollectionSwitcher = (function() {
3753    var selectors = {
3754      trigger: '.collection-switcher__trigger',
3755      collection: '.collection-switcher__collection',
3756      slider: '.collection-switcher__collection-grid'
3757    };
3758  
3759    function CollectionSwitcher(container) {
3760      this.$container = $(container);
3761      this.sectionId = this.$container.attr('data-section-id');
3762  
3763      this.config = {
3764        perRow: this.$container.attr('data-per-row'),
3765        perRowMobile: 2 // fixed always
3766      };
3767  
3768      this.sliderArgs = {
3769        arrows: true,
3770        infinite: false,
3771        slidesToShow: this.config.perRow,
3772        slidesToScroll: this.config.perRow
3773      };
3774  
3775      this.$activeSlider = null;
3776  
3777      this.init();
3778    }
3779  
3780    CollectionSwitcher.prototype = $.extend({}, CollectionSwitcher.prototype, {
3781      init: function() {
3782        this.$triggers = this.$container.find(selectors.trigger);
3783        this.$collections = this.$container.find(selectors.collection);
3784  
3785        var $firstSlider = this.$container.find(selectors.slider).first();
3786        if ($firstSlider.length) {
3787          this.initSlider($firstSlider);
3788  
3789          $('body').on('matchSmall unmatchSmall', function() {
3790            this.initSlider();
3791          }.bind(this));
3792        }
3793  
3794        this.$triggers.on('click', this.switch.bind(this));
3795      },
3796  
3797      initSlider: function($slider) {
3798        if (this.$activeSlider) {
3799          this.$activeSlider
3800            .off('afterChange')
3801            .slick('unslick');
3802        }
3803  
3804        if (!$slider) {
3805          $slider = this.$activeSlider;
3806        }
3807  
3808        if (!$slider.length) {
3809          return;
3810        }
3811  
3812        // Mobile-specific per-row setting
3813        if (theme.config.bpSmall) {
3814          this.sliderArgs.slidesToShow = this.config.perRowMobile;
3815          this.sliderArgs.slidesToScroll = this.config.perRowMobile;
3816        } else {
3817          this.sliderArgs.slidesToShow = parseInt(this.config.perRow);
3818          this.sliderArgs.slidesToScroll = parseInt(this.config.perRow);
3819        }
3820  
3821        this.$activeSlider = $slider.slick(this.sliderArgs);
3822  
3823        if (AOS) {
3824          AOS.refresh();
3825        }
3826      },
3827  
3828      switch: function(evt) {
3829        evt.preventDefault();
3830  
3831        this.$triggers.removeClass('is-active');
3832  
3833        var containerId = $(evt.currentTarget).addClass('is-active').attr('aria-controls');
3834        var $container = $('#' + containerId);
3835        var $newSlider = $container.find(selectors.slider);
3836  
3837        // Hide collections, show new one
3838        this.$collections.addClass('hide');
3839        $container.removeClass('hide');
3840  
3841        this.initSlider($newSlider);
3842      },
3843  
3844      onUnload: function() {
3845  
3846      }
3847  
3848    });
3849  
3850    return CollectionSwitcher;
3851  })();
3852  
3853  theme.HeaderSection = (function() {
3854  
3855    var selectors = {
3856      drawer: '#NavDrawer',
3857      mobileSubNavToggle: '.mobile-nav__toggle-btn',
3858      hasSublist: '.mobile-nav__has-sublist'
3859    };
3860  
3861    var classes = {
3862      navExpanded: 'mobile-nav--expanded'
3863    };
3864  
3865    function Header(container) {
3866      var $container = this.$container = $(container);
3867      var sectionId = this.sectionId = $container.attr('data-section-id');
3868  
3869      // Reload any slideshow if when the header is reloaded to make sure the
3870      // sticky header works as expected (it can be anywhere in the sections.instance array)
3871      for (var i = 0; i < sections.instances.length; i++) {
3872        var instance = sections.instances[i];
3873        if (instance['type'] === 'slideshow-section') {
3874          instance.forceReload();
3875        }
3876      }
3877  
3878      theme.currencySwitcher.init()
3879      this.initDrawers();
3880      theme.headerNav.init();
3881      theme.announcementBar.init();
3882    }
3883  
3884    Header.prototype = $.extend({}, Header.prototype, {
3885      initDrawers: function() {
3886        theme.NavDrawer = new theme.Drawers('NavDrawer', 'nav');
3887        if (theme.settings.cartType === 'drawer') {
3888          new theme.CartDrawer();
3889        }
3890  
3891        this.drawerMenuButtons();
3892      },
3893  
3894      drawerMenuButtons: function() {
3895        $(selectors.drawer).find('.js-drawer-close').on('click', function(evt){
3896          evt.preventDefault();
3897          theme.NavDrawer.close();
3898        });
3899  
3900        var $mobileSubNavToggle = $(selectors.mobileSubNavToggle);
3901  
3902        $mobileSubNavToggle.attr('aria-expanded', 'false');
3903        $mobileSubNavToggle.each(function (i, el) {
3904          var $el = $(el);
3905          $el.attr('aria-controls', $el.attr('data-aria-controls'));
3906        });
3907  
3908        $mobileSubNavToggle.on('click', function() {
3909          var $el = $(this);
3910          var currentlyExpanded = $el.attr('aria-expanded');
3911          var toggleState = false;
3912  
3913          // Updated aria-expanded value based on state pre-click
3914          if (currentlyExpanded === 'true') {
3915            $el.attr('aria-expanded', 'false');
3916          } else {
3917            $el.attr('aria-expanded', 'true');
3918            toggleState = true;
3919          }
3920  
3921          // Toggle class that expands/collapses sublist
3922          $el.closest(selectors.hasSublist).toggleClass(classes.navExpanded, toggleState);
3923        });
3924      },
3925  
3926      onUnload: function() {
3927        theme.NavDrawer.close();
3928        theme.headerNav.unload();
3929        theme.announcementBar.unload();
3930      }
3931    });
3932  
3933    return Header;
3934  
3935  })();
3936  
3937  theme.FeaturedContentSection = (function() {
3938  
3939    function FeaturedContent() {
3940      $('.rte').find('a:not(:has(img))').addClass('text-link');
3941    }
3942  
3943    return FeaturedContent;
3944  })();
3945  
3946  theme.slideshows = {};
3947  
3948  theme.SlideshowSection = (function() {
3949  
3950    function SlideshowSection(container) {
3951      var $container = this.$container = $(container);
3952      var $section = $container.parent();
3953      var sectionId = $container.attr('data-section-id');
3954      var slideshow = this.slideshow = '#Slideshow-' + sectionId;
3955  
3956      var $imageContainer = $(container).find('.hero');
3957      if ($imageContainer.length) {
3958        theme.loadImageSection($imageContainer);
3959      }
3960  
3961      this.init();
3962    }
3963  
3964    SlideshowSection.prototype = $.extend({}, SlideshowSection.prototype, {
3965      init: function() {
3966        var args = {
3967          arrows: $(this.slideshow).data('arrows'),
3968          dots: $(this.slideshow).data('dots')
3969        };
3970  
3971        theme.slideshows[this.slideshow] = new theme.Slideshow(this.slideshow, args);
3972      },
3973  
3974      forceReload: function() {
3975        this.onUnload();
3976        this.init();
3977      },
3978  
3979      onUnload: function() {
3980        theme.slideshows[this.slideshow].destroy();
3981        delete theme.slideshows[this.slideshow];
3982      },
3983  
3984      onSelect: function() {
3985        $(this.slideshow).slick('slickPause');
3986      },
3987  
3988      onDeselect: function() {
3989        $(this.slideshow).slick('slickPlay');
3990      },
3991  
3992      onBlockSelect: function(evt) {
3993        var $slideshow = $(this.slideshow);
3994  
3995        // Ignore the cloned version
3996        var $slide = $('.slideshow__slide--' + evt.detail.blockId + ':not(.slick-cloned)');
3997        var slideIndex = $slide.data('slick-index');
3998  
3999        // Go to selected slide, pause autoplay
4000        $slideshow.slick('slickGoTo', slideIndex).slick('slickPause');
4001      },
4002  
4003      onBlockDeselect: function() {
4004        $(this.slideshow).slick('slickPlay');
4005      }
4006    });
4007  
4008    return SlideshowSection;
4009  })();
4010  
4011  theme.VideoSection = (function() {
4012    var youtubeReady;
4013    var videos = [];
4014    var youtubePlayers = [];
4015    var youtubeVideoOptions = {
4016      width: 1280,
4017      height: 720,
4018      playerVars: {
4019        autohide: 0,
4020        branding: 0,
4021        cc_load_policy: 0,
4022        controls: 0,
4023        fs: 0,
4024        iv_load_policy: 3,
4025        modestbranding: 1,
4026        playsinline: 1,
4027        quality: 'hd720',
4028        rel: 0,
4029        showinfo: 0,
4030        wmode: 'opaque'
4031      },
4032      events: {
4033        onReady: onVideoPlayerReady,
4034        onStateChange: onVideoStateChange
4035      }
4036    };
4037  
4038    var vimeoReady = false;
4039    var vimeoVideoOptions = {
4040      byline: false,
4041      title: false,
4042      portrait: false,
4043      loop: true
4044    };
4045  
4046    var selectors = {
4047      videoParent: '.video-parent-section'
4048    };
4049  
4050    var classes = {
4051      loading: 'loading',
4052      loaded: 'loaded',
4053      interactable: 'video-interactable'
4054    };
4055  
4056    function videoSection(container) {
4057      var $container = this.$container = $(container);
4058      var sectionId = this.sectionId = $container.attr('data-section-id');
4059      var youtubePlayerId = this.youtubePlayerId = 'YouTubeVideo-' + this.sectionId;
4060      this.namespace = '.' + youtubePlayerId;
4061      var vimeoPlayerId = this.vimeoPlayerId = 'Vimeo-' + this.sectionId;
4062      var $vimeoTrigger = this.$vimeoTrigger = $('#VimeoTrigger-' + this.sectionId);
4063      var mp4Video = 'Mp4Video-' + this.sectionId;
4064  
4065      var $youtubeDiv = $('#' + youtubePlayerId);
4066      var $vimeoDiv = $('#' + vimeoPlayerId);
4067      var $mp4Div = $('#' + mp4Video);
4068  
4069      this.vimeoPlayer = [];
4070  
4071      if ($youtubeDiv.length) {
4072        this.youtubeVideoId = $youtubeDiv.data('video-id');
4073        this.initYoutubeVideo();
4074      }
4075  
4076      if ($vimeoDiv.length) {
4077        this.vimeoVideoId = $vimeoDiv.data('video-id');
4078        this.initVimeoVideo();
4079      }
4080  
4081      if ($mp4Div.length) {
4082        setParentAsLoaded($mp4Div);
4083  
4084        startMp4Playback(mp4Video).then(function() {
4085          // Video played as expected, don't do anything
4086        }).catch(function(error) {
4087          // Video cannot be played with autoplay, so let
4088          // user interact with video element itself
4089          setVideoToBeInteractedWith($mp4Div);
4090        })
4091      }
4092    }
4093  
4094    function startMp4Playback(mp4Video) {
4095      return document.querySelector('#' + mp4Video).play();
4096    }
4097  
4098    function onVideoPlayerReady(evt) {
4099      var $player = $(evt.target.a);
4100      var playerId = $player.attr('id');
4101      youtubePlayers[playerId] = evt.target; // update stored player
4102      var player = youtubePlayers[playerId];
4103  
4104      setParentAsLoading($player);
4105  
4106      youtubePlayers[playerId].mute();
4107  
4108      // Remove from tabindex because YouTube iframes are annoying and you can focus
4109      // on the YouTube logo and it breaks
4110      $player.attr('tabindex', '-1');
4111  
4112      // Add out of view pausing
4113      videoVisibilityCheck(playerId);
4114      $(window).on('scroll.' + playerId, {id: playerId}, $.throttle(150, videoVisibilityCheck));
4115    }
4116  
4117    function videoVisibilityCheck(id) {
4118      var playerId;
4119  
4120      if (typeof id === 'string') {
4121        playerId = id;
4122      } else {
4123        // Data comes in as part of the scroll event
4124        playerId = id.data.id;
4125      }
4126  
4127      if (theme.isElementVisible($('#' + playerId))) {
4128        playVisibleVideo(playerId);
4129      } else {
4130        pauseHiddenVideo(playerId);
4131      }
4132    }
4133  
4134    function playVisibleVideo(id) {
4135      if (youtubePlayers[id] && typeof youtubePlayers[id].playVideo === 'function') {
4136        youtubePlayers[id].playVideo();
4137      }
4138    }
4139  
4140    function pauseHiddenVideo(id) {
4141      if (youtubePlayers[id] && typeof youtubePlayers[id].pauseVideo === 'function') {
4142        youtubePlayers[id].pauseVideo();
4143      }
4144    }
4145  
4146    function onVideoStateChange(evt) {
4147      var $player = $(evt.target.a);
4148      var playerId = $player.attr('id');
4149      var player = youtubePlayers[playerId];
4150  
4151      switch (evt.data) {
4152        case -1: // unstarted
4153          // Handle low power state on iOS by checking if
4154          // video is reset to unplayed after attempting to buffer
4155          if (videos[playerId].attemptedToPlay) {
4156            setParentAsLoaded($player);
4157            setVideoToBeInteractedWith($player);
4158          }
4159          break;
4160        case 0: // ended
4161          player.playVideo();
4162          break;
4163        case 1: // playing
4164          setParentAsLoaded($player);
4165          break;
4166        case 3: // buffering
4167          videos[playerId].attemptedToPlay = true;
4168          break;
4169      }
4170    }
4171  
4172    function setParentAsLoading($el) {
4173      $el
4174        .closest(selectors.videoParent)
4175        .addClass(classes.loading);
4176    }
4177  
4178    function setParentAsLoaded($el) {
4179      $el
4180        .closest(selectors.videoParent)
4181        .removeClass(classes.loading)
4182        .addClass(classes.loaded);
4183    }
4184  
4185    function setVideoToBeInteractedWith($el) {
4186      $el
4187        .closest(selectors.videoParent)
4188        .addClass(classes.interactable);
4189    }
4190  
4191    videoSection.prototype = $.extend({}, videoSection.prototype, {
4192      initYoutubeVideo: function() {
4193        videos[this.youtubePlayerId] = {
4194          id: this.youtubePlayerId,
4195          videoId: this.youtubeVideoId,
4196          type: 'youtube',
4197          attemptedToPlay: false
4198        };
4199  
4200        if (!youtubeReady) {
4201          window.loadYouTube();
4202          $('body').on('youTubeReady' + this.namespace, this.loadYoutubeVideo.bind(this));
4203        } else {
4204          this.loadYoutubeVideo();
4205        }
4206      },
4207  
4208      loadYoutubeVideo: function() {
4209        var args = $.extend({}, youtubeVideoOptions, videos[this.youtubePlayerId]);
4210        args.playerVars.controls = 0;
4211        youtubePlayers[this.youtubePlayerId] = new YT.Player(this.youtubePlayerId, args);
4212  
4213        youtubeReady = true;
4214      },
4215  
4216      initVimeoVideo: function() {
4217        videos[this.vimeoPlayerId] = {
4218          divId: this.vimeoPlayerId,
4219          id: this.vimeoVideoId,
4220          type: 'vimeo'
4221        };
4222  
4223        var $player = $('#' + this.vimeoPlayerId);
4224        setParentAsLoading($player);
4225  
4226        // Button to play video on mobile
4227        this.$vimeoTrigger.on('click', + this.namespace, function(evt) {
4228          // $(evt.currentTarget).addClass('hide');
4229          this.requestToPlayVimeoVideo(this.vimeoPlayerId);
4230        }.bind(this));
4231  
4232        if (!vimeoReady) {
4233          window.loadVimeo();
4234          $('body').on('vimeoReady' + this.namespace, this.loadVimeoVideo.bind(this));
4235        } else {
4236          this.loadVimeoVideo();
4237        }
4238      },
4239  
4240      loadVimeoVideo: function() {
4241        var args = $.extend({}, vimeoVideoOptions, videos[this.vimeoPlayerId]);
4242        this.vimeoPlayer[this.vimeoPlayerId] = new Vimeo.Player(videos[this.vimeoPlayerId].divId, args);
4243  
4244        vimeoReady = true;
4245  
4246        // Only autoplay on larger screens
4247        if (!theme.config.bpSmall) {
4248          this.requestToPlayVimeoVideo(this.vimeoPlayerId);
4249        } else {
4250          var $player = $('#' + this.vimeoPlayerId);
4251          setParentAsLoaded($player);
4252        }
4253      },
4254  
4255      requestToPlayVimeoVideo: function(id) {
4256        // The slider may initialize and attempt to play the video before
4257        // the API is even ready, because it sucks.
4258  
4259        if (!vimeoReady) {
4260          // Wait for the trigger, then play it
4261          $('body').on('vimeoReady' + this.namespace, function() {
4262            this.playVimeoVideo(id);
4263          }.bind(this))
4264          return;
4265        }
4266  
4267        this.playVimeoVideo(id);
4268      },
4269  
4270      playVimeoVideo: function(id) {
4271        this.vimeoPlayer[id].play();
4272        this.vimeoPlayer[id].setVolume(0);
4273  
4274        var $player = $('#' + id);
4275        setParentAsLoaded($player);
4276      },
4277  
4278      onUnload: function(evt) {
4279        var sectionId = evt.target.id.replace('shopify-section-', '');
4280        var playerId = 'YouTubeVideo-' + sectionId;
4281        youtubePlayers[playerId].destroy();
4282        $(window).off('scroll' + this.namespace);
4283        $('body').off('vimeoReady' + this.namespace);
4284      }
4285    });
4286  
4287    return videoSection;
4288  })();
4289  
4290  theme.Testimonials = (function() {
4291    var slideCount = 0;
4292    var defaults = {
4293      accessibility: true,
4294      arrows: false,
4295      dots: true,
4296      autoplay: false,
4297      touchThreshold: 20,
4298      slidesToShow: 3,
4299      slidesToScroll: 3
4300    };
4301  
4302    function Testimonials(container) {
4303      var $container = this.$container = $(container);
4304      var sectionId = $container.attr('data-section-id');
4305      var wrapper = this.wrapper = '.testimonials-wrapper';
4306      var slider = this.slider = '#Testimonials-' + sectionId;
4307      var $slider = $(slider);
4308  
4309      this.sliderActive = false;
4310      var mobileOptions = $.extend({}, defaults, {
4311        slidesToShow: 1,
4312        slidesToScroll: 1,
4313        adaptiveHeight: true
4314      });
4315  
4316      slideCount = $slider.data('count');
4317  
4318      // Override slidesToShow/Scroll if there are not enough blocks
4319      if (slideCount < defaults.slidesToShow) {
4320        defaults.slidesToShow = slideCount;
4321        defaults.slidesToScroll = slideCount;
4322      }
4323  
4324      $slider.on('init', this.a11y.bind(this));
4325  
4326      if (theme.config.bpSmall) {
4327        this.init($slider, mobileOptions);
4328      } else {
4329        this.init($slider, defaults);
4330      }
4331  
4332      $('body').on('matchSmall', function() {
4333        this.init($slider, mobileOptions);
4334      }.bind(this));
4335  
4336      $('body').on('unmatchSmall', function() {
4337        this.init($slider, defaults);
4338      }.bind(this));
4339    }
4340  
4341    Testimonials.prototype = $.extend({}, Testimonials.prototype, {
4342      onUnload: function() {
4343        $(this.slider, this.wrapper).slick('unslick');
4344      },
4345  
4346      onBlockSelect: function(evt) {
4347        // Ignore the cloned version
4348        var $slide = $('.testimonials-slide--' + evt.detail.blockId + ':not(.slick-cloned)');
4349        var slideIndex = $slide.data('slick-index');
4350  
4351        // Go to selected slide, pause autoplay
4352        $(this.slider, this.wrapper).slick('slickGoTo', slideIndex);
4353      },
4354  
4355      init: function(obj, args) {
4356        if (this.sliderActive) {
4357          obj.slick('unslick');
4358          this.sliderActive = false;
4359        }
4360  
4361        obj.slick(args);
4362        this.sliderActive = true;
4363  
4364        if (AOS) {
4365          AOS.refresh();
4366        }
4367      },
4368  
4369      a11y: function(event, obj) {
4370        var $list = obj.$list;
4371        var $wrapper = $(this.wrapper, this.$container);
4372  
4373        // Remove default Slick aria-live attr until slider is focused
4374        $list.removeAttr('aria-live');
4375  
4376        // When an element in the slider is focused set aria-live
4377        $wrapper.on('focusin', function(evt) {
4378          if ($wrapper.has(evt.target).length) {
4379            $list.attr('aria-live', 'polite');
4380          }
4381        });
4382  
4383        // Remove aria-live
4384        $wrapper.on('focusout', function(evt) {
4385          if ($wrapper.has(evt.target).length) {
4386            $list.removeAttr('aria-live');
4387          }
4388        });
4389      }
4390    });
4391  
4392    return Testimonials;
4393  })();
4394  
4395  theme.Instagram = (function() {
4396    var isInit = false;
4397  
4398    function Instagram(container) {
4399      var $container = this.$container = $(container);
4400      var sectionId = $container.attr('data-section-id');
4401      this.namespace = '.instagram-' + sectionId;
4402      this.$target = $('#Instafeed-' + sectionId);
4403  
4404      if (!this.$target.length) {
4405        return;
4406      }
4407  
4408      this.checkVisibility();
4409      $(window).on('scroll' + this.namespace, $.throttle(100, this.checkVisibility.bind(this)));
4410    }
4411  
4412    Instagram.prototype = $.extend({}, Instagram.prototype, {
4413      checkVisibility: function() {
4414        if (isInit) {
4415          $(window).off('scroll' + this.namespace);
4416          return;
4417        }
4418  
4419        if (theme.isElementVisible(this.$container)) {
4420          this.init();
4421        }
4422      },
4423  
4424      init: function() {
4425        isInit = true;
4426  
4427        var userId = this.$target.data('user-id');
4428        var clientId = this.$target.data('client-id');
4429        var count = parseInt(this.$target.data('count'));
4430  
4431        // Ask for 2 more images than we'll actually show because
4432        // Instagram sometimes doesn't send enough
4433        var feed = this.feed = new Instafeed({
4434          target: this.$target[0],
4435          accessToken: clientId,
4436          get: 'user',
4437          userId: userId,
4438          limit: count + 2,
4439          template: '<div class="grid__item small--one-half medium-up--one-fifth"><div class="image-wrap"><a href="{{link}}" target="_blank" style="background-image: url({{image}}); display: block; padding-bottom: 100%; background-size: cover; background-position: center;"></a></div></div>',
4440          resolution: 'standard_resolution'
4441        });
4442  
4443        feed.run();
4444  
4445        this.$target.addClass('instagram-holder--active');
4446      }
4447    });
4448  
4449    return Instagram;
4450  })();
4451  
4452  
4453  theme.NewsletterPopup = (function() {
4454    function NewsletterPopup(container) {
4455      var $container = this.$container = $(container);
4456      var sectionId = $container.attr('data-section-id');
4457      this.cookieName = 'newsletter-' + sectionId;
4458  
4459      if (!$container.length) {
4460        return;
4461      }
4462  
4463      // Prevent popup on Shopify robot challenge page
4464      if (window.location.pathname === '/challenge') {
4465        return;
4466      }
4467  
4468      this.data = {
4469        secondsBeforeShow: $container.data('delay-seconds'),
4470        daysBeforeReappear: $container.data('delay-days'),
4471        cookie: Cookies.get(this.cookieName),
4472        testMode: $container.data('test-mode')
4473      };
4474  
4475      this.modal = new theme.Modals('NewsletterPopup-' + sectionId, 'newsletter-popup-modal');
4476  
4477      // Open modal if errors or success message exist
4478      if ($container.find('.errors').length || $container.find('.note--success').length) {
4479        this.modal.open();
4480      }
4481  
4482      // Set cookie as opened if success message
4483      if ($container.find('.note--success').length) {
4484        this.closePopup(true);
4485        return;
4486      }
4487  
4488      $('body').on('modalClose.' + $container.attr('id'), this.closePopup.bind(this));
4489  
4490      if (!this.data.cookie || this.data.testMode) {
4491        this.initPopupDelay();
4492      }
4493    }
4494  
4495    NewsletterPopup.prototype = $.extend({}, NewsletterPopup.prototype, {
4496      initPopupDelay: function() {
4497        setTimeout(function() {
4498          this.modal.open();
4499        }.bind(this), this.data.secondsBeforeShow * 1000);
4500      },
4501  
4502      closePopup: function(success) {
4503        // Remove a cookie in case it was set in test mode
4504        if (this.data.testMode) {
4505          Cookies.remove(this.cookieName, { path: '/' });
4506          return;
4507        }
4508  
4509        var expiry = success ? 200 : this.data.daysBeforeReappear;
4510  
4511        Cookies.set(this.cookieName, 'opened', { path: '/', expires: expiry });
4512      },
4513  
4514      onLoad: function() {
4515        this.modal.open();
4516      },
4517  
4518      onSelect: function() {
4519        this.modal.open();
4520      },
4521  
4522      onDeselect: function() {
4523        this.modal.close();
4524      },
4525  
4526      onUnload: function() {}
4527    });
4528  
4529    return NewsletterPopup;
4530  })();
4531  
4532  theme.FadingImages = (function() {
4533  
4534    var classes = {
4535      activeImage: 'active-image',
4536      finishedImage: 'finished-image',
4537      activeTitles: 'active-titles',
4538      finishedTitles: 'finished-titles',
4539      compensation: 'compensation'
4540    };
4541  
4542    function FadingImages(container) {
4543      var $container = this.$container = $(container);
4544      var sectionId = $container.attr('data-section-id');
4545      var namespace = this.namespace = '.fading-images-' + sectionId;
4546  
4547      if (!$container.length) {
4548        return;
4549      }
4550  
4551      var $imageContainer = $container.find('.fading-images');
4552      theme.loadImageSection($imageContainer);
4553  
4554      this.data = {
4555        isInit: false,
4556        interval: $container.data('interval'),
4557        block_count: $container.data('count'),
4558        finish_interval: 1000,
4559        timer_offset: 400,
4560        active_image: 1,
4561        active_title: 1,
4562        removed_compensation: false
4563      };
4564  
4565      this.selectors = {
4566        $allTitles: $container.find('.fading-images-overlay__titles')
4567      };
4568  
4569      this.checkVisibility();
4570      $(window).on('scroll' + this.namespace, $.throttle(100, this.checkVisibility.bind(this)));
4571    }
4572  
4573    FadingImages.prototype = $.extend({}, FadingImages.prototype, {
4574      checkVisibility: function() {
4575        if (this.data.isInit) {
4576          $(window).off('scroll' + this.namespace);
4577          return;
4578        }
4579  
4580        if (theme.isElementVisible(this.$container)) {
4581          this.startImages();
4582          this.startTitles();
4583          this.data.isInit = true;
4584        }
4585      },
4586  
4587      nextImage: function() {
4588        var $container = this.$container;
4589  
4590        if (!this.data.removed_compensation) {
4591          $container.find('.fading-images__item[data-slide-index=' + this.data.active_image + ']').removeClass(classes.compensation);
4592          this.data.removed_compensation = true;
4593        }
4594  
4595        $container
4596          .find('.fading-images__item[data-slide-index=' + this.data.active_image + ']')
4597          .removeClass(classes.activeImage)
4598          .addClass(classes.finishedImage);
4599  
4600        var target_image = this.data.active_image;
4601        window.setTimeout(function() {
4602          $container.find('.fading-images__item[data-slide-index=' + target_image + ']').removeClass(classes.finishedImage);
4603        }, this.data.finish_interval);
4604  
4605        this.data.active_image++;
4606        if (this.data.active_image > this.data.block_count) {
4607          this.data.active_image = 1;
4608        }
4609  
4610        $container.find('.fading-images__item[data-slide-index=' + this.data.active_image + ']').addClass(classes.activeImage);
4611      },
4612  
4613      nextTitle: function() {
4614        var $container = this.$container;
4615        var $allTitles = this.selectors.$allTitles;
4616  
4617        this.selectors.$allTitles.removeClass(classes.activeTitles).addClass(classe
4617s.finishedTitles);
4618  
4619        this.data.active_title++;
4620        if (this.data.active_title > this.data.block_count) {
4621          this.data.active_title = 1;
4622        }
4623  
4624        var target_title = this.data.active_title;
4625        window.setTimeout(function() {
4626          var newText1 = $container.find('.fading-images__item[data-slide-index=' + target_title + ']').attr('data-slide-title1');
4627          var newText2 = $container.find('.fading-images__item[data-slide-index=' + target_title + ']').attr('data-slide-title2');
4628          $container.find('.fading-images-overlay__title--1').text(newText1);
4629          $container.find('.fading-images-overlay__title--2').text(newText2);
4630          $allTitles.removeClass(classes.finishedTitles).addClass(classes.activeTitles);
4631        }, this.data.finish_interval - 200);
4632      },
4633  
4634      startImages: function() {
4635        // Prep and show first image
4636        this.$container.find('.fading-images__item[data-slide-index=' + this.data.active_image + ']').addClass(classes.activeImage).addClass(classe
4636s.compensation);
4637  
4638        // Begin timer
4639        var o = this;
4640        window.setTimeout(function() {
4641          var fading_images_timer = window.setInterval(o.nextImage.bind(o), o.data.interval);
4642        }, this.data.timer_offset);
4643      },
4644  
4645      startTitles: function() {
4646        var $container = this.$container;
4647        var $allTitles = this.selectors.$allTitles;
4648        // Prep and show first titles
4649        var target_title = this.data.active_title;
4650        window.setTimeout(function() {
4651          var newText1 = $container.find('.fading-images__item[data-slide-index=' + target_title + ']').attr('data-slide-title1');
4652          var newText2 = $container.find('.fading-images__item[data-slide-index=' + target_title + ']').attr('data-slide-title2');
4653          $container.find('.fading-images-overlay__title--1').text(newText1);
4654          $container.find('.fading-images-overlay__title--2').text(newText2);
4655          $allTitles.addClass(classes.activeTitles);
4656        }, 750);
4657  
4658        // Begin timer
4659        var fading_titles_timer = window.setInterval(this.nextTitle.bind(this), this.data.interval);
4660      },
4661  
4662      onLoad: function() {
4663      },
4664  
4665      onSelect: function() {
4666      },
4667  
4668      onDeselect: function() {
4669      },
4670  
4671      onUnload: function() {
4672      }
4673    });
4674  
4675    return FadingImages;
4676  })();
4677  
4678  theme.Maps = (function() {
4679    var config = {
4680      zoom: 14
4681    };
4682    var apiStatus = null;
4683    var mapsToLoad = [];
4684  
4685    var errors = {
4686      addressNoResults: theme.strings.addressNoResults,
4687      addressQueryLimit: theme.strings.addressQueryLimit,
4688      addressError: theme.strings.addressError,
4689      authError: theme.strings.authError
4690    };
4691  
4692    var selectors = {
4693      section: '[data-section-type="map"]',
4694      map: '[data-map]',
4695      mapOverlay: '[data-map-overlay]'
4696    };
4697  
4698    var classes = {
4699      mapError: 'map-section--load-error',
4700      errorMsg: 'map-section__error errors text-center'
4701    };
4702  
4703    // Global function called by Google on auth errors.
4704    // Show an auto error message on all map instances.
4705    window.gm_authFailure = function() {
4706      if (!Shopify.designMode) {
4707        return;
4708      }
4709  
4710      $(selectors.section).addClass(classes.mapError);
4711      $(selectors.map).remove();
4712      $(selectors.mapOverlay).after(
4713        '<div class="' +
4714          classes.errorMsg +
4715          '">' +
4716          theme.strings.authError +
4717          '</div>'
4718      );
4719    };
4720  
4721    function Map(container) {
4722      this.$container = $(container);
4723      this.sectionId = this.$container.attr('data-section-id');
4724      this.namespace = '.map-' + this.sectionId;
4725      this.$map = this.$container.find(selectors.map);
4726      this.key = this.$map.data('api-key');
4727  
4728      if (!this.key) {
4729        return;
4730      }
4731  
4732      // Lazyload API
4733      this.checkVisibility();
4734      $(window).on('scroll' + this.namespace, $.throttle(50, this.checkVisibility.bind(this)));
4735    }
4736  
4737    function initAllMaps() {
4738      // API has loaded, load all Map instances in queue
4739      $.each(mapsToLoad, function(index, instance) {
4740        instance.createMap();
4741      });
4742    }
4743  
4744    function geolocate($map) {
4745      var deferred = $.Deferred();
4746      var geocoder = new google.maps.Geocoder();
4747      var address = $map.data('address-setting');
4748  
4749      geocoder.geocode({ address: address }, function(results, status) {
4750        if (status !== google.maps.GeocoderStatus.OK) {
4751          deferred.reject(status);
4752        }
4753  
4754        deferred.resolve(results);
4755      });
4756  
4757      return deferred;
4758    }
4759  
4760    Map.prototype = $.extend({}, Map.prototype, {
4761      prepMapApi: function() {
4762        if (apiStatus === 'loaded') {
4763          this.createMap();
4764        } else {
4765          mapsToLoad.push(this);
4766  
4767          if (apiStatus !== 'loading') {
4768            apiStatus = 'loading';
4769            if (typeof window.google === 'undefined' || typeof window.google.maps === 'undefined' ) {
4770              $.getScript(
4771                'https://maps.googleapis.com/maps/api/js?key=' + this.key
4772              ).then(function() {
4773                apiStatus = 'loaded';
4774                initAllMaps();
4775              });
4776            }
4777          }
4778        }
4779      },
4780  
4781      createMap: function() {
4782        var $map = this.$map;
4783  
4784        return geolocate($map)
4785          .then(
4786            function(results) {
4787              var mapOptions = {
4788                zoom: config.zoom,
4789                backgroundColor: 'none',
4790                center: results[0].geometry.location,
4791                draggable: false,
4792                clickableIcons: false,
4793                scrollwheel: false,
4794                disableDoubleClickZoom: true,
4795                disableDefaultUI: true
4796              };
4797  
4798              var map = (this.map = new google.maps.Map($map[0], mapOptions));
4799              var center = (this.center = map.getCenter());
4800  
4801              var marker = new google.maps.Marker({
4802                map: map,
4803                position: map.getCenter()
4804              });
4805  
4806              google.maps.event.addDomListener(
4807                window,
4808                'resize',
4809                $.debounce(250, function() {
4810                  google.maps.event.trigger(map, 'resize');
4811                  map.setCenter(center);
4812                  $map.removeAttr('style');
4813                })
4814              );
4815            }.bind(this)
4816          )
4817          .fail(function() {
4818            var errorMessage;
4819  
4820            switch (status) {
4821              case 'ZERO_RESULTS':
4822                errorMessage = errors.addressNoResults;
4823                break;
4824              case 'OVER_QUERY_LIMIT':
4825                errorMessage = errors.addressQueryLimit;
4826                break;
4827              case 'REQUEST_DENIED':
4828                errorMessage = errors.authError;
4829                break;
4830              default:
4831                errorMessage = errors.addressError;
4832                break;
4833            }
4834  
4835            // Show errors only to merchant in the editor.
4836            if (Shopify.designMode) {
4837              $map
4838                .parent()
4839                .addClass(classes.mapError)
4840                .html(
4841                  '<div class="' +
4842                    classes.errorMsg +
4843                    '">' +
4844                    errorMessage +
4845                    '</div>'
4846                );
4847            }
4848          });
4849      },
4850  
4851      checkVisibility: function() {
4852        if (theme.isElementVisible(this.$container, 600)) {
4853          this.prepMapApi();
4854          $(window).off(this.namespace);
4855        }
4856      },
4857  
4858      onUnload: function() {
4859        if (this.$map.length === 0) {
4860          return;
4861        }
4862        // Causes a harmless JS error when a section without an active map is reloaded
4863        google.maps.event.clearListeners(this.map, 'resize');
4864      }
4865    });
4866  
4867    return Map;
4868  })();
4869  
4870  theme.Blog = (function() {
4871  
4872    function Blog(container) {
4873      this.tagFilters();
4874    }
4875  
4876    Blog.prototype = $.extend({}, Blog.prototype, {
4877      tagFilters: function() {
4878        var $filterBy = $('#BlogTagFilter');
4879  
4880        if (!$filterBy.length) {
4881          return;
4882        }
4883  
4884        $filterBy.on('change', function() {
4885          location.href = $(this).val();
4886        });
4887      },
4888  
4889      onUnload: function() {
4890  
4891      }
4892    });
4893  
4894    return Blog;
4895  })();
4896  
4897  theme.Photoswipe = (function() {
4898    var selectors = {
4899      trigger: '.js-photoswipe__zoom',
4900      images: '.photoswipe__image',
4901      activeImage: '.slick-active .photoswipe__image'
4902    };
4903  
4904    function Photoswipe(container, sectionId) {
4905      this.$container = $(container);
4906      this.sectionId = sectionId;
4907      this.namespace = '.photoswipe-' + this.sectionId;
4908      this.gallery;
4909      this.$images;
4910      this.inSlideshow = false;
4911  
4912      if (this.$container.attr('data-zoom') === 'false') {
4913        return;
4914      }
4915  
4916      if (this.$container.attr('data-has-slideshow') === 'true') {
4917        this.inSlideshow = true;
4918      }
4919  
4920      this.init();
4921    }
4922  
4923    Photoswipe.prototype = $.extend({}, Photoswipe.prototype, {
4924      init: function() {
4925        var $trigger = this.$container.find(selectors.trigger);
4926        this.$images = this.$container.find(selectors.images);
4927        var items = this.getImageData();
4928  
4929        // Init gallery on active image
4930        $trigger.on('click' + this.namespace, function(evt) {
4931          if (this.inSlideshow) {
4932            var index = this.$container.find(selectors.activeImage).data('index');
4933          } else {
4934            var index = $(evt.currentTarget).data('index');
4935          }
4936          this.initGallery(items, index);
4937        }.bind(this));
4938      },
4939  
4940      getImageData: function() {
4941        var haveImages = false;
4942        var items = [];
4943        var options = {};
4944  
4945        this.$images.each(function() {
4946          var haveImages = true;
4947          var smallSrc = $(this).prop('currentSrc') || $(this).prop('src');
4948          var item = {
4949            msrc: smallSrc,
4950            src: $(this).data('photoswipe-src'),
4951            w: $(this).data('photoswipe-width'),
4952            h: $(this).data('photoswipe-height'),
4953            el: $(this)[0],
4954            initialZoomLevel: 0.85
4955          };
4956  
4957          items.push(item);
4958        });
4959  
4960        return items;
4961      },
4962  
4963      initGallery: function(items, index) {
4964        var pswpElement = document.querySelectorAll('.pswp')[0];
4965  
4966        var options = {
4967          allowPanToNext: false,
4968          captionEl: false,
4969          closeOnScroll: false,
4970          counterEl: false,
4971          history: false,
4972          index: index - 1,
4973          pinchToClose: false,
4974          preloaderEl: false,
4975          scaleMode: 'zoom',
4976          shareEl: false,
4977          tapToToggleControls: false,
4978          getThumbBoundsFn: function(index) {
4979            var pageYScroll = window.pageYOffset || document.documentElement.scrollTop;
4980            var thumbnail = items[index].el;
4981            var rect = thumbnail.getBoundingClientRect();
4982            return {x:rect.left, y:rect.top + pageYScroll, w:rect.width};
4983          }
4984        }
4985  
4986        this.gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
4987  
4988        this.gallery.init();
4989        this.gallery.listen('afterChange', this.afterChange.bind(this));
4990      },
4991  
4992      afterChange: function() {
4993        if (this.inSlideshow) {
4994          var $slideshow = $('#ProductPhotos-' + this.sectionId);
4995          if ($slideshow.hasClass('slick-initialized')) {
4996            var newIndex = this.gallery.getCurrentIndex();
4997            $slideshow.slick('slickGoTo', newIndex);
4998          }
4999        }
5000      }
5001    });
5002  
5003    return Photoswipe;
5004  })();
5005  
5006  
5007
5008  // Medium breakpoint is also set in theme.scss.liquid and inline throughout some templates.
5009  // Do not change values unless you know what you're doing.
5010  theme.bp = {};
5011  theme.bp.smallUp = 590;
5012  theme.bp.small = theme.bp.smallUp - 1;
5013
5014  theme.config = {
5015    bpSmall: false,
5016    hasSessionStorage: true,
5017    mediaQuerySmall: 'screen and (max-width: '+ theme.bp.small +'px)',
5018    mediaQuerySmallUp: 'screen and (min-width: '+ theme.bp.smallUp +'px)',
5019    youTubeReady: false,
5020    vimeoReady: false,
5021    vimeoLoading: false,
5022    isTouch: ('ontouchstart' in window) || window.DocumentTouch && window.document instanceof DocumentTouch || window.navigator.maxTouchPoints || window.navigator.msMaxTouchPoints ? true : false
5023  };
5024
5025  window.onYouTubeIframeAPIReady = function() {
5026    theme.config.youTubeReady = true;
5027    $('body').trigger('youTubeReady');
5028  }
5029
5030  window.loadYouTube = function() {
5031    if (theme.config.youtubeReady) {
5032      return;
5033    }
5034
5035    var tag = document.createElement('script');
5036    tag.src = "https://www.youtube.com/iframe_api";
5037    var firstScriptTag = document.getElementsByTagName('script')[0];
5038    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
5039  }
5040
5041  window.loadVimeo = function() {
5042    if (theme.config.vimeoLoading) {
5043      return;
5044    }
5045
5046    if (!theme.config.vimeoReady) {
5047      theme.config.vimeoLoading = true;
5048      var tag = document.createElement('script');
5049      tag.src = "https://player.vimeo.com/api/player.js";
5050      var firstScriptTag = document.getElementsByTagName('script')[0];
5051      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
5052
5053      // Because there's no way to check for the Vimeo API being loaded
5054      // asynchronously, we use this terrible timeout to wait for it being ready
5055      checkIfVimeoIsReady()
5056        .then(function() {
5057          theme.config.vimeoReady = true;
5058          theme.config.vimeoLoading = false;
5059          $('body').trigger('vimeoReady');
5060        })
5061        .fail(function() {
5062          // No vimeo API to talk to
5063        });
5064    }
5065  }
5066
5067  function checkIfVimeoIsReady() {
5068    var deferred = $.Deferred();
5069    var wait;
5070    var timeout;
5071
5072    wait = setInterval(function() {
5073      if (!Vimeo) {
5074        return;
5075      }
5076
5077      clearInterval(wait);
5078      clearTimeout(timeout);
5079      deferred.resolve();
5080    }, 500);
5081
5082    timeout = setTimeout(function() {
5083      clearInterval(wait);
5084      deferred.reject();
5085    }, 4000); // subjective. test up to 8 times over 4 seconds
5086
5087    return deferred;
5088  }
5089
5090  theme.init = function() {
5091    theme.setGlobals();
5092    theme.pageTransitions();
5093    theme.initQuickShop();
5094    theme.videoModal();
5095    theme.articleImages.init();
5096    theme.customerTemplates.init();
5097    theme.collapsibles.init();
5098
5099    slate.rte.init();
5100
5101    AOS.init({
5102      easing: 'ease-out-quad',
5103      once: true,
5104      offset: 60,
5105      disableMutationObserver: true
5106    });
5107
5108    $(document.documentElement).on('keyup.tab', function(evt) {
5109      if (evt.keyCode === 9) {
5110        $(document.documentElement).addClass('tab-outline');
5111        $(document.documentElement).off('keyup.tab');
5112      }
5113    });
5114  };
5115
5116  theme.setGlobals = function() {
5117    theme.config.hasSessionStorage = theme.isSessionStorageSupported();
5118
5119    if (theme.config.isTouch) {
5120      $('body').addClass('supports-touch');
5121    }
5122
5123    enquire.register(theme.config.mediaQuerySmall, {
5124      match: function() {
5125        theme.config.bpSmall = true;
5126        $('body').trigger('matchSmall');
5127      },
5128      unmatch: function() {
5129        theme.config.bpSmall = false;
5130        $('body').trigger('unmatchSmall');
5131      }
5132    });
5133  };
5134
5135  theme.loadImageSection = function($container) {
5136    // Wait until images inside container have lazyloaded class
5137    function setAsLoaded() {
5138      $container.removeClass('loading loading--delayed').addClass('loaded');
5139    }
5140
5141    function checkForLazyloadedImage() {
5142      return $container.find('.lazyloaded').length;
5143    }
5144
5145    // If it has SVGs it's in the onboarding state so set as loaded
5146    if ($container.find('svg').length) {
5147      setAsLoaded();
5148      return;
5149    };
5150
5151    if (checkForLazyloadedImage() > 0) {
5152      setAsLoaded();
5153      return;
5154    }
5155
5156    var interval = setInterval(function() {
5157      if (checkForLazyloadedImage() > 0) {
5158        clearInterval(interval);
5159        setAsLoaded();
5160      }
5161    }, 80);
5162  }
5163
5164  theme.isSessionStorageSupported = function() {
5165    // Return false if we are in an iframe without access to sessionStorage
5166    if (window.self !== window.top) {
5167      return false;
5168    }
5169
5170    var testKey = 'test';
5171    var storage = window.sessionStorage;
5172    try {
5173      storage.setItem(testKey, '1');
5174      storage.removeItem(testKey);
5175      return true;
5176    } catch (error) {
5177      return false;
5178    }
5179  };
5180
5181  theme.isElementVisible = function($el, threshold) {
5182    var rect = $el[0].getBoundingClientRect();
5183    var windowHeight = window.innerHeight || document.documentElement.clientHeight;
5184    threshold = threshold ? threshold : 0;
5185
5186    return (
5187      rect.bottom >= 0 &&
5188      rect.right >= 0 &&
5189      rect.top <= (windowHeight + threshold) &&
5190      rect.left <= (window.innerWidth || document.documentElement.clientWidth)
5191    );
5192  };
5193
5194  theme.pageTransitions = function() {
5195    if ($('body').data('transitions') == true) {
5196
5197      // Hack test to fix Safari page cache issue.
5198      // window.onpageshow doesn't always run when navigating
5199      // back to the page, so the unloading class remains, leaving
5200      // a white page. Setting a timeout to remove that class when leaving
5201      // the page actually finishes running when they come back.
5202      if (!!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/)) {
5203        $('a').on('click', function() {
5204          window.setTimeout(function() {
5205            $('body').removeClass('unloading');
5206          }, 1200);
5207        });
5208      }
5209
5210      // Add disable transition class to malito, anchor, and YouTube links
5211      $('a[href^="mailto:"], a[href^="#"], a[target="_blank"], a[href*="youtube.com/watch"], a[href*="youtu.be/"]').each(function() {
5212        $(this).addClass('js-no-transition');
5213      });
5214
5215      $('a:not(.js-no-transition)').bind('click', function(evt) {
5216        if (evt.metaKey) return true;
5217        evt.preventDefault();
5218        $('body').addClass('unloading');
5219        var src = $(this).attr('href');
5220        window.setTimeout(function() {
5221          location.href = src;
5222        }, 50);
5223      });
5224
5225      $('a.mobile-nav__link').bind('click', function() {
5226        theme.NavDrawer.close();
5227      });
5228    }
5229  };
5230
5231  theme.reinitProductGridItem = function($scope) {
5232    if (AOS) {
5233      AOS.refreshHard();
5234    }
5235
5236    // Refresh currency
5237    if (theme.settings.currenciesEnabled) {
5238      theme.currencySwitcher.ajaxrefresh();
5239    }
5240
5241    // Reload quick shop buttons
5242    theme.initQuickShop(true);
5243
5244    // Refresh reviews app
5245    if (window.SPR) {
5246      SPR.initDomEls();SPR.loadBadges();
5247    }
5248
5249    // Re-register product templates in quick view modals.
5250    // Will not double-register.
5251    sections.register('product-template', theme.Product, $scope);
5252
5253    // Re-hook up collapsible box triggers
5254    theme.collapsibles.init();
5255  };
5256
5257  window.onpageshow = function(evt) {
5258    // Removes unload class when returning to page via history
5259    if (evt.persisted) {
5260      $('body').removeClass('unloading');
5261    }
5262  };
5263
5264  $(document).ready(function() {
5265    theme.init();
5266
5267    window.sections = new theme.Sections();
5268    sections.register('header-section', theme.HeaderSection);
5269    sections.register('slideshow-section', theme.SlideshowSection);
5270    sections.register('video-section', theme.VideoSection);
5271    sections.register('product', theme.Product);
5272    sections.register('product-recommendations', theme.Recommendations);
5273    sections.register('product-template', theme.Product);
5274    sections.register('collection-template', theme.Collection);
5275    sections.register('featured-content-section', theme.FeaturedContentSection);
5276    sections.register('collection-switcher', theme.CollectionSwitcher);
5277    sections.register('testimonials', theme.Testimonials);
5278    sections.register('instagram', theme.Instagram);
5279    sections.register('newsletter-popup', theme.NewsletterPopup);
5280    sections.register('fading-images', theme.FadingImages);
5281    sections.register('map', theme.Maps);
5282    sections.register('blog', theme.Blog);
5283    sections.register('photoswipe', theme.Photoswipe);
5284  });
5285})(theme.jQuery);

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.