PageSourceSearch

http://www.bobbleit.co/s/files/1/0258/7010/6677/t/4/assets/theme_v-16622794514396426885.js

js bobbleit.co collected 2026-10-05 22:36:36 UTC 145,655 bytes, 5,120 lines download raw bytes

1window.theme = window.theme || {};
2
3/* ================ SLATE ================ */
4window.theme = window.theme || {};
5
6theme.Sections = function Sections() {
7  this.constructors = {};
8  this.instances = [];
9
10  $(document)
11    .on('shopify:section:load', this._onSectionLoad.bind(this))
12    .on('shopify:section:unload', this._onSectionUnload.bind(this))
13    .on('shopify:section:select', this._onSelect.bind(this))
14    .on('shopify:section:deselect', this._onDeselect.bind(this))
15    .on('shopify:block:select', this._onBlockSelect.bind(this))
16    .on('shopify:block:deselect', this._onBlockDeselect.bind(this));
17};
18
19theme.Sections.prototype = _.assignIn({}, theme.Sections.prototype, {
20  _createInstance: function(container, constructor) {
21    var $container = $(container);
22    var id = $container.attr('data-section-id');
23    var type = $container.attr('data-section-type');
24
25    constructor = constructor || this.constructors[type];
26
27    if (_.isUndefined(constructor)) {
28      return;
29    }
30
31    var instance = _.assignIn(new constructor(container), {
32      id: id,
33      type: type,
34      container: container
35    });
36
37    this.instances.push(instance);
38  },
39
40  _onSectionLoad: function(evt) {
41    var container = $('[data-section-id]', evt.target)[0];
42    if (container) {
43      this._createInstance(container);
44    }
45  },
46
47  _onSectionUnload: function(evt) {
48    this.instances = _.filter(this.instances, function(instance) {
49      var isEventInstance = instance.id === evt.detail.sectionId;
50
51      if (isEventInstance) {
52        if (_.isFunction(instance.onUnload)) {
53          instance.onUnload(evt);
54        }
55      }
56
57      return !isEventInstance;
58    });
59  },
60
61  _onSelect: function(evt) {
62    // eslint-disable-next-line no-shadow
63    var instance = _.find(this.instances, function(instance) {
64      return instance.id === evt.detail.sectionId;
65    });
66
67    if (!_.isUndefined(instance) && _.isFunction(instance.onSelect)) {
68      instance.onSelect(evt);
69    }
70  },
71
72  _onDeselect: function(evt) {
73    // eslint-disable-next-line no-shadow
74    var instance = _.find(this.instances, function(instance) {
75      return instance.id === evt.detail.sectionId;
76    });
77
78    if (!_.isUndefined(instance) && _.isFunction(instance.onDeselect)) {
79      instance.onDeselect(evt);
80    }
81  },
82
83  _onBlockSelect: function(evt) {
84    // eslint-disable-next-line no-shadow
85    var instance = _.find(this.instances, function(instance) {
86      return instance.id === evt.detail.sectionId;
87    });
88
89    if (!_.isUndefined(instance) && _.isFunction(instance.onBlockSelect)) {
90      instance.onBlockSelect(evt);
91    }
92  },
93
94  _onBlockDeselect: function(evt) {
95    // eslint-disable-next-line no-shadow
96    var instance = _.find(this.instances, function(instance) {
97      return instance.id === evt.detail.sectionId;
98    });
99
100    if (!_.isUndefined(instance) && _.isFunction(instance.onBlockDeselect)) {
101      instance.onBlockDeselect(evt);
102    }
103  },
104
105  register: function(type, constructor) {
106    this.constructors[type] = constructor;
107
108    $('[data-section-type=' + type + ']').each(
109      function(index, container) {
110        this._createInstance(container, constructor);
111      }.bind(this)
112    );
113  }
114});
115
116window.slate = window.slate || {};
117
118/**
119 * Slate utilities
120 * -----------------------------------------------------------------------------
121 * A collection of useful utilities to help build your theme
122 *
123 *
124 * @namespace utils
125 */
126
127slate.utils = {
128  /**
129   * Get the query params in a Url
130   * Ex
131   * https://mysite.com/search?q=noodles&b
132   * getParameterByName('q') = "noodles"
133   * getParameterByName('b') = "" (empty value)
134   * getParameterByName('test') = null (absent)
135   */
136  getParameterByName: function(name, url) {
137    if (!url) url = window.location.href;
138    name = name.replace(/[[\]]/g, '\\$&');
139    var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
140      results = regex.exec(url);
141    if (!results) return null;
142    if (!results[2]) return '';
143    return decodeURIComponent(results[2].replace(/\+/g, ' '));
144  },
145
146  keyboardKeys: {
147    TAB: 9,
148    ENTER: 13,
149    ESCAPE: 27,
150    LEFTARROW: 37,
151    RIGHTARROW: 39
152  }
153};
154
155window.slate = window.slate || {};
156
157/**
158 * iFrames
159 * -----------------------------------------------------------------------------
160 * Wrap videos in div to force responsive layout.
161 *
162 * @namespace iframes
163 */
164
165slate.rte = {
166  /**
167   * Wrap tables in a container div to make them scrollable when needed
168   *
169   * @param {object} options - Options to be used
170   * @param {jquery} options.$tables - jquery object(s) of the table(s) to wrap
171   * @param {string} options.tableWrapperClass - table wrapper class name
172   */
173  wrapTable: function(options) {
174    options.$tables.wrap(
175      '<div class="' + options.tableWrapperClass + '"></div>'
176    );
177  },
178
179  /**
180   * Wrap iframes in a container div to make them responsive
181   *
182   * @param {object} options - Options to be used
183   * @param {jquery} options.$iframes - jquery object(s) of the iframe(s) to wrap
184   * @param {string} options.iframeWrapperClass - class name used on the wrapping div
185   */
186  wrapIframe: function(options) {
187    options.$iframes.each(function() {
188      // Add wrapper to make video responsive
189      $(this).wrap('<div class="' + options.iframeWrapperClass + '"></div>');
190
191      // Re-set the src attribute on each iframe after page load
192      // for Chrome's "incorrect iFrame content on 'back'" bug.
193      // https://code.google.com/p/chromium/issues/detail?id=395791
194      // Need to specifically target video and admin bar
195      this.src = this.src;
196    });
197  }
198};
199
200window.slate = window.slate || {};
201
202/**
203 * A11y Helpers
204 * -----------------------------------------------------------------------------
205 * A collection of useful functions that help make your theme more accessible
206 * to users with visual impairments.
207 *
208 *
209 * @namespace a11y
210 */
211
212slate.a11y = {
213  /**
214   * For use when focus shifts to a container rather than a link
215   * eg for In-page links, after scroll, focus shifts to content area so that
216   * next `tab` is where user expects if focusing a link, just $link.focus();
217   *
218   * @param {JQuery} $element - The element to be acted upon
219   */
220  pageLinkFocus: function($element) {
221    var focusClass = 'js-focus-hidden';
222
223    $element
224      .first()
225      .attr('tabIndex', '-1')
226      .focus()
227      .addClass(focusClass)
228      .one('blur', callback);
229
230    function callback() {
231      $element
232        .first()
233        .removeClass(focusClass)
234        .removeAttr('tabindex');
235    }
236  },
237
238  /**
239   * If there's a hash in the url, focus the appropriate element
240   */
241  focusHash: function() {
242    var hash = window.location.hash;
243
244    // is there a hash in the url? is it an element on the page?
245    if (hash && document.getElementById(hash.slice(1))) {
246      this.pageLinkFocus($(hash));
247    }
248  },
249
250  /**
251   * When an in-page (url w/hash) link is clicked, focus the appropriate element
252   */
253  bindInPageLinks: function() {
254    $('a[href*=#]').on(
255      'click',
256      function(evt) {
257        this.pageLinkFocus($(evt.currentTarget.hash));
258      }.bind(this)
259    );
260  },
261
262  /**
263   * Traps the focus in a particular container
264   *
265   * @param {object} options - Options to be used
266   * @param {jQuery} options.$container - Container to trap focus within
267   * @param {jQuery} options.$elementToFocus - Element to be focused when focus leaves container
268   * @param {string} options.namespace - Namespace used for new focus event handler
269   */
270  trapFocus: function(options) {
271    var eventsName = {
272      focusin: options.namespace ? 'focusin.' + options.namespace : 'focusin',
273      focusout: options.namespace
274        ? 'focusout.' + options.namespace
275        : 'focusout',
276      keydown: options.namespace
277        ? 'keydown.' + options.namespace
278        : 'keydown.handleFocus'
279    };
280
281    /**
282     * Get every possible visible focusable element
283     */
284    var $focusableElements = options.$container.find(
285      $(
286        'button, [href], input, select, textarea, [tabindex]:not([tabindex^="-"])'
287      ).filter(':visible')
288    );
289    var firstFocusable = $focusableElements[0];
290    var lastFocusable = $focusableElements[$focusableElements.length - 1];
291
292    if (!options.$elementToFocus) {
293      options.$elementToFocus = options.$container;
294    }
295
296    function _manageFocus(evt) {
297      if (evt.keyCode !== slate.utils.keyboardKeys.TAB) return;
298
299      /**
300       * On the last focusable element and tab forward,
301       * focus the first element.
302       */
303      if (evt.target === lastFocusable && !evt.shiftKey) {
304        evt.preventDefault();
305        firstFocusable.focus();
306      }
307      /**
308       * On the first focusable element and tab backward,
309       * focus the last element.
310       */
311      if (evt.target === firstFocusable && evt.shiftKey) {
312        evt.preventDefault();
313        lastFocusable.focus();
314      }
315    }
316
317    options.$container.attr('tabindex', '-1');
318    options.$elementToFocus.focus();
319
320    $(document).off('focusin');
321
322    $(document).on(eventsName.focusout, function() {
323      $(document).off(eventsName.keydown);
324    });
325
326    $(document).on(eventsName.focusin, function(evt) {
327      if (evt.target !== lastFocusable && evt.target !== firstFocusable) return;
328
329      $(document).on(eventsName.keydown, function(evt) {
330        _manageFocus(evt);
331      });
332    });
333  },
334
335  /**
336   * Removes the trap of focus in a particular container
337   *
338   * @param {object} options - Options to be used
339   * @param {jQuery} options.$container - Container to trap focus within
340   * @param {string} options.namespace - Namespace used for new focus event handler
341   */
342  removeTrapFocus: function(options) {
343    var eventName = options.namespace
344      ? 'focusin.' + options.namespace
345      : 'focusin';
346
347    if (options.$container && options.$container.length) {
348      options.$container.removeAttr('tabindex');
349    }
350
351    $(document).off(eventName);
352  },
353
354  /**
355   * Add aria-describedby attribute to external and new window links
356   *
357   * @param {object} options - Options to be used
358   * @param {object} options.messages - Custom messages to be used
359   * @param {jQuery} options.$links - Specific links to be targeted
360   */
361  accessibleLinks: function(options) {
362    var body = document.querySelector('body');
363
364    var idSelectors = {
365      newWindow: 'a11y-new-window-message',
366      external: 'a11y-external-message',
367      newWindowExternal: 'a11y-new-window-external-message'
368    };
369
370    if (options.$links === undefined || !options.$links.jquery) {
371      options.$links = $('a[href]:not([aria-describedby])');
372    }
373
374    function generateHTML(customMessages) {
375      if (typeof customMessages !== 'object') {
376        customMessages = {};
377      }
378
379      var messages = $.extend(
380        {
381          newWindow: 'Opens in a new window.',
382          external: 'Opens external website.',
383          newWindowExternal: 'Opens external website in a new window.'
384        },
385        customMessages
386      );
387
388      var container = document.createElement('ul');
389      var htmlMessages = '';
390
391      for (var message in messages) {
392        htmlMessages +=
393          '<li id=' + idSelectors[message] + '>' + messages[message] + '</li>';
394      }
395
396      container.setAttribute('hidden', true);
397      container.innerHTML = htmlMessages;
398
399      body.appendChild(container);
400    }
401
402    function _externalSite($link) {
403      var hostname = window.location.hostname;
404
405      return $link[0].hostname !== hostname;
406    }
407
408    $.each(options.$links, function() {
409      var $link = $(this);
410      var target = $link.attr('target');
411      var rel = $link.attr('rel');
412      var isExternal = _externalSite($link);
413      var isTargetBlank = target === '_blank';
414
415      if (isExternal) {
416        $link.attr('aria-describedby', idSelectors.external);
417      }
418      if (isTargetBlank) {
419        if (rel === undefined || rel.indexOf('noopener') === -1) {
420          $link.attr('rel', function(i, val) {
421            var relValue = val === undefined ? '' : val + ' ';
422            return relValue + 'noopener';
423          });
424        }
425        $link.attr('aria-describedby', idSelectors.newWindow);
426      }
427      if (isExternal && isTargetBlank) {
428        $link.attr('aria-describedby', idSelectors.newWindowExternal);
429      }
430    });
431
432    generateHTML(options.messages);
433  }
434};
435
436/**
437 * Image Helper Functions
438 * -----------------------------------------------------------------------------
439 * A collection of functions that help with basic image operations.
440 *
441 */
442
443theme.Images = (function() {
444  /**
445   * Preloads an image in memory and uses the browsers cache to store it until needed.
446   *
447   * @param {Array} images - A list of image urls
448   * @param {String} size - A shopify image size attribute
449   */
450
451  function preload(images, size) {
452    if (typeof images === 'string') {
453      images = [images];
454    }
455
456    for (var i = 0; i < images.length; i++) {
457      var image = images[i];
458      this.loadImage(this.getSizedImageUrl(image, size));
459    }
460  }
461
462  /**
463   * Loads and caches an image in the browsers cache.
464   * @param {string} path - An image url
465   */
466  function loadImage(path) {
467    new Image().src = path;
468  }
469
470  /**
471   * Swaps the src of an image for another OR returns the imageURL to the callback function
472   * @param image
473   * @param element
474   * @param callback
475   */
476  function switchImage(image, element, callback) {
477    var size = this.imageSize(element.src);
478    var imageUrl = this.getSizedImageUrl(image.src, size);
479
480    if (callback) {
481      callback(imageUrl, image, element); // eslint-disable-line callback-return
482    } else {
483      element.src = imageUrl;
484    }
485  }
486
487  /**
488   * +++ Useful
489   * Find the Shopify image attribute size
490   *
491   * @param {string} src
492   * @returns {null}
493   */
494  function imageSize(src) {
495    var match = src.match(
496      /.+_((?:pico|icon|thumb|small|compact|medium|large|grande)|\d{1,4}x\d{0,4}|x\d{1,4})[_\\.@]/
497    );
498
499    if (match !== null) {
500      if (match[2] !== undefined) {
501        return match[1] + match[2];
502      } else {
503        return match[1];
504      }
505    } else {
506      return null;
507    }
508  }
509
510  /**
511   * +++ Useful
512   * Adds a Shopify size attribute to a URL
513   *
514   * @param src
515   * @param size
516   * @returns {*}
517   */
518  function getSizedImageUrl(src, size) {
519    if (size === null) {
520      return src;
521    }
522
523    if (size === 'master') {
524      return this.removeProtocol(src);
525    }
526
527    var match = src.match(
528      /\.(jpg|jpeg|gif|png|bmp|bitmap|tiff|tif)(\?v=\d+)?$/i
529    );
530
531    if (match !== null) {
532      var prefix = src.split(match[0]);
533      var suffix = match[0];
534
535      return this.removeProtocol(prefix[0] + '_' + size + suffix);
536    }
537
538    return null;
539  }
540
541  function removeProtocol(path) {
542    return path.replace(/http(s)?:/, '');
543  }
544
545  return {
546    preload: preload,
547    loadImage: loadImage,
548    switchImage: switchImage,
549    imageSize: imageSize,
550    getSizedImageUrl: getSizedImageUrl,
551    removeProtocol: removeProtocol
552  };
553})();
554
555/**
556 * Currency Helpers
557 * -----------------------------------------------------------------------------
558 * A collection of useful functions that help with currency formatting
559 *
560 * Current contents
561 * - formatMoney - Takes an amount in cents and returns it as a formatted dollar value.
562 *
563 * Alternatives
564 * - Accounting.js - http://openexchangerates.github.io/accounting.js/
565 *
566 */
567
568theme.Currency = (function() {
569  var moneyFormat = '${{amount}}'; // eslint-disable-line camelcase
570
571  function formatMoney(cents, format) {
572    if (typeof cents === 'string') {
573      cents = cents.replace('.', '');
574    }
575    var value = '';
576    var placeholderRegex = /\{\{\s*(\w+)\s*\}\}/;
577    var formatString = format || moneyFormat;
578
579    function formatWithDelimiters(number, precision, thousands, decimal) {
580      thousands = thousands || ',';
581      decimal = decimal || '.';
582
583      if (isNaN(number) || number === null) {
584        return 0;
585      }
586
587      number = (number / 100.0).toFixed(precision);
588
589      var parts = number.split('.');
590      var dollarsAmount = parts[0].replace(
591        /(\d)(?=(\d\d\d)+(?!\d))/g,
592        '$1' + thousands
593      );
594      var centsAmount = parts[1] ? decimal + parts[1] : '';
595
596      return dollarsAmount + centsAmount;
597    }
598
599    switch (formatString.match(placeholderRegex)[1]) {
600      case 'amount':
601        value = formatWithDelimiters(cents, 2);
602        break;
603      case 'amount_no_decimals':
604        value = formatWithDelimiters(cents, 0);
605        break;
606      case 'amount_with_comma_separator':
607        value = formatWithDelimiters(cents, 2, '.', ',');
608        break;
609      case 'amount_no_decimals_with_comma_separator':
610        value = formatWithDelimiters(cents, 0, '.', ',');
611        break;
612      case 'amount_no_decimals_with_space_separator':
613        value = formatWithDelimiters(cents, 0, ' ');
614        break;
615      case 'amount_with_apostrophe_separator':
616        value = formatWithDelimiters(cents, 2, "'");
617        break;
618    }
619
620    return formatString.replace(placeholderRegex, value);
621  }
622
623  return {
624    formatMoney: formatMoney
625  };
626})();
627
628/**
629 * Variant Selection scripts
630 * ------------------------------------------------------------------------------
631 *
632 * Handles change events from the variant inputs in any `cart/add` forms that may
633 * exist.  Also updates the master select and triggers updates when the variants
634 * price or image changes.
635 *
636 * @namespace variants
637 */
638
639slate.Variants = (function() {
640  /**
641   * Variant constructor
642   *
643   * @param {object} options - Settings from `product.js`
644   */
645  function Variants(options) {
646    this.$container = options.$container;
647    this.product = options.product;
648    this.singleOptionSelector = options.singleOptionSelector;
649    this.originalSelectorId = options.originalSelectorId;
650    this.enableHistoryState = options.enableHistoryState;
651    this.currentVariant = this._getVariantFromOptions();
652
653    $(this.singleOptionSelector, this.$container).on(
654      'change',
655      this._onSelectChange.bind(this)
656    );
657  }
658
659  Variants.prototype = _.assignIn({}, Variants.prototype, {
660    /**
661     * Get the currently selected options from add-to-cart form. Works with all
662     * form input elements.
663     *
664     * @return {array} options - Values of currently selected variants
665     */
666    _getCurrentOptions: function() {
667      var currentOptions = _.map(
668        $(this.singleOptionSelector, this.$container),
669        function(element) {
670          var $element = $(element);
671          var type = $element.attr('type');
672          var currentOption = {};
673
674          if (type === 'radio' || type === 'checkbox') {
675            if ($element[0].checked) {
676              currentOption.value = $element.val();
677              currentOption.index = $element.data('index');
678
679              return currentOption;
680            } else {
681              return false;
682            }
683          } else {
684            currentOption.value = $element.val();
685            currentOption.index = $element.data('index');
686
687            return currentOption;
688          }
689        }
690      );
691
692      // remove any unchecked input values if using radio buttons or checkboxes
693      currentOptions = _.compact(currentOptions);
694
695      return currentOptions;
696    },
697
698    /**
699     * Find variant based on selected values.
700     *
701     * @param  {array} selectedValues - Values of variant inputs
702     * @return {object || undefined} found - Variant object from product.variants
703     */
704    _getVariantFromOptions: function() {
705      var selectedValues = this._getCurrentOptions();
706      var variants = this.product.variants;
707
708      var found = _.find(variants, function(variant) {
709        return selectedValues.every(function(values) {
710          return _.isEqual(variant[values.index], values.value);
711        });
712      });
713
714      return found;
715    },
716
717    /**
718     * Event handler for when a variant input changes.
719     */
720    _onSelectChange: function() {
721      var variant = this._getVariantFromOptions();
722
723      this.$container.trigger({
724        type: 'variantChange',
725        variant: variant
726      });
727
728      if (!variant) {
729        return;
730      }
731
732      this._updateMasterSelect(variant);
733      this._updateImages(variant);
734      this._updatePrice(variant);
735      this._updateSKU(variant);
736      this.currentVariant = variant;
737
738      if (this.enableHistoryState) {
739        this._updateHistoryState(variant);
740      }
741    },
742
743    /**
744     * Trigger event when variant image changes
745     *
746     * @param  {object} variant - Currently selected variant
747     * @return {event}  variantImageChange
748     */
749    _updateImages: function(variant) {
750      var variantImage = variant.featured_image || {};
751      var currentVariantImage = this.currentVariant.featured_image || {};
752
753      if (
754        !variant.featured_image ||
755        variantImage.src === currentVariantImage.src
756      ) {
757        return;
758      }
759
760      this.$container.trigger({
761        type: 'variantImageChange',
762        variant: variant
763      });
764    },
765
766    /**
767     * Trigger event when variant price changes.
768     *
769     * @param  {object} variant - Currently selected variant
770     * @return {event} variantPriceChange
771     */
772    _updatePrice: function(variant) {
773      if (
774        variant.price === this.currentVariant.price &&
775        variant.compare_at_price === this.currentVariant.compare_at_price
776      ) {
777        return;
778      }
779
780      this.$container.trigger({
781        type: 'variantPriceChange',
782        variant: variant
783      });
784    },
785
786    /**
787     * Trigger event when variant sku changes.
788     *
789     * @param  {object} variant - Currently selected variant
790     * @return {event} variantSKUChange
791     */
792    _updateSKU: function(variant) {
793      if (variant.sku === this.currentVariant.sku) {
794        return;
795      }
796
797      this.$container.trigger({
798        type: 'variantSKUChange',
799        variant: variant
800      });
801    },
802
803    /**
804     * Update history state for product deeplinking
805     *
806     * @param  {variant} variant - Currently selected variant
807     * @return {k}         [description]
808     */
809    _updateHistoryState: function(variant) {
810      if (!history.replaceState || !variant) {
811        return;
812      }
813
814      var newurl =
815        window.location.protocol +
816        '//' +
817        window.location.host +
818        window.location.pathname +
819        '?variant=' +
820        variant.id;
821      window.history.propertyIsEnumerable({ path: newurl }, '', newurl);
822    },
823
824    /**
825     * Update hidden master select of variant change
826     *
827     * @param  {variant} variant - Currently selected variant
828     */
829    _updateMasterSelect: function(variant) {
830      $(this.originalSelectorId, this.$container).val(variant.id);
831    }
832  });
833
834  return Variants;
835})();
836
837
838/* ================ GLOBAL ================ */
839/*============================================================================
840  Drawer modules
841==============================================================================*/
842theme.Drawers = (function() {
843  function Drawer(id, position, options) {
844    var defaults = {
845      close: '.js-drawer-close',
846      open: '.js-drawer-open-' + position,
847      openClass: 'js-drawer-open',
848      dirOpenClass: 'js-drawer-open-' + position
849    };
850
851    this.nodes = {
852      $parent: $('html').add('body'),
853      $page: $('#PageContainer')
854    };
855
856    this.config = $.extend(defaults, options);
857    this.position = position;
858
859    this.$drawer = $('#' + id);
860
861    if (!this.$drawer.length) {
862      return false;
863    }
864
865    this.drawerIsOpen = false;
866    this.init();
867  }
868
869  Drawer.prototype.init = function() {
870    $(this.config.open).on('click', $.proxy(this.open, this));
871    this.$drawer.on('click', this.config.close, $.proxy(this.close, this));
872  };
873
874  Drawer.prototype.open = function(evt) {
875    // Keep track if drawer was opened from a click, or called by another function
876    var externalCall = false;
877
878    // Prevent following href if link is clicked
879    if (evt) {
880      evt.preventDefault();
881    } else {
882      externalCall = true;
883    }
884
885    // Without this, the drawer opens, the click event bubbles up to nodes.$page
886    // which closes the drawer.
887    if (evt && evt.stopPropagation) {
888      evt.stopPropagation();
889      // save the source of the click, we'll focus to this on close
890      this.$activeSource = $(evt.currentTarget);
891    }
892
893    if (this.drawerIsOpen && !externalCall) {
894      return this.close();
895    }
896
897    // Add is-transitioning class to moved elements on open so drawer can have
898    // transition for close animation
899    this.$drawer.prepareTransition();
900
901    this.nodes.$parent.addClass(
902      this.config.openClass + ' ' + this.config.dirOpenClass
903    );
904    this.drawerIsOpen = true;
905
906    // Set focus on drawer
907    slate.a11y.trapFocus({
908      $container: this.$drawer,
909      namespace: 'drawer_focus'
910    });
911
912    // Run function when draw opens if set
913    if (
914      this.config.onDrawerOpen &&
915      typeof this.config.onDrawerOpen === 'function'
916    ) {
917      if (!externalCall) {
918        this.config.onDrawerOpen();
919      }
920    }
921
922    if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
923      this.$activeSource.attr('aria-expanded', 'true');
924    }
925
926    this.bindEvents();
927
928    return this;
929  };
930
931  Drawer.prototype.close = function() {
932    if (!this.drawerIsOpen) {
933      // don't close a closed drawer
934      return;
935    }
936
937    // deselect any focused form elements
938    $(document.activeElement).trigger('blur');
939
940    // Ensure closing transition is applied to moved elements, like the nav
941    this.$drawer.prepareTransition();
942
943    this.nodes.$parent.removeClass(
944      this.config.dirOpenClass + ' ' + this.config.openClass
945    );
946
947    if (this.$activeSource && this.$activeSource.attr('aria-expanded')) {
948      this.$activeSource.attr('aria-expanded', 'false');
949    }
950
951    this.drawerIsOpen = false;
952
953    // Remove focus on drawer
954    slate.a11y.removeTrapFocus({
955      $container: this.$drawer,
956      namespace: 'drawer_focus'
957    });
958
959    this.unbindEvents();
960
961    // Run function when draw closes if set
962    if (
963      this.config.onDrawerClose &&
964      typeof this.config.onDrawerClose === 'function'
965    ) {
966      this.config.onDrawerClose();
967    }
968  };
969
970  Drawer.prototype.bindEvents = function() {
971    this.nodes.$parent.on(
972      'keyup.drawer',
973      $.proxy(function(evt) {
974        // close on 'esc' keypress
975        if (evt.keyCode === 27) {
976          this.close();
977          return false;
978        } else {
979          return true;
980        }
981      }, this)
982    );
983
984    // Lock scrolling on mobile
985    this.nodes.$page.on('touchmove.drawer', function() {
986      return false;
987    });
988
989    this.nodes.$page.on(
990      'click.drawer',
991      $.proxy(function() {
992        this.close();
993        return false;
994      }, this)
995    );
996  };
997
998  Drawer.prototype.unbindEvents = function() {
999    this.nodes.$page.off('.drawer');
1000    this.nodes.$parent.off('.drawer');
1001  };
1002
1003  return Drawer;
1004})();
1005
1006
1007/* ================ MODULES ================ */
1008window.theme = window.theme || {};
1009
1010theme.Header = (function() {
1011  var selectors = {
1012    body: 'body',
1013    multicurrencySelector: '[data-currency-selector]',
1014    navigation: '#AccessibleNav',
1015    siteNavHasDropdown: '[data-has-dropdowns]',
1016    siteNavChildLinks: '.site-nav__child-link',
1017    siteNavActiveDropdown: '.site-nav--active-dropdown',
1018    siteNavHasCenteredDropdown: '.site-nav--has-centered-dropdown',
1019    siteNavCenteredDropdown: '.site-nav__dropdown--centered',
1020    siteNavLinkMain: '.site-nav__link--main',
1021    siteNavChildLink: '.site-nav__link--last',
1022    siteNavDropdown: '.site-nav__dropdown',
1023    siteHeader: '.site-header'
1024  };
1025
1026  var config = {
1027    activeClass: 'site-nav--active-dropdown',
1028    childLinkClass: 'site-nav__child-link',
1029    rightDropdownClass: 'site-nav__dropdown--right',
1030    leftDropdownClass: 'site-nav__dropdown--left'
1031  };
1032
1033  var cache = {};
1034
1035  function init() {
1036    cacheSelectors();
1037    styleDropdowns($(selectors.siteNavHasDropdown));
1038    positionFullWidthDropdowns();
1039
1040    cache.$parents.on('click.siteNav', function() {
1041      var $el = $(this);
1042      $el.hasClass(config.activeClass) ? hideDropdown($el) : showDropdown($el);
1043    });
1044
1045    // check when we're leaving a dropdown and close the active dropdown
1046    $(selectors.siteNavChildLink).on('focusout.siteNav', function() {
1047      setTimeout(function() {
1048        if (
1049          $(document.activeElement).hasClass(config.childLinkClass) ||
1050          !cache.$activeDropdown.length
1051        ) {
1052          return;
1053        }
1054
1055        hideDropdown(cache.$activeDropdown);
1056      });
1057    });
1058
1059    // close dropdowns when on top level nav
1060    cache.$topLevel.on('focus.siteNav', function() {
1061      if (cache.$activeDropdown.length) {
1062        hideDropdown(cache.$activeDropdown);
1063      }
1064    });
1065
1066    cache.$subMenuLinks.on('click.siteNav', function(evt) {
1067      // Prevent click on body from firing instead of link
1068      evt.stopImmediatePropagation();
1069    });
1070
1071    $(selectors.multicurrencySelector).on('change', function() {
1072      $(this)
1073        .parents('form')
1074        .submit();
1075    });
1076
1077    $(window).resize(
1078      $.debounce(50, function() {
1079        styleDropdowns($(selectors.siteNavHasDropdown));
1080        positionFullWidthDropdowns();
1081      })
1082    );
1083  }
1084
1085  function cacheSelectors() {
1086    cache = {
1087      $nav: $(selectors.navigation),
1088      $topLevel: $(selectors.siteNavLinkMain),
1089      $parents: $(selectors.navigation).find(selectors.siteNavHasDropdown),
1090      $subMenuLinks: $(selectors.siteNavChildLinks),
1091      $activeDropdown: $(selectors.siteNavActiveDropdown),
1092      $siteHeader: $(selectors.siteHeader)
1093    };
1094  }
1095
1096  function showDropdown($el) {
1097    $el.addClass(config.activeClass);
1098
1099    // close open dropdowns
1100    if (cache.$activeDropdown.length) {
1101      hideDropdown(cache.$activeDropdown);
1102    }
1103
1104    cache.$activeDropdown = $el;
1105
1106    // set expanded on open dropdown
1107    $el.find(selectors.siteNavLinkMain).attr('aria-expanded', 'true');
1108
1109    setTimeout(function() {
1110      $(window).on('keyup.siteNav', function(evt) {
1111        if (evt.keyCode === 27) {
1112          hideDropdown($el);
1113        }
1114      });
1115
1116      $(selectors.body).on('click.siteNav', function() {
1117        hideDropdown($el);
1118      });
1119    }, 250);
1120  }
1121
1122  function hideDropdown($el) {
1123    // remove aria on open dropdown
1124    $el.find(selectors.siteNavLinkMain).attr('aria-expanded', 'false');
1125    $el.removeClass(config.activeClass);
1126
1127    // reset active dropdown
1128    cache.$activeDropdown = $(selectors.siteNavActiveDropdown);
1129
1130    $(selectors.body).off('click.siteNav');
1131    $(window).off('keyup.siteNav');
1132  }
1133
1134  function styleDropdowns($dropdownListItems) {
1135    $dropdownListItems.each(function() {
1136      var $dropdownLi = $(this).find(selectors.siteNavDropdown);
1137      if (!$dropdownLi.length) {
1138        return;
1139      }
1140      var isRightOfLogo =
1141        Math.ceil($(this).offset().left) >
1142        Math.floor(cache.$siteHeader.outerWidth()) / 2
1143          ? true
1144          : false;
1145      if (isRightOfLogo) {
1146        $dropdownLi
1147          .removeClass(config.leftDropdownClass)
1148          .addClass(config.rightDropdownClass);
1149      } else {
1150        $dropdownLi
1151          .removeClass(config.rightDropdownClass)
1152          .addClass(config.leftDropdownClass);
1153      }
1154    });
1155  }
1156
1157  function positionFullWidthDropdowns() {
1158    var $listWithCenteredDropdown = $(selectors.siteNavHasCenteredDropdown);
1159
1160    $listWithCenteredDropdown.each(function() {
1161      var $hasCenteredDropdown = $(this);
1162      var $fullWidthDropdown = $hasCenteredDropdown.find(
1163        selectors.siteNavCenteredDropdown
1164      );
1165
1166      var fullWidthDropdownOffset = $hasCenteredDropdown.position().top + 41;
1167      $fullWidthDropdown.css('top', fullWidthDropdownOffset);
1168    });
1169  }
1170
1171  function unload() {
1172    $(window).off('.siteNav');
1173    cache.$parents.off('.siteNav');
1174    cache.$subMenuLinks.off('.siteNav');
1175    cache.$topLevel.off('.siteNav');
1176    $(selectors.siteNavChildLink).off('.siteNav');
1177    $(selectors.body).off('.siteNav');
1178  }
1179
1180  return {
1181    init: init,
1182    unload: unload
1183  };
1184})();
1185
1186window.theme = window.theme || {};
1187
1188theme.MobileNav = (function() {
1189  var classes = {
1190    mobileNavOpenIcon: 'mobile-nav--open',
1191    mobileNavCloseIcon: 'mobile-nav--close',
1192    navLinkWrapper: 'mobile-nav__item',
1193    navLink: 'mobile-nav__link',
1194    subNavLink: 'mobile-nav__sublist-link',
1195    return: 'mobile-nav__return-btn',
1196    subNavActive: 'is-active',
1197    subNavClosing: 'is-closing',
1198    navOpen: 'js-menu--is-open',
1199    subNavShowing: 'sub-nav--is-open',
1200    thirdNavShowing: 'third-nav--is-open',
1201    subNavToggleBtn: 'js-toggle-submenu'
1202  };
1203  var cache = {};
1204  var isTransitioning;
1205  var $activeSubNav;
1206  var $activeTrigger;
1207  var menuLevel = 1;
1208  // Breakpoints from src/stylesheets/global/variables.scss.liquid
1209  var mediaQuerySmall = 'screen and (max-width: 749px)';
1210
1211  function init() {
1212    cacheSelectors();
1213
1214    cache.$mobileNavToggle.on('click', toggleMobileNav);
1215    cache.$subNavToggleBtn.on('click.subNav', toggleSubNav);
1216
1217    // Close mobile nav when unmatching mobile breakpoint
1218    enquire.register(mediaQuerySmall, {
1219      unmatch: function() {
1220        if (cache.$mobileNavContainer.hasClass(classes.navOpen)) {
1221          closeMobileNav();
1222        }
1223      }
1224    });
1225  }
1226
1227  function toggleMobileNav() {
1228    if (cache.$mobileNavToggle.hasClass(classes.mobileNavCloseIcon)) {
1229      closeMobileNav();
1230    } else {
1231      openMobileNav();
1232    }
1233  }
1234
1235  function cacheSelectors() {
1236    cache = {
1237      $pageContainer: $('#PageContainer'),
1238      $siteHeader: $('.site-header'),
1239      $mobileNavToggle: $('.js-mobile-nav-toggle'),
1240      $mobileNavContainer: $('.mobile-nav-wrapper'),
1241      $mobileNav: $('#MobileNav'),
1242      $sectionHeader: $('#shopify-section-header'),
1243      $subNavToggleBtn: $('.' + classes.subNavToggleBtn)
1244    };
1245  }
1246
1247  function openMobileNav() {
1248    var translateHeaderHeight = cache.$siteHeader.outerHeight();
1249
1250    cache.$mobileNavContainer.prepareTransition().addClass(classe
1250s.navOpen);
1251
1252    cache.$mobileNavContainer.css({
1253      transform: 'translateY(' + translateHeaderHeight + 'px)'
1254    });
1255
1256    cache.$pageContainer.css({
1257      transform:
1258        'translate3d(0, ' + cache.$mobileNavContainer[0].scrollHeight + 'px, 0)'
1259    });
1260
1261    slate.a11y.trapFocus({
1262      $container: cache.$sectionHeader,
1263      $elementToFocus: cache.$mobileNavToggle,
1264      namespace: 'navFocus'
1265    });
1266
1267    cache.$mobileNavToggle
1268      .addClass(classes.mobileNavCloseIcon)
1269      .removeClass(classes.mobileNavOpenIcon)
1270      .attr('aria-expanded', true);
1271
1272    // close on escape
1273    $(window).on('keyup.mobileNav', function(evt) {
1274      if (evt.which === 27) {
1275        closeMobileNav();
1276      }
1277    });
1278  }
1279
1280  function closeMobileNav() {
1281    cache.$mobileNavContainer.prepareTransition().removeClass(classes.navOpen);
1282
1283    cache.$mobileNavContainer.css({
1284      transform: 'translateY(-100%)'
1285    });
1286
1287    cache.$pageContainer.removeAttr('style');
1288
1289    slate.a11y.trapFocus({
1290      $container: $('html'),
1291      $elementToFocus: $('body')
1292    });
1293
1294    cache.$mobileNavContainer.one(
1295      'TransitionEnd.navToggle webkitTransitionEnd.navToggle transitionend.navToggle oTransitionEnd.navToggle',
1296      function() {
1297        slate.a11y.removeTrapFocus({
1298          $container: cache.$mobileNav,
1299          namespace: 'navFocus'
1300        });
1301      }
1302    );
1303
1304    cache.$mobileNavToggle
1305      .addClass(classes.mobileNavOpenIcon)
1306      .removeClass(classes.mobileNavCloseIcon)
1307      .attr('aria-expanded', false)
1308      .focus();
1309
1310    $(window).off('keyup.mobileNav');
1311
1312    scrollTo(0, 0);
1313  }
1314
1315  function toggleSubNav(evt) {
1316    if (isTransitioning) {
1317      return;
1318    }
1319
1320    var $toggleBtn = $(evt.currentTarget);
1321    var isReturn = $toggleBtn.hasClass(classes.return);
1322    isTransitioning = true;
1323
1324    if (isReturn) {
1325      // Close all subnavs by removing active class on buttons
1326      $(
1327        '.' + classes.subNavToggleBtn + '[data-level="' + (menuLevel - 1) + '"]'
1328      ).removeClass(classes.subNavActive);
1329
1330      if ($activeTrigger && $activeTrigger.length) {
1331        $activeTrigger.removeClass(classes.subNavActive);
1332      }
1333    } else {
1334      $toggleBtn.addClass(classes.subNavActive);
1335    }
1336
1337    $activeTrigger = $toggleBtn;
1338
1339    goToSubnav($toggleBtn.data('target'));
1340  }
1341
1342  function goToSubnav(target) {
1343    /*eslint-disable shopify/jquery-dollar-sign-reference */
1344
1345    var $targetMenu = target
1346      ? $('.mobile-nav__dropdown[data-parent="' + target + '"]')
1347      : cache.$mobileNav;
1348
1349    menuLevel = $targetMenu.data('level') ? $targetMenu.data('level') : 1;
1350
1351    if ($activeSubNav && $activeSubNav.length) {
1352      $activeSubNav.prepareTransition().addClass(classes.subNavClosing);
1353    }
1354
1355    $activeSubNav = $targetMenu;
1356
1357    /*eslint-enable shopify/jquery-dollar-sign-reference */
1358
1359    var translateMenuHeight = $targetMenu.outerHeight();
1360
1361    var openNavClass =
1362      menuLevel > 2 ? classes.thirdNavShowing : classes.subNavShowing;
1363
1364    cache.$mobileNavContainer
1365      .css('height', translateMenuHeight)
1366      .removeClass(classes.thirdNavShowing)
1367      .addClass(openNavClass);
1368
1369    if (!target) {
1370      // Show top level nav
1371      cache.$mobileNavContainer
1372        .removeClass(classes.thirdNavShowing)
1373        .removeClass(classes.subNavShowing);
1374    }
1375
1376    /* if going back to first subnav, focus is on whole header */
1377    var $container = menuLevel === 1 ? cache.$sectionHeader : $targetMenu;
1378
1379    var $menuTitle = $targetMenu.find('[data-menu-title=' + menuLevel + ']');
1380    var $elementToFocus = $menuTitle ? $menuTitle : $targetMenu;
1381
1382    // Focusing an item in the subnav early forces element into view and breaks the animation.
1383    cache.$mobileNavContainer.one(
1384      'TransitionEnd.subnavToggle webkitTransitionEnd.subnavToggle transitionend.subnavToggle oTransitionEnd.subnavToggle',
1385      function() {
1386        slate.a11y.trapFocus({
1387          $container: $container,
1388          $elementToFocus: $elementToFocus,
1389          namespace: 'subNavFocus'
1390        });
1391
1392        cache.$mobileNavContainer.off('.subnavToggle');
1393        isTransitioning = false;
1394      }
1395    );
1396
1397    // Match height of subnav
1398    cache.$pageContainer.css({
1399      transform: 'translateY(' + translateMenuHeight + 'px)'
1400    });
1401
1402    $activeSubNav.removeClass(classes.subNavClosing);
1403  }
1404
1405  return {
1406    init: init,
1407    closeMobileNav: closeMobileNav
1408  };
1409})(jQuery);
1410
1411window.theme = window.theme || {};
1412
1413theme.Search = (function() {
1414  var selectors = {
1415    search: '.search',
1416    searchSubmit: '.search__submit',
1417    searchInput: '.search__input',
1418
1419    siteHeader: '.site-header',
1420    siteHeaderSearchToggle: '.site-header__search-toggle',
1421    siteHeaderSearch: '.site-header__search',
1422
1423    searchDrawer: '.search-bar',
1424    searchDrawerInput: '.search-bar__input',
1425
1426    searchHeader: '.search-header',
1427    searchHeaderInput: '.search-header__input',
1428    searchHeaderSubmit: '.search-header__submit',
1429
1430    searchResultSubmit: '#SearchResultSubmit',
1431    searchResultInput: '#SearchInput',
1432    searchResultMessage: '[data-search-error-message]',
1433
1434    mobileNavWrapper: '.mobile-nav-wrapper'
1435  };
1436
1437  var classes = {
1438    focus: 'search--focus',
1439    hidden: 'hide',
1440    mobileNavIsOpen: 'js-menu--is-open',
1441    searchTemplate: 'template-search'
1442  };
1443
1444  function init() {
1445    if (!$(selectors.siteHeader).length) {
1446      return;
1447    }
1448
1449    this.$searchResultInput = $(selectors.searchResultInput);
1450    this.$searchErrorMessage = $(selectors.searchResultMessage);
1451
1452    initDrawer();
1453
1454    var isSearchPage =
1455      slate.utils.getParameterByName('q') !== null &&
1456      $('body').hasClass(classes.searchTemplate);
1457
1458    if (isSearchPage) {
1459      validateSearchResultForm.call(this);
1460    }
1461
1462    $(selectors.searchResultSubmit).on(
1463      'click',
1464      validateSearchResultForm.bind(this)
1465    );
1466
1467    $(selectors.searchHeaderInput)
1468      .add(selectors.searchHeaderSubmit)
1469      .on('focus blur', function() {
1470        $(selectors.searchHeader).toggleClass(classes.focus);
1471      });
1472
1473    $(selectors.siteHeaderSearchToggle).on('click', function() {
1474      var searchHeight = $(selectors.siteHeader).outerHeight();
1475      var searchOffset = $(selectors.siteHeader).offset().top - searchHeight;
1476
1477      $(selectors.searchDrawer).css({
1478        height: searchHeight + 'px',
1479        top: searchOffset + 'px'
1480      });
1481    });
1482  }
1483
1484  function initDrawer() {
1485    // Add required classes to HTML
1486    $('#PageContainer').addClass('drawer-page-content');
1487    $('.js-drawer-open-top')
1488      .attr('aria-controls', 'SearchDrawer')
1489      .attr('aria-expanded', 'false')
1490      .attr('aria-haspopup', 'dialog');
1491
1492    theme.SearchDrawer = new theme.Drawers('SearchDrawer', 'top', {
1493      onDrawerOpen: searchDrawerFocus,
1494      onDrawerClose: searchDrawerFocusClose
1495    });
1496  }
1497
1498  function searchDrawerFocus() {
1499    searchFocus($(selectors.searchDrawerInput));
1500
1501    if ($(selectors.mobileNavWrapper).hasClass(classes.mobileNavIsOpen)) {
1502      theme.MobileNav.closeMobileNav();
1503    }
1504  }
1505
1506  function searchFocus($el) {
1507    $el.focus();
1508    // set selection range hack for iOS
1509    $el[0].setSelectionRange(0, $el[0].value.length);
1510  }
1511
1512  function searchDrawerFocusClose() {
1513    $(selectors.siteHeaderSearchToggle).focus();
1514  }
1515
1516  /**
1517   * Remove the aria-attributes and hide the error messages
1518   */
1519  function hideErrorMessage() {
1520    this.$searchErrorMessage.addClass(classes.hidden);
1521    this.$searchResultInput
1522      .removeAttr('aria-describedby')
1523      .removeAttr('aria-invalid');
1524  }
1525
1526  /**
1527   * Add the aria-attributes and show the error messages
1528   */
1529  function showErrorMessage() {
1530    this.$searchErrorMessage.removeClass(classes.hidden);
1531    this.$searchResultInput
1532      .attr('aria-describedby', 'error-search-form')
1533      .attr('aria-invalid', true);
1534  }
1535
1536  function validateSearchResultForm(evt) {
1537    var isInputValueEmpty = this.$searchResultInput.val().trim().length === 0;
1538
1539    if (!isInputValueEmpty) {
1540      hideErrorMessage.call(this);
1541      return;
1542    }
1543
1544    if (typeof evt !== 'undefined') {
1545      evt.preventDefault();
1546    }
1547
1548    searchFocus(this.$searchResultInput);
1549    showErrorMessage.call(this);
1550  }
1551
1552  return {
1553    init: init
1554  };
1555})();
1556
1557(function() {
1558  var selectors = {
1559    backButton: '.return-link'
1560  };
1561
1562  var $backButton = $(selectors.backButton);
1563
1564  if (!document.referrer || !$backButton.length || !window.history.length) {
1565    return;
1566  }
1567
1568  $backButton.one('click', function(evt) {
1569    evt.preventDefault();
1570
1571    var referrerDomain = urlDomain(document.referrer);
1572    var shopDomain = urlDomain(window.location.href);
1573
1574    if (shopDomain === referrerDomain) {
1575      history.back();
1576    }
1577
1578    return false;
1579  });
1580
1581  function urlDomain(url) {
1582    var anchor = document.createElement('a');
1583    anchor.ref = url;
1584
1585    return anchor.hostname;
1586  }
1587})();
1588
1589theme.Slideshow = (function() {
1590  this.$slideshow = null;
1591  var classes = {
1592    slideshow: 'slideshow',
1593    slickActiveMobile: 'slick-active-mobile',
1594    controlsHover: 'slideshow__controls--hover',
1595    isPaused: 'is-paused'
1596  };
1597
1598  var selectors = {
1599    section: '.shopify-section',
1600    wrapper: '#SlideshowWrapper-',
1601    slides: '.slideshow__slide',
1602    textWrapperMobile: '.slideshow__text-wrap--mobile',
1603    textContentMobile: '.slideshow__text-content--mobile',
1604    controls: '.slideshow__controls',
1605    pauseButton: '.slideshow__pause',
1606    dots: '.slick-dots',
1607    arrows: '.slideshow__arrows',
1608    arrowsMobile: '.slideshow__arrows--mobile',
1609    arrowLeft: '.slideshow__arrow-left',
1610    arrowRight: '.slideshow__arrow-right'
1611  };
1612
1613  function slideshow(el, sectionId) {
1614    var $slideshow = (this.$slideshow = $(el));
1615    this.adaptHeight = this.$slideshow.data('adapt-height');
1616    this.$wrapper = this.$slideshow.closest(selectors.wrapper + sectionId);
1617    this.$section = this.$wrapper.closest(selectors.section);
1618    this.$controls = this.$wrapper.find(selectors.controls);
1619    this.$arrows = this.$section.find(selectors.arrows);
1620    this.$arrowsMobile = this.$section.find(selectors.arrowsMobile);
1621    this.$pause = this.$controls.find(selectors.pauseButton);
1622    this.$textWrapperMobile = this.$section.find(selectors.textWrapperMobile);
1623    this.autorotate = this.$slideshow.data('autorotate');
1624    var autoplaySpeed = this.$slideshow.data('speed');
1625    var loadSlideA11yString = this.$slideshow.data('slide-nav-a11y');
1626
1627    this.settings = {
1628      accessibility: true,
1629      arrows: false,
1630      dots: true,
1631      fade: true,
1632      draggable: true,
1633      touchThreshold: 20,
1634      autoplay: this.autorotate,
1635      autoplaySpeed: autoplaySpeed,
1636      // eslint-disable-next-line shopify/jquery-dollar-sign-reference
1637      appendDots: this.$arrows,
1638      customPaging: function(slick, index) {
1639        return (
1640          '<a href="' +
1641          selectors.wrapper +
1642          sectionId +
1643          '" aria-label="' +
1644          loadSlideA11yString.replace('[slide_number]', index + 1) +
1645          '" data-slide-number="' +
1646          index +
1647          '"></a>'
1648        );
1649      }
1650    };
1651
1652    this.$slideshow.on('beforeChange', beforeChange.bind(this));
1653    this.$slideshow.on('init', slideshowA11ySetup.bind(this));
1654
1655    // Add class to style mobile dots & show the correct text content for the
1656    // first slide on mobile when the slideshow initialises
1657    this.$slideshow.on(
1658      'init',
1659      function() {
1660        this.$mobileDots
1661          .find('li:first-of-type')
1662          .addClass(classes.slickActiveMobile);
1663        this.showMobileText(0);
1664      }.bind(this)
1665    );
1666
1667    // Stop the autorotate when you scroll past the mobile controls, resume when
1668    // they are scrolled back into view
1669    if (this.autorotate) {
1670      $(document).scroll(
1671        $.debounce(
1672          250,
1673          function() {
1674            if (
1675              this.$arrowsMobile.offset().top +
1676                this.$arrowsMobile.outerHeight() <
1677              window.pageYOffset
1678            ) {
1679              $slideshow.slick('slickPause');
1680            } else if (!this.$pause.hasClass(classes.isPaused)) {
1681              $slideshow.slick('slickPlay');
1682            }
1683          }.bind(this)
1684        )
1685      );
1686    }
1687
1688    if (this.adaptHeight) {
1689      this.setSlideshowHeight();
1690      $(window).resize($.debounce(50, this.setSlideshowHeight.bind(this)));
1691    }
1692
1693    this.$slideshow.slick(this.settings);
1694
1695    // This can't be called when the slick 'init' event fires due to how slick
1696    // adds a11y features.
1697    slideshowPostInitA11ySetup.bind(this)();
1698
1699    this.$arrows.find(selectors.arrowLeft).on('click', function() {
1700      $slideshow.slick('slickPrev');
1701    });
1702    this.$arrows.find(selectors.arrowRight).on('click', function() {
1703      $slideshow.slick('slickNext');
1704    });
1705
1706    this.$pause.on('click', this.togglePause.bind(this));
1707  }
1708
1709  function slideshowA11ySetup(event, obj) {
1710    var $slider = obj.$slider;
1711    var $list = obj.$list;
1712    this.$dots = this.$section.find(selectors.dots);
1713    this.$mobileDots = this.$dots.eq(1);
1714
1715    // Remove default Slick aria-live attr until slider is focused
1716    $list.removeAttr('aria-live');
1717
1718    this.$wrapper.on('keyup', keyboardNavigation.bind(this));
1719    this.$controls.on('keyup', keyboardNavigation.bind(this));
1720    this.$textWrapperMobile.on('keyup', keyboardNavigation.bind(this));
1721
1722    // When an element in the slider is focused
1723    // pause slideshow and set aria-live.
1724    this.$wrapper
1725      .on(
1726        'focusin',
1727        function(evt) {
1728          if (!this.$wrapper.has(evt.target).length) {
1729            return;
1730          }
1731
1732          $list.attr('aria-live', 'polite');
1733          if (this.autorotate) {
1734            $slider.slick('slickPause');
1735          }
1736        }.bind(this)
1737      )
1738      .on(
1739        'focusout',
1740        function(evt) {
1741          if (!this.$wrapper.has(evt.target).length) {
1742            return;
1743          }
1744
1745          $list.removeAttr('aria-live');
1746          if (this.autorotate) {
1747            // Only resume playing if the user hasn't paused using the pause
1748            // button
1749            if (!this.$pause.is('.is-paused')) {
1750              $slider.slick('slickPlay');
1751            }
1752          }
1753        }.bind(this)
1754      );
1755
1756    // Add arrow key support when focused
1757    if (this.$dots) {
1758      this.$dots
1759        .find('a')
1760        .each(function() {
1761          var $dot = $(this);
1762          $dot.on('click keyup', function(evt) {
1763            if (
1764              evt.type === 'keyup' &&
1765              evt.which !== slate.utils.keyboardKeys.ENTER
1766            )
1767              return;
1768
1769            evt.preventDefault();
1770
1771            var slideNumber = $(evt.target).data('slide-number');
1772
1773            $slider.attr('tabindex', -1).slick('slickGoTo', slideNumber);
1774
1775            if (evt.type === 'keyup') {
1776              $slider.focus();
1777            }
1778          });
1779        })
1780        .eq(0)
1781        .attr('aria-current', 'true');
1782    }
1783
1784    this.$controls
1785      .on('focusin', highlightControls.bind(this))
1786      .on('focusout', unhighlightControls.bind(this));
1787  }
1788
1789  function slideshowPostInitA11ySetup() {
1790    var $slides = this.$slideshow.find(selectors.slides);
1791
1792    $slides.removeAttr('role').removeAttr('aria-labelledby');
1793    this.$dots
1794      .removeAttr('role')
1795      .find('li')
1796      .removeAttr('role')
1797      .removeAttr('aria-selected')
1798      .each(function() {
1799        var $dot = $(this);
1800        var ariaControls = $dot.attr('aria-controls');
1801        $dot
1802          .removeAttr('aria-controls')
1803          .find('a')
1804          .attr('aria-controls', ariaControls);
1805      });
1806  }
1807
1808  function beforeChange(event, slick, currentSlide, nextSlide) {
1809    var $dotLinks = this.$dots.find('a');
1810    var $mobileDotLinks = this.$mobileDots.find('li');
1811
1812    $dotLinks
1813      .removeAttr('aria-current')
1814      .eq(nextSlide)
1815      .attr('aria-current', 'true');
1816
1817    $mobileDotLinks
1818      .removeClass(classes.slickActiveMobile)
1819      .eq(nextSlide)
1820      .addClass(classes.slickActiveMobile);
1821    this.showMobileText(nextSlide);
1822  }
1823
1824  function keyboardNavigation() {
1825    if (event.keyCode === slate.utils.keyboardKeys.LEFTARROW) {
1826      this.$slideshow.slick('slickPrev');
1827    }
1828    if (event.keyCode === slate.utils.keyboardKeys.RIGHTARROW) {
1829      this.$slideshow.slick('slickNext');
1830    }
1831  }
1832
1833  function highlightControls() {
1834    this.$controls.addClass(classes.controlsHover);
1835  }
1836
1837  function unhighlightControls() {
1838    this.$controls.removeClass(classes.controlsHover);
1839  }
1840
1841  slideshow.prototype.togglePause = function() {
1842    var slideshowSelector = getSlideshowId(this.$pause);
1843    if (this.$pause.hasClass(classes.isPaused)) {
1844      this.$pause.removeClass(classes.isPaused).attr('aria-pressed', 'false');
1845      if (this.autorotate) {
1846        $(slideshowSelector).slick('slickPlay');
1847      }
1848    } else {
1849      this.$pause.addClass(classes.isPaused).attr('aria-pressed', 'true');
1850      if (this.autorotate) {
1851        $(slideshowSelector).slick('slickPause');
1852      }
1853    }
1854  };
1855
1856  slideshow.prototype.setSlideshowHeight = function() {
1857    var minAspectRatio = this.$slideshow.data('min-aspect-ratio');
1858    this.$slideshow.height($(document).width() / minAspectRatio);
1859  };
1860
1861  slideshow.prototype.showMobileText = function(slideIndex) {
1862    var $allTextContent = this.$textWrapperMobile.find(
1863      selectors.textContentMobile
1864    );
1865    var currentTextContentSelector =
1866      selectors.textContentMobile + '-' + slideIndex;
1867    var $currentTextContent = this.$textWrapperMobile.find(
1868      currentTextContentSelector
1869    );
1870    if (
1871      !$currentTextContent.length &&
1872      this.$slideshow.find(selectors.slides).length === 1
1873    ) {
1874      this.$textWrapperMobile.hide();
1875    } else {
1876      this.$textWrapperMobile.show();
1877    }
1878    $allTextContent.hide();
1879    $currentTextContent.show();
1880  };
1881
1882  function getSlideshowId($el) {
1883    return '#Slideshow-' + $el.data('id');
1884  }
1885
1886  return slideshow;
1887})();
1888
1889// Youtube API callback
1890// eslint-disable-next-line no-unused-vars
1891function onYouTubeIframeAPIReady() {
1892  theme.Video.loadVideos();
1893}
1894
1895theme.Video = (function() {
1896  var autoplayCheckComplete = false;
1897  var playOnClickChecked = false;
1898  var playOnClick = false;
1899  var youtubeLoaded = false;
1900  var videos = {};
1901  var videoPlayers = [];
1902  var videoOptions = {
1903    ratio: 16 / 9,
1904    scrollAnimationDuration: 400,
1905    playerVars: {
1906      // eslint-disable-next-line camelcase
1907      iv_load_policy: 3,
1908      modestbranding: 1,
1909      autoplay: 0,
1910      controls: 0,
1911      wmode: 'opaque',
1912      branding: 0,
1913      autohide: 0,
1914      rel: 0
1915    },
1916    events: {
1917      onReady: onPlayerReady,
1918      onStateChange: onPlayerChange
1919    }
1920  };
1921  var classes = {
1922    playing: 'video-is-playing',
1923    paused: 'video-is-paused',
1924    loading: 'video-is-loading',
1925    loaded: 'video-is-loaded',
1926    backgroundVideoWrapper: 'video-background-wrapper',
1927    videoWithImage: 'video--image_with_play',
1928    backgroundVideo: 'video--background',
1929    userPaused: 'is-paused',
1930    supportsAutoplay: 'autoplay',
1931    supportsNoAutoplay: 'no-autoplay',
1932    wrapperMinHeight: 'video-section-wrapper--min-height'
1933  };
1934
1935  var selectors = {
1936    section: '.video-section',
1937    videoWrapper: '.video-section-wrapper',
1938    playVideoBtn: '.video-control__play',
1939    closeVideoBtn: '.video-control__close-wrapper',
1940    pauseVideoBtn: '.video__pause',
1941    pauseVideoStop: '.video__pause-stop',
1942    pauseVideoResume: '.video__pause-resume',
1943    fallbackText: '.icon__fallback-text'
1944  };
1945
1946  /**
1947   * Public functions
1948   */
1949  function init($video) {
1950    if (!$video.length) {
1951      return;
1952    }
1953
1954    videos[$video.attr('id')] = {
1955      id: $video.attr('id'),
1956      videoId: $video.data('id'),
1957      type: $video.data('type'),
1958      status:
1959        $video.data('type') === 'image_with_play' ? 'closed' : 'background', // closed, open, background
1960      $video: $video,
1961      $videoWrapper: $video.closest(selectors.videoWrapper),
1962      $section: $video.closest(selectors.section),
1963      controls: $video.data('type') === 'background' ? 0 : 1
1964    };
1965
1966    if (!youtubeLoaded) {
1967      // This code loads the IFrame Player API code asynchronously.
1968      var tag = document.createElement('script');
1969      tag.src = 'https://www.youtube.com/iframe_api';
1970      var firstScriptTag = document.getElementsByTagName('script')[0];
1971      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
1972    }
1973
1974    playOnClickCheck();
1975  }
1976
1977  function customPlayVideo(playerId) {
1978    // Make sure we have carried out the playOnClick check first
1979    if (!playOnClickChecked && !playOnClick) {
1980      return;
1981    }
1982
1983    if (playerId && typeof videoPlayers[playerId].playVideo === 'function') {
1984      privatePlayVideo(playerId);
1985    }
1986  }
1987
1988  function pauseVideo(playerId) {
1989    if (
1990      videoPlayers[playerId] &&
1991      typeof videoPlayers[playerId].pauseVideo === 'function'
1992    ) {
1993      videoPlayers[playerId].pauseVideo();
1994    }
1995  }
1996
1997  function loadVideos() {
1998    for (var key in videos) {
1999      if (videos.hasOwnProperty(key)) {
2000        createPlayer(key);
2001      }
2002    }
2003
2004    initEvents();
2005    youtubeLoaded = true;
2006  }
2007
2008  function editorLoadVideo(key) {
2009    if (!youtubeLoaded) {
2010      return;
2011    }
2012    createPlayer(key);
2013
2014    initEvents();
2015  }
2016
2017  /**
2018   * Private functions
2019   */
2020
2021  function privatePlayVideo(id, clicked) {
2022    var videoData = videos[id];
2023    var player = videoPlayers[id];
2024    var $videoWrapper = videoData.$videoWrapper;
2025
2026    if (playOnClick) {
2027      // playOnClick means we are probably on mobile (no autoplay).
2028      // setAsPlaying will show the iframe, requiring another click
2029      // to play the video.
2030      setAsPlaying(videoData);
2031    } else if (clicked || autoplayCheckComplete) {
2032      // Play if autoplay is available or clicked to play
2033      $videoWrapper.removeClass(classes.loading);
2034      setAsPlaying(videoData);
2035      player.playVideo();
2036      return;
2037    } else {
2038      player.playVideo();
2039    }
2040  }
2041
2042  function setAutoplaySupport(supported) {
2043    var supportClass = supported
2044      ? classes.supportsAutoplay
2045      : classes.supportsNoAutoplay;
2046    $(document.documentElement)
2047      .removeClass(classes.supportsAutoplay)
2048      .removeClass(classes.supportsNoAutoplay)
2049      .addClass(supportClass);
2050
2051    if (!supported) {
2052      playOnClick = true;
2053    }
2054
2055    autoplayCheckComplete = true;
2056  }
2057
2058  function playOnClickCheck() {
2059    // Bail early for a few instances:
2060    // - small screen
2061    // - device sniff mobile browser
2062
2063    if (playOnClickChecked) {
2064      return;
2065    }
2066
2067    if (isMobile()) {
2068      playOnClick = true;
2069    }
2070
2071    if (playOnClick) {
2072      // No need to also do the autoplay check
2073      setAutoplaySupport(false);
2074    }
2075
2076    playOnClickChecked = true;
2077  }
2078
2079  // The API will call this function when each video player is ready
2080  function onPlayerReady(evt) {
2081    evt.target.setPlaybackQuality('hd1080');
2082    var videoData = getVideoOptions(evt);
2083    var videoTitle = evt.target.getVideoData().title;
2084    playOnClickCheck();
2085
2086    // Prevent tabbing through YouTube player controls until visible
2087    $('#' + videoData.id).attr('tabindex', '-1');
2088
2089    sizeBackgroundVideos();
2090    setButtonLabels(videoData.$videoWrapper, videoTitle);
2091
2092    // Customize based on options from the video ID
2093    if (videoData.type === 'background') {
2094      evt.target.mute();
2095      privatePlayVideo(videoData.id);
2096    }
2097
2098    videoData.$videoWrapper.addClass(classes.loaded);
2099  }
2100
2101  function onPlayerChange(evt) {
2102    var videoData = getVideoOptions(evt);
2103    if (
2104      videoData.status === 'background' &&
2105      !isMobile() &&
2106      !autoplayCheckComplete &&
2107      (evt.data === YT.PlayerState.PLAYING ||
2108        evt.data === YT.PlayerState.BUFFERING)
2109    ) {
2110      setAutoplaySupport(true);
2111      autoplayCheckComplete = true;
2112      videoData.$videoWrapper.removeClass(classes.loading);
2113    }
2114    switch (evt.data) {
2115      case YT.PlayerState.ENDED:
2116        setAsFinished(videoData);
2117        break;
2118      case YT.PlayerState.PAUSED:
2119        // Seeking on a YouTube video also fires a PAUSED state change,
2120        // checking the state after a delay prevents us pausing the video when
2121        // the user is seeking instead of pausing
2122        setTimeout(function() {
2123          if (evt.target.getPlayerState() === YT.PlayerState.PAUSED) {
2124            setAsPaused(videoData);
2125          }
2126        }, 200);
2127        break;
2128    }
2129  }
2130
2131  function setAsFinished(videoData) {
2132    switch (videoData.type) {
2133      case 'background':
2134        videoPlayers[videoData.id].seekTo(0);
2135        break;
2136      case 'image_with_play':
2137        closeVideo(videoData.id);
2138        toggleExpandVideo(videoData.id, false);
2139        break;
2140    }
2141  }
2142
2143  function setAsPlaying(videoData) {
2144    var $videoWrapper = videoData.$videoWrapper;
2145    var $pauseButton = $videoWrapper.find(selectors.pauseVideoBtn);
2146
2147    $videoWrapper.removeClass(classes.loading);
2148
2149    if ($pauseButton.hasClass(classes.userPaused)) {
2150      $pauseButton.removeClass(classes.userPaused);
2151    }
2152
2153    // Do not change element visibility if it is a background video
2154    if (videoData.status === 'background') {
2155      return;
2156    }
2157
2158    $('#' + videoData.id).attr('tabindex', '0');
2159
2160    if (videoData.type === 'image_with_play') {
2161      $videoWrapper.removeClass(classes.paused).addClass(classes.playing);
2162    }
2163
2164    // Update focus to the close button so we stay within the video wrapper,
2165    // allowing time for the scroll animation
2166    setTimeout(function() {
2167      $videoWrapper.find(selectors.closeVideoBtn).focus();
2168    }, videoOptions.scrollAnimationDuration);
2169  }
2170
2171  function setAsPaused(videoData) {
2172    var $videoWrapper = videoData.$videoWrapper;
2173
2174    // YT's events fire after our click event. This status flag ensures
2175    // we don't interact with a closed or background video.
2176    if (videoData.type === 'image_with_play') {
2177      if (videoData.status === 'closed') {
2178        $videoWrapper.removeClass(classe
2178s.paused);
2179      } else {
2180        $videoWrapper.addClass(classes.paused);
2181      }
2182    }
2183
2184    $videoWrapper.removeClass(classes.playing);
2185  }
2186
2187  function closeVideo(playerId) {
2188    var videoData = videos[playerId];
2189    var $videoWrapper = videoData.$videoWrapper;
2190    var classesToRemove = [classes.paused, classes.playing].join(' ');
2191
2192    if (isMobile()) {
2193      $videoWrapper.removeAttr('style');
2194    }
2195
2196    $('#' + videoData.id).attr('tabindex', '-1');
2197
2198    videoData.status = 'closed';
2199
2200    switch (videoData.type) {
2201      case 'image_with_play':
2202        videoPlayers[playerId].stopVideo();
2203        setAsPaused(videoData); // in case the video is already paused
2204        break;
2205      case 'background':
2206        videoPlayers[playerId].mute();
2207        setBackgroundVideo(playerId);
2208        break;
2209    }
2210
2211    $videoWrapper.removeClass(classesToRemove);
2212  }
2213
2214  function getVideoOptions(evt) {
2215    return videos[evt.target.a.id];
2216  }
2217
2218  function toggleExpandVideo(playerId, expand) {
2219    var video = videos[playerId];
2220    var elementTop = video.$videoWrapper.offset().top;
2221    var $playButton = video.$videoWrapper.find(selectors.playVideoBtn);
2222    var offset = 0;
2223    var newHeight = 0;
2224
2225    if (isMobile()) {
2226      video.$videoWrapper.parent().toggleClass('page-width', !expand);
2227    }
2228
2229    if (expand) {
2230      if (isMobile()) {
2231        newHeight = $(window).width() / videoOptions.ratio;
2232      } else {
2233        newHeight = video.$videoWrapper.width() / videoOptions.ratio;
2234      }
2235      offset = ($(window).height() - newHeight) / 2;
2236
2237      video.$videoWrapper
2238        .removeClass(classes.wrapperMinHeight)
2239        .animate({ height: newHeight }, 600);
2240
2241      // Animate doesn't work in mobile editor, so we don't use it
2242      if (!(isMobile() && Shopify.designMode)) {
2243        $('html, body').animate(
2244          {
2245            scrollTop: elementTop - offset
2246          },
2247          videoOptions.scrollAnimationDuration
2248        );
2249      }
2250    } else {
2251      if (isMobile()) {
2252        newHeight = video.$videoWrapper.data('mobile-height');
2253      } else {
2254        newHeight = video.$videoWrapper.data('desktop-height');
2255      }
2256
2257      video.$videoWrapper
2258        .height(video.$videoWrapper.width() / videoOptions.ratio)
2259        .animate({ height: newHeight }, 600);
2260      setTimeout(function() {
2261        video.$videoWrapper.addClass(classes.wrapperMinHeight);
2262      }, 600);
2263      $playButton.focus();
2264    }
2265  }
2266
2267  function togglePause(playerId) {
2268    var $pauseButton = videos[playerId].$videoWrapper.find(
2269      selectors.pauseVideoBtn
2270    );
2271    var paused = $pauseButton.hasClass(classes.userPaused);
2272    if (paused) {
2273      $pauseButton.removeClass(classes.userPaused);
2274      customPlayVideo(playerId);
2275    } else {
2276      $pauseButton.addClass(classes.userPaused);
2277      pauseVideo(playerId);
2278    }
2279    $pauseButton.attr('aria-pressed', !paused);
2280  }
2281
2282  function startVideoOnClick(playerId) {
2283    var video = videos[playerId];
2284
2285    // add loading class to wrapper
2286    video.$videoWrapper.addClass(classes.loading);
2287
2288    // Explicity set the video wrapper height (needed for height transition)
2289    video.$videoWrapper.attr(
2290      'style',
2291      'height: ' + video.$videoWrapper.height() + 'px'
2292    );
2293
2294    video.status = 'open';
2295
2296    switch (video.type) {
2297      case 'image_with_play':
2298        privatePlayVideo(playerId, true);
2299        break;
2300      case 'background':
2301        unsetBackgroundVideo(playerId, video);
2302        videoPlayers[playerId].unMute();
2303        privatePlayVideo(playerId, true);
2304        break;
2305    }
2306
2307    toggleExpandVideo(playerId, true);
2308
2309    // esc to close video player
2310    $(document).on('keydown.videoPlayer', function(evt) {
2311      var playerId = $(document.activeElement).data('controls');
2312      if (evt.keyCode !== slate.utils.keyboardKeys.ESCAPE || !playerId) {
2313        return;
2314      }
2315
2316      closeVideo(playerId);
2317      toggleExpandVideo(playerId, false);
2318    });
2319  }
2320
2321  function sizeBackgroundVideos() {
2322    $('.' + classes.backgroundVideo).each(function(index, el) {
2323      sizeBackgroundVideo($(el));
2324    });
2325  }
2326
2327  function sizeBackgroundVideo($videoPlayer) {
2328    if (!youtubeLoaded) {
2329      return;
2330    }
2331
2332    if (isMobile()) {
2333      $videoPlayer.removeAttr('style');
2334    } else {
2335      var $videoWrapper = $videoPlayer.closest(selectors.videoWrapper);
2336      var videoWidth = $videoWrapper.width();
2337      var playerWidth = $videoPlayer.width();
2338      var desktopHeight = $videoWrapper.data('desktop-height');
2339
2340      // when screen aspect ratio differs from video, video must center and underlay one dimension
2341      if (videoWidth / videoOptions.ratio < desktopHeight) {
2342        playerWidth = Math.ceil(desktopHeight * videoOptions.ratio); // get new player width
2343        $videoPlayer
2344          .width(playerWidth)
2345          .height(desktopHeight)
2346          .css({
2347            left: (videoWidth - playerWidth) / 2,
2348            top: 0
2349          }); // player width is greater, offset left; reset top
2350      } else {
2351        // new video width < window width (gap to right)
2352        desktopHeight = Math.ceil(videoWidth / videoOptions.ratio); // get new player height
2353        $videoPlayer
2354          .width(videoWidth)
2355          .height(desktopHeight)
2356          .css({
2357            left: 0,
2358            top: (desktopHeight - desktopHeight) / 2
2359          }); // player height is greater, offset top; reset left
2360      }
2361
2362      $videoPlayer.prepareTransition();
2363      $videoWrapper.addClass(classes.loaded);
2364    }
2365  }
2366
2367  function unsetBackgroundVideo(playerId) {
2368    // Switch the background video to a chrome-only player once played
2369    $('#' + playerId)
2370      .removeClass(classes.backgroundVideo)
2371      .addClass(classes.videoWithImage);
2372
2373    setTimeout(function() {
2374      $('#' + playerId).removeAttr('style');
2375    }, 600);
2376
2377    videos[playerId].$videoWrapper
2378      .removeClass(classes.backgroundVideoWrapper)
2379      .addClass(classes.playing);
2380
2381    videos[playerId].status = 'open';
2382  }
2383
2384  function setBackgroundVideo(playerId) {
2385    $('#' + playerId)
2386      .removeClass(classes.videoWithImage)
2387      .addClass(classes.backgroundVideo);
2388
2389    videos[playerId].$videoWrapper.addClass(classes.backgroundVideoWrapper);
2390
2391    videos[playerId].status = 'background';
2392    sizeBackgroundVideo($('#' + playerId));
2393  }
2394
2395  function isMobile() {
2396    return $(window).width() < 750 || window.mobileCheck();
2397  }
2398
2399  function initEvents() {
2400    $(document).on('click.videoPlayer', selectors.playVideoBtn, function(evt) {
2401      var playerId = $(evt.currentTarget).data('controls');
2402
2403      startVideoOnClick(playerId);
2404    });
2405
2406    $(document).on('click.videoPlayer', selectors.closeVideoBtn, function(evt) {
2407      var playerId = $(evt.currentTarget).data('controls');
2408
2409      $(evt.currentTarget).blur();
2410      closeVideo(playerId);
2411      toggleExpandVideo(playerId, false);
2412    });
2413
2414    $(document).on('click.videoPlayer', selectors.pauseVideoBtn, function(evt) {
2415      var playerId = $(evt.currentTarget).data('controls');
2416      togglePause(playerId);
2417    });
2418
2419    // Listen to resize to keep a background-size:cover-like layout
2420    $(window).on(
2421      'resize.videoPlayer',
2422      $.debounce(200, function() {
2423        if (!youtubeLoaded) return;
2424        var key;
2425        var fullscreen = window.innerHeight === screen.height;
2426
2427        sizeBackgroundVideos();
2428
2429        if (isMobile()) {
2430          for (key in videos) {
2431            if (videos.hasOwnProperty(key)) {
2432              if (videos[key].$videoWrapper.hasClass(classes.playing)) {
2433                if (!fullscreen) {
2434                  pauseVideo(key);
2435                  setAsPaused(videos[key]);
2436                }
2437              }
2438              videos[key].$videoWrapper.height(
2439                $(document).width() / videoOptions.ratio
2440              );
2441            }
2442          }
2443          setAutoplaySupport(false);
2444        } else {
2445          setAutoplaySupport(true);
2446          for (key in videos) {
2447            if (
2448              videos[key].$videoWrapper.find('.' + classes.videoWithImage)
2449                .length
2450            ) {
2451              continue;
2452            }
2453            videoPlayers[key].playVideo();
2454            setAsPlaying(videos[key]);
2455          }
2456        }
2457      })
2458    );
2459
2460    $(window).on(
2461      'scroll.videoPlayer',
2462      $.debounce(50, function() {
2463        if (!youtubeLoaded) return;
2464
2465        for (var key in videos) {
2466          if (videos.hasOwnProperty(key)) {
2467            var $videoWrapper = videos[key].$videoWrapper;
2468
2469            // Close the video if more than 75% of it is scrolled out of view
2470            if (
2471              $videoWrapper.hasClass(classes.playing) &&
2472              ($videoWrapper.offset().top + $videoWrapper.height() * 0.75 <
2473                $(window).scrollTop() ||
2474                $videoWrapper.offset().top + $videoWrapper.height() * 0.25 >
2475                  $(window).scrollTop() + $(window).height())
2476            ) {
2477              closeVideo(key);
2478              toggleExpandVideo(key, false);
2479            }
2480          }
2481        }
2482      })
2483    );
2484  }
2485
2486  function createPlayer(key) {
2487    var args = $.extend({}, videoOptions, videos[key]);
2488    args.playerVars.controls = args.controls;
2489    videoPlayers[key] = new YT.Player(key, args);
2490  }
2491
2492  function removeEvents() {
2493    $(document).off('.videoPlayer');
2494    $(window).off('.videoPlayer');
2495  }
2496
2497  function setButtonLabels($videoWrapper, title) {
2498    var $playButtons = $videoWrapper.find(selectors.playVideoBtn);
2499    var $closeButton = $videoWrapper.find(selectors.closeVideoBtn);
2500    var $pauseButton = $videoWrapper.find(selectors.pauseVideoBtn);
2501    var $closeButtonText = $closeButton.find(selectors.fallbackText);
2502    var $pauseButtonStopText = $pauseButton
2503      .find(selectors.pauseVideoStop)
2504      .find(selectors.fallbackText);
2505    var $pauseButtonResumeText = $pauseButton
2506      .find(selectors.pauseVideoResume)
2507      .find(selectors.fallbackText);
2508
2509    // Insert the video title retrieved from YouTube into the instructional text
2510    // for each button
2511    $playButtons.each(function() {
2512      var $playButton = $(this);
2513      var $playButtonText = $playButton.find(selectors.fallbackText);
2514
2515      $playButtonText.text(
2516        $playButtonText.text().replace('[video_title]', title)
2517      );
2518    });
2519    $closeButtonText.text(
2520      $closeButtonText.text().replace('[video_title]', title)
2521    );
2522    $pauseButtonStopText.text(
2523      $pauseButtonStopText.text().replace('[video_title]', title)
2524    );
2525    $pauseButtonResumeText.text(
2526      $pauseButtonResumeText.text().replace('[video_title]', title)
2527    );
2528  }
2529
2530  return {
2531    init: init,
2532    editorLoadVideo: editorLoadVideo,
2533    loadVideos: loadVideos,
2534    playVideo: customPlayVideo,
2535    pauseVideo: pauseVideo,
2536    removeEvents: removeEvents
2537  };
2538})();
2539
2540window.theme = window.theme || {};
2541
2542theme.FormStatus = (function() {
2543  var selectors = {
2544    statusMessage: '[data-form-status]'
2545  };
2546
2547  function init() {
2548    this.$statusMessage = $(selectors.statusMessage);
2549
2550    if (!this.$statusMessage) return;
2551
2552    this.$statusMessage.attr('tabindex', -1).focus();
2553
2554    this.$statusMessage.on('blur', handleBlur.bind(this));
2555  }
2556
2557  function handleBlur() {
2558    this.$statusMessage.removeAttr('tabindex');
2559  }
2560
2561  return {
2562    init: init
2563  };
2564})();
2565
2566theme.Hero = (function() {
2567  var classes = {
2568    indexSectionFlush: 'index-section--flush'
2569  };
2570
2571  var selectors = {
2572    heroFixedWidthContent: '.hero-fixed-width__content',
2573    heroFixedWidthImage: '.hero-fixed-width__image'
2574  };
2575
2576  function hero(el, sectionId) {
2577    this.$hero = $(el);
2578    this.layout = this.$hero.data('layout');
2579    var $parentSection = $('#shopify-section-' + sectionId);
2580    var $heroContent = $parentSection.find(selectors.heroFixedWidthContent);
2581    var $heroImage = $parentSection.find(selectors.heroFixedWidthImage);
2582
2583    if (this.layout !== 'fixed_width') {
2584      return;
2585    }
2586
2587    $parentSection.removeClass(classes.indexSectionFlush);
2588    heroFixedHeight();
2589    $(window).resize(
2590      $.debounce(50, function() {
2591        heroFixedHeight();
2592      })
2593    );
2594
2595    function heroFixedHeight() {
2596      var contentHeight = $heroContent.height() + 50;
2597      var imageHeight = $heroImage.height();
2598
2599      if (contentHeight > imageHeight) {
2600        $heroImage.css('min-height', contentHeight);
2601      }
2602    }
2603  }
2604
2605  return hero;
2606})();
2607
2608
2609/* ================ TEMPLATES ================ */
2610(function() {
2611  var $filterBy = $('#BlogTagFilter');
2612
2613  if (!$filterBy.length) {
2614    return;
2615  }
2616
2617  $filterBy.on('change', function() {
2618    location.href = $(this).val();
2619  });
2620})();
2621
2622window.theme = theme || {};
2623
2624theme.customerTemplates = (function() {
2625  var selectors = {
2626    RecoverHeading: '#RecoverHeading',
2627    RecoverEmail: '#RecoverEmail',
2628    LoginHeading: '#LoginHeading'
2629  };
2630
2631  function initEventListeners() {
2632    this.$RecoverHeading = $(selectors.RecoverHeading);
2633    this.$RecoverEmail = $(selectors.RecoverEmail);
2634    this.$LoginHeading = $(selectors.LoginHeading);
2635
2636    // Show reset password form
2637    $('#RecoverPassword').on(
2638      'click',
2639      function(evt) {
2640        evt.preventDefault();
2641        showRecoverPasswordForm();
2642        this.$RecoverHeading.attr('tabindex', '-1').focus();
2643      }.bind(this)
2644    );
2645
2646    // Hide reset password form
2647    $('#HideRecoverPasswordLink').on(
2648      'click',
2649      function(evt) {
2650        evt.preventDefault();
2651        hideRecoverPasswordForm();
2652        this.$LoginHeading.attr('tabindex', '-1').focus();
2653      }.bind(this)
2654    );
2655
2656    this.$RecoverHeading.on('blur', function() {
2657      $(this).removeAttr('tabindex');
2658    });
2659
2660    this.$LoginHeading.on('blur', function() {
2661      $(this).removeAttr('tabindex');
2662    });
2663  }
2664
2665  /**
2666   *
2667   *  Show/Hide recover password form
2668   *
2669   */
2670
2671  function showRecoverPasswordForm() {
2672    $('#RecoverPasswordForm').removeClass('hide');
2673    $('#CustomerLoginForm').addClass('hide');
2674
2675    if (this.$RecoverEmail.attr('aria-invalid') === 'true') {
2676      this.$RecoverEmail.focus();
2677    }
2678  }
2679
2680  function hideRecoverPasswordForm() {
2681    $('#RecoverPasswordForm').addClass('hide');
2682    $('#CustomerLoginForm').removeClass('hide');
2683  }
2684
2685  /**
2686   *
2687   *  Show reset password success message
2688   *
2689   */
2690  function resetPasswordSuccess() {
2691    var $formState = $('.reset-password-success');
2692
2693    // check if reset password form was successfully submited.
2694    if (!$formState.length) {
2695      return;
2696    }
2697
2698    // show success message
2699    $('#ResetSuccess')
2700      .removeClass('hide')
2701      .focus();
2702  }
2703
2704  /**
2705   *
2706   *  Show/hide customer address forms
2707   *
2708   */
2709  function customerAddressForm() {
2710    var $newAddressForm = $('#AddressNewForm');
2711    var $newAddressFormButton = $('#AddressNewButton');
2712
2713    if (!$newAddressForm.length) {
2714      return;
2715    }
2716
2717    // Initialize observers on address selectors, defined in shopify_common.js
2718    if (Shopify) {
2719      // eslint-disable-next-line no-new
2720      new Shopify.CountryProvinceSelector(
2721        'AddressCountryNew',
2722        'AddressProvinceNew',
2723        {
2724          hideElement: 'AddressProvinceContainerNew'
2725        }
2726      );
2727    }
2728
2729    // Initialize each edit form's country/province selector
2730    $('.address-country-option').each(function() {
2731      var formId = $(this).data('form-id');
2732      var countrySelector = 'AddressCountry_' + formId;
2733      var provinceSelector = 'AddressProvince_' + formId;
2734      var containerSelector = 'AddressProvinceContainer_' + formId;
2735
2736      // eslint-disable-next-line no-new
2737      new Shopify.CountryProvinceSelector(countrySelector, provinceSelector, {
2738        hideElement: containerSelector
2739      });
2740    });
2741
2742    // Toggle new/edit address forms
2743    $('.address-new-toggle').on('click', function() {
2744      var isExpanded = $newAddressFormButton.attr('aria-expanded') === 'true';
2745
2746      $newAddressForm.toggleClass('hide');
2747      $newAddressFormButton.attr('aria-expanded', !isExpanded).focus();
2748    });
2749
2750    $('.address-edit-toggle').on('click', function() {
2751      var formId = $(this).data('form-id');
2752      var $editButton = $('#EditFormButton_' + formId);
2753      var $editAddress = $('#EditAddress_' + formId);
2754      var isExpanded = $editButton.attr('aria-expanded') === 'true';
2755
2756      $editAddress.toggleClass('hide');
2757      $editButton.attr('aria-expanded', !isExpanded).focus();
2758    });
2759
2760    $('.address-delete').on('click', function() {
2761      var $el = $(this);
2762      var target = $el.data('target');
2763      var confirmMessage = $el.data('confirm-message');
2764
2765      // eslint-disable-next-line no-alert
2766      if (
2767        confirm(
2768          confirmMessage || 'Are you sure you wish to delete this address?'
2769        )
2770      ) {
2771        Shopify.postLink(target, {
2772          parameters: { _method: 'delete' }
2773        });
2774      }
2775    });
2776  }
2777
2778  /**
2779   *
2780   *  Check URL for reset password hash
2781   *
2782   */
2783  function checkUrlHash() {
2784    var hash = window.location.hash;
2785
2786    // Allow deep linking to recover password form
2787    if (hash === '#recover') {
2788      showRecoverPasswordForm.bind(this)();
2789    }
2790  }
2791
2792  return {
2793    init: function() {
2794      initEventListeners();
2795      checkUrlHash();
2796      resetPasswordSuccess();
2797      customerAddressForm();
2798    }
2799  };
2800})();
2801
2802
2803/*================ SECTIONS ================*/
2804window.theme = window.theme || {};
2805
2806theme.Cart = (function() {
2807  var selectors = {
2808    cartCount: '[data-cart-count]',
2809    cartCountBubble: '[data-cart-count-bubble]',
2810    cartDiscount: '[data-cart-discount]',
2811    cartDiscountTitle: '[data-cart-discount-title]',
2812    cartDiscountAmount: '[data-cart-discount-amount]',
2813    cartDiscountWrapper: '[data-cart-discount-wrapper]',
2814    cartErrorMessage: '[data-cart-error-message]',
2815    cartErrorMessageWrapper: '[data-cart-error-message-wrapper]',
2816    cartItem: '[data-cart-item]',
2817    cartItemDetails: '[data-cart-item-details]',
2818    cartItemDiscount: '[data-cart-item-discount]',
2819    cartItemDiscountedPriceGroup: '[data-cart-item-discounted-price-group]',
2820    cartItemDiscountTitle: '[data-cart-item-discount-title]',
2821    cartItemDiscountAmount: '[data-cart-item-discount-amount]',
2822    cartItemDiscountList: '[data-cart-item-discount-list]',
2823    cartItemFinalPrice: '[data-cart-item-final-price]',
2824    cartItemImage: '[data-cart-item-image]',
2825    cartItemLinePrice: '[data-cart-item-line-price]',
2826    cartItemOriginalPrice: '[data-cart-item-original-price]',
2827    cartItemPrice: '[data-cart-item-price]',
2828    cartItemPriceList: '[data-cart-item-price-list]',
2829    cartItemProperty: '[data-cart-item-property]',
2830    cartItemPropertyName: '[data-cart-item-property-name]',
2831    cartItemPropertyValue: '[data-cart-item-property-value]',
2832    cartItemRegularPriceGroup: '[data-cart-item-regular-price-group]',
2833    cartItemRegularPrice: '[data-cart-item-regular-price]',
2834    cartItemTitle: '[data-cart-item-title]',
2835    cartItemOption: '[data-cart-item-option]',
2836    cartLineItems: '[data-cart-line-items]',
2837    cartNote: '[data-cart-notes]',
2838    cartQuantityErrorMessage: '[data-cart-quantity-error-message]',
2839    cartQuantityErrorMessageWrapper:
2840      '[data-cart-quantity-error-message-wrapper]',
2841    cartRemove: '[data-cart-remove]',
2842    cartStatus: '[data-cart-status]',
2843    cartSubtotal: '[data-cart-subtotal]',
2844    cartTableCell: '[data-cart-table-cell]',
2845    cartWrapper: '[data-cart-wrapper]',
2846    emptyPageContent: '[data-empty-page-content]',
2847    quantityInput: '[data-quantity-input]',
2848    quantityInputMobile: '[data-quantity-input-mobile]',
2849    quantityInputDesktop: '[data-quantity-input-desktop]',
2850    quantityLabelMobile: '[data-quantity-label-mobile]',
2851    quantityLabelDesktop: '[data-quantity-label-desktop]',
2852    inputQty: '[data-quantity-input]',
2853    thumbnails: '.cart__image',
2854    unitPrice: '[data-unit-price]',
2855    unitPriceBaseUnit: '[data-unit-price-base-unit]',
2856    unitPriceGroup: '[data-unit-price-group]'
2857  };
2858
2859  var classes = {
2860    cartNoCookies: 'cart--no-cookies',
2861    cartRemovedProduct: 'cart__removed-product',
2862    hide: 'hide',
2863    inputError: 'input--error'
2864  };
2865
2866  var attributes = {
2867    cartItemIndex: 'data-cart-item-index',
2868    cartItemKey: 'data-cart-item-key',
2869    cartItemQuantity: 'data-cart-item-quantity',
2870    cartItemTitle: 'data-cart-item-title',
2871    cartItemUrl: 'data-cart-item-url',
2872    quantityItem: 'data-quantity-item'
2873  };
2874
2875  var mediumUpQuery = '(min-width: ' + theme.breakpoints.medium + 'px)';
2876
2877  function Cart(container) {
2878    this.$container = $(container);
2879    this.$thumbnails = $(selectors.thumbnails, this.$container);
2880    this.ajaxEnabled = this.$container.data('ajax-enabled');
2881
2882    if (!this.cookiesEnabled()) {
2883      this.$container.addClass(classes.cartNoCookies);
2884    }
2885
2886    this.$thumbnails.css('cursor', 'pointer');
2887    this.$container.on(
2888      'click',
2889      selectors.thumbnails,
2890      this._handleThumbnailClick
2891    );
2892
2893    this.$container.on(
2894      'change',
2895      selectors.inputQty,
2896      $.debounce(500, this._handleInputQty.bind(this))
2897    );
2898
2899    this.mql = window.matchMedia(mediumUpQuery);
2900    this.mql.addListener(this.setQuantityFormControllers.bind(this));
2901    this.setQuantityFormControllers();
2902
2903    if (this.ajaxEnabled) {
2904      /**
2905       * Because the entire cart is recreated when a cart item is updated,
2906       * we cannot cache the elements in the cart. Instead, we add the event
2907       * listeners on the cart's container to allow us to retain the event
2908       * listeners after rebuilding the cart when an item is updated.
2909       */
2910
2911      this.$container.on(
2912        'change',
2913        selectors.cartNote,
2914        this._onNoteChange.bind(this)
2915      );
2916
2917      this.$container.on(
2918        'click',
2919        selectors.cartRemove,
2920        this._onRemoveItem.bind(this)
2921      );
2922
2923      this._setupCartTemplates();
2924    }
2925  }
2926
2927  Cart.prototype = _.assignIn({}
2927, Cart.prototype, {
2928    _setupCartTemplates: function() {
2929      this.$itemTemplate = $(selectors.cartItem, this.$container)
2930        .first()
2931        .clone();
2932      this.$itemDiscountTemplate = $(
2933        selectors.cartItemDiscount,
2934        this.$itemTemplate
2935      ).clone();
2936      this.$itemOptionTemplate = $(
2937        selectors.cartItemOption,
2938        this.$itemTemplate
2939      ).clone();
2940      this.$itemPropertyTemplate = $(
2941        selectors.cartItemProperty,
2942        this.$itemTemplate
2943      ).clone();
2944      this.$itemPriceListTemplate = $(
2945        selectors.cartItemPriceList,
2946        this.$itemTemplate
2947      ).clone();
2948      this.$itemLinePriceTemplate = $(
2949        selectors.cartItemLinePrice,
2950        this.$itemTemplate
2951      ).clone();
2952      this.$cartDiscountTemplate = $(
2953        selectors.cartDiscount,
2954        this.$container
2955      ).clone();
2956    },
2957
2958    _handleInputQty: function(evt) {
2959      var $input = $(evt.target);
2960      var itemIndex = $input.data('quantity-item');
2961      var $itemElement = $input.closest(selectors.cartItem);
2962      var $itemQtyInputs = $('[data-quantity-item=' + itemIndex + ']');
2963      var value = parseInt($input.val());
2964      var isValidValue = !(value < 0 || isNaN(value));
2965      $itemQtyInputs.val(value);
2966
2967      this._hideCartError();
2968      this._hideQuantityErrorMessage();
2969
2970      if (!isValidValue) {
2971        this._showQuantityErrorMessages($itemElement);
2972        return;
2973      }
2974
2975      if (isValidValue && this.ajaxEnabled) {
2976        this._updateItemQuantity(
2977          itemIndex,
2978          $itemElement,
2979          $itemQtyInputs,
2980          value
2981        );
2982      }
2983    },
2984
2985    _updateItemQuantity: function(
2986      itemIndex,
2987      $itemElement,
2988      $itemQtyInputs,
2989      value
2990    ) {
2991      var key = $itemElement.attr(attributes.cartItemKey);
2992      var index = $itemElement.attr(attributes.cartItemIndex);
2993
2994      var params = {
2995        url: '/cart/change.js',
2996        data: { quantity: value, line: index },
2997        dataType: 'json'
2998      };
2999
3000      $.post(params)
3001        .done(
3002          function(state) {
3003            if (state.item_count === 0) {
3004              this._emptyCart();
3005            } else {
3006              this._createCart(state);
3007
3008              if (value === 0) {
3009                this._showRemoveMessage($itemElement.clone());
3010              } else {
3011                var $lineItem = $('[data-cart-item-key="' + key + '"]');
3012                var item = this.getItem(key, state);
3013
3014                $(selectors.quantityInput, $lineItem).focus();
3015                this._updateLiveRegion(item);
3016              }
3017            }
3018
3019            this._setCartCountBubble(state.item_count);
3020          }.bind(this)
3021        )
3022        .fail(
3023          function() {
3024            this._showCartError($itemQtyInputs);
3025          }.bind(this)
3026        );
3027    },
3028
3029    getItem: function(key, state) {
3030      return state.items.find(function(item) {
3031        return item.key === key;
3032      });
3033    },
3034
3035    _liveRegionText: function(item) {
3036      // Dummy content for live region
3037      var liveRegionText =
3038        theme.strings.update +
3039        ': [QuantityLabel]: [Quantity], [Regular] [$$] [DiscountedPrice] [$]. [PriceInformation]';
3040
3041      // Update Quantity
3042      liveRegionText = liveRegionText
3043        .replace('[QuantityLabel]', theme.strings.quantity)
3044        .replace('[Quantity]', item.quantity);
3045
3046      // Update pricing information
3047      var regularLabel = '';
3048      var regularPrice = theme.Currency.formatMoney(
3049        item.original_line_price,
3050        theme.moneyFormat
3051      );
3052      var discountLabel = '';
3053      var discountPrice = '';
3054      var discountInformation = '';
3055
3056      if (item.original_line_price > item.final_line_price) {
3057        regularLabel = theme.strings.regularTotal;
3058
3059        discountLabel = theme.strings.discountedTotal;
3060        discountPrice = theme.Currency.formatMoney(
3061          item.final_line_price,
3062          theme.moneyFormat
3063        );
3064
3065        discountInformation = theme.strings.priceColumn;
3066      }
3067
3068      liveRegionText = liveRegionText
3069        .replace('[Regular]', regularLabel)
3070        .replace('[$$]', regularPrice)
3071        .replace('[DiscountedPrice]', discountLabel)
3072        .replace('[$]', discountPrice)
3073        .replace('[PriceInformation]', discountInformation)
3074        .trim();
3075
3076      return liveRegionText;
3077    },
3078
3079    _updateLiveRegion: function(item) {
3080      var $liveRegion = $(selectors.cartStatus);
3081      $liveRegion.html(this._liveRegionText(item)).attr('aria-hidden', false);
3082
3083      // hide content from accessibility tree after announcement
3084      setTimeout(function() {
3085        $liveRegion.attr('aria-hidden', true);
3086      }, 1000);
3087    },
3088
3089    _createCart: function(state) {
3090      var cartDiscountList = this._createCartDiscountList(state);
3091
3092      $(selectors.cartLineItems, this.$container).html(
3093        this._createLineItemList(state)
3094      );
3095
3096      this.setQuantityFormControllers();
3097
3098      $(selectors.cartNote, this.$container).val(state.note);
3099
3100      if (cartDiscountList.length === 0) {
3101        $(selectors.cartDiscountWrapper, this.$container)
3102          .html('')
3103          .addClass(classes.hide);
3104      } else {
3105        $(selectors.cartDiscountWrapper, this.$container)
3106          .html(cartDiscountList)
3107          .removeClass(classes.hide);
3108      }
3109
3110      $(selectors.cartSubtotal, this.$container).html(
3111        theme.Currency.formatMoney(
3112          state.total_price,
3113          theme.moneyFormatWithCurrency
3114        )
3115      );
3116    },
3117
3118    _createCartDiscountList: function(cart) {
3119      return $.map(
3120        cart.cart_level_discount_applications,
3121        function(discount) {
3122          var $discount = this.$cartDiscountTemplate.clone();
3123          $discount.find(selectors.cartDiscountTitle).text(discount.title);
3124          $discount
3125            .find(selectors.cartDiscountAmount)
3126            .html(
3127              theme.Currency.formatMoney(
3128                discount.total_allocated_amount,
3129                theme.moneyFormat
3130              )
3131            );
3132          return $discount[0];
3133        }.bind(this)
3134      );
3135    },
3136
3137    _createLineItemList: function(state) {
3138      return $.map(
3139        state.items,
3140        function(item, index) {
3141          var $item = this.$itemTemplate.clone();
3142          var $itemPriceList = this.$itemPriceListTemplate.clone();
3143
3144          this._setLineItemAttributes($item, item, index);
3145          this._setLineItemImage($item, item.featured_image);
3146
3147          $(selectors.cartItemTitle, $item)
3148            .text(item.product_title)
3149            .attr('href', item.url);
3150
3151          var productDetailsList = this._createProductDetailsList(
3152            item.product_has_only_default_variant,
3153            item.options_with_values,
3154            item.properties
3155          );
3156          this._setProductDetailsList($item, productDetailsList);
3157
3158          this._setItemRemove($item, item.title);
3159
3160          $itemPriceList.html(
3161            this._createItemPrice(
3162              item.original_price,
3163              item.final_price,
3164              this.$itemPriceListTemplate
3165            )
3166          );
3167
3168          if (item.unit_price_measurement) {
3169            $itemPriceList.append(
3170              this._createUnitPrice(
3171                item.unit_price,
3172                item.unit_price_measurement,
3173                this.$itemPriceListTemplate
3174              )
3175            );
3176          }
3177
3178          this._setItemPrice($item, $itemPriceList);
3179
3180          var itemDiscountList = this._createItemDiscountList(item);
3181          this._setItemDiscountList($item, itemDiscountList);
3182
3183          this._setQuantityInputs($item, item, index);
3184
3185          var itemLinePrice = this._createItemPrice(
3186            item.original_line_price,
3187            item.final_line_price,
3188            this.$itemLinePriceTemplate
3189          );
3190          this._setItemLinePrice($item, itemLinePrice);
3191
3192          return $item[0];
3193        }.bind(this)
3194      );
3195    },
3196
3197    _setLineItemAttributes: function($item, item, index) {
3198      $item
3199        .attr(attributes.cartItemKey, item.key)
3200        .attr(attributes.cartItemUrl, item.url)
3201        .attr(attributes.cartItemTitle, item.title)
3202        .attr(attributes.cartItemIndex, index + 1)
3203        .attr(attributes.cartItemQuantity, item.quantity);
3204    },
3205
3206    _setLineItemImage: function($item, featuredImage) {
3207      var $image = $(selectors.cartItemImage, $item);
3208
3209      var sizedImageUrl =
3210        featuredImage.url !== null
3211          ? theme.Images.getSizedImageUrl(featuredImage.url, 'x190')
3212          : null;
3213
3214      if (sizedImageUrl) {
3215        $image
3216          .attr('alt', featuredImage.alt)
3217          .attr('src', sizedImageUrl)
3218          .removeClass(classes.hide);
3219      } else {
3220        $image.remove();
3221      }
3222    },
3223
3224    _setProductDetailsList: function($item, productDetailsList) {
3225      var $itemDetails = $(selectors.cartItemDetails, $item);
3226
3227      if (productDetailsList.length === 0) {
3228        $itemDetails.addClass(classes.hide).text('');
3229      } else {
3230        $itemDetails.removeClass(classes.hide).html(productDetailsList);
3231      }
3232    },
3233
3234    _setItemPrice: function($item, price) {
3235      $(selectors.cartItemPrice, $item).html(price);
3236    },
3237
3238    _setItemDiscountList: function($item, discountList) {
3239      var $itemDiscountList = $(selectors.cartItemDiscountList, $item);
3240
3241      if (discountList.length === 0) {
3242        $itemDiscountList.html('').addClass(classes.hide);
3243      } else {
3244        $itemDiscountList.html(discountList).removeClass(classes.hide);
3245      }
3246    },
3247
3248    _setItemRemove: function($item, title) {
3249      $(selectors.cartRemove, $item).attr(
3250        'aria-label',
3251        theme.strings.removeLabel.replace('[product]', title)
3252      );
3253    },
3254
3255    _setQuantityInputs: function($item, item, index) {
3256      $(selectors.quantityInputMobile, $item)
3257        .attr('id', 'updates_' + item.key)
3258        .attr(attributes.quantityItem, index + 1)
3259        .val(item.quantity);
3260
3261      $(selectors.quantityInputDesktop, $item)
3262        .attr('id', 'updates_large_' + item.key)
3263        .attr(attributes.quantityItem, index + 1)
3264        .val(item.quantity);
3265
3266      $(selectors.quantityLabelMobile, $item).attr(
3267        'for',
3268        'updates_' + item.key
3269      );
3270
3271      $(selectors.quantityLabelDesktop, $item).attr(
3272        'for',
3273        'updates_large_' + item.key
3274      );
3275    },
3276
3277    setQuantityFormControllers: function() {
3278      if (this.mql.matches) {
3279        $(selectors.quantityInputDesktop).attr('name', 'updates[]');
3280        $(selectors.quantityInputMobile).removeAttr('name');
3281      } else {
3282        $(selectors.quantityInputMobile).attr('name', 'updates[]');
3283        $(selectors.quantityInputDesktop).removeAttr('name');
3284      }
3285    },
3286
3287    _setItemLinePrice: function($item, price) {
3288      $(selectors.cartItemLinePrice, $item).html(price);
3289    },
3290
3291    _createProductDetailsList: function(
3292      product_has_only_default_variant,
3293      options,
3294      properties
3295    ) {
3296      var optionsPropertiesHTML = [];
3297
3298      if (!product_has_only_default_variant) {
3299        optionsPropertiesHTML = optionsPropertiesHTML.concat(
3300          this._getOptionList(options)
3301        );
3302      }
3303
3304      if (properties !== null && Object.keys(properties).length !== 0) {
3305        optionsPropertiesHTML = optionsPropertiesHTML.concat(
3306          this._getPropertyList(properties)
3307        );
3308      }
3309
3310      return optionsPropertiesHTML;
3311    },
3312
3313    _getOptionList: function(options) {
3314      return $.map(
3315        options,
3316        function(option) {
3317          var $optionElement = this.$itemOptionTemplate.clone();
3318
3319          $optionElement
3320            .text(option.name + ': ' + option.value)
3321            .removeClass(classes.hide);
3322
3323          return $optionElement[0];
3324        }.bind(this)
3325      );
3326    },
3327
3328    _getPropertyList: function(properties) {
3329      var propertiesArray =
3330        properties !== null ? Object.entries(properties) : [];
3331
3332      return $.map(
3333        propertiesArray,
3334        function(property) {
3335          var $propertyElement = this.$itemPropertyTemplate.clone();
3336
3337          // Line item properties prefixed with an underscore are not to be displayed
3338          if (property[0].charAt(0) === '_') return;
3339
3340          // if the property value has a length of 0 (empty), don't display it
3341          if (property[1].length === 0) return;
3342
3343          $propertyElement
3344            .find(selectors.cartItemPropertyName)
3345            .text(property[0]);
3346
3347          if (property[0].indexOf('/uploads/') === -1) {
3348            $propertyElement
3349              .find(selectors.cartItemPropertyValue)
3350              .text(': ' + property[1]);
3351          } else {
3352            $propertyElement
3353              .find(selectors.cartItemPropertyValue)
3354              .html(
3355                ': <a href="' +
3356                  property[1] +
3357                  '"> ' +
3358                  property[1].split('/').pop() +
3359                  '</a>'
3360              );
3361          }
3362
3363          $propertyElement.removeClass(classes.hide);
3364
3365          return $propertyElement[0];
3366        }.bind(this)
3367      );
3368    },
3369
3370    _createItemPrice: function(original_price, final_price, $priceTemplate) {
3371      if (original_price !== final_price) {
3372        var $discountedPrice = $(
3373          selectors.cartItemDiscountedPriceGroup,
3374          $priceTemplate
3375        ).clone();
3376
3377        $(selectors.cartItemOriginalPrice, $discountedPrice).html(
3378          theme.Currency.formatMoney(original_price, theme.moneyFormat)
3379        );
3380        $(selectors.cartItemFinalPrice, $discountedPrice).html(
3381          theme.Currency.formatMoney(final_price, theme.moneyFormat)
3382        );
3383        $discountedPrice.removeClass(classes.hide);
3384
3385        return $discountedPrice[0];
3386      } else {
3387        var $regularPrice = $(
3388          selectors.cartItemRegularPriceGroup,
3389          $priceTemplate
3390        ).clone();
3391
3392        $(selectors.cartItemRegularPrice, $regularPrice).html(
3393          theme.Currency.formatMoney(original_price, theme.moneyFormat)
3394        );
3395
3396        $regularPrice.removeClass(classes.hide);
3397
3398        return $regularPrice[0];
3399      }
3400    },
3401
3402    _createUnitPrice: function(
3403      unitPrice,
3404      unitPriceMeasurement,
3405      $itemPriceGroup
3406    ) {
3407      var $unitPriceGroup = $(
3408        selectors.unitPriceGroup,
3409        $itemPriceGroup
3410      ).clone();
3411
3412      var unitPriceBaseUnit =
3413        (unitPriceMeasurement.reference_value !== 1
3414          ? unitPriceMeasurement.reference_value
3415          : '') + unitPriceMeasurement.reference_unit;
3416
3417      $(selectors.unitPriceBaseUnit, $unitPriceGroup).text(unitPriceBaseUnit);
3418      $(selectors.unitPrice, $unitPriceGroup).html(
3419        theme.Currency.formatMoney(unitPrice, theme.moneyFormat)
3420      );
3421
3422      $unitPriceGroup.removeClass(classes.hide);
3423
3424      return $unitPriceGroup[0];
3425    },
3426
3427    _createItemDiscountList: function(item) {
3428      return $.map(
3429        item.line_level_discount_allocations,
3430        function(discount) {
3431          var $discount = this.$itemDiscountTemplate.clone();
3432          $discount
3433            .find(selectors.cartItemDiscountTitle)
3434            .text(discount.discount_application.title);
3435          $discount
3436            .find(selectors.cartItemDiscountAmount)
3437            .html(
3438              theme.Currency.formatMoney(discount.amount, theme.moneyFormat)
3439            );
3440          return $discount[0];
3441        }.bind(this)
3442      );
3443    },
3444
3445    _showQuantityErrorMessages: function(itemElement) {
3446      $(selectors.cartQuantityErrorMessage, itemElement).text(
3447        theme.strings.quantityMinimumMessage
3448      );
3449
3450      $(selectors.cartQuantityErrorMessageWrapper, itemElement).removeClass(
3451        classes.hide
3452      );
3453
3454      $(selectors.inputQty, itemElement)
3455        .addClass(classes.inputError)
3456        .focus();
3457    },
3458
3459    _hideQuantityErrorMessage: function() {
3460      var $errorMessages = $(
3461        selectors.cartQuantityErrorMessageWrapper
3462      ).addClass(classes.hide);
3463
3464      $(selectors.cartQuantityErrorMessage, $errorMessages).text('');
3465
3466      $(selectors.inputQty, this.$container).removeClass(classes.inputError);
3467    },
3468
3469    _handleThumbnailClick: function(evt) {
3470      var url = $(evt.target)
3471        .closest(selectors.cartItem)
3472        .data('cart-item-url');
3473
3474      window.location.href = url;
3475    },
3476
3477    _onNoteChange: function(evt) {
3478      var note = evt.currentTarget.value;
3479      this._hideCartError();
3480      this._hideQuantityErrorMessage();
3481
3482      var params = {
3483        url: '/cart/update.js',
3484        data: { note: note },
3485        dataType: 'json'
3486      };
3487
3488      $.post(params).fail(
3489        function() {
3490          this._showCartError(evt.currentTarget);
3491        }.bind(this)
3492      );
3493    },
3494
3495    _showCartError: function(elementToFocus) {
3496      $(selectors.cartErrorMessage).text(theme.strings.cartError);
3497
3498      $(selectors.cartErrorMessageWrapper).removeClass(classes.hide);
3499
3500      elementToFocus.focus();
3501    },
3502
3503    _hideCartError: function() {
3504      $(selectors.cartErrorMessageWrapper).addClass(classes.hide);
3505      $(selectors.cartErrorMessage).text('');
3506    },
3507
3508    _onRemoveItem: function(evt) {
3509      evt.preventDefault();
3510      var $remove = $(evt.target);
3511      var $lineItem = $remove.closest(selectors.cartItem);
3512      var index = $lineItem.attr(attributes.cartItemIndex);
3513      this._hideCartError();
3514
3515      var params = {
3516        url: '/cart/change.js',
3517        data: { quantity: 0, line: index },
3518        dataType: 'json'
3519      };
3520
3521      $.post(params)
3522        .done(
3523          function(state) {
3524            if (state.item_count === 0) {
3525              this._emptyCart();
3526            } else {
3527              this._createCart(state);
3528              this._showRemoveMessage($lineItem.clone());
3529            }
3530
3531            this._setCartCountBubble(state.item_count);
3532          }.bind(this)
3533        )
3534        .fail(
3535          function() {
3536            this._showCartError(null);
3537          }.bind(this)
3538        );
3539    },
3540
3541    _showRemoveMessage: function(lineItem) {
3542      var index = lineItem.data('cart-item-index');
3543      var removeMessage = this._getRemoveMessage(lineItem);
3544      var $lineItemAtIndex;
3545
3546      if (index - 1 === 0) {
3547        $lineItemAtIndex = $('[data-cart-item-index="1"]', this.$container);
3548        $(removeMessage).insertBefore($lineItemAtIndex);
3549      } else {
3550        $lineItemAtIndex = $(
3551          '[data-cart-item-index="' + (index - 1) + '"]',
3552          this.$container
3553        );
3554        removeMessage.insertAfter($lineItemAtIndex);
3555      }
3556      removeMessage.focus();
3557    },
3558
3559    _getRemoveMessage: function(lineItem) {
3560      var formattedMessage = this._formatRemoveMessage(lineItem);
3561
3562      var $tableCell = $(selectors.cartTableCell, lineItem).clone();
3563      $tableCell
3564        .removeClass()
3565        .addClass(classes.cartRemovedProduct)
3566        .attr('colspan', '4')
3567        .html(formattedMessage);
3568
3569      lineItem
3570        .attr('role', 'alert')
3571        .html($tableCell)
3572        .attr('tabindex', '-1');
3573
3574      return lineItem;
3575    },
3576
3577    _formatRemoveMessage: function(lineItem) {
3578      var quantity = lineItem.data('cart-item-quantity');
3579      var url = lineItem.attr(attributes.cartItemUrl);
3580      var title = lineItem.attr(attributes.cartItemTitle);
3581
3582      return theme.strings.removedItemMessage
3583        .replace('[quantity]', quantity)
3584        .replace(
3585          '[link]',
3586          '<a ' +
3587            'href="' +
3588            url +
3589            '" class="text-link text-link--accent">' +
3590            title +
3591            '</a>'
3592        );
3593    },
3594
3595    _setCartCountBubble: function(quantity) {
3596      this.$cartCountBubble =
3597        this.$cartCountBubble || $(selectors.cartCountBubble);
3598      this.$cartCount = this.$cartCount || $(selectors.cartCount);
3599
3600      if (quantity > 0) {
3601        this.$cartCountBubble.removeClass(classes.hide);
3602        this.$cartCount.html(quantity);
3603      } else {
3604        this.$cartCountBubble.addClass(classes.hide);
3605        this.$cartCount.html('');
3606      }
3607    },
3608
3609    _emptyCart: function() {
3610      this.$emptyPageContent =
3611        this.$emptyPageContent ||
3612        $(selectors.emptyPageContent, this.$container);
3613      this.$cartWrapper =
3614        this.$cartWrapper || $(selectors.cartWrapper, this.$container);
3615
3616      this.$emptyPageContent.removeClass(classes.hide);
3617      this.$cartWrapper.addClass(classes.hide);
3618    },
3619
3620    cookiesEnabled: function() {
3621      var cookieEnabled = navigator.cookieEnabled;
3622
3623      if (!cookieEnabled) {
3624        document.cookie = 'testcookie';
3625        cookieEnabled = document.cookie.indexOf('testcookie') !== -1;
3626      }
3627      return cookieEnabled;
3628    }
3629  });
3630
3631  return Cart;
3632})();
3633
3634window.theme = window.theme || {};
3635
3636theme.Filters = (function() {
3637  var settings = {
3638    // Breakpoints from src/stylesheets/global/variables.scss.liquid
3639    mediaQueryMediumUp: 'screen and (min-width: 750px)'
3640  };
3641
3642  var selectors = {
3643    mainContent: '#MainContent',
3644    filterSelection: '#FilterTags',
3645    sortSelection: '#SortBy'
3646  };
3647
3648  var data = {
3649    sortBy: 'data-default-sortby'
3650  };
3651
3652  function Filters(container) {
3653    var $container = (this.$container = $(container));
3654
3655    this.$filterSelect = $(selectors.filterSelection, $container);
3656    this.$sortSelect = $(selectors.sortSelection, $container);
3657    this.$selects = $(selectors.filterSelection, $container).add(
3658      $(selectors.sortSelection, $container)
3659    );
3660
3661    this.defaultSort = this._getDefaultSortValue();
3662    this.$selects.removeClass('hidden');
3663
3664    this.$filterSelect.on('change', this._onFilterChange.bind(this));
3665    this.$sortSelect.on('change', this._onSortChange.bind(this));
3666    this._initBreakpoints();
3667    this._initParams();
3668  }
3669
3670  Filters.prototype = _.assignIn({}, Filters.prototype, {
3671    _initBreakpoints: function() {
3672      var self = this;
3673
3674      enquire.register(settings.mediaQueryMediumUp, {
3675        match: function() {
3676          self._resizeSelect(self.$selects);
3677        }
3678      });
3679    },
3680
3681    _initParams: function() {
3682      self.queryParams = {};
3683      if (location.search.length) {
3684        var aKeyValue;
3685        var aCouples = location.search.substr(1).split('&');
3686        for (var i = 0; i < aCouples.length; i++) {
3687          aKeyValue = aCouples[i].split('=');
3688          if (aKeyValue.length > 1) {
3689            self.queryParams[
3690              decodeURIComponent(aKeyValue[0])
3691            ] = decodeURIComponent(aKeyValue[1]);
3692          }
3693        }
3694      }
3695    },
3696
3697    _onSortChange: function() {
3698      self.queryParams.sort_by = this._getSortValue();
3699
3700      if (self.queryParams.page) {
3701        delete self.queryParams.page;
3702      }
3703      window.location.search = decodeURIComponent($.param(self.queryParams));
3704    },
3705
3706    _onFilterChange: function() {
3707      document.location.href = this._getFilterValue();
3708    },
3709
3710    _getFilterValue: function() {
3711      return this.$filterSelect.val();
3712    },
3713
3714    _getSortValue: function() {
3715      return this.$sortSelect.val() || this.defaultSort;
3716    },
3717
3718    _getDefaultSortValue: function() {
3719      return this.$sortSelect.attr(data.sortBy);
3720    },
3721
3722    _resizeSelect: function($selection) {
3723      $selection.each(function() {
3724        var $this = $(this);
3725        var arrowWidth = 10;
3726        // create test element
3727        var text = $this.find('option:selected').text();
3728        var $test = $('<span>').html(text);
3729
3730        // add to body, get width, and get out
3731        $test.appendTo('body');
3732        var width = $test.width();
3733        $test.remove();
3734
3735        // set select width
3736        $this.width(width + arrowWidth);
3737      });
3738    },
3739
3740    onUnload: function() {
3741      this.$filterSelect.off('change', this._onFilterChange);
3742      this.$sortSelect.off('change', this._onSortChange);
3743    }
3744  });
3745
3746  return Filters;
3747})();
3748
3749window.theme = window.theme || {};
3750
3751theme.HeaderSection = (function() {
3752  function Header() {
3753    theme.Header.init();
3754    theme.MobileNav.init();
3755    theme.Search.init();
3756  }
3757
3758  Header.prototype = _.assignIn({}, Header.prototype, {
3759    onUnload: function() {
3760      theme.Header.unload();
3761    }
3762  });
3763
3764  return Header;
3765})();
3766
3767theme.Maps = (function() {
3768  var config = {
3769    zoom: 14
3770  };
3771  var apiStatus = null;
3772  var mapsToLoad = [];
3773
3774  var errors = {
3775    addressNoResults: theme.strings.addressNoResults,
3776    addressQueryLimit: theme.strings.addressQueryLimit,
3777    addressError: theme.strings.addressError,
3778    authError: theme.strings.authError
3779  };
3780
3781  var selectors = {
3782    section: '[data-section-type="map"]',
3783    map: '[data-map]',
3784    mapOverlay: '[data-map-overlay]'
3785  };
3786
3787  var classes = {
3788    mapError: 'map-section--load-error',
3789    errorMsg: 'map-section__error errors text-center'
3790  };
3791
3792  // Global function called by Google on auth errors.
3793  // Show an auto error message on all map instances.
3794  // eslint-disable-next-line camelcase, no-unused-vars
3795  window.gm_authFailure = function() {
3796    if (!Shopify.designMode) {
3797      return;
3798    }
3799
3800    $(selectors.section).addClass(classes.mapError);
3801    $(selectors.map).remove();
3802    $(selectors.mapOverlay).after(
3803      '<div class="' +
3804        classes.errorMsg +
3805        '">' +
3806        theme.strings.authError +
3807        '</div>'
3808    );
3809  };
3810
3811  function Map(container) {
3812    this.$container = $(container);
3813    this.$map = this.$container.find(selectors.map);
3814    this.key = this.$map.data('api-key');
3815
3816    if (typeof this.key === 'undefined') {
3817      return;
3818    }
3819
3820    if (apiStatus === 'loaded') {
3821      this.createMap();
3822    } else {
3823      mapsToLoad.push(this);
3824
3825      if (apiStatus !== 'loading') {
3826        apiStatus = 'loading';
3827        if (typeof window.google === 'undefined') {
3828          $.getScript(
3829            'https://maps.googleapis.com/maps/api/js?key=' + this.key
3830          ).then(function() {
3831            apiStatus = 'loaded';
3832            initAllMaps();
3833          });
3834        }
3835      }
3836    }
3837  }
3838
3839  function initAllMaps() {
3840    // API has loaded, load all Map instances in queue
3841    $.each(mapsToLoad, function(index, instance) {
3842      instance.createMap();
3843    });
3844  }
3845
3846  function geolocate($map) {
3847    var deferred = $.Deferred();
3848    var geocoder = new google.maps.Geocoder();
3849    var address = $map.data('address-setting');
3850
3851    geocoder.geocode({ address: address }, function(results, status) {
3852      if (status !== google.maps.GeocoderStatus.OK) {
3853        deferred.reject(status);
3854      }
3855
3856      deferred.resolve(results);
3857    });
3858
3859    return deferred;
3860  }
3861
3862  Map.prototype = _.assignIn({}, Map.prototype, {
3863    createMap: function() {
3864      var $map = this.$map;
3865
3866      return geolocate($map)
3867        .then(
3868          function(results) {
3869            var mapOptions = {
3870              zoom: config.zoom,
3871              center: results[0].geometry.location,
3872              draggable: false,
3873              clickableIcons: false,
3874              scrollwheel: false,
3875              disableDoubleClickZoom: true,
3876              disableDefaultUI: true
3877            };
3878
3879            var map = (this.map = new google.maps.Map($map[0], mapOptions));
3880            var center = (this.center = map.getCenter());
3881
3882            //eslint-disable-next-line no-unused-vars
3883            var marker = new google.maps.Marker({
3884              map: map,
3885              position: map.getCenter()
3886            });
3887
3888            google.maps.event.addDomListener(
3889              window,
3890              'resize',
3891              $.debounce(250, function() {
3892                google.maps.event.trigger(map, 'resize');
3893                map.setCenter(center);
3894                $map.removeAttr('style');
3895              })
3896            );
3897          }.bind(this)
3898        )
3899        .fail(function() {
3900          var errorMessage;
3901
3902          switch (status) {
3903            case 'ZERO_RESULTS':
3904              errorMessage = errors.addressNoResults;
3905              break;
3906            case 'OVER_QUERY_LIMIT':
3907              errorMessage = errors.addressQueryLimit;
3908              break;
3909            case 'REQUEST_DENIED':
3910              errorMessage = errors.authError;
3911              break;
3912            default:
3913              errorMessage = errors.addressError;
3914              break;
3915          }
3916
3917          // Show errors only to merchant in the editor.
3918          if (Shopify.designMode) {
3919            $map
3920              .parent()
3921              .addClass(classes.mapError)
3922              .html(
3923                '<div class="' +
3924                  classes.errorMsg +
3925                  '">' +
3926                  errorMessage +
3927                  '</div>'
3928              );
3929          }
3930        });
3931    },
3932
3933    onUnload: function() {
3934      if (this.$map.length === 0) {
3935        return;
3936      }
3937      google.maps.event.clearListeners(this.map, 'resize');
3938    }
3939  });
3940
3941  return Map;
3942})();
3943
3944/* eslint-disable no-new */
3945theme.Product = (function() {
3946  function Product(container) {
3947    var $container = (this.$container = $(container));
3948    var sectionId = $container.attr('data-section-id');
3949    this.ajaxEnabled = $container.data('ajax-enabled');
3950
3951    this.settings = {
3952      // Breakpoints from src/stylesheets/global/variables.scss.liquid
3953      mediaQueryMediumUp: 'screen and (min-width: 750px)',
3954      mediaQuerySmall: 'screen and (max-width: 749px)',
3955      bpSmall: false,
3956      enableHistoryState: $container.data('enable-history-state') || false,
3957      namespace: '.slideshow-' + sectionId,
3958      sectionId: sectionId,
3959      sliderActive: false,
3960      zoomEnabled: false
3961    };
3962
3963    this.selectors = {
3964      addToCart: '[data-add-to-cart]',
3965      addToCartText: '[data-add-to-cart-text]',
3966      cartCount: '[data-cart-count]',
3967      cartCountBubble: '[data-cart-count-bubble]',
3968      cartPopup: '[data-cart-popup]',
3969      cartPopupCartQuantity: '[data-cart-popup-cart-quantity]',
3970      cartPopupClose: '[data-cart-popup-close]',
3971      cartPopupDismiss: '[data-cart-popup-dismiss]',
3972      cartPopupImage: '[data-cart-popup-image]',
3973      cartPopupImageWrapper: '[data-cart-popup-image-wrapper]',
3974      cartPopupImagePlaceholder: '[data-cart-popup-image-placeholder]',
3975      cartPopupPlaceholderSize: '[data-placeholder-size]',
3976      cartPopupProductDetails: '[data-cart-popup-product-details]',
3977      cartPopupQuantity: '[data-cart-popup-quantity]',
3978      cartPopupQuantityLabel: '[data-cart-popup-quantity-label]',
3979      cartPopupTitle: '[data-cart-popup-title]',
3980      cartPopupWrapper: '[data-cart-popup-wrapper]',
3981      loader: '[data-loader]',
3982      loaderStatus: '[data-loader-status]',
3983      quantity: '[data-quantity-input]',
3984      SKU: '.variant-sku',
3985      productStatus: '[data-product-status]',
3986      originalSelectorId: '#ProductSelect-' + sectionId,
3987      productForm: '[data-product-form]',
3988      errorMessage: '[data-error-message]',
3989      errorMessageWrapper: '[data-error-message-wrapper]',
3990      productImageWraps: '.product-single__photo',
3991      productThumbImages: '.product-single__thumbnail--' + sectionId,
3992      productThumbs: '.product-single__thumbnails-' + sectionId,
3993      productThumbListItem: '.product-single__thumbnails-item',
3994      productFeaturedImage: '.product-featured-img',
3995      productThumbsWrapper: '.thumbnails-wrapper',
3996      saleLabel: '.product-price__sale-label-' + sectionId,
3997      singleOptionSelector: '.single-option-selector-' + sectionId,
3998      shopifyPaymentButton: '.shopify-payment-button',
3999      priceContainer: '[data-price]',
4000      regularPrice: '[data-regular-price]',
4001      salePrice: '[data-sale-price]',
4002      unitPrice: '[data-unit-price]',
4003      unitPriceBaseUnit: '[data-unit-price-base-unit]'
4004    };
4005
4006    this.classes = {
4007      cartPopupWrapperHidden: 'cart-popup-wrapper--hidden',
4008      hidden: 'hide',
4009      inputError: 'input--error',
4010      productOnSale: 'price--on-sale',
4011      productUnitAvailable: 'price--unit-available',
4012      productUnavailable: 'price--unavailable',
4013      cartImage: 'cart-popup-item__image',
4014      productFormErrorMessageWrapperHidden:
4015        'product-form__error-message-wrapper--hidden',
4016      activeClass: 'active-thumb'
4017    };
4018
4019    this.$quantityInput = $(this.selectors.quantity, $container);
4020    this.$errorMessageWrapper = $(
4021      this.selectors.errorMessageWrapper,
4022      $container
4023    );
4024    this.$addToCart = $(this.selectors.addToCart, $container);
4025    this.$addToCartText = $(this.selectors.addToCartText, this.$addToCart);
4026    this.$shopifyPaymentButton = $(
4027      this.selectors.shopifyPaymentButton,
4028      $container
4029    );
4030
4031    this.$loader = $(this.selectors.loader, this.$addToCart);
4032    this.$loaderStatus = $(this.selectors.loaderStatus, $container);
4033
4034    // Stop parsing if we don't have the product json script tag when loading
4035    // section in the Theme Editor
4036    if (!$('#ProductJson-' + sectionId).html()) {
4037      return;
4038    }
4039
4040    this.productSingleObject = JSON.parse(
4041      document.getElementById('ProductJson-' + sectionId).innerHTML
4042    );
4043
4044    this.settings.zoomEnabled = $(this.selectors.productImageWraps).hasClass(
4045      'js-zoom-enabled'
4046    );
4047
4048    this._initBreakpoints();
4049    this._stringOverrides();
4050    this._initVariants();
4051    this._initImageSwitch();
4052    this._initAddToCart();
4053    this._setActiveThumbnail();
4054  }
4055
4056  Product.prototype = _.assignIn({}, Product.prototype, {
4057    _stringOverrides: function() {
4058      theme.productStrings = theme.productStrings || {};
4059      $.extend(theme.strings, theme.productStrings);
4060    },
4061
4062    _initBreakpoints: function() {
4063      var self = this;
4064
4065      enquire.register(this.settings.mediaQuerySmall, {
4066        match: function() {
4067          // initialize thumbnail slider on mobile if more than three thumbnails
4068          if ($(self.selectors.productThumbImages).length > 3) {
4069            self._initThumbnailSlider();
4070          }
4071
4072          // destroy image zooming if enabled
4073          if (self.settings.zoomEnabled) {
4074            $(self.selectors.productImageWraps).each(function() {
4075              _destroyZoom(this);
4076            });
4077          }
4078
4079          self.settings.bpSmall = true;
4080        },
4081        unmatch: function() {
4082          if (self.settings.sliderActive) {
4083            self._destroyThumbnailSlider();
4084          }
4085
4086          self.settings.bpSmall = false;
4087        }
4088      });
4089
4090      enquire.register(this.settings.mediaQueryMediumUp, {
4091        match: function() {
4092          if (self.settings.zoomEnabled) {
4093            $(self.selectors.productImageWraps).each(function() {
4094              _enableZoom(this);
4095            });
4096          }
4097        }
4098      });
4099    },
4100
4101    _initVariants: function() {
4102      var options = {
4103        $container: this.$container,
4104        enableHistoryState:
4105          this.$container.data('enable-history-state') || false,
4106        singleOptionSelector: this.selectors.singleOptionSelector,
4107        originalSelectorId: this.selectors.originalSelectorId,
4108        product: this.productSingleObject
4109      };
4110
4111      this.variants = new slate.Variants(options);
4112
4113      this.$container.on(
4114        'variantChange' + this.settings.namespace,
4115        this._updateAvailability.bind(this)
4116      );
4117      this.$container.on(
4118        'variantImageChange' + this.settings.namespace,
4119        this._updateImages.bind(this)
4120      );
4121      this.$container.on(
4122        'variantPriceChange' + this.settings.namespace,
4123        this._updatePrice.bind(this)
4124      );
4125      this.$container.on(
4126        'variantSKUChange' + this.settings.namespace,
4127        this._updateSKU.bind(this)
4128      );
4129    },
4130
4131    _initImageSwitch: function() {
4132      if (!$(this.selectors.productThumbImages).length) {
4133        return;
4134      }
4135
4136      var self = this;
4137
4138      $(this.selectors.productThumbImages)
4139        .on('click', function(evt) {
4140          evt.preventDefault();
4141          var $el = $(this);
4142
4143          var imageId = $el.data('thumbnail-id');
4144
4145          self._switchImage(imageId);
4146          self._setActiveThumbnail(imageId);
4147        })
4148        .on('keyup', self._handleImageFocus.bind(self));
4149    },
4150
4151    _initAddToCart: function() {
4152      $(this.selectors.productForm, this.$container).on(
4153        'submit',
4154        function(evt) {
4155          if (this.$addToCart.is('[aria-disabled]')) {
4156            evt.preventDefault();
4157            return;
4158          }
4159
4160          if (!this.ajaxEnabled) return;
4161
4162          evt.preventDefault();
4163
4164          this.$previouslyFocusedElement = $(':focus');
4165
4166          var isInvalidQuantity = this.$quantityInput.val() <= 0;
4167
4168          if (isInvalidQuantity) {
4169            this._showErrorMessage(theme.strings.quantityMinimumMessage);
4170            return;
4171          }
4172
4173          if (!isInvalidQuantity && this.ajaxEnabled) {
4174            // disable the addToCart and dynamic checkout button while
4175            // request/cart popup is loading and handle loading state
4176            this._handleButtonLoadingState(true);
4177            var $data = $(this.selectors.productForm, this.$container);
4178            this._addItemToCart($data);
4179            return;
4180          }
4181        }.bind(this)
4182      );
4183    },
4184
4185    _addItemToCart: function(data) {
4186      var params = {
4187        url: '/cart/add.js',
4188        data: $(data).serialize(),
4189        dataType: 'json'
4190      };
4191
4192      $.post(params)
4193        .done(
4194          function(item) {
4195            this._hideErrorMessage();
4196            this._setupCartPopup(item);
4197          }.bind(this)
4198        )
4199        .fail(
4200          function(response) {
4201            this.$previouslyFocusedElement.focus();
4202            var errorMessage = response.responseJSON
4203              ? response.responseJSON.description
4204              : theme.strings.cartError;
4205            this._showErrorMessage(errorMessage);
4206            this._handleButtonLoadingState(false);
4207          }.bind(this)
4208        );
4209    },
4210
4211    _handleButtonLoadingState: function(isLoading) {
4212      if (isLoading) {
4213        this.$addToCart.attr('aria-disabled', true);
4214        this.$addToCartText.addClass(this.classes.hidden);
4215        this.$loader.removeClass(this.classes.hidden);
4216        this.$shopifyPaymentButton.attr('disabled', true);
4217        this.$loaderStatus.attr('aria-hidden', false);
4218      } else {
4219        this.$addToCart.removeAttr('aria-disabled');
4220        this.$addToCartText.removeClass(this.classes.hidden);
4221        this.$loader.addClass(this.classes.hidden);
4222        this.$shopifyPaymentButton.removeAttr('disabled');
4223        this.$loaderStatus.attr('aria-hidden', true);
4224      }
4225    },
4226
4227    _showErrorMessage: function(errorMessage) {
4228      $(this.selectors.errorMessage, this.$container).html(errorMessage);
4229
4230      if (this.$quantityInput.length !== 0) {
4231        this.$quantityInput.addClass(this.classes.inputError);
4232      }
4233
4234      this.$errorMessageWrapper
4235        .removeClass(this.classes.productFormErrorMessageWrapperHidden)
4236        .attr('aria-hidden', true)
4237        .removeAttr('aria-hidden');
4238    },
4239
4240    _hideErrorMessage: function() {
4241      this.$errorMessageWrapper.addClass(
4242        this.classes.productFormErrorMessageWrapperHidden
4243      );
4244
4245      if (this.$quantityInput.length !== 0) {
4246        this.$quantityInput.removeClass(this.classes.inputError);
4247      }
4248    },
4249
4250    _setupCartPopup: function(item) {
4251      this.$cartPopup = this.$cartPopup || $(this.selectors.cartPopup);
4252      this.$cartPopupWrapper =
4253        this.$cartPopupWrapper || $(this.selectors.cartPopupWrapper);
4254      this.$cartPopupTitle =
4255        this.$cartPopupTitle || $(this.selectors.cartPopupTitle);
4256      this.$cartPopupQuantity =
4257        this.$cartPopupQuantity || $(this.selectors.cartPopupQuantity);
4258      this.$cartPopupQuantityLabel =
4259        this.$cartPopupQuantityLabel ||
4260        $(this.selectors.cartPopupQuantityLabel);
4261      this.$cartPopupClose =
4262        this.$cartPopupClose || $(this.selectors.cartPopupClose);
4263      this.$cartPopupDismiss =
4264        this.$cartPopupDismiss || $(this.selectors.cartPopupDismiss);
4265      this.$cartPopupImagePlaceholder =
4266        this.$cartPopupImagePlaceholder ||
4267        $(this.selectors.cartPopupImagePlaceholder);
4268
4269      this._setupCartPopupEventListeners();
4270
4271      this._updateCartPopupContent(item);
4272    },
4273
4274    _updateCartPopupContent: function(item) {
4275      var quantity = this.$quantityInput.length ? this.$quantityInput.val() : 1;
4276
4277      this.$cartPopupTitle.text(item.product_title);
4278      this.$cartPopupQuantity.text(quantity);
4279      this.$cartPopupQuantityLabel.text(
4280        theme.strings.quantityLabel.replace('[count]', quantity)
4281      );
4282
4283      this._setCartPopupPlaceholder(
4284        item.featured_image.url,
4285        item.featured_image.aspect_ratio
4286      );
4287      this._setCartPopupImage(item.featured_image.url, item.featured_image.alt);
4288      this._setCartPopupProductDetails(
4289        item.product_has_only_default_variant,
4290        item.options_with_values,
4291        item.properties
4292      );
4293
4294      $.getJSON('/cart.js').then(
4295        function(cart) {
4296          this._setCartQuantity(cart.item_count);
4297          this._setCartCountBubble(cart.item_count);
4298          this._showCartPopup();
4299        }.bind(this)
4300      );
4301    },
4302
4303    _setupCartPopupEventListeners: function() {
4304      this.$cartPopupWrapper.on(
4305        'keyup',
4306        function(event) {
4307          if (event.keyCode === slate.utils.keyboardKeys.ESCAPE) {
4308            this._hideCartPopup(event);
4309          }
4310        }.bind(this)
4311      );
4312
4313      this.$cartPopupClose.on('click', this._hideCartPopup.bind(this));
4314      this.$cartPopupDismiss.on('click', this._hideCartPopup.bind(this));
4315      $('body').on('click', this._onBodyClick.bind(this));
4316    },
4317
4318    _setCartPopupPlaceholder: function(imageUrl, imageAspectRatio) {
4319      this.$cartPopupImageWrapper =
4320        this.$cartPopupImageWrapper || $(this.selectors.cartPopupImageWrapper);
4321
4322      if (imageUrl === null) {
4323        this.$cartPopupImageWrapper.addClass(this.classes.hidden);
4324        return;
4325      }
4326
4327      var $placeholder = $(this.selectors.cartPopupPlaceholderSize);
4328      var maxWidth = 95 * imageAspectRatio;
4329      var heightRatio = 100 / imageAspectRatio;
4330
4331      this.$cartPopupImagePlaceholder.css('max-width', maxWidth);
4332
4333      $placeholder.css('padding-top', heightRatio + '%');
4334    },
4335
4336    _setCartPopupImage: function(imageUrl, imageAlt) {
4337      if (imageUrl === null) return;
4338
4339      this.$cartPopupImageWrapper.removeClass(this.classes.hidden);
4340      var sizedImageUrl = theme.Images.getSizedImageUrl(imageUrl, '200x');
4341      var image = document.createElement('img');
4342      image.src = sizedImageUrl;
4343      image.alt = imageAlt;
4344      image.classList.add(this.classes.cartImage);
4345      image.dataset.cartPopupImage = '';
4346
4347      image.onload = function() {
4348        this.$cartPopupImagePlaceholder.addClass(this.classes.hidden);
4349        this.$cartPopupImageWrapper.append(image);
4350      }.bind(this);
4351    },
4352
4353    _setCartPopupProductDetails: function(
4354      product_has_only_default_variant,
4355      options,
4356      properties
4357    ) {
4358      this.$cartPopupProductDetails =
4359        this.$cartPopupProductDetails ||
4360        $(this.selectors.cartPopupProductDetails);
4361      var variantPropertiesHTML = '';
4362
4363      if (!product_has_only_default_variant) {
4364        variantPropertiesHTML =
4365          variantPropertiesHTML + this._getVariantOptionList(options);
4366      }
4367
4368      if (properties !== null && Object.keys(properties).length !== 0) {
4369        variantPropertiesHTML =
4370          variantPropertiesHTML + this._getPropertyList(properties);
4371      }
4372
4373      if (variantPropertiesHTML.length === 0) {
4374        this.$cartPopupProductDetails.html('');
4375        this.$cartPopupProductDetails.attr('hidden', '');
4376      } else {
4377        this.$cartPopupProductDetails.html(variantPropertiesHTML);
4378        this.$cartPopupProductDetails.removeAttr('hidden');
4379      }
4380    },
4381
4382    _getVariantOptionList: function(variantOptions) {
4383      var variantOptionListHTML = '';
4384
4385      variantOptions.forEach(function(variantOption) {
4386        variantOptionListHTML =
4387          variantOptionListHTML +
4388          '<li class="product-details__item product-details__item--variant-option">' +
4389          variantOption.name +
4390          ': ' +
4391          variantOption.value +
4392          '</li>';
4393      });
4394
4395      return variantOptionListHTML;
4396    },
4397
4398    _getPropertyList: function(properties) {
4399      var propertyListHTML = '';
4400      var propertiesArray = Object.entries(properties);
4401
4402      propertiesArray.forEach(function(property) {
4403        // Line item properties prefixed with an underscore are not to be displayed
4404        if (property[0].charAt(0) === '_') return;
4405
4406        // if the property value has a length of 0 (empty), don't display it
4407        if (property[1].length === 0) return;
4408
4409        propertyListHTML =
4410          propertyListHTML +
4411          '<li class="product-details__item product-details__item--property">' +
4412          '<span class="product-details__property-label">' +
4413          property[0] +
4414          ': </span>' +
4415          property[1];
4416        ': ' + '</li>';
4417      });
4418
4419      return propertyListHTML;
4420    },
4421
4422    _setCartQuantity: function(quantity) {
4423      this.$cartPopupCartQuantity =
4424        this.$cartPopupCartQuantity || $(this.selectors.cartPopupCartQuantity);
4425      var ariaLabel;
4426
4427      if (quantity === 1) {
4428        ariaLabel = theme.strings.oneCartCount;
4429      } else if (quantity > 1) {
4430        ariaLabel = theme.strings.otherCartCount.replace('[count]', quantity);
4431      }
4432
4433      this.$cartPopupCartQuantity.text(quantity).attr('aria-label', ariaLabel);
4434    },
4435
4436    _setCartCountBubble: function(quantity) {
4437      this.$cartCountBubble =
4438        this.$cartCountBubble || $(this.selectors.cartCountBubble);
4439      this.$cartCount = this.$cartCount || $(this.selectors.cartCount);
4440
4441      this.$cartCountBubble.removeClass(this.classes.hidden);
4442      this.$cartCount.text(quantity);
4443    },
4444
4445    _showCartPopup: function() {
4446      this.$cartPopupWrapper
4447        .prepareTransition()
4448        .removeClass(this.classes.cartPopupWrapperHidden);
4449      this._handleButtonLoadingState(false);
4450
4451      slate.a11y.trapFocus({
4452        $container: this.$cartPopupWrapper,
4453        $elementToFocus: this.$cartPopup,
4454        namespace: 'cartPopupFocus'
4455      });
4456    },
4457
4458    _hideCartPopup: function(event) {
4459      var setFocus = event.detail === 0 ? true : false;
4460      this.$cartPopupWrapper
4461        .prepareTransition()
4462        .addClass(this.classes.cartPopupWrapperHidden);
4463
4464      $(this.selectors.cartPopupImage).remove();
4465      this.$cartPopupImagePlaceholder.removeClass(this.classes.hidden);
4466
4467      slate.a11y.removeTrapFocus({
4468        $container: this.$cartPopupWrapper,
4469        namespace: 'cartPopupFocus'
4470      });
4471
4472      if (setFocus) this.$previouslyFocusedElement[0].focus();
4473
4474      this.$cartPopupWrapper.off('keyup');
4475      this.$cartPopupClose.off('click');
4476      this.$cartPopupDismiss.off('click');
4477      $('body').off('click');
4478    },
4479
4480    _onBodyClick: function(event) {
4481      var $target = $(event.target);
4482
4483      if (
4484        $target[0] !== this.$cartPopupWrapper[0] &&
4485        !$target.parents(this.selectors.cartPopup).length
4486      ) {
4487        this._hideCartPopup(event);
4488      }
4489    },
4490
4491    _setActiveThumbnail: function(imageId) {
4492      // If there is no element passed, find it by the current product image
4493      if (typeof imageId === 'undefined') {
4494        imageId = $(
4495          this.selectors.productImageWraps + ':not(.hide)',
4496          this.$container
4497        ).data('image-id');
4498      }
4499
4500      var $thumbnailWrappers = $(
4501        this.selectors.productThumbListItem + ':not(.slick-cloned)',
4502        this.$container
4503      );
4504
4505      var $activeThumbnail = $thumbnailWrappers.find(
4506        this.selectors.productThumbImages +
4507          "[data-thumbnail-id='" +
4508          imageId +
4509          "']"
4510      );
4511
4512      $(this.selectors.productThumbImages)
4513        .removeClass(this.classes.activeClass)
4514        .removeAttr('aria-current');
4515
4516      $activeThumbnail.addClass(this.classes.activeClass);
4517      $activeThumbnail.attr('aria-current', true);
4518
4519      if (!$thumbnailWrappers.hasClass('slick-slide')) {
4520        return;
4521      }
4522
4523      var slideIndex = $activeThumbnail.parent().data('slick-index');
4524
4525      $(this.selectors.productThumbs).slick('slickGoTo', slideIndex, true);
4526    },
4527
4528    _switchImage: function(imageId) {
4529      var $newImage = $(
4530        this.selectors.productImageWraps + "[data-image-id='" + imageId + "']",
4531        this.$container
4532      );
4533      var $otherImages = $(
4534        this.selectors.productImageWraps +
4535          ":not([data-image-id='" +
4536          imageId +
4537          "'])",
4538        this.$container
4539      );
4540
4541      $newImage.removeClass(this.classes.hidden);
4542      $otherImages.addClass(this.classes.hidden);
4543    },
4544
4545    _handleImageFocus: function(evt) {
4546      if (evt.keyCode !== slate.utils.keyboardKeys.ENTER) return;
4547
4548      $(this.selectors.productFeaturedImage + ':visible').focus();
4549    },
4550
4551    _initThumbnailSlider: function() {
4552      var options = {
4553        slidesToShow: 4,
4554        slidesToScroll: 3,
4555        infinite: false,
4556        prevArrow: '.thumbnails-slider__prev--' + this.settings.sectionId,
4557        nextArrow: '.thumbnails-slider__next--' + this.settings.sectionId,
4558        responsive: [
4559          {
4560            breakpoint: 321,
4561            settings: {
4562              slidesToShow: 3
4563            }
4564          }
4565        ]
4566      };
4567
4568      $(this.selectors.productThumbs).slick(options);
4569
4570      // Accessibility concerns not yet fixed in Slick Slider
4571      $(this.selectors.productThumbsWrapper, this.$container)
4572        .find('.slick-list')
4573        .removeAttr('aria-live');
4574      $(this.selectors.productThumbsWrapper, this.$container)
4575        .find('.slick-disabled')
4576        .removeAttr('aria-disabled');
4577
4578      this.settings.sliderActive = true;
4579    },
4580
4581    _destroyThumbnailSlider: function() {
4582      $(this.selectors.productThumbs).slick('unslick');
4583      this.settings.sliderActive = false;
4584
4585      // Accessibility concerns not yet fixed in Slick Slider
4586      $(this.selectors.productThumbsWrapper, this.$container)
4587        .find('[tabindex="-1"]')
4588        .removeAttr('tabindex');
4589    },
4590
4591    _liveRegionText: function(variant) {
4592      // Dummy content for live region
4593      var liveRegionText =
4594        '[Availability] [Regular] [$$] [Sale] [$]. [UnitPrice] [$$$]';
4595
4596      if (!variant) {
4597        liveRegionText = theme.strings.unavailable;
4598        return liveRegionText;
4599      }
4600
4601      // Update availability
4602      var availability = variant.available ? '' : theme.strings.soldOut + ',';
4603      liveRegionText = liveRegionText.replace('[Availability]', availability);
4604
4605      // Update pricing information
4606      var regularLabel = '';
4607      var regularPrice = theme.Currency.formatMoney(
4608        variant.price,
4609        theme.moneyFormat
4610      );
4611      var saleLabel = '';
4612      var salePrice = '';
4613      var unitLabel = '';
4614      var unitPrice = '';
4615
4616      if (variant.compare_at_price > variant.price) {
4617        regularLabel = theme.strings.regularPrice;
4618        regularPrice =
4619          theme.Currency.formatMoney(
4620            variant.compare_at_price,
4621            theme.moneyFormat
4622          ) + ',';
4623        saleLabel = theme.strings.sale;
4624        salePrice = theme.Currency.formatMoney(
4625          variant.price,
4626          theme.moneyFormat
4627        );
4628      }
4629
4630      if (variant.unit_price) {
4631        unitLabel = theme.strings.unitPrice;
4632        unitPrice =
4633          theme.Currency.formatMoney(variant.unit_price, theme.moneyFormat) +
4634          ' ' +
4635          theme.strings.unitPriceSeparator +
4636          ' ' +
4637          this._getBaseUnit(variant);
4638      }
4639
4640      liveRegionText = liveRegionText
4641        .replace('[Regular]', regularLabel)
4642        .replace('[$$]', regularPrice)
4643        .replace('[Sale]', saleLabel)
4644        .replace('[$]', salePrice)
4645        .replace('[UnitPrice]', unitLabel)
4646        .replace('[$$$]', unitPrice)
4647        .trim();
4648
4649      return liveRegionText;
4650    },
4651
4652    _updateLiveRegion: function(evt) {
4653      var variant = evt.variant;
4654      var liveRegion = this.container.querySelector(
4655        this.selectors.productStatus
4656      );
4657      liveRegion.innerHTML = this._liveRegionText(variant);
4658      liveRegion.setAttribute('aria-hidden', false);
4659
4660      // hide content from accessibility tree after announcement
4661      setTimeout(function() {
4662        liveRegion.setAttribute('aria-hidden', true);
4663      }, 1000);
4664    },
4665
4666    _updateAddToCart: function(evt) {
4667      var variant = evt.variant;
4668
4669      if (variant) {
4670        if (variant.available) {
4671          this.$addToCart
4672            .removeAttr('aria-disabled')
4673            .attr('aria-label', theme.strings.addToCart);
4674          $(this.selectors.addToCartText, this.$container).text(
4675            theme.strings.addToCart
4676          );
4677          this.$shopifyPaymentButton.show();
4678        } else {
4679          // The variant doesn't exist, disable submit button and change the text.
4680          // This may be an error or notice that a specific variant is not available.
4681          this.$addToCart
4682            .attr('aria-disabled', true)
4683            .attr('aria-label', theme.strings.soldOut);
4684          $(this.selectors.addToCartText, this.$container).text(
4685            theme.strings.soldOut
4686          );
4687          this.$shopifyPaymentButton.hide();
4688        }
4689      } else {
4690        this.$addToCart
4691          .attr('aria-disabled', true)
4692          .attr('aria-label', theme.strings.unavailable);
4693        $(this.selectors.addToCartText, this.$container).text(
4694          theme.strings.unavailable
4695        );
4696        this.$shopifyPaymentButton.hide();
4697      }
4698    },
4699
4700    _updateAvailability: function(evt) {
4701      // remove error message if one is showing
4702      this._hideErrorMessage();
4703
4704      // update form submit
4705      this._updateAddToCart(evt);
4706      // update live region
4707      this._updateLiveRegion(evt);
4708
4709      this._updatePrice(evt);
4710    },
4711
4712    _updateImages: function(evt) {
4713      var variant = evt.variant;
4714      var imageId = variant.featured_image.id;
4715
4716      this._switchImage(imageId);
4717      this._setActiveThumbnail(imageId);
4718    },
4719
4720    _updatePrice: function(evt) {
4721      var variant = evt.variant;
4722
4723      var $priceContainer = $(this.selectors.priceContainer, this.$container);
4724      var $regularPrice = $(this.selectors.regularPrice, $priceContainer);
4725      var $salePrice = $(this.selectors.salePrice, $priceContainer);
4726      var $unitPrice = $(this.selectors.unitPrice, $priceContainer);
4727      var $unitPriceBaseUnit = $(
4728        this.selectors.unitPriceBaseUnit,
4729        $priceContainer
4730      );
4731
4732      // Reset product price state
4733      $priceContainer
4734        .removeClass(this.classes.productUnavailable)
4735        .removeClass(this.classes.productOnSale)
4736        .removeClass(this.classes.productUnitAvailable)
4737        .removeAttr('aria-hidden');
4738
4739      // Unavailable
4740      if (!variant) {
4741        $priceContainer
4742          .addClass(this.classes.productUnavailable)
4743          .attr('aria-hidden', true);
4744        return;
4745      }
4746
4747      // On sale
4748      if (variant.compare_at_price > variant.price) {
4749        $regularPrice.html(
4750          theme.Currency.formatMoney(
4751            variant.compare_at_price,
4752            theme.moneyFormat
4753          )
4754        );
4755        $salePrice.html(
4756          theme.Currency.formatMoney(variant.price, theme.moneyFormat)
4757        );
4758        $priceContainer.addClass(this.classes.productOnSale);
4759      } else {
4760        // Regular price
4761        $regularPrice.html(
4762          theme.Currency.formatMoney(variant.price, theme.moneyFormat)
4763        );
4764      }
4765
4766      // Unit price
4767      if (variant.unit_price) {
4768        $unitPrice.html(
4769          theme.Currency.formatMoney(variant.unit_price, theme.moneyFormat)
4770        );
4771        $unitPriceBaseUnit.html(this._getBaseUnit(variant));
4772        $priceContainer.addClass(this.classes.productUnitAvailable);
4773      }
4774    },
4775
4776    _getBaseUnit: function(variant) {
4777      return variant.unit_price_measurement.reference_value === 1
4778        ? variant.unit_price_measurement.reference_unit
4779        : variant.unit_price_measurement.reference_value +
4780            variant.unit_price_measurement.reference_unit;
4781    },
4782
4783    _updateSKU: function(evt) {
4784      var variant = evt.variant;
4785
4786      // Update the sku
4787      $(this.selectors.SKU).html(variant.sku);
4788    },
4789
4790    onUnload: function() {
4791      this.$container.off(this.settings.namespace);
4792    }
4793  });
4794
4795  function _enableZoom(el) {
4796    var zoomUrl = $(el).data('zoom');
4797    $(el).zoom({
4798      url: zoomUrl
4799    });
4800  }
4801
4802  function _destroyZoom(el) {
4803    $(el).trigger('zoom.destroy');
4804  }
4805
4806  return Product;
4807})();
4808
4809theme.ProductRecommendations = (function() {
4810  function ProductRecommendations(container) {
4811    this.$container = $(container);
4812
4813    var productId = this.$container.data('productId');
4814    var recommendationsSectionUrl =
4815      '/recommendations/products?&section_id=product-recommendations&product_id=' +
4816      productId +
4817      '&limit=4';
4818
4819    $.get(recommendationsSectionUrl).then(
4820      function(section) {
4821        var recommendationsMarkup = $(section).html();
4822        if (recommendationsMarkup.trim() !== '') {
4823          this.$container.html(recommendationsMarkup);
4824        }
4825      }.bind(this)
4826    );
4827  }
4828
4829  return ProductRecommendations;
4830})();
4831
4832theme.Quotes = (function() {
4833  var config = {
4834    mediaQuerySmall: 'screen and (max-width: 749px)',
4835    mediaQueryMediumUp: 'screen and (min-width: 750px)',
4836    slideCount: 0
4837  };
4838  var defaults = {
4839    accessibility: true,
4840    arrows: false,
4841    dots: true,
4842    autoplay: false,
4843    touchThreshold: 20,
4844    slidesToShow: 3,
4845    slidesToScroll: 3
4846  };
4847
4848  function Quotes(container) {
4849    var $container = (this.$container = $(container));
4850    var sectionId = $container.attr('data-section-id');
4851    var wrapper = (this.wrapper = '.quotes-wrapper');
4852    var slider = (this.slider = '#Quotes-' + sectionId);
4853    var $slider = $(slider, wrapper);
4854
4855    var sliderActive = false;
4856    var mobileOptions = $.extend({}, defaults, {
4857      slidesToShow: 1,
4858      slidesToScroll: 1,
4859      adaptiveHeight: true
4860    });
4861
4862    config.slideCount = $slider.data('count');
4863
4864    // Override slidesToShow/Scroll if there are not enough blocks
4865    if (config.slideCount < defaults.slidesToShow) {
4866      defaults.slidesToShow = config.slideCount;
4867      defaults.slidesToScroll = config.slideCount;
4868    }
4869
4870    $slider.on('init', this.a11y.bind(this));
4871
4872    enquire.register(config.mediaQuerySmall, {
4873      match: function() {
4874        initSlider($slider, mobileOptions);
4875      }
4876    });
4877
4878    enquire.register(config.mediaQueryMediumUp, {
4879      match: function() {
4880        initSlider($slider, defaults);
4881      }
4882    });
4883
4884    function initSlider(sliderObj, args) {
4885      if (sliderActive) {
4886        sliderObj.slick('unslick');
4887        sliderActive = false;
4888      }
4889
4890      sliderObj.slick(args);
4891      sliderActive = true;
4892    }
4893  }
4894
4895  Quotes.prototype = _.assignIn({}, Quotes.prototype, {
4896    onUnload: function() {
4897      enquire.unregister(config.mediaQuerySmall);
4898      enquire.unregister(config.mediaQueryMediumUp);
4899
4900      $(this.slider, this.wrapper).slick('unslick');
4901    },
4902
4903    onBlockSelect: function(evt) {
4904      // Ignore the cloned version
4905      var $slide = $(
4906        '.quotes-slide--' + evt.detail.blockId + ':not(.slick-cloned)'
4907      );
4908      var slideIndex = $slide.data('slick-index');
4909
4910      // Go to selected slide, pause autoplay
4911      $(this.slider, this.wrapper).slick('slickGoTo', slideIndex);
4912    },
4913
4914    a11y: function(event, obj) {
4915      var $list = obj.$list;
4916      var $wrapper = $(this.wrapper, this.$container);
4917
4918      // Remove default Slick aria-live attr until slider is focused
4919      $list.removeAttr('aria-live');
4920
4921      // When an element in the slider is focused set aria-live
4922      $wrapper.on('focusin', function(evt) {
4923        if ($wrapper.has(evt.target).length) {
4924          $list.attr('aria-live', 'polite');
4925        }
4926      });
4927
4928      // Remove aria-live
4929      $wrapper.on('focusout', function(evt) {
4930        if ($wrapper.has(evt.target).length) {
4931          $list.removeAttr('aria-live');
4932        }
4933      });
4934    }
4935  });
4936
4937  return Quotes;
4938})();
4939
4940theme.slideshows = {};
4941
4942theme.SlideshowSection = (function() {
4943  function SlideshowSection(container) {
4944    var $container = (this.$container = $(container));
4945    var sectionId = $container.attr('data-section-id');
4946    var slideshow = (this.slideshow = '#Slideshow-' + sectionId);
4947
4948    theme.slideshows[slideshow] = new theme.Slideshow(slideshow, sectionId);
4949  }
4950
4951  return SlideshowSection;
4952})();
4953
4954theme.SlideshowSection.prototype = _.assignIn(
4955  {},
4956  theme.SlideshowSection.prototype,
4957  {
4958    onUnload: function() {
4959      delete theme.slideshows[this.slideshow];
4960    },
4961
4962    onBlockSelect: function(evt) {
4963      var $slideshow = $(this.slideshow);
4964      var adaptHeight = $slideshow.data('adapt-height');
4965
4966      if (adaptHeight) {
4967        theme.slideshows[this.slideshow].setSlideshowHeight();
4968      }
4969
4970      // Ignore the cloned version
4971      var $slide = $(
4972        '.slideshow__slide--' + evt.detail.blockId + ':not(.slick-cloned)'
4973      );
4974      var slideIndex = $slide.data('slick-index');
4975
4976      // Go to selected slide, pause auto-rotate
4977      $slideshow.slick('slickGoTo', slideIndex).slick('slickPause');
4978    },
4979
4980    onBlockDeselect: function() {
4981      // Resume auto-rotate
4982      $(this.slideshow).slick('slickPlay');
4983    }
4984  }
4985);
4986
4987theme.slideshows = {};
4988
4989theme.VideoSection = (function() {
4990  function VideoSection(container) {
4991    var $container = (this.$container = $(container));
4992
4993    $('.video', $container).each(function() {
4994      var $el = $(this);
4995      theme.Video.init($el);
4996      theme.Video.editorLoadVideo($el.attr('id'));
4997    });
4998  }
4999
5000  return VideoSection;
5001})();
5002
5003theme.VideoSection.prototype = _.assignIn({}, theme.VideoSection.prototype, {
5004  onUnload: function() {
5005    theme.Video.removeEvents();
5006  }
5007});
5008
5009theme.heros = {};
5010
5011theme.HeroSection = (function() {
5012  function HeroSection(container) {
5013    var $container = (this.$container = $(container));
5014    var sectionId = $container.attr('data-section-id');
5015    var hero = '#Hero-' + sectionId;
5016    theme.heros[hero] = new theme.Hero(hero, sectionId);
5017  }
5018
5019  return HeroSection;
5020})();
5021
5022
5023$(document).ready(function() {
5024  var sections = new theme.Sections();
5025
5026  sections.register('cart-template', theme.Cart);
5027  sections.register('product', theme.Product);
5028  sections.register('collection-template', theme.Filters);
5029  sections.register('product-template', theme.Product);
5030  sections.register('header-section', theme.HeaderSection);
5031  sections.register('map', theme.Maps);
5032  sections.register('slideshow-section', theme.SlideshowSection);
5033  sections.register('video-section', theme.VideoSection);
5034  sections.register('quotes', theme.Quotes);
5035  sections.register('hero-section', theme.HeroSection);
5036  sections.register('product-recommendations', theme.ProductRecommendations);
5037});
5038
5039theme.init = function() {
5040  theme.customerTemplates.init();
5041
5042  // Theme-specific selectors to make tables scrollable
5043  var tableSelectors = '.rte table,' + '.custom__item-inner--html table';
5044
5045  slate.rte.wrapTable({
5046    $tables: $(tableSelectors),
5047    tableWrapperClass: 'scrollable-wrapper'
5048  });
5049
5050  // Theme-specific selectors to make iframes responsive
5051  var iframeSelectors =
5052    '.rte iframe[src*="youtube.com/embed"],' +
5053    '.rte iframe[src*="player.vimeo"],' +
5054    '.custom__item-inner--html iframe[src*="youtube.com/embed"],' +
5055    '.custom__item-inner--html iframe[src*="player.vimeo"]';
5056
5057  slate.rte.wrapIframe({
5058    $iframes: $(iframeSelectors),
5059    iframeWrapperClass: 'video-wrapper'
5060  });
5061
5062  // Common a11y fixes
5063  slate.a11y.pageLinkFocus($(window.location.hash));
5064
5065  $('.in-page-link').on('click', function(evt) {
5066    slate.a11y.pageLinkFocus($(evt.currentTarget.hash));
5067  });
5068
5069  $('a[href="#"]').on('click', function(evt) {
5070    evt.preventDefault();
5071  });
5072
5073  slate.a11y.accessibleLinks({
5074    messages: {
5075      newWindow: theme.strings.newWindow,
5076      external: theme.strings.external,
5077      newWindowExternal: theme.strings.newWindowExternal
5078    },
5079    $links: $('a[href]:not([aria-describedby], .product-single__thumbnail)')
5080  });
5081
5082  theme.FormStatus.init();
5083
5084  var selectors = {
5085    image: '[data-image]',
5086    imagePlaceholder: '[data-image-placeholder]',
5087    imageWithPlaceholderWrapper: '[data-image-with-placeholder-wrapper]'
5088  };
5089
5090  var classes = {
5091    hidden: 'hide'
5092  };
5093
5094  $(document).on('lazyloaded', function(e) {
5095    var $target = $(e.target);
5096
5097    if (!$target.is(selectors.image)) {
5098      return;
5099    }
5100
5101    $target
5102      .closest(selectors.imageWithPlaceholderWrapper)
5103      .find(selectors.imagePlaceholder)
5104      .addClass(classes.hidden);
5105  });
5106
5107  // When the theme loads, lazysizes might load images before the "lazyloaded"
5108  // event listener has been attached. When this happens, the following function
5109  // hides the loading placeholders.
5110  function onLoadHideLazysizesAnimation() {
5111    $(selectors.image + '.lazyloaded')
5112      .closest(selectors.imageWithPlaceholderWrapper)
5113      .find(selectors.imagePlaceholder)
5114      .addClass(classes.hidden);
5115  }
5116
5117  onLoadHideLazysizesAnimation();
5118};
5119
5120$(theme.init);

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.