{"version":3,"file":"default/js/main.js","sources":["webpack:///webpack/bootstrap","webpack:///./cartridges/app_lyonscg/cartridge/client/default/js/components/carousels.js","webpack:///./cartridges/app_lyonscg/cartridge/client/default/js/components/clientSideValidation.js","webpack:///./cartridges/app_lyonscg/cartridge/client/default/js/components/tooltips.js","webpack:///./cartridges/app_lyonscg/cartridge/client/default/js/config/slickConfigs.js","webpack:///./cartridges/app_lyonscg/cartridge/client/default/js/config/zoomConfigs.js","webpack:///./cartridges/app_lyonscg/cartridge/client/default/js/product/base.js","webpack:///./cartridges/app_lyonscg/cartridge/client/default/js/thirdParty/bootstrap.js","webpack:///./cartridges/app_lyonscg/cartridge/client/default/js/util/utils.js","webpack:///./cartridges/app_storefront_base/cartridge/client/default/js/components/clientSideValidation.js","webpack:///./cartridges/app_storefront_base/cartridge/client/default/js/components/cookie.js","webpack:///./cartridges/app_storefront_base/cartridge/client/default/js/components/countrySelector.js","webpack:///./cartridges/app_storefront_base/cartridge/client/default/js/components/errorNotification.js","webpack:///./cartridges/app_storefront_base/cartridge/client/default/js/components/focus.js","webpack:///./cartridges/app_storefront_base/cartridge/client/default/js/components/keyboardAccessibility.js","webpack:///./cartridges/app_storefront_base/cartridge/client/default/js/product/base.js","webpack:///./cartridges/app_storefront_base/cartridge/client/default/js/util.js","webpack:///./cartridges/int_googletags/cartridge/client/default/js/tagManager.js","webpack:///./cartridges/int_ordergroove/cartridge/client/default/js/cart/cart.js","webpack:///./cartridges/int_ordergroove_custom/cartridge/client/default/js/optins.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/cart/cart.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/cart/recommendationIntercept.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/accessibleTabs.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/ada.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/backtotop.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/carousels.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/clientSideValidation.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/collapsibleItem.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/consentTracking.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/countrySelector.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/einsteinObserver.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/emailSignup.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/focus.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/menu.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/miniCart.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/quantitySelector.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/search.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/components/spinner.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/config/slickConfigs.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/main.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/product/base.js","webpack:///./cartridges/org_bissell/cartridge/client/default/js/util/utils.js","webpack:///./cartridges/plugin_blog/cartridge/client/default/js/components/carousels.js","webpack:///./cartridges/plugin_blog/cartridge/client/default/js/components/video.js","webpack:///./cartridges/plugin_blog/cartridge/client/default/js/config/slickConfigs.js","webpack:///./cartridges/plugin_einstein/cartridge/client/default/js/components/einstein.js","webpack:///./cartridges/plugin_pagedesigner_capgemini/cartridge/client/default/js/experience/carousels.js","webpack:///./cartridges/plugin_pagedesigner_capgemini/cartridge/client/default/js/experience/heros.js","webpack:///./cartridges/plugin_pagedesigner_capgemini/cartridge/client/default/js/experience/modal.js","webpack:///./cartridges/plugin_pagedesigner_capgemini/cartridge/client/default/js/experience/pdProductTiles.js","webpack:///./cartridges/plugin_pagedesigner_capgemini/cartridge/client/default/js/experience/stickyComponents.js","webpack:///./cartridges/plugin_pagedesigner_capgemini/cartridge/client/default/js/experience/tabs.js","webpack:///./cartridges/plugin_pagedesigner_capgemini/cartridge/client/default/js/util/utils.js","webpack:///./cartridges/plugin_pagedesigner_capgemini/cartridge/client/default/js/util/videos.js","webpack:///./node_modules/@babel/polyfill/lib/index.js","webpack:///./node_modules/@babel/polyfill/node_modules/regenerator-runtime/runtime.js","webpack:///./node_modules/bootstrap/js/src/alert.js","webpack:///./node_modules/bootstrap/js/src/carousel.js","webpack:///./node_modules/bootstrap/js/src/collapse.js","webpack:///./node_modules/bootstrap/js/src/modal.js","webpack:///./node_modules/bootstrap/js/src/scrollspy.js","webpack:///./node_modules/bootstrap/js/src/tab.js","webpack:///./node_modules/bootstrap/js/src/tools/sanitizer.js","webpack:///./node_modules/bootstrap/js/src/tooltip.js","webpack:///./node_modules/bootstrap/js/src/util.js","webpack:///./node_modules/core-js/es6/index.js","webpack:///./node_modules/core-js/fn/array/includes.js","webpack:///./node_modules/core-js/fn/object/entries.js","webpack:///./node_modules/core-js/fn/object/get-own-property-descriptors.js","webpack:///./node_modules/core-js/fn/object/values.js","webpack:///./node_modules/core-js/fn/promise/finally.js","webpack:///./node_modules/core-js/fn/string/pad-end.js","webpack:///./node_modules/core-js/fn/string/pad-start.js","webpack:///./node_modules/core-js/fn/symbol/async-iterator.js","webpack:///./node_modules/core-js/modules/_a-function.js","webpack:///./node_modules/core-js/modules/_a-number-value.js","webpack:///./node_modules/core-js/modules/_add-to-unscopables.js","webpack:///./node_modules/core-js/modules/_advance-string-index.js","webpack:///./node_modules/core-js/modules/_an-instance.js","webpack:///./node_modules/core-js/modules/_an-object.js","webpack:///./node_modules/core-js/modules/_array-copy-within.js","webpack:///./node_modules/core-js/modules/_array-fill.js","webpack:///./node_modules/core-js/modules/_array-includes.js","webpack:///./node_modules/core-js/modules/_array-methods.js","webpack:///./node_modules/core-js/modules/_array-reduce.js","webpack:///./node_modules/core-js/modules/_array-species-constructor.js","webpack:///./node_modules/core-js/modules/_array-species-create.js","webpack:///./node_modules/core-js/modules/_bind.js","webpack:///./node_modules/core-js/modules/_classof.js","webpack:///./node_modules/core-js/modules/_cof.js","webpack:///./node_modules/core-js/modules/_collection-strong.js","webpack:///./node_modules/core-js/modules/_collection-weak.js","webpack:///./node_modules/core-js/modules/_collection.js","webpack:///./node_modules/core-js/modules/_core.js","webpack:///./node_modules/core-js/modules/_create-property.js","webpack:///./node_modules/core-js/modules/_ctx.js","webpack:///./node_modules/core-js/modules/_date-to-iso-string.js","webpack:///./node_modules/core-js/modules/_date-to-primitive.js","webpack:///./node_modules/core-js/modules/_defined.js","webpack:///./node_modules/core-js/modules/_descriptors.js","webpack:///./node_modules/core-js/modules/_dom-create.js","webpack:///./node_modules/core-js/modules/_enum-bug-keys.js","webpack:///./node_modules/core-js/modules/_enum-keys.js","webpack:///./node_modules/core-js/modules/_export.js","webpack:///./node_modules/core-js/modules/_fails-is-regexp.js","webpack:///./node_modules/core-js/modules/_fails.js","webpack:///./node_modules/core-js/modules/_fix-re-wks.js","webpack:///./node_modules/core-js/modules/_flags.js","webpack:///./node_modules/core-js/modules/_for-of.js","webpack:///./node_modules/core-js/modules/_global.js","webpack:///./node_modules/core-js/modules/_has.js","webpack:///./node_modules/core-js/modules/_hide.js","webpack:///./node_modules/core-js/modules/_html.js","webpack:///./node_modules/core-js/modules/_ie8-dom-define.js","webpack:///./node_modules/core-js/modules/_inherit-if-required.js","webpack:///./node_modules/core-js/modules/_invoke.js","webpack:///./node_modules/core-js/modules/_iobject.js","webpack:///./node_modules/core-js/modules/_is-array-iter.js","webpack:///./node_modules/core-js/modules/_is-array.js","webpack:///./node_modules/core-js/modules/_is-integer.js","webpack:///./node_modules/core-js/modules/_is-object.js","webpack:///./node_modules/core-js/modules/_is-regexp.js","webpack:///./node_modules/core-js/modules/_iter-call.js","webpack:///./node_modules/core-js/modules/_iter-create.js","webpack:///./node_modules/core-js/modules/_iter-define.js","webpack:///./node_modules/core-js/modules/_iter-detect.js","webpack:///./node_modules/core-js/modules/_iter-step.js","webpack:///./node_modules/core-js/modules/_iterators.js","webpack:///./node_modules/core-js/modules/_library.js","webpack:///./node_modules/core-js/modules/_math-expm1.js","webpack:///./node_modules/core-js/modules/_math-fround.js","webpack:///./node_modules/core-js/modules/_math-log1p.js","webpack:///./node_modules/core-js/modules/_math-sign.js","webpack:///./node_modules/core-js/modules/_meta.js","webpack:///./node_modules/core-js/modules/_microtask.js","webpack:///./node_modules/core-js/modules/_new-promise-capability.js","webpack:///./node_modules/core-js/modules/_object-assign.js","webpack:///./node_modules/core-js/modules/_object-create.js","webpack:///./node_modules/core-js/modules/_object-dp.js","webpack:///./node_modules/core-js/modules/_object-dps.js","webpack:///./node_modules/core-js/modules/_object-gopd.js","webpack:///./node_modules/core-js/modules/_object-gopn-ext.js","webpack:///./node_modules/core-js/modules/_object-gopn.js","webpack:///./node_modules/core-js/modules/_object-gops.js","webpack:///./node_modules/core-js/modules/_object-gpo.js","webpack:///./node_modules/core-js/modules/_object-keys-internal.js","webpack:///./node_modules/core-js/modules/_object-keys.js","webpack:///./node_modules/core-js/modules/_object-pie.js","webpack:///./node_modules/core-js/modules/_object-sap.js","webpack:///./node_modules/core-js/modules/_object-to-array.js","webpack:///./node_modules/core-js/modules/_own-keys.js","webpack:///./node_modules/core-js/modules/_parse-float.js","webpack:///./node_modules/core-js/modules/_parse-int.js","webpack:///./node_modules/core-js/modules/_perform.js","webpack:///./node_modules/core-js/modules/_promise-resolve.js","webpack:///./node_modules/core-js/modules/_property-desc.js","webpack:///./node_modules/core-js/modules/_redefine-all.js","webpack:///./node_modules/core-js/modules/_redefine.js","webpack:///./node_modules/core-js/modules/_regexp-exec-abstract.js","webpack:///./node_modules/core-js/modules/_regexp-exec.js","webpack:///./node_modules/core-js/modules/_same-value.js","webpack:///./node_modules/core-js/modules/_set-proto.js","webpack:///./node_modules/core-js/modules/_set-species.js","webpack:///./node_modules/core-js/modules/_set-to-string-tag.js","webpack:///./node_modules/core-js/modules/_shared-key.js","webpack:///./node_modules/core-js/modules/_shared.js","webpack:///./node_modules/core-js/modules/_species-constructor.js","webpack:///./node_modules/core-js/modules/_strict-method.js","webpack:///./node_modules/core-js/modules/_string-at.js","webpack:///./node_modules/core-js/modules/_string-context.js","webpack:///./node_modules/core-js/modules/_string-html.js","webpack:///./node_modules/core-js/modules/_string-pad.js","webpack:///./node_modules/core-js/modules/_string-repeat.js","webpack:///./node_modules/core-js/modules/_string-trim.js","webpack:///./node_modules/core-js/modules/_string-ws.js","webpack:///./node_modules/core-js/modules/_task.js","webpack:///./node_modules/core-js/modules/_to-absolute-index.js","webpack:///./node_modules/core-js/modules/_to-index.js","webpack:///./node_modules/core-js/modules/_to-integer.js","webpack:///./node_modules/core-js/modules/_to-iobject.js","webpack:///./node_modules/core-js/modules/_to-length.js","webpack:///./node_modules/core-js/modules/_to-object.js","webpack:///./node_modules/core-js/modules/_to-primitive.js","webpack:///./node_modules/core-js/modules/_typed-array.js","webpack:///./node_modules/core-js/modules/_typed-buffer.js","webpack:///./node_modules/core-js/modules/_typed.js","webpack:///./node_modules/core-js/modules/_uid.js","webpack:///./node_modules/core-js/modules/_user-agent.js","webpack:///./node_modules/core-js/modules/_validate-collection.js","webpack:///./node_modules/core-js/modules/_wks-define.js","webpack:///./node_modules/core-js/modules/_wks-ext.js","webpack:///./node_modules/core-js/modules/_wks.js","webpack:///./node_modules/core-js/modules/core.get-iterator-method.js","webpack:///./node_modules/core-js/modules/es6.array.copy-within.js","webpack:///./node_modules/core-js/modules/es6.array.every.js","webpack:///./node_modules/core-js/modules/es6.array.fill.js","webpack:///./node_modules/core-js/modules/es6.array.filter.js","webpack:///./node_modules/core-js/modules/es6.array.find-index.js","webpack:///./node_modules/core-js/modules/es6.array.find.js","webpack:///./node_modules/core-js/modules/es6.array.for-each.js","webpack:///./node_modules/core-js/modules/es6.array.from.js","webpack:///./node_modules/core-js/modules/es6.array.index-of.js","webpack:///./node_modules/core-js/modules/es6.array.is-array.js","webpack:///./node_modules/core-js/modules/es6.array.iterator.js","webpack:///./node_modules/core-js/modules/es6.array.join.js","webpack:///./node_modules/core-js/modules/es6.array.last-index-of.js","webpack:///./node_modules/core-js/modules/es6.array.map.js","webpack:///./node_modules/core-js/modules/es6.array.of.js","webpack:///./node_modules/core-js/modules/es6.array.reduce-right.js","webpack:///./node_modules/core-js/modules/es6.array.reduce.js","webpack:///./node_modules/core-js/modules/es6.array.slice.js","webpack:///./node_modules/core-js/modules/es6.array.some.js","webpack:///./node_modules/core-js/modules/es6.array.sort.js","webpack:///./node_modules/core-js/modules/es6.array.species.js","webpack:///./node_modules/core-js/modules/es6.date.now.js","webpack:///./node_modules/core-js/modules/es6.date.to-iso-string.js","webpack:///./node_modules/core-js/modules/es6.date.to-json.js","webpack:///./node_modules/core-js/modules/es6.date.to-primitive.js","webpack:///./node_modules/core-js/modules/es6.date.to-string.js","webpack:///./node_modules/core-js/modules/es6.function.bind.js","webpack:///./node_modules/core-js/modules/es6.function.has-instance.js","webpack:///./node_modules/core-js/modules/es6.function.name.js","webpack:///./node_modules/core-js/modules/es6.map.js","webpack:///./node_modules/core-js/modules/es6.math.acosh.js","webpack:///./node_modules/core-js/modules/es6.math.asinh.js","webpack:///./node_modules/core-js/modules/es6.math.atanh.js","webpack:///./node_modules/core-js/modules/es6.math.cbrt.js","webpack:///./node_modules/core-js/modules/es6.math.clz32.js","webpack:///./node_modules/core-js/modules/es6.math.cosh.js","webpack:///./node_modules/core-js/modules/es6.math.expm1.js","webpack:///./node_modules/core-js/modules/es6.math.fround.js","webpack:///./node_modules/core-js/modules/es6.math.hypot.js","webpack:///./node_modules/core-js/modules/es6.math.imul.js","webpack:///./node_modules/core-js/modules/es6.math.log10.js","webpack:///./node_modules/core-js/modules/es6.math.log1p.js","webpack:///./node_modules/core-js/modules/es6.math.log2.js","webpack:///./node_modules/core-js/modules/es6.math.sign.js","webpack:///./node_modules/core-js/modules/es6.math.sinh.js","webpack:///./node_modules/core-js/modules/es6.math.tanh.js","webpack:///./node_modules/core-js/modules/es6.math.trunc.js","webpack:///./node_modules/core-js/modules/es6.number.constructor.js","webpack:///./node_modules/core-js/modules/es6.number.epsilon.js","webpack:///./node_modules/core-js/modules/es6.number.is-finite.js","webpack:///./node_modules/core-js/modules/es6.number.is-integer.js","webpack:///./node_modules/core-js/modules/es6.number.is-nan.js","webpack:///./node_modules/core-js/modules/es6.number.is-safe-integer.js","webpack:///./node_modules/core-js/modules/es6.number.max-safe-integer.js","webpack:///./node_modules/core-js/modules/es6.number.min-safe-integer.js","webpack:///./node_modules/core-js/modules/es6.number.parse-float.js","webpack:///./node_modules/core-js/modules/es6.number.parse-int.js","webpack:///./node_modules/core-js/modules/es6.number.to-fixed.js","webpack:///./node_modules/core-js/modules/es6.number.to-precision.js","webpack:///./node_modules/core-js/modules/es6.object.assign.js","webpack:///./node_modules/core-js/modules/es6.object.create.js","webpack:///./node_modules/core-js/modules/es6.object.define-properties.js","webpack:///./node_modules/core-js/modules/es6.object.define-property.js","webpack:///./node_modules/core-js/modules/es6.object.freeze.js","webpack:///./node_modules/core-js/modules/es6.object.get-own-property-descriptor.js","webpack:///./node_modules/core-js/modules/es6.object.get-own-property-names.js","webpack:///./node_modules/core-js/modules/es6.object.get-prototype-of.js","webpack:///./node_modules/core-js/modules/es6.object.is-extensible.js","webpack:///./node_modules/core-js/modules/es6.object.is-frozen.js","webpack:///./node_modules/core-js/modules/es6.object.is-sealed.js","webpack:///./node_modules/core-js/modules/es6.object.is.js","webpack:///./node_modules/core-js/modules/es6.object.keys.js","webpack:///./node_modules/core-js/modules/es6.object.prevent-extensions.js","webpack:///./node_modules/core-js/modules/es6.object.seal.js","webpack:///./node_modules/core-js/modules/es6.object.set-prototype-of.js","webpack:///./node_modules/core-js/modules/es6.object.to-string.js","webpack:///./node_modules/core-js/modules/es6.parse-float.js","webpack:///./node_modules/core-js/modules/es6.parse-int.js","webpack:///./node_modules/core-js/modules/es6.promise.js","webpack:///./node_modules/core-js/modules/es6.reflect.apply.js","webpack:///./node_modules/core-js/modules/es6.reflect.construct.js","webpack:///./node_modules/core-js/modules/es6.reflect.define-property.js","webpack:///./node_modules/core-js/modules/es6.reflect.delete-property.js","webpack:///./node_modules/core-js/modules/es6.reflect.enumerate.js","webpack:///./node_modules/core-js/modules/es6.reflect.get-own-property-descriptor.js","webpack:///./node_modules/core-js/modules/es6.reflect.get-prototype-of.js","webpack:///./node_modules/core-js/modules/es6.reflect.get.js","webpack:///./node_modules/core-js/modules/es6.reflect.has.js","webpack:///./node_modules/core-js/modules/es6.reflect.is-extensible.js","webpack:///./node_modules/core-js/modules/es6.reflect.own-keys.js","webpack:///./node_modules/core-js/modules/es6.reflect.prevent-extensions.js","webpack:///./node_modules/core-js/modules/es6.reflect.set-prototype-of.js","webpack:///./node_modules/core-js/modules/es6.reflect.set.js","webpack:///./node_modules/core-js/modules/es6.regexp.constructor.js","webpack:///./node_modules/core-js/modules/es6.regexp.exec.js","webpack:///./node_modules/core-js/modules/es6.regexp.flags.js","webpack:///./node_modules/core-js/modules/es6.regexp.match.js","webpack:///./node_modules/core-js/modules/es6.regexp.replace.js","webpack:///./node_modules/core-js/modules/es6.regexp.search.js","webpack:///./node_modules/core-js/modules/es6.regexp.split.js","webpack:///./node_modules/core-js/modules/es6.regexp.to-string.js","webpack:///./node_modules/core-js/modules/es6.set.js","webpack:///./node_modules/core-js/modules/es6.string.anchor.js","webpack:///./node_modules/core-js/modules/es6.string.big.js","webpack:///./node_modules/core-js/modules/es6.string.blink.js","webpack:///./node_modules/core-js/modules/es6.string.bold.js","webpack:///./node_modules/core-js/modules/es6.string.code-point-at.js","webpack:///./node_modules/core-js/modules/es6.string.ends-with.js","webpack:///./node_modules/core-js/modules/es6.string.fixed.js","webpack:///./node_modules/core-js/modules/es6.string.fontcolor.js","webpack:///./node_modules/core-js/modules/es6.string.fontsize.js","webpack:///./node_modules/core-js/modules/es6.string.from-code-point.js","webpack:///./node_modules/core-js/modules/es6.string.includes.js","webpack:///./node_modules/core-js/modules/es6.string.italics.js","webpack:///./node_modules/core-js/modules/es6.string.iterator.js","webpack:///./node_modules/core-js/modules/es6.string.link.js","webpack:///./node_modules/core-js/modules/es6.string.raw.js","webpack:///./node_modules/core-js/modules/es6.string.repeat.js","webpack:///./node_modules/core-js/modules/es6.string.small.js","webpack:///./node_modules/core-js/modules/es6.string.starts-with.js","webpack:///./node_modules/core-js/modules/es6.string.strike.js","webpack:///./node_modules/core-js/modules/es6.string.sub.js","webpack:///./node_modules/core-js/modules/es6.string.sup.js","webpack:///./node_modules/core-js/modules/es6.string.trim.js","webpack:///./node_modules/core-js/modules/es6.symbol.js","webpack:///./node_modules/core-js/modules/es6.typed.array-buffer.js","webpack:///./node_modules/core-js/modules/es6.typed.data-view.js","webpack:///./node_modules/core-js/modules/es6.typed.float32-array.js","webpack:///./node_modules/core-js/modules/es6.typed.float64-array.js","webpack:///./node_modules/core-js/modules/es6.typed.int16-array.js","webpack:///./node_modules/core-js/modules/es6.typed.int32-array.js","webpack:///./node_modules/core-js/modules/es6.typed.int8-array.js","webpack:///./node_modules/core-js/modules/es6.typed.uint16-array.js","webpack:///./node_modules/core-js/modules/es6.typed.uint32-array.js","webpack:///./node_modules/core-js/modules/es6.typed.uint8-array.js","webpack:///./node_modules/core-js/modules/es6.typed.uint8-clamped-array.js","webpack:///./node_modules/core-js/modules/es6.weak-map.js","webpack:///./node_modules/core-js/modules/es6.weak-set.js","webpack:///./node_modules/core-js/modules/es7.array.includes.js","webpack:///./node_modules/core-js/modules/es7.object.entries.js","webpack:///./node_modules/core-js/modules/es7.object.get-own-property-descriptors.js","webpack:///./node_modules/core-js/modules/es7.object.values.js","webpack:///./node_modules/core-js/modules/es7.promise.finally.js","webpack:///./node_modules/core-js/modules/es7.string.pad-end.js","webpack:///./node_modules/core-js/modules/es7.string.pad-start.js","webpack:///./node_modules/core-js/modules/es7.symbol.async-iterator.js","webpack:///./node_modules/core-js/modules/web.dom.iterable.js","webpack:///./node_modules/core-js/modules/web.immediate.js","webpack:///./node_modules/core-js/modules/web.timers.js","webpack:///./node_modules/core-js/web/index.js","webpack:///./node_modules/ev-emitter/ev-emitter.js","webpack:///./node_modules/imagesloaded/imagesloaded.js","webpack:///./node_modules/jquery/dist/jquery.js","webpack:///./node_modules/lazysizes/lazysizes.js","webpack:///./node_modules/lodash/_Symbol.js","webpack:///./node_modules/lodash/_baseGetTag.js","webpack:///./node_modules/lodash/_baseTrim.js","webpack:///./node_modules/lodash/_freeGlobal.js","webpack:///./node_modules/lodash/_getRawTag.js","webpack:///./node_modules/lodash/_objectToString.js","webpack:///./node_modules/lodash/_root.js","webpack:///./node_modules/lodash/_trimmedEndIndex.js","webpack:///./node_modules/lodash/debounce.js","webpack:///./node_modules/lodash/isObject.js","webpack:///./node_modules/lodash/isObjectLike.js","webpack:///./node_modules/lodash/isSymbol.js","webpack:///./node_modules/lodash/lodash.js","webpack:///./node_modules/lodash/now.js","webpack:///./node_modules/lodash/toNumber.js","webpack:///./node_modules/picturefill/dist/picturefill.js","webpack:///../../src/utils/isBrowser.js","webpack:///../../src/utils/debounce.js","webpack:///../../src/utils/isFunction.js","webpack:///../../src/utils/getStyleComputedProperty.js","webpack:///../../src/utils/getParentNode.js","webpack:///../../src/utils/getScrollParent.js","webpack:///../../src/utils/getReferenceNode.js","webpack:///../../src/utils/isIE.js","webpack:///../../src/utils/getOffsetParent.js","webpack:///../../src/utils/isOffsetContainer.js","webpack:///../../src/utils/getRoot.js","webpack:///../../src/utils/findCommonOffsetParent.js","webpack:///../../src/utils/getScroll.js","webpack:///../../src/utils/includeScroll.js","webpack:///../../src/utils/getBordersSize.js","webpack:///../../src/utils/getWindowSizes.js","webpack:///../../src/utils/getClientRect.js","webpack:///../../src/utils/getBoundingClientRect.js","webpack:///../../src/utils/getOffsetRectRelativeToArbitraryNode.js","webpack:///../../src/utils/getViewportOffsetRectRelativeToArtbitraryNode.js","webpack:///../../src/utils/isFixed.js","webpack:///../../src/utils/getFixedPositionOffsetParent.js","webpack:///../../src/utils/getBoundaries.js","webpack:///../../src/utils/computeAutoPlacement.js","webpack:///../../src/utils/getReferenceOffsets.js","webpack:///../../src/utils/getOuterSizes.js","webpack:///../../src/utils/getOppositePlacement.js","webpack:///../../src/utils/getPopperOffsets.js","webpack:///../../src/utils/find.js","webpack:///../../src/utils/findIndex.js","webpack:///../../src/utils/runModifiers.js","webpack:///../../src/methods/update.js","webpack:///../../src/utils/isModifierEnabled.js","webpack:///../../src/utils/getSupportedPropertyName.js","webpack:///../../src/methods/destroy.js","webpack:///../../src/utils/getWindow.js","webpack:///../../src/utils/setupEventListeners.js","webpack:///../../src/methods/enableEventListeners.js","webpack:///../../src/utils/removeEventListeners.js","webpack:///../../src/methods/disableEventListeners.js","webpack:///../../src/utils/isNumeric.js","webpack:///../../src/utils/setStyles.js","webpack:///../../src/utils/setAttributes.js","webpack:///../../src/modifiers/applyStyle.js","webpack:///../../src/utils/getRoundedOffsets.js","webpack:///../../src/modifiers/computeStyle.js","webpack:///../../src/utils/isModifierRequired.js","webpack:///../../src/modifiers/arrow.js","webpack:///../../src/utils/getOppositeVariation.js","webpack:///../../src/methods/placements.js","webpack:///../../src/utils/clockwise.js","webpack:///../../src/modifiers/flip.js","webpack:///../../src/modifiers/keepTogether.js","webpack:///../../src/modifiers/offset.js","webpack:///../../src/modifiers/preventOverflow.js","webpack:///../../src/modifiers/shift.js","webpack:///../../src/modifiers/hide.js","webpack:///../../src/modifiers/inner.js","webpack:///../../src/modifiers/index.js","webpack:///../../src/methods/defaults.js","webpack:///../../src/index.js","webpack:///./node_modules/slick-carousel/slick/slick.js","webpack:///./node_modules/svg4everybody/dist/svg4everybody.js","webpack:///(webpack)/buildin/amd-options.js","webpack:///(webpack)/buildin/global.js","webpack:///(webpack)/buildin/module.js"],"sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"./cartridges/org_bissell/cartridge/client/default/js/main.js\");\n","'use strict';\nvar slickConfigs = require('../config/slickConfigs');\n\n/**\n * Init globally reusable carousels\n */\n\nmodule.exports = {\n heroCarousels: function () {\n $('.hero-caro').slick(slickConfigs.hero);\n },\n productTileCarousels: function () {\n $('.product-tile-caro').slick(slickConfigs.productTiles);\n }\n};\n","'use strict';\n\nvar baseClientSideValidation = require('base/components/clientSideValidation');\n\nmodule.exports = $.extend(baseClientSideValidation, {\n /**\n * Clears the validation errors of an invalidated form element after a change occurs\n */\n clearErrorsOnChange: function () {\n $(':input, .form-control').on('change input', function () {\n if ($(this).hasClass('is-invalid')) {\n $(this).removeClass('is-invalid');\n $(this).closest('.invalid-feedback').remove();\n }\n });\n }\n});\n","'use strict';\n\n$(document).ready(function () {\n $(function () {\n $('[data-toggle=\"tooltip\"]').tooltip();\n });\n});\n","'use strict';\n\n/**\n * Reusable slick carousel configurations\n * @example - $('.product-carousel').slick(slickConfigs.pdp)\n */\n\nmodule.exports = {\n hero: {\n autoplay: true,\n autoplaySpeed: 5000,\n easing: 'swing',\n infinite: true,\n speed: 800,\n dots: true,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1\n },\n\n productTiles: {\n infinite: true,\n speed: 300,\n dots: false,\n arrows: true,\n slidesToShow: 4,\n slidesToScroll: 1,\n responsive: [\n {\n breakpoint: 991,\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: 768,\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n }\n ]\n },\n\n pdp: {\n infinite: true,\n speed: 400,\n dots: false,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1\n }\n};\n","'use strict';\n\n/**\n * Default Zoom config\n */\nmodule.exports = {\n url: false,\n on: 'mouseover',\n duration: 120,\n target: false,\n touch: true,\n magnify: 1,\n callback: false,\n onZoomIn: false,\n onZoomOut: false\n};\n","'use strict';\n\nvar base = require('base/product/base');\nvar slickConfigs = require('../config/slickConfigs');\nvar zoomConfigs = require('../config/zoomConfigs');\nvar imagesloaded = require('imagesloaded');\nvar utils = require('../util/utils');\n\n/**\n * Disable PDP Zoom\n */\nfunction disableZoom() {\n $('.slide-link').trigger('zoom.destroy');\n}\n\n/**\n * Init PDP Zoom\n */\nfunction initZoom() {\n disableZoom();\n\n var isDesktop = utils.mediaBreakpointUp('lg');\n var $activeSlide = $('.product-carousel .slick-active');\n var $image = $activeSlide.find('.slide-link.zoom-hires');\n var url = $image.attr('href');\n\n if ($image.length > 0 && url && url !== 'null' && isDesktop) {\n // Start spinner while zoom image loads\n $activeSlide.spinner().start();\n\n var config = {\n url: url,\n callback: function () {\n // Stop spinner when zoom image loaded\n $activeSlide.spinner().stop();\n }\n };\n config = $.extend({}, zoomConfigs, config);\n\n $image.zoom(config);\n }\n}\n\n/**\n * Init the product carousel using a predefined slick configuration\n */\nfunction carouselInit() {\n var $carousel = $('.product-carousel');\n\n if ($carousel.length) {\n imagesloaded($carousel).on('done', function () {\n if ($carousel.hasClass('slick-initialized')) {\n $carousel.off('init', initZoom);\n $carousel.off('afterChange', initZoom);\n $carousel.slick('unslick');\n }\n $carousel.on('init', initZoom);\n $carousel.on('afterChange', initZoom);\n $carousel.not('.slick-initialized').slick(slickConfigs.pdp);\n });\n }\n}\n\n/**\n * Deconstruct (unslick) the carousel, removing classes and handlers added on slick initialize.\n */\nfunction carouselUnslick() {\n var $carousel = $('.product-carousel');\n\n if ($carousel.length && $carousel.hasClass('slick-initialized')) {\n $carousel.off('init', initZoom);\n $carousel.off('afterChange', initZoom);\n $carousel.slick('unslick');\n }\n}\n\n/**\n * Init the product carousel using a predefined slick configuration for bonus item\n */\nfunction carouselInitBonus() {\n var $carouselBonus = $('.bonus-product-item');\n if ($carouselBonus.length) {\n $carouselBonus.each(function () {\n var $carouselBonusDiv = $(this).find('.product-carousel');\n if ($carouselBonusDiv.length) {\n imagesloaded($carouselBonusDiv).on('done', function () {\n $carouselBonusDiv.on('init', initZoom);\n $carouselBonusDiv.on('afterChange', initZoom);\n $carouselBonusDiv.not('.slick-initialized').slick(slickConfigs.pdp);\n });\n }\n });\n }\n}\n\n/**\n * Deconstruct (unslick) the carousel, removing classes and handlers added on slick initialize.\n */\nfunction carouselUnslickBonus() {\n var $carouselBonus = $('.bonus-product-item');\n if ($carouselBonus.length) {\n $carouselBonus.each(function () {\n var $carouselBonusDiv = $(this).find('.product-carousel');\n if ($carouselBonusDiv.length && $carouselBonusDiv.hasClass('slick-initialized')) {\n $carouselBonusDiv.off('init', initZoom);\n $carouselBonusDiv.off('afterChange', initZoom);\n $carouselBonusDiv.slick('unslick');\n }\n });\n }\n}\n/**\n * @param {json} product - Product json\n */\nfunction updateMainImages(product) {\n var images = product.images;\n var productID = product.id;\n var imagesZoom = product.imagesZoom['hi-res'];\n var htmlString = '';\n\n images.large.forEach(function (image, idx) {\n var zoomImage = imagesZoom[idx] ? imagesZoom[idx] : image;\n var zoomClass = imagesZoom[idx] ? 'zoom-hires' : 'zoom-disabled';\n var htmlSlide = '
'\n + ''\n + '
';\n\n htmlString += htmlSlide;\n });\n var $varName = $('.product-detail .set-items');\n var $carouselImage;\n var $tempDiv;\n\n if ($varName.length) {\n $tempDiv = $('div.set-items').find('div[data-pid=\"' + productID + '\"]');\n $carouselImage = $tempDiv.find('.product-carousel');\n } else {\n $carouselImage = $('.product-carousel');\n }\n $carouselImage.html(htmlString);\n}\n\nvar exportBase = $.extend({}, base, {\n carouselInit: carouselInit,\n carouselInitBonus: carouselInitBonus,\n carouselUnslick: carouselUnslick,\n carouselUnslickBonus: carouselUnslickBonus,\n updateMainImages: updateMainImages\n});\n\nmodule.exports = exportBase;\n","require('bootstrap/js/src/util.js');\nrequire('bootstrap/js/src/alert.js');\n// require('bootstrap/js/src/button.js');\nrequire('bootstrap/js/src/carousel.js');\nrequire('bootstrap/js/src/collapse.js');\n// require('bootstrap/js/src/dropdown.js');\nrequire('bootstrap/js/src/modal.js');\nrequire('bootstrap/js/src/scrollspy.js');\nrequire('bootstrap/js/src/tab.js');\nrequire('bootstrap/js/src/tooltip.js');\n// require('bootstrap/js/src/popover.js');\n","'use strict';\n\nvar _ = require('lodash');\n\nvar utils = {\n /**\n * @desc Media breakpoints that are used throughout the Javascript\n */\n breakpoints: { // TODO: LFED Update with project breakpoints\n xs: 0,\n sm: 576,\n md: 768,\n lg: 992,\n xl: 1200\n },\n\n /**\n * @function\n * @description Returns either an object with all of the available breakpoints or a specific viewport based on the given size\n * @param {string=} size The viewport to return\n * @param {Object=} breakpoints A custom breakpoints object\n * @returns {Object|string} - breakpoints or specific viewport\n */\n getViewports: function (size, breakpoints) {\n var bps = breakpoints || this.breakpoints;\n\n if (size) {\n var viewport = bps[size];\n\n if (viewport) {\n return viewport;\n }\n\n window.console.error('Unexpected viewport size given in util.getViewports');\n throw new Error('Unexpected viewport size given in util.getViewports');\n } else {\n return breakpoints;\n }\n },\n\n /**\n * @function\n * @description Returns the current viewport name (ex: 'medium') or 'max' if the current window is larger than any defined viewport width\n * @returns {string} - current viewport name\n */\n getCurrentViewport: function () {\n var w = window.innerWidth;\n var viewports = utils.getViewports();\n var viewport = 'max';\n // Traverse the object from small up to desktop, and return the first match\n _.each(viewports, function (value, name) {\n if (w <= value) {\n viewport = name;\n }\n });\n return viewport;\n },\n\n /**\n * @function\n * @description appends the parameter with the given name and value to the given url and returns the changed url\n * @param {string} url the url to which the parameter will be added\n * @param {string} name the name of the parameter\n * @param {string} value the value of the parameter\n * @returns {string} - URL with parameter\n */\n appendParamToURL: function (url, name, value) {\n // quit if the param already exists\n if (url.indexOf(name + '=') !== -1) {\n return url;\n }\n var separator = url.indexOf('?') !== -1 ? '&' : '?';\n return url + separator + name + '=' + encodeURIComponent(value);\n },\n\n /**\n * @function\n * @description remove the parameter and its value from the given url and returns the changed url\n * @param {string} url the url from which the parameter will be removed\n * @param {string} name the name of parameter that will be removed from url\n * @returns {string} - URL without parameter\n */\n removeParamFromURL: function (url, name) {\n if (url.indexOf('?') === -1 || url.indexOf(name + '=') === -1) {\n return url;\n }\n var hash;\n var params;\n var domain = url.split('?')[0];\n var paramUrl = url.split('?')[1];\n var newParams = [];\n // if there is a hash at the end, store the hash\n if (paramUrl.indexOf('#') > -1) {\n hash = paramUrl.split('#')[1] || '';\n paramUrl = paramUrl.split('#')[0];\n }\n params = paramUrl.split('&');\n for (var i = 0; i < params.length; i++) {\n // put back param to newParams array if it is not the one to be removed\n if (params[i].split('=')[0] !== name) {\n newParams.push(params[i]);\n }\n }\n return domain + '?' + newParams.join('&') + (hash ? '#' + hash : '');\n },\n\n /**\n * appends params to a url\n * @param {string} url - Original url\n * @param {Object} params - Parameters to append\n * @returns {string} result url with appended parameters\n */\n appendToUrl: function (url, params) {\n var newUrl = url;\n newUrl += (newUrl.indexOf('?') !== -1 ? '&' : '?') + Object.keys(params).map(function (key) {\n return key + '=' + encodeURIComponent(params[key]);\n }).join('&');\n\n return newUrl;\n },\n\n /**\n * @function\n * @description extract the query string from URL\n * @param {string} url the url to extra query string from\n * @returns {string|Object} - Query String from URL\n **/\n getQueryString: function (url) {\n var qs;\n if (!_.isString(url)) {\n return null;\n }\n var a = document.createElement('a');\n a.href = url;\n if (a.search) {\n qs = a.search.substr(1); // remove the leading ?\n }\n return qs;\n },\n\n /**\n * @function\n * @description Checks to see if the given element is in the current viewport\n * @param {string} el - Element to check\n * @param {string} offsetToTop - Offset to give the top value\n * @returns {boolean} - Whether or not the element is in the viewport\n */\n elementInViewport: function (el, offsetToTop) {\n var top = el.offsetTop;\n var left = el.offsetLeft;\n var width = el.offsetWidth;\n var height = el.offsetHeight;\n\n while (el.offsetParent) {\n var offsetParent = el.offsetParent;\n top += offsetParent.offsetTop;\n left += offsetParent.offsetLeft;\n }\n\n if (typeof offsetToTop !== 'undefined') {\n top -= offsetToTop;\n }\n\n if (window.pageXOffset !== null) {\n return (\n top < (window.pageYOffset + window.innerHeight) &&\n left < (window.pageXOffset + window.innerWidth) &&\n (top + height) > window.pageYOffset &&\n (left + width) > window.pageXOffset\n );\n }\n\n if (document.compatMode === 'CSS1Compat') {\n return (\n top < (window.document.documentElement.scrollTop + window.document.documentElement.clientHeight) &&\n left < (window.document.documentElement.scrollLeft + window.document.documentElement.clientWidth) &&\n (top + height) > window.document.documentElement.scrollTop &&\n (left + width) > window.document.documentElement.scrollLeft\n );\n }\n\n return false;\n },\n\n /**\n * @function\n * @description Appends the parameter 'format=ajax' to a given path\n * @param {string} path the relative path\n * @returns {string} - URL with format param of ajax\n */\n ajaxUrl: function (path) {\n return this.appendParamToURL(path, 'format', 'ajax');\n },\n\n /**\n * @function\n * @description Converts the given relative URL to an absolute URL\n * @param {string} url - URL to convert\n * @returns {string} - Absolute URL\n */\n toAbsoluteUrl: function (url) {\n var absURL = url;\n if (url.indexOf('http') !== 0 && url.charAt(0) !== '/') {\n absURL = '/' + url;\n }\n return absURL;\n },\n\n /**\n * @function\n * @description Loads css dynamically from given urls\n * @param {Array} urls Array of urls from which css will be dynamically loaded.\n */\n loadDynamicCss: function (urls) {\n var i = urls.length;\n var len = urls.length;\n for (i = 0; i < len; i++) {\n this.loadedCssFiles.push(this.loadCssFile(urls[i]));\n }\n },\n\n /**\n * @function\n * @description Loads css file dynamically from given url\n * @param {string} url The url from which css file will be dynamically loaded.\n * @returns {jQuery} - CSS Link Element\n */\n loadCssFile: function (url) {\n return $('').appendTo($('head')).attr({\n type: 'text/css',\n rel: 'stylesheet'\n }).attr('href', url); // for i.e. <9, href must be added after link has been appended to head\n },\n // array to keep track of the dynamically loaded CSS files\n loadedCssFiles: [],\n\n /**\n * @function\n * @description Removes all css files which were dynamically loaded\n */\n clearDynamicCss: function () {\n var i = this.loadedCssFiles.length;\n while (i-- > 0) {\n $(this.loadedCssFiles[i]).remove();\n }\n this.loadedCssFiles = [];\n },\n\n /**\n * @function\n * @description Extracts all parameters from a given query string into an object\n * @param {string} qs The query string from which the parameters will be extracted\n * @returns {Object} - Object with params from the query string\n */\n getQueryStringParams: function (qs) {\n if (!qs || qs.length === 0) { return {}; }\n var params = {};\n var unescapedQS = decodeURIComponent(qs);\n // Use the String::replace method to iterate over each\n // name-value pair in the string.\n unescapedQS.replace(new RegExp('([^?=&]+)(=([^&]*))?', 'g'),\n function ($0, $1, $2, $3) {\n params[$1] = $3;\n }\n );\n return params;\n },\n\n /**\n * @function\n * @description Fills in the given form with the given address information\n * @param {Object} address - Address fields object\n * @param {Object} $form - Form jQuery object\n */\n fillAddressFields: function (address, $form) {\n var fields = Object.keys(address);\n for (var i = 0; i < fields.length; i++) {\n var field = fields[i];\n if (field !== 'ID' && field !== 'UUID' && field !== 'key') {\n // if the key in address object ends with 'Code', remove that suffix\n // keys that ends with 'Code' are postalCode, stateCode and countryCode\n $form.find('[name$=\"' + field.replace('Code', '') + '\"]').val(address[field]);\n // update the state fields\n if (field === 'countryCode') {\n $form.find('[name$=\"country\"]').trigger('change');\n // retrigger state selection after country has changed\n // this results in duplication of the state code, but is a necessary evil\n // for now because sometimes countryCode comes after stateCode\n $form.find('[name$=\"state\"]').val(address.stateCode);\n }\n }\n }\n },\n\n /**\n * @function\n * @description Binds the onclick-event to a delete button on a given container,\n * which opens a confirmation box with a given message\n * @param {string} container - The name of element to which the function will be bind\n * @param {string} message - The message the will be shown upon a click\n */\n setDeleteConfirmation: function (container, message) {\n $(container).on('click', '.delete', function () {\n return window.confirm(message); // eslint-disable-line\n });\n },\n\n /**\n * @function\n * @description Scrolls a browser window to a given x point\n * @param {string} xLocation - The x coordinate\n */\n scrollBrowser: function (xLocation) {\n $('html, body').animate({\n scrollTop: xLocation\n }, 500);\n },\n\n /**\n * @function\n * @desc Determines if the device that is being used is mobile\n * @returns {boolean} - Wether or not the browser is currently mobile\n */\n isMobile: function () {\n var mobileAgentHash = ['mobile', 'tablet', 'phone', 'ipad', 'ipod', 'android', 'blackberry', 'windows ce', 'opera mini', 'palm'];\n var idx = 0;\n var isMobile = false;\n var userAgent = (navigator.userAgent).toLowerCase();\n\n while (mobileAgentHash[idx] && !isMobile) {\n isMobile = (userAgent.indexOf(mobileAgentHash[idx]) >= 0);\n idx++;\n }\n return isMobile;\n },\n\n /**\n * @function\n * @description Executes a callback function when the user has stopped resizing the screen.\n * @param {function} callback - Callback function for the resize event\n * @return {function} - Callback function for the resize event\n */\n smartResize: function (callback) {\n var windowWidth = $(window).innerWidth();\n\n $(window).on('resize', _.debounce(function () {\n if (windowWidth !== $(window).innerWidth()) {\n callback();\n windowWidth = $(window).innerWidth();\n }\n }, 100));\n\n return callback;\n },\n\n /**\n * @function\n * @desc Generates a min-width matchMedia media query based on the given params\n * @param {string} size - Breakpoint to use for the media query\n * @param {Object} breakpoints - Override of the util breakpoints (optional)\n * @returns {boolean} - Wether or not the given media query matches\n */\n mediaBreakpointUp: function (size, breakpoints) {\n var breakpoint = this.getViewports(size, breakpoints);\n var mediaQuery = window.matchMedia('(min-width: ' + breakpoint + 'px)');\n return mediaQuery.matches;\n },\n\n /**\n * @function\n * @desc Generates a min-width matchMedia media query based on the given params\n * @param {string} size - Breakpoint to use for the media query\n * @param {Object} breakpoints - Override of the util breakpoints object (optional)\n * @returns {boolean} - Wether or not the given media query matches\n */\n mediaBreakpointDown: function (size, breakpoints) {\n var bps = typeof breakpoints !== 'undefined' ? breakpoints : this.breakpoints;\n var nextSize = this.getNextObjectKey(bps, size);\n\n if (typeof nextSize === 'string') {\n var breakpoint = this.getViewports(nextSize, breakpoints) - 1;\n var mediaQuery = window.matchMedia('(max-width: ' + breakpoint + 'px)');\n return mediaQuery.matches;\n }\n\n return true;\n },\n\n /**\n * @function\n * @desc Generates a min-width and max-width matchMedia media queries based on the given params\n * @param {string} sizeMin - Min breakpoint to use for the media query\n * @param {string} sizeMax - Max breakpoint to use for the media query\n * @param {Object} breakpoints - Override of the util breakpoints object (optional)\n * @returns {boolean} - Wether or not the given media query matches\n */\n mediaBreakpointBetween: function (sizeMin, sizeMax, breakpoints) {\n var min = this.mediaBreakpointUp(sizeMin, breakpoints);\n var max = this.mediaBreakpointDown(sizeMax, breakpoints);\n\n return min && max;\n },\n\n /**\n * @function\n * @desc Generates a min-width and max-width matchMedia media query based on the given params\n * @param {string} size - Breakpoint to use for the media query\n * @param {Object} breakpoints - Override of the util breakpoints object (optional)\n * @returns {boolean} - Wether or not the given media query matches\n */\n mediaBreakpointOnly: function (size, breakpoints) {\n return this.mediaBreakpointBetween(size, size, breakpoints);\n },\n\n /**\n * @function\n * @desc Retrieves the next key in the object or null if it doesn't exist\n * @param {Object} obj - Object to get the next key from\n * @param {string} key - Key to base the next key index on\n * @returns {string}|{null} - The next key of the given object or null if one doesn't exist\n */\n getNextObjectKey: function (obj, key) {\n var keys = Object.keys(obj);\n var nextIndex = keys.indexOf(key) + 1;\n\n if (keys.length > nextIndex) {\n return keys[nextIndex];\n }\n\n return null;\n },\n\n /**\n * @function\n * @desc Retrieves the util breakpoints object\n * @returns {Object} - All of the breakpoints\n */\n getBreakpoints: function () {\n return this.breakpoints;\n }\n};\n\nmodule.exports = utils;\n","'use strict';\n\n/**\n * Validate whole form. Requires `this` to be set to form object\n * @param {jQuery.event} event - Event to be canceled if form is invalid.\n * @returns {boolean} - Flag to indicate if form is valid\n */\nfunction validateForm(event) {\n var valid = true;\n if (this.checkValidity && !this.checkValidity()) {\n // safari\n valid = false;\n if (event) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n }\n $(this).find('input, select').each(function () {\n if (!this.validity.valid) {\n $(this).trigger('invalid', this.validity);\n }\n });\n }\n return valid;\n}\n\n/**\n * Remove all validation. Should be called every time before revalidating form\n * @param {element} form - Form to be cleared\n * @returns {void}\n */\nfunction clearForm(form) {\n $(form).find('.form-control.is-invalid').removeClass('is-invalid');\n}\n\nmodule.exports = {\n invalid: function () {\n $('form input, form select').on('invalid', function (e) {\n e.preventDefault();\n this.setCustomValidity('');\n if (!this.validity.valid) {\n var validationMessage = this.validationMessage;\n $(this).addClass('is-invalid');\n if (this.validity.patternMismatch && $(this).data('pattern-mismatch')) {\n validationMessage = $(this).data('pattern-mismatch');\n }\n if ((this.validity.rangeOverflow || this.validity.rangeUnderflow)\n && $(this).data('range-error')) {\n validationMessage = $(this).data('range-error');\n }\n if ((this.validity.tooLong || this.validity.tooShort)\n && $(this).data('range-error')) {\n validationMessage = $(this).data('range-error');\n }\n if (this.validity.valueMissing && $(this).data('missing-error')) {\n validationMessage = $(this).data('missing-error');\n }\n $(this).parents('.form-group').find('.invalid-feedback')\n .text(validationMessage);\n }\n });\n },\n\n submit: function () {\n $('form').on('submit', function (e) {\n return validateForm.call(this, e);\n });\n },\n\n buttonClick: function () {\n $('form button[type=\"submit\"], form input[type=\"submit\"]').on('click', function () {\n // clear all errors when trying to submit the form\n clearForm($(this).parents('form'));\n });\n },\n\n functions: {\n validateForm: function (form, event) {\n validateForm.call($(form), event || null);\n },\n clearForm: clearForm\n }\n};\n","'use strict';\n\n/**\n * Get cookie value by cookie name from browser\n * @param {string} cookieName - name of the cookie\n * @returns {string} cookie value of the found cookie name\n */\nfunction getCookie(cookieName) {\n var name = cookieName + '=';\n var decodedCookie = decodeURIComponent(document.cookie);\n var cookieArray = decodedCookie.split(';');\n for (var i = 0; i < cookieArray.length; i++) {\n var cookieItem = cookieArray[i];\n while (cookieItem.charAt(0) === ' ') {\n cookieItem = cookieItem.substring(1);\n }\n if (cookieItem.indexOf(name) === 0) {\n return cookieItem.substring(name.length, cookieItem.length);\n }\n }\n return '';\n}\n\nmodule.exports = function () {\n if ($('.valid-cookie-warning').length > 0) {\n var previousSessionID = window.localStorage.getItem('previousSid');\n var currentSessionID = getCookie('sid');\n if (!previousSessionID && currentSessionID) {\n // When a user first time visit the home page,\n // set the previousSessionID to currentSessionID\n // and Show the cookie alert\n previousSessionID = currentSessionID;\n window.localStorage.setItem('previousSid', previousSessionID);\n $('.cookie-warning-messaging').show();\n } else if (previousSessionID && previousSessionID === currentSessionID) {\n // Hide the cookie alert if user is in the same session\n $('.cookie-warning-messaging').hide();\n } else {\n // Clear the previousSessionID from localStorage\n // when user session is changed or expired\n window.localStorage.removeItem('previousSid');\n }\n }\n};\n","'use strict';\n\nvar keyboardAccessibility = require('./keyboardAccessibility');\n\nmodule.exports = function () {\n $('.country-selector a').click(function (e) {\n e.preventDefault();\n var action = $('.page').data('action');\n var localeCode = $(this).data('locale');\n var localeCurrencyCode = $(this).data('currencycode');\n var queryString = $('.page').data('querystring');\n var url = $('.country-selector').data('url');\n\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n data: {\n code: localeCode,\n queryString: queryString,\n CurrencyCode: localeCurrencyCode,\n action: action\n },\n success: function (response) {\n $.spinner().stop();\n if (response && response.redirectUrl) {\n window.location.href = response.redirectUrl;\n }\n },\n error: function () {\n $.spinner().stop();\n }\n });\n });\n\n keyboardAccessibility('.navbar-header .country-selector',\n {\n 40: function ($countryOptions) { // down\n if ($(this).is(':focus')) {\n $countryOptions.first().focus();\n } else {\n $(':focus').next().focus();\n }\n },\n 38: function ($countryOptions) { // up\n if ($countryOptions.first().is(':focus') || $(this).is(':focus')) {\n $(this).focus();\n $(this).removeClass('show');\n } else {\n $(':focus').prev().focus();\n }\n },\n 27: function () { // escape\n $(this).focus();\n $(this).removeClass('show').children('.dropdown-menu').removeClass('show');\n },\n 9: function () { // tab\n $(this).removeClass('show').children('.dropdown-menu').removeClass('show');\n }\n },\n function () {\n if (!($(this).hasClass('show'))) {\n $(this).addClass('show');\n }\n return $(this).find('.dropdown-country-selector').children('a');\n }\n );\n\n $('.navbar-header .country-selector').on('focusin', function () {\n $(this).addClass('show').children('.dropdown-menu').addClass('show');\n });\n};\n","'use strict';\n\nmodule.exports = function (element, message) {\n var errorHtml = '
' +\n '' + message + '
';\n\n $(element).append(errorHtml);\n};\n","'use strict';\n\nmodule.exports = {\n setTabNextFocus: function (focusParams) {\n var KEYCODE_TAB = 9;\n var isTabPressed = (focusParams.event.key === 'Tab' || focusParams.event.keyCode === KEYCODE_TAB);\n\n if (!isTabPressed) {\n return;\n }\n\n var firstFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.firstElementSelector);\n var lastFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.lastElementSelector);\n\n if ($(focusParams.containerSelector + ' ' + focusParams.lastElementSelector).is(':disabled')) {\n lastFocusableEl = $(focusParams.containerSelector + ' ' + focusParams.nextToLastElementSelector);\n if ($('.product-quickview.product-set').length > 0) {\n var linkElements = $(focusParams.containerSelector + ' a#fa-link.share-icons');\n lastFocusableEl = linkElements[linkElements.length - 1];\n }\n }\n\n if (focusParams.event.shiftKey) /* shift + tab */ {\n if ($(':focus').is(firstFocusableEl)) {\n lastFocusableEl.focus();\n focusParams.event.preventDefault();\n }\n } else /* tab */ {\n if ($(':focus').is(lastFocusableEl)) { // eslint-disable-line\n firstFocusableEl.focus();\n focusParams.event.preventDefault();\n }\n }\n }\n};\n","'use strict';\n\nmodule.exports = function (selector, keyFunctions, preFunction) {\n $(selector).on('keydown', function (e) {\n var key = e.which;\n var supportedKeyCodes = [37, 38, 39, 40, 27];\n if (supportedKeyCodes.indexOf(key) >= 0) {\n e.preventDefault();\n }\n var returnedScope = preFunction.call(this);\n if (keyFunctions[key]) {\n keyFunctions[key].call(this, returnedScope);\n }\n });\n};\n","'use strict';\nvar focusHelper = require('../components/focus');\n\n/**\n * Retrieves the relevant pid value\n * @param {jquery} $el - DOM container for a given add to cart button\n * @return {string} - value to be used when adding product to cart\n */\nfunction getPidValue($el) {\n var pid;\n\n if ($('#quickViewModal').hasClass('show') && !$('.product-set').length) {\n pid = $($el).closest('.modal-content').find('.product-quickview').data('pid');\n } else if ($('.product-set-detail').length || $('.product-set').length) {\n pid = $($el).closest('.product-detail').find('.product-id').text();\n } else {\n pid = $('.product-detail:not(\".bundle-item\")').data('pid');\n }\n\n return pid;\n}\n\n/**\n * Retrieve contextual quantity selector\n * @param {jquery} $el - DOM container for the relevant quantity\n * @return {jquery} - quantity selector DOM container\n */\nfunction getQuantitySelector($el) {\n var quantitySelected;\n if ($el && $('.set-items').length) {\n quantitySelected = $($el).closest('.product-detail').find('.quantity-select');\n } else if ($el && $('.product-bundle').length) {\n var quantitySelectedModal = $($el).closest('.modal-footer').find('.quantity-select');\n var quantitySelectedPDP = $($el).closest('.bundle-footer').find('.quantity-select');\n if (quantitySelectedModal.val() === undefined) {\n quantitySelected = quantitySelectedPDP;\n } else {\n quantitySelected = quantitySelectedModal;\n }\n } else {\n quantitySelected = $('.quantity-select');\n }\n return quantitySelected;\n}\n\n/**\n * Retrieves the value associated with the Quantity pull-down menu\n * @param {jquery} $el - DOM container for the relevant quantity\n * @return {string} - value found in the quantity input\n */\nfunction getQuantitySelected($el) {\n return getQuantitySelector($el).val();\n}\n\n/**\n * Process the attribute values for an attribute that has image swatches\n *\n * @param {Object} attr - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {Object[]} attr.values - Array of attribute value objects\n * @param {string} attr.values.value - Attribute coded value\n * @param {string} attr.values.url - URL to de/select an attribute value of the product\n * @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be\n * selected. If there is no variant that corresponds to a specific combination of attribute\n * values, an attribute may be disabled in the Product Detail Page\n * @param {jQuery} $productContainer - DOM container for a given product\n * @param {Object} msgs - object containing resource messages\n */\nfunction processSwatchValues(attr, $productContainer, msgs) {\n attr.values.forEach(function (attrValue) {\n var $attrValue = $productContainer.find('[data-attr=\"' + attr.id + '\"] [data-attr-value=\"' +\n attrValue.value + '\"]');\n var $swatchButton = $attrValue.parent();\n\n if (attrValue.selected) {\n $attrValue.addClass('selected');\n $attrValue.siblings('.selected-assistive-text').text(msgs.assistiveSelectedText);\n } else {\n $attrValue.removeClass('selected');\n $attrValue.siblings('.selected-assistive-text').empty();\n }\n\n if (attrValue.url) {\n $swatchButton.attr('data-url', attrValue.url);\n } else {\n $swatchButton.removeAttr('data-url');\n }\n\n // Disable if not selectable\n $attrValue.removeClass('selectable unselectable');\n\n $attrValue.addClass(attrValue.selectable ? 'selectable' : 'unselectable');\n });\n}\n\n/**\n * Process attribute values associated with an attribute that does not have image swatches\n *\n * @param {Object} attr - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {Object[]} attr.values - Array of attribute value objects\n * @param {string} attr.values.value - Attribute coded value\n * @param {string} attr.values.url - URL to de/select an attribute value of the product\n * @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be\n * selected. If there is no variant that corresponds to a specific combination of attribute\n * values, an attribute may be disabled in the Product Detail Page\n * @param {jQuery} $productContainer - DOM container for a given product\n */\nfunction processNonSwatchValues(attr, $productContainer) {\n var $attr = '[data-attr=\"' + attr.id + '\"]';\n var $defaultOption = $productContainer.find($attr + ' .select-' + attr.id + ' option:first');\n $defaultOption.attr('value', attr.resetUrl);\n\n attr.values.forEach(function (attrValue) {\n var $attrValue = $productContainer\n .find($attr + ' [data-attr-value=\"' + attrValue.value + '\"]');\n $attrValue.attr('value', attrValue.url)\n .removeAttr('disabled');\n\n if (!attrValue.selectable) {\n $attrValue.attr('disabled', true);\n }\n });\n}\n\n/**\n * Routes the handling of attribute processing depending on whether the attribute has image\n * swatches or not\n *\n * @param {Object} attrs - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {jQuery} $productContainer - DOM element for a given product\n * @param {Object} msgs - object containing resource messages\n */\nfunction updateAttrs(attrs, $productContainer, msgs) {\n // Currently, the only attribute type that has image swatches is Color.\n var attrsWithSwatches = ['color'];\n\n attrs.forEach(function (attr) {\n if (attrsWithSwatches.indexOf(attr.id) > -1) {\n processSwatchValues(attr, $productContainer, msgs);\n } else {\n processNonSwatchValues(attr, $productContainer);\n }\n });\n}\n\n/**\n * Updates the availability status in the Product Detail Page\n *\n * @param {Object} response - Ajax response object after an\n * attribute value has been [de]selected\n * @param {jQuery} $productContainer - DOM element for a given product\n */\nfunction updateAvailability(response, $productContainer) {\n var availabilityValue = '';\n var availabilityMessages = response.product.availability.messages;\n if (!response.product.readyToOrder) {\n availabilityValue = '
  • ' + response.resources.info_selectforstock + '
  • ';\n } else {\n availabilityMessages.forEach(function (message) {\n availabilityValue += '
  • ' + message + '
  • ';\n });\n }\n\n $($productContainer).trigger('product:updateAvailability', {\n product: response.product,\n $productContainer: $productContainer,\n message: availabilityValue,\n resources: response.resources\n });\n}\n\n/**\n * Generates html for product attributes section\n *\n * @param {array} attributes - list of attributes\n * @return {string} - Compiled HTML\n */\nfunction getAttributesHtml(attributes) {\n if (!attributes) {\n return '';\n }\n\n var html = '';\n\n attributes.forEach(function (attributeGroup) {\n if (attributeGroup.ID === 'mainAttributes') {\n attributeGroup.attributes.forEach(function (attribute) {\n html += '
    ' + attribute.label + ': '\n + attribute.value + '
    ';\n });\n }\n });\n\n return html;\n}\n\n/**\n * @typedef UpdatedOptionValue\n * @type Object\n * @property {string} id - Option value ID for look up\n * @property {string} url - Updated option value selection URL\n */\n\n/**\n * @typedef OptionSelectionResponse\n * @type Object\n * @property {string} priceHtml - Updated price HTML code\n * @property {Object} options - Updated Options\n * @property {string} options.id - Option ID\n * @property {UpdatedOptionValue[]} options.values - Option values\n */\n\n/**\n * Updates DOM using post-option selection Ajax response\n *\n * @param {OptionSelectionResponse} optionsHtml - Ajax response optionsHtml from selecting a product option\n * @param {jQuery} $productContainer - DOM element for current product\n */\nfunction updateOptions(optionsHtml, $productContainer) {\n\t// Update options\n $productContainer.find('.product-options').empty().html(optionsHtml);\n}\n\n/**\n * Dynamically creates Bootstrap carousel from response containing images\n * @param {Object[]} imgs - Array of large product images,along with related information\n * @param {jQuery} $productContainer - DOM element for a given product\n */\nfunction createCarousel(imgs, $productContainer) {\n var carousel = $productContainer.find('.carousel');\n $(carousel).carousel('dispose');\n var carouselId = $(carousel).attr('id');\n $(carousel).empty().append('
      ' + $(carousel).data('prev') + '' + $(carousel).data('next') + '');\n for (var i = 0; i < imgs.length; i++) {\n $('
      \"'
      ').appendTo($(carousel).find('.carousel-inner'));\n $('
    1. ').appendTo($(carousel).find('.carousel-indicators'));\n }\n $($(carousel).find('.carousel-item')).first().addClass('active');\n $($(carousel).find('.carousel-indicators > li')).first().addClass('active');\n if (imgs.length === 1) {\n $($(carousel).find('.carousel-indicators, a[class^=\"carousel-control-\"]')).detach();\n }\n $(carousel).carousel();\n $($(carousel).find('.carousel-indicators')).attr('aria-hidden', true);\n}\n\n/**\n * Parses JSON from Ajax call made whenever an attribute value is [de]selected\n * @param {Object} response - response from Ajax call\n * @param {Object} response.product - Product object\n * @param {string} response.product.id - Product ID\n * @param {Object[]} response.product.variationAttributes - Product attributes\n * @param {Object[]} response.product.images - Product images\n * @param {boolean} response.product.hasRequiredAttrsSelected - Flag as to whether all required\n * attributes have been selected. Used partially to\n * determine whether the Add to Cart button can be enabled\n * @param {jQuery} $productContainer - DOM element for a given product.\n */\nfunction handleVariantResponse(response, $productContainer) {\n var isChoiceOfBonusProducts =\n $productContainer.parents('.choose-bonus-product-dialog').length > 0;\n var isVaraint;\n if (response.product.variationAttributes) {\n updateAttrs(response.product.variationAttributes, $productContainer, response.resources);\n isVaraint = response.product.productType === 'variant';\n if (isChoiceOfBonusProducts && isVaraint) {\n $productContainer.parent('.bonus-product-item')\n .data('pid', response.product.id);\n\n $productContainer.parent('.bonus-product-item')\n .data('ready-to-order', response.product.readyToOrder);\n }\n }\n\n // Update primary images\n var primaryImageUrls = response.product.images.large;\n createCarousel(primaryImageUrls, $productContainer);\n\n // Update pricing\n if (!isChoiceOfBonusProducts) {\n var $priceSelector = $('.prices .price', $productContainer).length\n ? $('.prices .price', $productContainer)\n : $('.prices .price');\n $priceSelector.replaceWith(response.product.price.html);\n }\n\n // Update promotions\n $productContainer.find('.promotions').empty().html(response.product.promotionsHtml);\n\n updateAvailability(response, $productContainer);\n\n if (isChoiceOfBonusProducts) {\n var $selectButton = $productContainer.find('.select-bonus-product');\n $selectButton.trigger('bonusproduct:updateSelectButton', {\n product: response.product, $productContainer: $productContainer\n });\n } else {\n // Enable \"Add to Cart\" button if all required attributes have been selected\n $('button.add-to-cart, button.add-to-cart-global, button.update-cart-product-global').trigger('product:updateAddToCart', {\n product: response.product, $productContainer: $productContainer\n }).trigger('product:statusUpdate', response.product);\n }\n\n // Update attributes\n $productContainer.find('.main-attributes').empty()\n .html(getAttributesHtml(response.product.attributes));\n}\n\n/**\n * @typespec UpdatedQuantity\n * @type Object\n * @property {boolean} selected - Whether the quantity has been selected\n * @property {string} value - The number of products to purchase\n * @property {string} url - Compiled URL that specifies variation attributes, product ID, options,\n * etc.\n */\n\n/**\n * Updates the quantity DOM elements post Ajax call\n * @param {UpdatedQuantity[]} quantities -\n * @param {jQuery} $productContainer - DOM container for a given product\n */\nfunction updateQuantities(quantities, $productContainer) {\n if ($productContainer.parent('.bonus-product-item').length <= 0) {\n var optionsHtml = quantities.map(function (quantity) {\n var selected = quantity.selected ? ' selected ' : '';\n return '';\n }).join('');\n getQuantitySelector($productContainer).empty().html(optionsHtml);\n }\n}\n\n/**\n * updates the product view when a product attribute is selected or deselected or when\n * changing quantity\n * @param {string} selectedValueUrl - the Url for the selected variation value\n * @param {jQuery} $productContainer - DOM element for current product\n */\nfunction attributeSelect(selectedValueUrl, $productContainer) {\n if (selectedValueUrl) {\n $('body').trigger('product:beforeAttributeSelect',\n { url: selectedValueUrl, container: $productContainer });\n\n $.ajax({\n url: selectedValueUrl,\n method: 'GET',\n success: function (data) {\n handleVariantResponse(data, $productContainer);\n updateOptions(data.product.optionsHtml, $productContainer);\n updateQuantities(data.product.quantities, $productContainer);\n $('body').trigger('product:afterAttributeSelect',\n { data: data, container: $productContainer });\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n }\n}\n\n/**\n * Retrieves url to use when adding a product to the cart\n *\n * @return {string} - The provided URL to use when adding a product to the cart\n */\nfunction getAddToCartUrl() {\n return $('.add-to-cart-url').val();\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n *\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
      ').append($.parseHTML(html));\n\n var body = $html.find('.choice-of-bonus-product');\n var footer = $html.find('.modal-footer').children();\n\n return { body: body, footer: footer };\n}\n\n/**\n * Retrieves url to use when adding a product to the cart\n *\n * @param {Object} data - data object used to fill in dynamic portions of the html\n */\nfunction chooseBonusProducts(data) {\n $('.modal-body').spinner().start();\n\n if ($('#chooseBonusProductModal').length !== 0) {\n $('#chooseBonusProductModal').remove();\n }\n var bonusUrl;\n if (data.bonusChoiceRuleBased) {\n bonusUrl = data.showProductsUrlRuleBased;\n } else {\n bonusUrl = data.showProductsUrlListBased;\n }\n\n var htmlString = ''\n + '
      '\n + ''\n + '
      '\n + ''\n + '
      '\n + '
      '\n + ' ' + data.labels.selectprods + ''\n + ' '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      ';\n $('body').append(htmlString);\n $('.modal-body').spinner().start();\n\n $.ajax({\n url: bonusUrl,\n method: 'GET',\n dataType: 'json',\n success: function (response) {\n var parsedHtml = parseHtml(response.renderedTemplate);\n $('#chooseBonusProductModal .modal-body').empty();\n $('#chooseBonusProductModal .enter-message').text(response.enterDialogMessage);\n $('#chooseBonusProductModal .modal-header .close .sr-only').text(response.closeButtonText);\n $('#chooseBonusProductModal .modal-body').html(parsedHtml.body);\n $('#chooseBonusProductModal .modal-footer').html(parsedHtml.footer);\n $('#chooseBonusProductModal').modal('show');\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n}\n\n/**\n * Updates the Mini-Cart quantity value after the customer has pressed the \"Add to Cart\" button\n * @param {string} response - ajax response from clicking the add to cart button\n */\nfunction handlePostCartAdd(response) {\n $('.minicart').trigger('count:update', response);\n var messageType = response.error ? 'alert-danger' : 'alert-success';\n // show add to cart toast\n if (response.newBonusDiscountLineItem\n && Object.keys(response.newBonusDiscountLineItem).length !== 0) {\n chooseBonusProducts(response.newBonusDiscountLineItem);\n } else {\n if ($('.add-to-cart-messages').length === 0) {\n $('body').append(\n '
      '\n );\n }\n\n $('.add-to-cart-messages').append(\n '
      '\n + response.message\n + '
      '\n );\n\n setTimeout(function () {\n $('.add-to-basket-alert').remove();\n }, 5000);\n }\n}\n\n/**\n * Retrieves the bundle product item ID's for the Controller to replace bundle master product\n * items with their selected variants\n *\n * @return {string[]} - List of selected bundle product item ID's\n */\nfunction getChildProducts() {\n var childProducts = [];\n $('.bundle-item').each(function () {\n childProducts.push({\n pid: $(this).find('.product-id').text(),\n quantity: parseInt($(this).find('label.quantity').data('quantity'), 10)\n });\n });\n\n return childProducts.length ? JSON.stringify(childProducts) : [];\n}\n\n/**\n * Retrieve product options\n *\n * @param {jQuery} $productContainer - DOM element for current product\n * @return {string} - Product options and their selected values\n */\nfunction getOptions($productContainer) {\n var options = $productContainer\n .find('.product-option')\n .map(function () {\n var $elOption = $(this).find('.options-select');\n var urlValue = $elOption.val();\n var selectedValueId = $elOption.find('option[value=\"' + urlValue + '\"]')\n .data('value-id');\n return {\n optionId: $(this).data('option-id'),\n selectedValueId: selectedValueId\n };\n }).toArray();\n\n return JSON.stringify(options);\n}\n\n/**\n * Makes a call to the server to report the event of adding an item to the cart\n *\n * @param {string | boolean} url - a string representing the end point to hit so that the event can be recorded, or false\n */\nfunction miniCartReportingUrl(url) {\n if (url) {\n $.ajax({\n url: url,\n method: 'GET',\n success: function () {\n // reporting urls hit on the server\n },\n error: function () {\n // no reporting urls hit on the server\n }\n });\n }\n}\n\nmodule.exports = {\n attributeSelect: attributeSelect,\n methods: {\n editBonusProducts: function (data) {\n chooseBonusProducts(data);\n }\n },\n\n focusChooseBonusProductModal: function () {\n $('body').on('shown.bs.modal', '#chooseBonusProductModal', function () {\n $('#chooseBonusProductModal').siblings().attr('aria-hidden', 'true');\n $('#chooseBonusProductModal .close').focus();\n });\n },\n\n onClosingChooseBonusProductModal: function () {\n $('body').on('hidden.bs.modal', '#chooseBonusProductModal', function () {\n $('#chooseBonusProductModal').siblings().attr('aria-hidden', 'false');\n });\n },\n\n trapChooseBonusProductModalFocus: function () {\n $('body').on('keydown', '#chooseBonusProductModal', function (e) {\n var focusParams = {\n event: e,\n containerSelector: '#chooseBonusProductModal',\n firstElementSelector: '.close',\n lastElementSelector: '.add-bonus-products'\n };\n focusHelper.setTabNextFocus(focusParams);\n });\n },\n\n colorAttribute: function () {\n $(document).on('click', '[data-attr=\"color\"] button', function (e) {\n e.preventDefault();\n\n if ($(this).attr('disabled')) {\n return;\n }\n var $productContainer = $(this).closest('.set-item');\n if (!$productContainer.length) {\n $productContainer = $(this).closest('.product-detail');\n }\n\n attributeSelect($(this).attr('data-url'), $productContainer);\n });\n },\n\n selectAttribute: function () {\n $(document).on('change', 'select[class*=\"select-\"], .options-select', function (e) {\n e.preventDefault();\n\n var $productContainer = $(this).closest('.set-item');\n if (!$productContainer.length) {\n $productContainer = $(this).closest('.product-detail');\n }\n attributeSelect(e.currentTarget.value, $productContainer);\n });\n },\n\n availability: function () {\n $(document).on('change', '.quantity-select', function (e) {\n e.preventDefault();\n\n var $productContainer = $(this).closest('.product-detail');\n if (!$productContainer.length) {\n $productContainer = $(this).closest('.modal-content').find('.product-quickview');\n }\n\n if ($('.bundle-items', $productContainer).length === 0) {\n attributeSelect($(e.currentTarget).find('option:selected').data('url'),\n $productContainer);\n }\n });\n },\n\n addToCart: function () {\n $(document).on('click', 'button.add-to-cart, button.add-to-cart-global', function () {\n var addToCartUrl;\n var pid;\n var pidsObj;\n var setPids;\n\n $('body').trigger('product:beforeAddToCart', this);\n\n if ($('.set-items').length && $(this).hasClass('add-to-cart-global')) {\n setPids = [];\n\n $('.product-detail').each(function () {\n if (!$(this).hasClass('product-set-detail')) {\n setPids.push({\n pid: $(this).find('.product-id').text(),\n qty: $(this).find('.quantity-select').val(),\n options: getOptions($(this))\n });\n }\n });\n pidsObj = JSON.stringify(setPids);\n }\n\n pid = getPidValue($(this));\n\n var $productContainer = $(this).closest('.product-detail');\n if (!$productContainer.length) {\n $productContainer = $(this).closest('.quick-view-dialog').find('.product-detail');\n }\n\n addToCartUrl = getAddToCartUrl();\n\n var form = {\n pid: pid,\n pidsObj: pidsObj,\n childProducts: getChildProducts(),\n quantity: getQuantitySelected($(this))\n };\n\n if (!$('.bundle-item').length) {\n form.options = getOptions($productContainer);\n }\n\n $(this).trigger('updateAddToCartFormData', form);\n if (addToCartUrl) {\n $.ajax({\n url: addToCartUrl,\n method: 'POST',\n data: form,\n success: function (data) {\n handlePostCartAdd(data);\n $('body').trigger('product:afterAddToCart', data);\n $.spinner().stop();\n miniCartReportingUrl(data.reportingURL);\n },\n error: function () {\n $.spinner().stop();\n }\n });\n }\n });\n },\n selectBonusProduct: function () {\n $(document).on('click', '.select-bonus-product', function () {\n var $choiceOfBonusProduct = $(this).parents('.choice-of-bonus-product');\n var pid = $(this).data('pid');\n var maxPids = $('.choose-bonus-product-dialog').data('total-qty');\n var submittedQty = parseInt($choiceOfBonusProduct.find('.bonus-quantity-select').val(), 10);\n var totalQty = 0;\n $.each($('#chooseBonusProductModal .selected-bonus-products .selected-pid'), function () {\n totalQty += $(this).data('qty');\n });\n totalQty += submittedQty;\n var optionID = $choiceOfBonusProduct.find('.product-option').data('option-id');\n var valueId = $choiceOfBonusProduct.find('.options-select option:selected').data('valueId');\n if (totalQty <= maxPids) {\n var selectedBonusProductHtml = ''\n + '
      '\n + '
      '\n + $choiceOfBonusProduct.find('.product-name').html()\n + '
      '\n + '
      '\n + '
      '\n ;\n $('#chooseBonusProductModal .selected-bonus-products').append(selectedBonusProductHtml);\n $('.pre-cart-products').html(totalQty);\n $('.selected-bonus-products .bonus-summary').removeClass('alert-danger');\n } else {\n $('.selected-bonus-products .bonus-summary').addClass('alert-danger');\n }\n });\n },\n removeBonusProduct: function () {\n $(document).on('click', '.selected-pid', function () {\n $(this).remove();\n var $selected = $('#chooseBonusProductModal .selected-bonus-products .selected-pid');\n var count = 0;\n if ($selected.length) {\n $selected.each(function () {\n count += parseInt($(this).data('qty'), 10);\n });\n }\n\n $('.pre-cart-products').html(count);\n $('.selected-bonus-products .bonus-summary').removeClass('alert-danger');\n });\n },\n enableBonusProductSelection: function () {\n $('body').on('bonusproduct:updateSelectButton', function (e, response) {\n $('button.select-bonus-product', response.$productContainer).attr('disabled',\n (!response.product.readyToOrder || !response.product.available));\n var pid = response.product.id;\n $('button.select-bonus-product', response.$productContainer).data('pid', pid);\n });\n },\n showMoreBonusProducts: function () {\n $(document).on('click', '.show-more-bonus-products', function () {\n var url = $(this).data('url');\n $('.modal-content').spinner().start();\n $.ajax({\n url: url,\n method: 'GET',\n success: function (html) {\n var parsedHtml = parseHtml(html);\n $('.modal-body').append(parsedHtml.body);\n $('.show-more-bonus-products:first').remove();\n $('.modal-content').spinner().stop();\n },\n error: function () {\n $('.modal-content').spinner().stop();\n }\n });\n });\n },\n addBonusProductsToCart: function () {\n $(document).on('click', '.add-bonus-products', function () {\n var $readyToOrderBonusProducts = $('.choose-bonus-product-dialog .selected-pid');\n var queryString = '?pids=';\n var url = $('.choose-bonus-product-dialog').data('addtocarturl');\n var pidsObject = {\n bonusProducts: []\n };\n\n $.each($readyToOrderBonusProducts, function () {\n var qtyOption =\n parseInt($(this)\n .data('qty'), 10);\n\n var option = null;\n if (qtyOption > 0) {\n if ($(this).data('optionid') && $(this).data('option-selected-value')) {\n option = {};\n option.optionId = $(this).data('optionid');\n option.productId = $(this).data('pid');\n option.selectedValueId = $(this).data('option-selected-value');\n }\n pidsObject.bonusProducts.push({\n pid: $(this).data('pid'),\n qty: qtyOption,\n options: [option]\n });\n pidsObject.totalQty = parseInt($('.pre-cart-products').html(), 10);\n }\n });\n queryString += JSON.stringify(pidsObject);\n queryString = queryString + '&uuid=' + $('.choose-bonus-product-dialog').data('uuid');\n queryString = queryString + '&pliuuid=' + $('.choose-bonus-product-dialog').data('pliuuid');\n $.spinner().start();\n $.ajax({\n url: url + queryString,\n method: 'POST',\n success: function (data) {\n $.spinner().stop();\n if (data.error) {\n $('#chooseBonusProductModal').modal('hide');\n if ($('.add-to-cart-messages').length === 0) {\n $('body').append('
      ');\n }\n $('.add-to-cart-messages').append(\n '
      '\n + data.errorMessage + '
      '\n );\n setTimeout(function () {\n $('.add-to-basket-alert').remove();\n }, 3000);\n } else {\n $('.configure-bonus-product-attributes').html(data);\n $('.bonus-products-step2').removeClass('hidden-xl-down');\n $('#chooseBonusProductModal').modal('hide');\n\n if ($('.add-to-cart-messages').length === 0) {\n $('body').append('
      ');\n }\n $('.minicart-quantity').html(data.totalQty);\n $('.add-to-cart-messages').append(\n '
      '\n + data.msgSuccess + '
      '\n );\n setTimeout(function () {\n $('.add-to-basket-alert').remove();\n if ($('.cart-page').length) {\n location.reload();\n }\n }, 1500);\n }\n },\n error: function () {\n $.spinner().stop();\n }\n });\n });\n },\n\n getPidValue: getPidValue,\n getQuantitySelected: getQuantitySelected,\n miniCartReportingUrl: miniCartReportingUrl\n};\n","'use strict';\n\nmodule.exports = function (include) {\n if (typeof include === 'function') {\n include();\n } else if (typeof include === 'object') {\n Object.keys(include).forEach(function (key) {\n if (typeof include[key] === 'function') {\n include[key]();\n }\n });\n }\n};\n","'use strict';\n/* global pageContext */\n\nvar dataLayer = window.dataLayer;\n\n/**\n * @param {string} email - user's email submitted\n * @param {string} hashedEmail - user's email submitted in a hased form\n * @description pushes email to dataLayer during footer email submit\n */\nfunction emailSubmit(email, hashedEmail) {\n var obj = {\n event: 'emailSubmit',\n email: email,\n hashedEmail: hashedEmail\n };\n\n dataLayer.push(obj);\n}\n\n/**\n * @param {Object} productObject The product data\n * @description gets the data for a product click\n */\nfunction productClick(productObject) {\n var obj = {\n event: 'productClick',\n ecommerce: {\n click: {\n actionField: {\n list: pageContext.type\n },\n products: []\n }\n }\n };\n\n obj.ecommerce.click.products.push(productObject);\n dataLayer.push(obj);\n}\n\n/**\n * @param {Object} productObject The product data\n * @description gets the data for a wishlist click\n */\nfunction wishlistClick(productObject) {\n var obj = {\n event: 'wishlistClick',\n ecommerce: {\n click: {\n actionField: {\n list: pageContext.type\n },\n products: []\n }\n }\n };\n\n obj.ecommerce.click.products.push(productObject);\n dataLayer.push(obj);\n}\n\n/**\n * @param {Object} productObject The product data\n * @description gets the data for product compare click\n */\nfunction compareClick(productObject) {\n var obj = {\n event: 'compareClick',\n ecommerce: {\n click: {\n actionField: {\n list: pageContext.type\n },\n products: []\n }\n }\n };\n\n obj.ecommerce.click.products.push(productObject);\n dataLayer.push(obj);\n}\n\n/**\n * @description Click event for add product to cart\n * @param {Object} productObject The product data\n * @param {string} quantity product quantity\n */\nfunction addToCart(productObject, quantity) {\n var quantityObj = {\n quantity: quantity\n };\n var obj = {\n event: 'addToCart',\n currencyCode: 'USD',\n ecommerce: {\n add: {\n products: []\n }\n }\n };\n obj.ecommerce.add.products.push($.extend(productObject, quantityObj));\n dataLayer.push(obj);\n}\n\n/**\n *\n * @param {Object} productObject product object\n * @param {string|number} quantity product quantity\n */\nfunction removeFromCart(productObject, quantity) {\n var quantityObj = {\n quantity: quantity\n };\n\n var obj = {\n event: 'removeFromCart',\n ecommerce: {\n remove: {\n products: []\n }\n }\n };\n\n obj.ecommerce.remove.products.push($.extend(productObject, quantityObj));\n dataLayer.push(obj);\n}\n\n/**\n * Updates the current step in the checkout flow\n * @param {Integer} step the step number the flow is currently on\n */\nfunction updateCheckoutStep(step) {\n var gtmDataString = $('#checkout-main').attr('data-gtmdata');\n if (gtmDataString) {\n var gtmData = $.parseJSON(gtmDataString);\n if (!gtmData) {\n return;\n }\n if (gtmData.ecommerce.checkout.actionField.step === step) {\n return;\n }\n gtmData.ecommerce.checkout.actionField.step = step;\n dataLayer.push(gtmData);\n }\n}\n\n/**\n * @description Convenience method for creating a click event.\n * @param {string} event event\n * @param {string} eventCategory event categroy\n * @param {string} eventAction event action\n * @param {string} eventLabel event layer\n */\nfunction pushEvent(event, eventCategory, eventAction, eventLabel) {\n dataLayer.push({\n event: event,\n eventCategory: eventCategory,\n eventAction: eventAction,\n eventLabel: eventLabel\n });\n}\n\nvar events = {\n account: function () {},\n cart: function () {\n $('.cart.cart-page .remove-product').on('click', function () {\n var gtmData = $(this).attr('data-gtmdata');\n var qty = $(this).attr('data-quantity');\n $('.cart-delete-confirmation-btn').on('click', function () {\n removeFromCart($.parseJSON(gtmData), qty);\n });\n });\n },\n checkout: function () {\n $('body').on('gtm:shipping', function (e) {\n updateCheckoutStep(2);\n });\n\n $('body').on('gtm:billing', function (e, data) {\n updateCheckoutStep(3);\n });\n\n $('body').on('gtm:placeOrder', function () {\n updateCheckoutStep(4);\n });\n },\n compare: function () {\n $('.wishlistTile').on('click', function () {\n wishlistClick($.parseJSON($(this).attr('data-gtmdata')));\n });\n },\n product: function () {},\n search: function () {\n $('.quickview').on('click', function () {\n productClick($.parseJSON($(this).closest('.product-tile').find('[data-gtmdata]').attr('data-gtmdata')));\n });\n $('.wishlistTile').on('click', function () {\n wishlistClick($.parseJSON($(this).attr('data-gtmdata')));\n });\n $('.compare input[type=checkbox]').on('click', function () {\n var checked = $(this).is(':checked');\n if (checked) {\n compareClick($.parseJSON($(this).attr('data-gtmdata')));\n }\n });\n },\n storefront: function () {},\n wishlist: function () {},\n // events that should happen on every page\n all: function () {\n $('.pdp-link .link').on('click', function () {\n productClick($.parseJSON($(this).attr('data-gtmdata')));\n });\n $('.nav-link.dropdown-toggle').on('click', function () {\n pushEvent('trackEvent', 'User Action', 'Header Click', $(this).html());\n });\n $('.navbar-header.brand').on('click', function () {\n pushEvent('trackEvent', 'User Action', 'Header Click', 'Home Link');\n });\n\n $(document).on('click', '.minicart .remove-product', function () {\n var gtmData = $(this).attr('data-gtmdata');\n var qty = $(this).attr('data-quantity');\n $('.cart-delete-confirmation-btn').on('click', function () {\n removeFromCart($.parseJSON(gtmData), qty);\n });\n });\n $(document).on('click', '.add-to-cart-global,.add-to-cart', function () {\n var qty = $('.quantity-input').val() || 1;\n addToCart($.parseJSON($(this).attr('data-gtmdata')), qty);\n });\n\n $(document).on('email-sign-up', function (e, data) {\n emailSubmit(data.emailAddress, data.hashedEmail);\n });\n }\n};\n\n\n/**\n *\n * @description Initialize the tag manager functionality\n * @param {string} nameSpace The current name space\n */\nexports.init = function (nameSpace) {\n var ns = nameSpace || pageContext.ns;\n if (ns && events[ns]) {\n events[ns]();\n }\n events.all();\n};\n","'use strict';\n\n/**\n * Informs OG a product has been removed from the cart\n */\nfunction removeFromCart() {\n $('body').on('click', '.cart-delete-confirmation-btn', function () {\n if (window.OG && window.OG.removeFromCart) {\n window.OG.removeFromCart({\n id: $(this).data('pid')\n }, { isAjax: true });\n }\n });\n}\n\nmodule.exports = {\n removeFromCart: removeFromCart(),\n\n updateCart: function () {\n $('body').on('change', '.quantity-form .quantity', function () {\n if (window.OG && window.OG.updateCart) {\n window.OG.updateCart({\n id: $(this).data('pid'),\n module: 'sc',\n quantity: $(this).val()\n }, { isAjax: true });\n }\n removeFromCart();\n });\n }\n};\n","'use strict';\n\nvar container = [];\nvar qtyFormsContainer = [];\n\n/**\n * A function to call the OrderGroove-PurchasePostTracking endpoint with the current OG optins.\n */\nfunction onOptinChanged() {\n window.doOG(function () {\n $.ajax({\n url: window.OrdergrooveTrackingUrl,\n method: 'POST',\n data: { tracking: JSON.stringify(window.OG.getOptins()) },\n success: function () {\n // We want to trigger a quantity change event even though the quantity isn't changing\n // so that we execute the same logic that refreshes discounts when the quantity changes\n if (qtyFormsContainer && qtyFormsContainer.length > 0) {\n for (var l = 0; l < qtyFormsContainer.length; l++) {\n var qtyElement = qtyFormsContainer.pop();\n qtyElement.find('.quantity-form > .quantity').trigger('change');\n }\n }\n if (container && container.length > 0) {\n for (var i = 0; i < container.length; i++) {\n var element = container.pop();\n element.find('.js-product-price').trigger('change');\n }\n }\n $('#order-summary-block-id').load(' #order-summary-block-id > *');\n }\n });\n });\n}\n\n/**\n * Trigger onChange\n * @param {Element} $el - dom element\n */\nfunction triggerOnChange($el) {\n window.doOG(function () {\n $el.find('og-optin-button:not([subscribed])').closest('.tile-body, .product-content').removeClass('og-selected');\n var $selectedOptins = $el.find('og-optin-button[subscribed]').closest('.tile-body, .product-content');\n if ($selectedOptins) {\n for (var i = 0; i <= $selectedOptins.length; i++) {\n var $selectedOptin = $($selectedOptins[i]);\n if ($selectedOptin && !$selectedOptin.hasClass('og-selected')) {\n $selectedOptin.addClass('og-selected');\n $selectedOptin.find('og-optin-button[subscribed]').trigger('click');\n }\n }\n }\n });\n}\n\nmodule.exports = function () {\n window.doOG(function () {\n if ((!window.OrdergrooveLegacyOffers) && window.OG) {\n window.OG.addOptinChangedCallback(onOptinChanged);\n }\n\n $('body').on('change', '.js-product-price', function () {\n var ogpid = $(this).data('ogpid');\n var allPrices = $('body').find('[data-ogpid=\"' + ogpid + '\"]').toArray();\n var url = $(this).data('action');\n var isSubscriptionTrigger = false;\n var subscriptions = window.OG.getOptins();\n for (var i = 0; i < subscriptions.length; i++) {\n if (subscriptions[i].product === ogpid.toString()) {\n isSubscriptionTrigger = true;\n break;\n }\n }\n\n if (url) {\n url += isSubscriptionTrigger ? '&isSubscription=true' : '';\n $.ajax({\n url: url,\n type: 'get',\n context: this,\n dataType: 'json',\n success: function (data) {\n allPrices.forEach(function (priceContainer) {\n $(priceContainer).empty().append(data.product.price.html);\n });\n },\n error: function () { }\n });\n }\n });\n });\n\n $('body').on('click', 'og-optin-button, og-optout-button', function () {\n var $qtyFormsContainer = $(this).closest('.card.product-info');\n if ($qtyFormsContainer && $qtyFormsContainer.length > 0) {\n qtyFormsContainer.push($qtyFormsContainer);\n }\n $qtyFormsContainer = $(this).closest('.card .card-body');\n if ($qtyFormsContainer && $qtyFormsContainer.length > 0) {\n qtyFormsContainer.push($qtyFormsContainer);\n var product = $qtyFormsContainer.find('og-offer[product]').attr('product');\n var $gridContainer = $('.grid-tile.product[data-pid=' + product + ']');\n if ($gridContainer && $gridContainer.length > 0) {\n container.push($gridContainer.find('.product-tile'));\n }\n var $pdpContainer = $('.product-detail[data-pid=' + product + ']');\n if ($pdpContainer && $pdpContainer.length > 0) {\n container.push($pdpContainer);\n }\n }\n var $container = $(this).closest('.product-tile');\n $container.find('og-optin-button:not([subscribed])').closest('.tile-body, .product-content').removeClass('og-selected');\n $container.find('og-optin-button[subscribed]').closest('.tile-body, .product-content').addClass('og-selected');\n if (!$container || $container.length === 0) {\n $container = $(this).closest('.product-detail');\n }\n if ($container && $container.length > 0) {\n container.push($container);\n }\n });\n\n $('.og-offer, og-offer').ready(function () {\n triggerOnChange($(this));\n });\n\n $('body').on('change', '.og-offer, og-offer', function () {\n triggerOnChange($(this));\n });\n\n $(window).on('load', function () {\n triggerOnChange($(this));\n });\n\n // we need to trigger onOptinChanged to update JSON reference in basket\n window.bslReady(function () {\n window.doOG(function () {\n var cartPage = $('body').find('.container.cart-page');\n if (cartPage.length) {\n if (window.OG.getOptins().length) {\n container.push(cartPage);\n qtyFormsContainer.push(cartPage);\n onOptinChanged();\n }\n }\n });\n });\n};\n","'use strict';\nvar base = require('../product/base');\nvar focusHelper = require('base/components/focus');\nvar recommendationIntercept = require('./recommendationIntercept');\nvar utils = require('lyonscg/util/utils');\nvar imagesloaded = require('imagesloaded');\nvar carouselsBonusProduct = require('../components/carousels');\n\n\n/**\n * Removes currency from formatted money string\n * @param {string} moneyString - the formatted money string\n * @returns {number} unformatted money value\n */\nfunction unformatMoney(moneyString) {\n if (!moneyString) return 0;\n return Number(moneyString.replace(/[^0-9.-]+/g, ''));\n}\n\n/**\n * updates checkout buttons\n * @param {Object} data - AJAX response from the server\n */\nfunction updateCheckoutButtons(data) {\n var $payPalButtonsContainer = $('.paypal-cart-buttons-container');\n var $affirmButtonsContainer = $('.affirm-cart-buttons-container');\n if (data && data.valid && data.valid.error) {\n $('.checkout-btn').addClass('disabled');\n $payPalButtonsContainer.addClass('d-none');\n $affirmButtonsContainer.addClass('d-none');\n } else {\n $('.checkout-btn').removeClass('disabled');\n $payPalButtonsContainer.removeClass('d-none');\n $affirmButtonsContainer.removeClass('d-none');\n }\n\n if (data && data.autoShip && !$payPalButtonsContainer.hasClass('d-none')) {\n $payPalButtonsContainer.addClass('d-none');\n $affirmButtonsContainer.addClass('d-none');\n } else if (data && !data.autoShip && data.valid && !data.valid.error) {\n $payPalButtonsContainer.removeClass('d-none');\n $affirmButtonsContainer.removeClass('d-none');\n }\n\n if (data && data.valid && data.valid.validOrderTotal) {\n $('.cart-order-threshold').addClass('d-none');\n $('.pet-donation-card').removeClass('d-none');\n $('.cart-shipping-method-selection').removeClass('d-none');\n } else {\n $('.cart-order-threshold').removeClass('d-none');\n $('.pet-donation-card').addClass('d-none');\n $('.cart-shipping-method-selection').addClass('d-none');\n }\n}\n\n/**\n * appends params to a url\n * @param {string} url - Original url\n * @param {Object} params - Parameters to append\n * @returns {string} result url with appended parameters\n */\nfunction appendToUrl(url, params) {\n var newUrl = url;\n newUrl += (newUrl.indexOf('?') !== -1 ? '&' : '?') + Object.keys(params).map(function (key) {\n return key + '=' + encodeURIComponent(params[key]);\n }).join('&');\n\n return newUrl;\n}\n\n/**\n * Checks whether the basket is valid. if invalid displays error message and disables\n * checkout button\n * @param {Object} data - AJAX response from the server\n */\nfunction validateBasket(data) {\n if (data.valid.error) {\n if (data.valid.message) {\n var errorHtml = '
      ' +\n '' + data.valid.message + '
      ';\n\n $('.cart-error').append(errorHtml);\n } else {\n $('.cart').empty().append('
      ' +\n '
      ' +\n '

      ' + data.resources.emptyCartMsg + '

      ' +\n '
      ' +\n '
      '\n );\n $('.number-of-items').empty().append(data.resources.numberOfItems);\n $('.minicart-quantity').empty().append(data.numItems);\n $('.minicart-link').attr({\n 'aria-label': data.resources.minicartCountOfItems,\n title: data.resources.minicartCountOfItems\n });\n $('.minicart .popover').empty();\n $('.minicart .popover').removeClass('show');\n }\n } else {\n $('.cart-error').empty();\n }\n}\n\n/**\n * re-renders the shipping EDD in the cart\n * @param {Object} data - AJAX response from the server\n */\nfunction updateEDD(data) {\n var lastEnd;\n data.data.items.forEach(function (item) {\n if (!item.isPetDonationProduct) {\n // get its start date span\n var startSelector = '.product-line-edd-start-' + item.UUID;\n $(startSelector).empty().append(item.edd.edd_begin_formatted);\n\n // determine if we need to show end date\n var endSelector = '.product-line-edd-end-' + item.UUID;\n $(endSelector).empty();\n if (item.edd.edd_begin !== item.edd.edd_end) {\n $(endSelector).append('- ' + item.edd.edd_end_formatted);\n }\n\n try {\n var eddEnd = new Date(item.edd.edd_end);\n if (!lastEnd || eddEnd > lastEnd) {\n lastEnd = eddEnd;\n }\n } catch (e) {\n window.bslLog(e);\n }\n }\n });\n\n window.logEstimatedDeliveryDate(lastEnd);\n\n // update calculated\n $('.estimated-delivery-dates-calculated-range').empty().append(data.narvarEDD.narvarCalculatedRange);\n}\n\nwindow.logEstimatedDeliveryDate = function logEstimatedDeliveryDate(dEstimatedDeliveryDate) {\n if (!dEstimatedDeliveryDate) return;\n try {\n var datToday = new Date(new Date().toJSON().slice(0, 10));\n var endDiff = parseInt(((new Date(dEstimatedDeliveryDate)).getTime() - datToday.getTime()) / (1000 * 3600 * 24), 10);\n\n window.bslLog('EstimatedDeliveryDateUpdate:' + dEstimatedDeliveryDate + ', ' + endDiff);\n if (endDiff > 0) {\n var curDiff = parseInt(window.bslReadDL('EstimatedDeliveryDays'), 10);\n if (curDiff !== endDiff) {\n window.dataLayer = window.dataLayer || [];\n window.dataLayer.push({ event: 'EstimatedDeliveryDateUpdate', EstimatedDeliveryDays: endDiff });\n }\n }\n } catch (e) {\n window.bslLog(e);\n }\n};\n\n/**\n * re-renders the order totals and the number of items in the cart\n * @param {Object} data - AJAX response from the server\n */\nfunction updateCartTotals(data) {\n if (data.cartTotalsHtml) {\n $('.js-cart-totals').empty().append(data.cartTotalsHtml);\n }\n if (data.cartPromoCodesHtml) {\n $('.js-cart-promo-code').empty().append(data.cartPromoCodesHtml);\n }\n $('.number-of-items').empty().append(data.resources.numberOfItems);\n $('.shipping-cost').empty().append(data.totals.totalShippingCost);\n $('.tax-total').empty().append(data.totals.totalTax);\n $('.grand-total').empty().append(data.totals.grandTotal);\n $('.sub-total').empty().append(data.totals.subTotal);\n $('.shippingMethods option:selected').text($('.shippingMethods option:selected').text().split('-')[0] + ' - ' + data.totals.totalShippingCost);\n $('.minicart-link').attr({\n 'aria-label': data.resources.minicartCountOfItems,\n title: data.resources.minicartCountOfItems\n });\n var estimatedArrivalMsg = $('.shippingMethods option:selected').attr('data-estimated-arrival');\n $('.estimated-arrival-time').empty().append(estimatedArrivalMsg);\n\n var isFreeShipping = data.totals && data.totals.shippingLevelDiscountTotal && data.totals.shippingLevelDiscountTotal.isFreeShipping;\n if (isFreeShipping) {\n $('.free-ship-block').removeClass('d-none').addClass('d-block');\n } else {\n $('.free-ship-block').removeClass('d-block').addClass('d-none');\n }\n\n var totalCalculated = unformatMoney(data.totals.grandTotal);\n /* Affirm block for refreshing promo message */\n $('.affirm-as-low-as').attr('data-amount', (totalCalculated * 100).toFixed());\n if (window.affirm && typeof window.affirm.jsReady === 'function' && window.affirm.jsReady()) {\n window.affirm.ui.refresh();\n }\n\n $('.minicart-quantity').empty().append(data.numItems);\n\n if (data.totals.orderLevelDiscountTotal.value > 0) {\n $('.order-discount').removeClass('hide-order-discount');\n $('.order-discount-total').empty()\n .append('- ' + data.totals.orderLevelDiscountTotal.formatted);\n } else {\n $('.order-discount').addClass('hide-order-discount');\n }\n\n if (data.totals.shippingLevelDiscountTotal.value > 0) {\n $('.shipping-discount').removeClass('hide-shipping-discount');\n $('.shipping-discount-total').empty().append('- ' +\n data.totals.shippingLevelDiscountTotal.formatted);\n } else {\n $('.shipping-discount').addClass('hide-shipping-discount');\n }\n\n data.items.forEach(function (item) {\n $('.item-' + item.UUID).empty().append(item.renderedPromotions);\n $('.item-total-' + item.UUID).empty().append(item.priceTotal.renderedPrice);\n $('.item-rewards-points-' + item.UUID).empty().append(item.priceTotal.renderedRewardsPoints);\n $('.line-item-price-' + item.UUID).find('.js-product-price').empty().append(item.price.html);\n $('.product-line-item[data-product-line-item=' + item.UUID + ']').find('.js-product-price').empty().append(item.price.html);\n });\n\n var $totalSavingsContainer = $('.total-savings-container');\n if ($totalSavingsContainer.length && data.totals.totalSavings && data.totals.totalSavings.formatted) {\n $totalSavingsContainer.find('.total-savings').empty().append(data.totals.totalSavings.formatted);\n if (data.totals.totalSavings.value !== 0) {\n $totalSavingsContainer.removeClass('d-none');\n } else {\n $totalSavingsContainer.addClass('d-none');\n }\n }\n}\n\n/**\n * re-renders the order totals and the number of items in the cart\n * @param {Object} message - Error message to display\n */\nfunction createErrorNotification(message) {\n var errorHtml = '
      ' +\n '' + message + '
      ';\n\n $('.cart-error').append(errorHtml);\n}\n\n/**\n * re-renders the approaching discount messages\n * @param {Object} approachingDiscounts - updated approaching discounts for the cart\n */\nfunction updateApproachingDiscounts(approachingDiscounts) {\n var html = '';\n\n if (approachingDiscounts.length > 0) {\n $('.promotion-information').empty();\n if (!$('.promotion-information').length) {\n $('.coupons-and-promos').prepend('
      ');\n }\n approachingDiscounts.forEach(function (item) {\n html += '
      ' + item.discountMsg;\n if (item.barProgress) {\n html += '
      $' + item.barFromValue + ''\n + '
      '\n + '$' + item.barToValue + ''\n + '
      ';\n }\n html += '
      ';\n });\n }\n $('.promotion-information').append(html);\n}\n\n/**\n * Updates the availability of a product line item\n * @param {Object} data - AJAX response from the server\n * @param {string} uuid - The uuid of the product line item to update\n */\nfunction updateAvailability(data, uuid) {\n var lineItem;\n var messages = '';\n\n for (var i = 0; i < data.items.length; i++) {\n if (data.items[i].UUID === uuid) {\n lineItem = data.items[i];\n break;\n }\n }\n\n $('.availability-' + lineItem.UUID).empty();\n\n if (lineItem.availability) {\n if (lineItem.availability.messages) {\n lineItem.availability.messages.forEach(function (message) {\n messages += '

      ' + message + '

      ';\n });\n }\n\n if (lineItem.availability.inStockDate) {\n messages += '

      '\n + lineItem.availability.inStockDate\n + '

      ';\n }\n }\n\n $('.availability-' + lineItem.UUID).html(messages);\n}\n\n/**\n * Finds an element in the array that matches search parameter\n * @param {array} array - array of items to search\n * @param {function} match - function that takes an element and returns a boolean indicating if the match is made\n * @returns {Object|null} - returns an element of the array that matched the query.\n */\nfunction findItem(array, match) {\n for (var i = 0, l = array.length; i < l; i++) {\n if (match.call(this, array[i])) {\n return array[i];\n }\n }\n return null;\n}\n\n/**\n * Updates details of a product line item\n * @param {Object} data - AJAX response from the server\n * @param {string} uuid - The uuid of the product line item to update\n */\nfunction updateProductDetails(data, uuid) {\n var lineItem = findItem(data.cartModel.items, function (item) {\n return item.UUID === uuid;\n });\n\n if (lineItem.variationAttributes) {\n var colorAttr = findItem(lineItem.variationAttributes, function (attr) {\n return attr.attributeId === 'color';\n });\n\n if (colorAttr) {\n var colorSelector = '.Color-' + uuid;\n var newColor = 'Color: ' + colorAttr.displayValue;\n $(colorSelector).text(newColor);\n }\n\n var sizeAttr = findItem(lineItem.variationAttributes, function (attr) {\n return attr.attributeId === 'size';\n });\n\n if (sizeAttr) {\n var sizeSelector = '.Size-' + uuid;\n var newSize = 'Size: ' + sizeAttr.displayValue;\n $(sizeSelector).text(newSize);\n }\n\n var imageSelector = '.card.product-info.uuid-' + uuid + ' .item-image > img';\n $(imageSelector).attr('src', lineItem.images.small[0].url);\n $(imageSelector).attr('alt', lineItem.images.small[0].alt);\n $(imageSelector).attr('title', lineItem.images.small[0].title);\n }\n\n var qtySelector = '.quantity[data-uuid=\"' + uuid + '\"]';\n $(qtySelector).val(lineItem.quantity);\n $(qtySelector).data('pid', data.newProductId);\n\n $('.remove-product[data-uuid=\"' + uuid + '\"]').data('pid', data.newProductId);\n\n var priceSelector = '.line-item-price-' + uuid + ' .sales .value';\n $(priceSelector).text(lineItem.price.sales.formatted);\n $(priceSelector).attr('content', lineItem.price.sales.decimalPrice);\n\n if (lineItem.price.list) {\n var listPriceSelector = '.line-item-price-' + uuid + ' .list .value';\n $(listPriceSelector).text(lineItem.price.list.formatted);\n $(listPriceSelector).attr('content', lineItem.price.list.decimalPrice);\n }\n}\n\n/**\n * Generates the modal window on the first call.\n *\n */\nfunction getModalHtmlElement() {\n if ($('#editProductModal').length !== 0) {\n $('#editProductModal').remove();\n }\n var htmlString = ''\n + '
      '\n + '
      '\n + ''\n + '
      '\n + '
      '\n + ' '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      ';\n $('body').append(htmlString);\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n *\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
      ').append($.parseHTML(html));\n\n var body = $html.find('.product-quickview');\n var footer = $html.find('.modal-footer').children();\n\n return { body: body, footer: footer };\n}\n\n/**\n * replaces the content in the modal window for product variation to be edited.\n * @param {string} editProductUrl - url to be used to retrieve a new product model\n */\nfunction fillModalElement(editProductUrl) {\n $('.modal-body').spinner().start();\n $.ajax({\n url: editProductUrl,\n method: 'GET',\n dataType: 'json',\n success: function (data) {\n var parsedHtml = parseHtml(data.renderedTemplate);\n\n $('#editProductModal .modal-body').empty();\n $('#editProductModal .modal-body').html(parsedHtml.body);\n $('#editProductModal .modal-footer').html(parsedHtml.footer);\n $('#editProductModal .modal-header .close .sr-only').text(data.closeButtonText);\n $('#editProductModal').modal('show');\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n}\n\n/**\n * replace content of modal\n * @param {string} actionUrl - url to be used to remove product\n * @param {string} productID - pid\n * @param {string} productName - product name\n * @param {string} uuid - uuid\n */\nfunction confirmDelete(actionUrl, productID, productName, uuid) {\n var $deleteConfirmBtn = $('.cart-delete-confirmation-btn');\n var $productToRemoveSpan = $('.product-to-remove');\n\n $deleteConfirmBtn.data('pid', productID);\n $deleteConfirmBtn.data('action', actionUrl);\n $deleteConfirmBtn.data('uuid', uuid);\n\n $productToRemoveSpan.empty().append(productName);\n}\n\n/**\n * Retrieve donation product options\n *\n * @param {jQuery} $productContainer - DOM element for current product\n * @return {string} - Product options and their selected values\n */\nfunction getDonationOptions($productContainer) {\n var options = $productContainer\n .find('.donation-option')\n .map(function () {\n var $elOption = $('input[type=radio][name^=\"variant-\"]:checked', $(this));\n var selectedValueId = $elOption.data('value-id');\n\n return {\n optionId: $(this).data('option-id'),\n selectedValueId: selectedValueId\n };\n }).toArray();\n\n return JSON.stringify(options);\n}\n\n/**\n * Submits \"add coupon\" form\n * @param {boolean} deleteExistingCoupons - flag for if existing coupons should be deleted\n */\nfunction submitAddCartCoupon(deleteExistingCoupons) {\n var $form = $('.promo-code-form');\n $('.promo-code-form .form-control').removeClass('is-invalid');\n $('.coupon-error-message').empty();\n\n var url = $form.attr('action');\n if (deleteExistingCoupons) {\n url = appendToUrl(url, {\n deleteExisting: true\n });\n }\n\n $.ajax({\n url: url,\n type: 'GET',\n dataType: 'json',\n data: $form.serialize(),\n success: function (data) {\n if (data.error) {\n $('.promo-code-form .form-control').addClass('is-invalid');\n $('.promo-code-form .form-control').attr('aria-describedby', 'invalidCouponCode');\n $('.coupon-error-message').empty().append(data.errorMessage);\n $('.promo-code-form .form-control').focus();\n } else if (data.showCouponInterceptModal) {\n // Show Coupon Modal Intercept\n $('#couponInterceptModal').modal('show');\n } else {\n location.reload();\n }\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.errorMessage);\n $.spinner().stop();\n }\n }\n });\n}\n\nmodule.exports = function () {\n $('body').on('click', '.remove-product', function (e) {\n e.preventDefault();\n\n var actionUrl = $(this).data('action');\n var productID = $(this).data('pid');\n var productName = $(this).data('name');\n var uuid = $(this).data('uuid');\n confirmDelete(actionUrl, productID, productName, uuid);\n });\n\n $('body').on('afterRemoveFromCart', function (e, data) {\n e.preventDefault();\n confirmDelete(data.actionUrl, data.productID, data.productName, data.uuid);\n });\n\n $('body').on('click', '.cart-delete-confirmation-btn', function (e) {\n e.preventDefault();\n var productID = $(this).data('pid');\n var url = $(this).data('action');\n var uuid = $(this).data('uuid');\n var isMiniCart = $(this).data('is-minicart');\n\n var urlParams = {\n pid: productID,\n uuid: uuid\n };\n\n url = appendToUrl(url, urlParams);\n\n $('body > .modal-backdrop').remove();\n\n $.spinner().start();\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n success: function (data) {\n $.spinner().stop();\n // do not refresh the page if we are deleting from the minicart\n if (isMiniCart) {\n if (data.basket.items.length === 0) {\n $('.cart').empty().append('
      ' +\n '
      ' +\n '

      ' + data.basket.resources.emptyCartMsg + '

      ' +\n '
      ' +\n '
      '\n );\n $('.number-of-items').empty().append(data.basket.resources.numberOfItems);\n $('.minicart-quantity').empty().append(data.basket.numItems);\n $('.minicart-link').attr({\n 'aria-label': data.basket.resources.minicartCountOfItems,\n title: data.basket.resources.minicartCountOfItems\n });\n $('.minicart .popover').empty();\n $('.minicart .popover').removeClass('show');\n $('body').removeClass('modal-open');\n $('html').removeClass('veiled');\n } else {\n if (data.toBeDeletedUUIDs && data.toBeDeletedUUIDs.length > 0) {\n for (var i = 0; i < data.toBeDeletedUUIDs.length; i++) {\n $('.uuid-' + data.toBeDeletedUUIDs[i]).remove();\n }\n }\n $('.uuid-' + uuid).remove();\n if (!data.basket.hasBonusProduct) {\n $('.bonus-product').remove();\n }\n $('.coupons-and-promos').empty().append(data.basket.totals.discountsHtml);\n updateCartTotals(data.basket);\n updateApproachingDiscounts(data.basket.approachingDiscounts);\n $('body').trigger('setShippingMethodSelection', data.basket);\n validateBasket(data.basket);\n updateCheckoutButtons(data.basket);\n }\n $('body').trigger('product:afterRemoveFromCart', data);\n $('body').trigger('cart:update');\n } else {\n location.replace(utils.appendParamToURL(location.href, 'showIntercept', false));\n }\n },\n error: function (err) {\n if (err.responseJSON && err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n });\n\n $('body').on('change', '.quantity-form > .quantity', function () {\n var preSelectQty = $(this).data('pre-select-qty');\n var quantity = $(this).val();\n var productID = $(this).data('pid');\n var url = $(this).data('action');\n var uuid = $(this).data('uuid');\n\n var urlParams = {\n pid: productID,\n quantity: quantity,\n uuid: uuid\n };\n url = appendToUrl(url, urlParams);\n\n $(this).parents('.card').spinner().start();\n\n $.ajax({\n url: url,\n type: 'get',\n context: this,\n dataType: 'json',\n mode: 'cors',\n success: function (data) {\n /**\n * if we updated the qty of a product that would potentially require recycling fees\n * refresh the page so that all recycling fee products are updated\n */\n if ($(this).parents('.product-info').hasClass('subject-to-recycling-fee')) {\n location.replace(utils.appendParamToURL(location.href, 'showIntercept', false));\n return;\n }\n\n $('.quantity[data-uuid=\"' + uuid + '\"]').val(quantity);\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n updateAvailability(data, uuid);\n validateBasket(data);\n updateCheckoutButtons(data);\n $(this).data('pre-select-qty', quantity);\n\n $('body').trigger('cart:update');\n\n $.spinner().stop();\n if ($(this).parents('.product-info').hasClass('bonus-product-line-item') && $('.cart-page').length) {\n location.replace(utils.appendParamToURL(location.href, 'showIntercept', false));\n }\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $(this).val(parseInt(preSelectQty, 10));\n $.spinner().stop();\n }\n }\n });\n });\n\n $('body').on('click', '#pwr-sms-optin', function () {\n var $input = $(this);\n var $pwrSmsOptIn = $input.attr('checked');\n\n if (typeof $pwrSmsOptIn === 'undefined') {\n $input.prop('checked', true).attr('checked', true);\n } else {\n $input.prop('checked', false).attr('checked', false);\n }\n });\n\n $('.shippingMethods').change(function () {\n var url = $(this).attr('data-actionUrl');\n var urlParams = {\n methodID: $(this).find(':selected').attr('data-shipping-id')\n };\n // url = appendToUrl(url, urlParams);\n\n $('.totals').spinner().start();\n $.ajax({\n url: url,\n type: 'post',\n dataType: 'json',\n data: urlParams,\n success: function (data) {\n if (data.error) {\n window.location.href = data.redirectUrl;\n } else {\n $('.coupons-and-promos').empty().append(data.totals.discountsHtml);\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n validateBasket(data);\n updateCheckoutButtons(data);\n updateEDD(data);\n }\n $.spinner().stop();\n },\n error: function (err) {\n if (err.redirectUrl) {\n window.location.href = err.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n });\n\n $(function () {\n if ($('#shippingCountrydefault').val() === 'Canada') {\n $('#shippingZipCodedefault').bind('input', function () {\n var shippingZipCodeLength = $('#shippingZipCodedefault').val().length;\n if (shippingZipCodeLength >= 4) {\n $(this).val(function (_, v) {\n return v.replace(/\\W/g, '').replace(/(...)/, '$1 ');\n });\n }\n });\n } else if ($('#shippingCountrydefault').val() === 'United States') {\n $('#shippingZipCodedefault, #billingZipCode').bind('input', function () {\n $(this).val(function (_, v) {\n return v.replace(/\\s+/g, '');\n });\n });\n }\n });\n\n $(function () {\n $('#couponCode').bind('input', function () {\n $(this).val(function (_, v) {\n return v.replace(/\\s+/g, '');\n });\n });\n });\n\n $('.promo-code-form').submit(function (e) {\n e.preventDefault();\n $.spinner().start();\n $('.coupon-missing-error').hide();\n $('.coupon-error-message').empty();\n if (!$('.coupon-code-field').val()) {\n $('.promo-code-form .form-control').addClass('is-invalid');\n $('.promo-code-form .form-control').attr('aria-describedby', 'missingCouponCode');\n $('.coupon-missing-error').show();\n $.spinner().stop();\n $('.promo-code-form .form-control').focus();\n return false;\n }\n submitAddCartCoupon(false);\n return false;\n });\n\n $('body').on('click', '.btn-coupon-intercept-delete', function (e) {\n e.preventDefault();\n $('body > .modal-backdrop').remove();\n $('#couponInterceptModal').modal('hide');\n $.spinner().start();\n submitAddCartCoupon(true);\n });\n\n $('body').on('click', '.btn-coupon-intercept-cancel', function (e) {\n e.preventDefault();\n $('body > .modal-backdrop').remove();\n $('#couponInterceptModal').modal('hide');\n $('.coupon-code-field').val('');\n });\n\n $('body').on('click', '.remove-coupon', function (e) {\n e.preventDefault();\n\n var couponCode = $(this).data('code');\n var uuid = $(this).data('uuid');\n var $deleteConfirmBtn = $('.delete-coupon-confirmation-btn');\n var $productToRemoveSpan = $('.coupon-to-remove');\n\n $deleteConfirmBtn.data('uuid', uuid);\n $deleteConfirmBtn.data('code', couponCode);\n\n $productToRemoveSpan.empty().append(couponCode);\n });\n\n $('body').on('click', '.delete-coupon-confirmation-btn', function (e) {\n e.preventDefault();\n\n var url = $(this).data('action');\n var uuid = $(this).data('uuid');\n var couponCode = $(this).data('code');\n var urlParams = {\n code: couponCode,\n uuid: uuid\n };\n\n url = appendToUrl(url, urlParams);\n\n $('body > .modal-backdrop').remove();\n\n $.spinner().start();\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n success: function (data) {\n $('.coupon-uuid-' + uuid).remove();\n updateCartTotals(data);\n updateApproachingDiscounts(data.approachingDiscounts);\n validateBasket(data);\n updateCheckoutButtons(data);\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n });\n\n $('body').on('click', '.cart-page .bonus-product-button', function () {\n $.spinner().start();\n $(this).addClass('launched-modal');\n $.ajax({\n url: $(this).data('url'),\n method: 'GET',\n dataType: 'json',\n success: function (data) {\n base.methods.editBonusProducts(data);\n $('.bonus-product-item').each(function () {\n var bonusItem = $(this);\n imagesloaded(bonusItem).on('done', function () {\n carouselsBonusProduct.quickviewCarousel();\n });\n });\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n });\n\n $('body').on('hidden.bs.modal', '#chooseBonusProductModal', function () {\n $('#chooseBonusProductModal').remove();\n $('.modal-backdrop').remove();\n $('body').removeClass('modal-open');\n\n if ($('.cart-page').length) {\n $('.launched-modal .btn-outline-primary').trigger('focus');\n $('.launched-modal').removeClass('launched-modal');\n } else {\n $('.product-detail .add-to-cart').focus();\n }\n });\n\n $('body').on('click', '.cart-page .product-edit .edit, .cart-page .bundle-edit .edit', function (e) {\n e.preventDefault();\n\n var editProductUrl = $(this).attr('href');\n getModalHtmlElement();\n fillModalElement(editProductUrl);\n });\n\n $('body').on('shown.bs.modal', '#editProductModal', function () {\n $('#editProductModal').siblings().attr('aria-hidden', 'true');\n $('#editProductModal .close').focus();\n });\n\n $('body').on('hidden.bs.modal', '#editProductModal', function () {\n $('#editProductModal').siblings().attr('aria-hidden', 'false');\n });\n\n $('body').on('keydown', '#editProductModal', function (e) {\n var focusParams = {\n event: e,\n containerSelector: '#editProductModal',\n firstElementSelector: '.close',\n lastElementSelector: '.update-cart-product-global',\n nextToLastElementSelector: '.modal-footer .quantity-select'\n };\n focusHelper.setTabNextFocus(focusParams);\n });\n\n $('body').on('product:updateAddToCart', function (e, response) {\n // update global add to cart (single products, bundles)\n var dialog = $(response.$productContainer)\n .closest('.quick-view-dialog');\n\n $('.update-cart-product-global', dialog).attr('disabled',\n !$('.global-availability', dialog).data('ready-to-order')\n || !$('.global-availability', dialog).data('available')\n || !$('.global-availability', dialog).data('sellable')\n );\n });\n\n $('body').on('product:updateAvailability', function (e, response) {\n // bundle individual products\n $('.product-availability', response.$productContainer)\n .data('ready-to-order', response.product.readyToOrder)\n .data('available', response.product.available)\n .data('sellable', response.product.sellable)\n .find('.availability-msg')\n .empty()\n .html(response.message);\n\n\n var dialog = $(response.$productContainer)\n .closest('.quick-view-dialog');\n\n if ($('.product-availability', dialog).length) {\n // bundle all products\n var allAvailable = $('.product-availability', dialog).toArray()\n .every(function (item) { return $(item).data('available'); });\n\n var allReady = $('.product-availability', dialog).toArray()\n .every(function (item) { return $(item).data('ready-to-order'); });\n\n var allSellable = $('.product-availability', dialog).toArray()\n .every(function (item) { return $(item).data('sellable'); });\n\n $('.global-availability', dialog)\n .data('ready-to-order', allReady)\n .data('available', allAvailable)\n .data('sellable', allSellable);\n\n $('.global-availability .availability-msg', dialog).empty()\n .html(allReady ? response.message : response.resources.info_selectforstock);\n } else {\n // single product\n $('.global-availability', dialog)\n .data('ready-to-order', response.product.readyToOrder)\n .data('available', response.product.available)\n .data('sellable', response.product.sellable)\n .find('.availability-msg')\n .empty()\n .html(response.message);\n }\n });\n\n $('body').on('product:afterAttributeSelect', function (e, response) {\n if ($('.modal.show .product-quickview .bundle-items').length) {\n $('.modal.show').find(response.container).data('pid', response.data.product.id);\n $('.modal.show').find(response.container).find('.product-id').text(response.data.product.id);\n } else {\n $('.modal.show .product-quickview').data('pid', response.data.product.id);\n }\n });\n\n $('body').on('change', '.quantity-select', function () {\n var selectedQuantity = $(this).val();\n $('.modal.show .update-cart-url').data('selected-quantity', selectedQuantity);\n });\n\n $('body').on('click', '.update-cart-product-global', function (e) {\n e.preventDefault();\n\n var updateProductUrl = $(this).closest('.cart-and-ipay').find('.update-cart-url').val();\n var selectedQuantity = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('selected-quantity');\n var uuid = $(this).closest('.cart-and-ipay').find('.update-cart-url').data('uuid');\n\n var form = {\n uuid: uuid,\n pid: base.getPidValue($(this)),\n quantity: selectedQuantity\n };\n\n $(this).parents('.card').spinner().start();\n if (updateProductUrl) {\n $.ajax({\n url: updateProductUrl,\n type: 'post',\n context: this,\n data: form,\n dataType: 'json',\n success: function (data) {\n $('#editProductModal').modal('hide');\n\n $('.coupons-and-promos').empty().append(data.cartModel.totals.discountsHtml);\n updateCartTotals(data.cartModel);\n updateApproachingDiscounts(data.cartModel.approachingDiscounts);\n updateAvailability(data.cartModel, uuid);\n updateProductDetails(data, uuid);\n\n if (data.uuidToBeDeleted) {\n $('.uuid-' + data.uuidToBeDeleted).remove();\n }\n\n validateBasket(data.cartModel);\n updateCheckoutButtons(data);\n\n $('body').trigger('cart:update');\n\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n createErrorNotification(err.responseJSON.errorMessage);\n $.spinner().stop();\n }\n }\n });\n }\n });\n\n\n $('body').on('click', '.pet-donation-add-to-cart', function (e) {\n e.preventDefault();\n\n $(this).spinner().start();\n var addToCartUrl = $('.add-to-cart-url').val();\n var $productContainer = $(this).closest('.product-detail');\n var donationOptions = JSON.parse(getDonationOptions($productContainer));\n var pid = donationOptions ? donationOptions[0].selectedValueId : '';\n\n var form = {\n pid: pid,\n quantity: 1,\n options: donationOptions\n };\n\n if (addToCartUrl) {\n $.ajax({\n url: addToCartUrl,\n method: 'POST',\n data: form,\n success: function () {\n $(this).spinner().stop();\n location.reload();\n },\n error: function () {\n $(this).spinner().stop();\n }\n });\n }\n });\n var htmlString = ''\n + '
      '\n + '
      '\n + ''\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      ';\n $('body').append(htmlString);\n var $modal = $('#contentModal');\n $modal.modal({ show: false });\n $modal.on('shown.bs.modal', function (e) {\n if ($('.modal-header .header-label').is(':empty')) {\n if ($(e.relatedTarget).data('title')) {\n $(this).find('.modal-header .header-label').append($(e.relatedTarget).data('title'));\n }\n }\n if ($('.modal-body').is(':empty')) {\n $.spinner().start();\n var loadurl = $(e.relatedTarget).attr('href') + '&ajax=true #maincontent *';\n $(this).find('.modal-body').load(loadurl, function () {\n $.spinner().stop();\n $modal.modal({ show: true });\n });\n }\n });\n\n $('body').on('click', '.zipcode-submit', function (e) {\n e.preventDefault();\n $.spinner().start();\n\n var $form = $('.zipcode-form');\n var url = $form.attr('action');\n\n $.ajax({\n url: url,\n type: 'GET',\n dataType: 'json',\n data: $form.serialize(),\n success: function (data) {\n if (data.error) {\n // display error message\n var errorHtml = '
      ' +\n '' +\n data.error +\n '
      ';\n\n $('.zipcode-field-error').removeClass('hide');\n $('.zipcode-field-error').append(errorHtml);\n } else {\n updateEDD(data);\n // Update displayed zip code\n $('.customer-zipcode-input').empty().append(data.narvarEDD.customerZipcode);\n\n // show entered zip container without page reload\n $('.zipcode-set-container').addClass('show-zipcode-edd-info');\n\n // Hide input without page reload\n $('.zipcode-reset-container').addClass('hide-zipcode-input-field');\n\n // show/hide EDDs per line\n data.data.items.forEach(function (item) {\n var eddContainerSelector = '.product-line-estimated-delivery-container.edd-item-' + item.UUID;\n if (item.isPetDonationProduct) {\n $(eddContainerSelector).removeClass('show-zipcode-edd-info');\n } else {\n $(eddContainerSelector).addClass('show-zipcode-edd-info');\n }\n });\n }\n\n $.spinner().stop();\n },\n error: function (err) {\n if (err.responseJSON && err.responseJSON.redirectUrl) {\n window.location.href = err.responseJSON.redirectUrl;\n } else {\n $.spinner().stop();\n }\n }\n });\n });\n\n $('body').on('click', '.zipcode-reset', function () {\n if ($('.zipcode-reset-container').hasClass('hide-zipcode-input-field')) {\n $('.zipcode-reset-container').removeClass('hide-zipcode-input-field');\n }\n });\n\n $('body').on('click', '.zipcode-cancel-btn', function () {\n if (!$('.zipcode-reset-container').hasClass('hide-zipcode-input-field')) {\n $('.zipcode-reset-container').addClass('hide-zipcode-input-field');\n }\n });\n\n base.selectAttribute();\n base.colorAttribute();\n base.removeBonusProduct();\n base.selectBonusProduct();\n base.enableBonusProductSelection();\n base.showMoreBonusProducts();\n base.addBonusProductsToCart();\n base.focusChooseBonusProductModal();\n base.trapChooseBonusProductModalFocus();\n base.onClosingChooseBonusProductModal();\n recommendationIntercept.methods.showCartIntercept();\n};\n","'use strict';\n\nvar carousels = require('../components/carousels');\nvar utils = require('../util/utils');\nvar imagesloaded = require('imagesloaded');\n\n/**\n * Init Asset Modal\n *\n */\nfunction initAssetModal() {\n $('.js-subscribe-details').off().on('click', function () {\n var $modal = $('#recommendationInterceptModal');\n var $modalAsset = $('#assetSubscribeDetails');\n\n $modal.modal('hide');\n $modalAsset.modal('show');\n });\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
      ').append($.parseHTML(html));\n var modal = {};\n var modalAsset = {};\n\n modal.body = $html.find('.recommendation-intercept-products');\n modal.header = $html.find('.recommendation-intercept-products-header').children();\n modal.footer = $html.find('.recommendation-intercept-footer').children();\n\n modalAsset.body = $html.find('.assetSubscribeDetails-body');\n modalAsset.header = $html.find('.assetSubscribeDetails-header').children();\n modalAsset.footer = $html.find('.assetSubscribeDetails-footer').children();\n\n return {\n modal: modal,\n modalAsset: modalAsset\n };\n}\n\n/**\n * Renders a modal window that will show recommendations\n * for either a product (PDP) or the entire basket (Cart)\n * @param {Object} data - data object used to fill in dynamic portions of the html\n * @returns {Object} a promise\n */\nfunction showRecommendationIntercept(data) {\n var defer = $.Deferred(); // eslint-disable-line\n\n if (!data || !data.url) {\n defer.reject();\n }\n\n var action = $('.page').data('action');\n // do not show recommendation intercept on cart page. it is handled through showCartIntercept()\n if (action && action === 'Cart-Show') {\n defer.reject({\n redirectUrl: utils.appendParamToURL(location.href, 'showIntercept', false)\n });\n }\n\n if ($('#recommendationInterceptModal').length !== 0) {\n $('#recommendationInterceptModal').remove();\n }\n\n var htmlString = ''\n + '
      '\n + '
      '\n + ''\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      ';\n\n var htmlAssetString = ''\n + '
      '\n + '
      '\n + ''\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      ';\n\n $('body').append(htmlString);\n $('body').append(htmlAssetString);\n\n $.spinner().start();\n $.ajax({\n type: 'GET',\n url: data.url,\n dataType: 'html'\n }).done(function (content, textStatus, request) {\n var response = request.getResponseHeader('Content-Type');\n if (/json/.test(response)) {\n response = jQuery.parseJSON(content);\n if (response && response.error) {\n defer.reject();\n }\n } else {\n var $modal = $('#recommendationInterceptModal');\n var $modalAsset = $('#assetSubscribeDetails');\n var parsedHtml = parseHtml(content);\n\n $modal.find('.modal-header').html(parsedHtml.modal.header);\n $modal.find('.modal-body').html(parsedHtml.modal.body);\n $modal.find('.modal-footer').html(parsedHtml.modal.footer);\n\n $modalAsset.find('.modal-header').html(parsedHtml.modalAsset.header);\n $modalAsset.find('.modal-body').html(parsedHtml.modalAsset.body);\n $modalAsset.find('.modal-footer').html(parsedHtml.modalAsset.footer);\n\n $modal.modal({ show: false });\n $modal.on('shown.bs.modal', function () {\n initAssetModal();\n imagesloaded(this).on('always', carousels.productTileInterceptCarousels);\n });\n $modal.modal('show');\n\n defer.resolve();\n }\n $.spinner().stop();\n }).fail(function () {\n defer.reject();\n $.spinner().stop();\n });\n\n return defer;\n}\n\n/**\n * Determines if an intercept modal should show on the cart page\n */\nfunction showCartIntercept() {\n var cartIntercept = $('.cart-recommendation-intercept');\n var qsShowIntercept = utils.getQueryStringParams(location).showIntercept;\n\n if (cartIntercept.length && cartIntercept.data('enabled') === true && cartIntercept.data('url') !== null && qsShowIntercept !== 'false') {\n showRecommendationIntercept({ url: cartIntercept.data('url') });\n }\n}\n\nmodule.exports = {\n methods: {\n showRecommendationIntercept: showRecommendationIntercept,\n showCartIntercept: showCartIntercept\n }\n};\n","'use strict';\n\n/*\n* Allows users to navigate Tab panels using the arrow keys.\n* This content is licensed according to the W3C Software License at\n* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document\n*/\n(function () {\n var tablist = $('[role=\"tablist\"]')[0];\n var tabs;\n var panels;\n\n // For easy reference\n var keys = {\n end: 35,\n home: 36,\n left: 37,\n up: 38,\n right: 39,\n down: 40,\n delete: 46,\n enter: 13,\n space: 32\n };\n\n // Add or subtract depending on key pressed\n var direction = {\n 37: -1,\n 38: -1,\n 39: 1,\n 40: 1\n };\n\n /**\n * Generates jQuery selector arrays\n */\n function generateArrays() {\n tabs = $('[role=\"tab\"]');\n panels = $('[role=\"tabpanel\"]');\n }\n\n /**\n * Focus first tab\n */\n function focusFirstTab() {\n tabs[0].focus();\n }\n\n /**\n * Focus last tab\n */\n function focusLastTab() {\n tabs[tabs.length - 1].focus();\n }\n\n /**\n * Deactivate all tabs and tab panels\n */\n function deactivateTabs() {\n for (var t = 0; t < tabs.length; t++) {\n tabs[t].setAttribute('tabindex', '-1');\n tabs[t].setAttribute('aria-selected', 'false');\n tabs[t].classList.remove('active-tab-link', 'active');\n }\n\n for (var p = 0; p < panels.length; p++) {\n panels[p].setAttribute('hidden', 'hidden');\n panels[p].classList.remove('active');\n }\n }\n\n /**\n * Activates any given tab panel\n * @param {Element} tab - DOM element (native js)\n * @param {boolean} setFocus - if we should set focus on this tab or not\n */\n function activateTab(tab, setFocus) {\n var useFocus = setFocus || true;\n // Deactivate all other tabs\n deactivateTabs();\n\n // Remove tabindex attribute\n tab.removeAttribute('tabindex');\n\n // Set the tab as selected\n tab.setAttribute('aria-selected', 'true');\n tab.classList.add('active-tab-link', 'active');\n\n // Get the value of aria-controls (which is an ID)\n var controls = tab.getAttribute('aria-controls');\n\n // Remove hidden attribute from tab panel to make it visible\n if (controls) {\n controls.split(' ').forEach(function (control) {\n $('#' + control).removeAttr('hidden').addClass('active');\n });\n }\n\n // Trigger custom event on tab anchor/link\n $(tab).trigger('shown.adatab');\n\n // Set focus when required\n if (useFocus) {\n tab.focus();\n }\n }\n\n /**\n * When a tab is clicked, activateTab is fired to activate it\n * @param {event} event - JS click, keydown, or keyup event\n */\n function clickEventListener(event) {\n var tab = $(event.target).closest('[role=\"tab\"]');\n if (tab.length) {\n activateTab(tab.get(0), false);\n }\n }\n\n /**\n * Either focus the next, previous, first, or last tab\n * depending on key pressed\n * @param {event} event - JS click, keydown, or keyup event\n */\n function switchTabOnArrowPress(event) {\n var pressed = event.keyCode;\n\n if (direction[pressed]) {\n var target = event.target;\n if (target.index !== undefined) {\n if (tabs[target.index + direction[pressed]]) {\n tabs[target.index + direction[pressed]].focus();\n } else if (pressed === keys.left || pressed === keys.up) {\n focusLastTab();\n } else if (pressed === keys.right || pressed === keys.down) {\n focusFirstTab();\n }\n activateTab(document.activeElement);\n }\n }\n }\n\n /**\n * When a tablist’s aria-orientation is set to vertical,\n * only up and down arrow should function.\n * In all other cases only left and right arrow function.\n * @param {event} event - JS click, keydown, or keyup event\n */\n function determineOrientation(event) {\n var key = event.keyCode;\n var vertical = tablist.getAttribute('aria-orientation') === 'vertical';\n var proceed = false;\n\n if (vertical) {\n if (key === keys.up || key === keys.down) {\n event.preventDefault();\n proceed = true;\n }\n } else if (key === keys.left || key === keys.right) {\n proceed = true;\n }\n\n if (proceed) {\n switchTabOnArrowPress(event);\n }\n }\n\n /**\n * Handle keydown on tabs\n * @param {event} event - JS click, keydown, or keyup event\n */\n function keydownEventListener(event) {\n var key = event.keyCode;\n\n switch (key) {\n case keys.end:\n event.preventDefault();\n // Activate last tab\n focusLastTab();\n activateTab(document.activeElement);\n break;\n case keys.home:\n event.preventDefault();\n // Activate first tab\n focusFirstTab();\n activateTab(document.activeElement);\n break;\n // Up and down are in keydown\n // because we need to prevent page scroll >:)\n case keys.up:\n case keys.down:\n determineOrientation(event);\n break;\n default:\n break;\n }\n }\n\n /**\n * Handle keyup on tabs\n * @param {event} event - JS click, keydown, or keyup event\n */\n function keyupEventListener(event) {\n var key = event.keyCode;\n\n switch (key) {\n case keys.left:\n case keys.right:\n switchTabOnArrowPress(event);\n break;\n default:\n break;\n }\n }\n\n /**\n * Add event listeners\n * @param {number} index - index of tab in tabs array\n */\n function addListeners(index) {\n tabs[index].addEventListener('click', clickEventListener);\n tabs[index].addEventListener('keydown', keydownEventListener);\n tabs[index].addEventListener('keyup', keyupEventListener);\n\n // Build an array with all tabs ('\n + ''\n + '
      '\n + '
      '\n + '
      '\n + '';\n $.spinner().start();\n $('body').append(htmlString);\n\n $.ajax({\n url: urlContent,\n type: 'get',\n dataType: 'html',\n success: function (response) {\n $('#consent-tracking .modal-body').html(response);\n },\n error: function () {\n $('#consent-tracking').remove();\n }\n });\n\n $('#consent-tracking button').click(function (e) {\n e.preventDefault();\n var url = $(this).data('url');\n $.ajax({\n url: url,\n type: 'get',\n dataType: 'json',\n success: function () {\n $('#consent-tracking').remove();\n $.spinner().stop();\n },\n error: function () {\n $('#consent-tracking').remove();\n $.spinner().stop();\n }\n });\n });\n}\n\nmodule.exports = function () {\n if ($('.consented').length === 0 && $('.tracking-consent').hasClass('api-true')) {\n showConsentModal();\n }\n\n if ($('.tracking-consent').hasClass('api-true')) {\n $('.tracking-consent').click(function () {\n showConsentModal();\n });\n }\n};\n","'use strict';\n\nvar countrySelector = require('base/components/countrySelector');\n\nvar exportCountrySelector = $.extend({}, countrySelector, {\n countrySelectorDropdown: function () {\n var countryDropdown = $('.country-selector').children('.dropdown-menu');\n\n $('.country-selector').on('mouseenter', function () {\n $(this).addClass('show');\n countryDropdown.addClass('show');\n setTimeout(function () {\n if ((countryDropdown).hasClass('show')) {\n countryDropdown.addClass('trigger-active');\n }\n }, 150);\n }).on('mouseleave', function () {\n $(this).removeClass('show');\n countryDropdown.removeClass('trigger-active');\n setTimeout(function () {\n countryDropdown.removeClass('show');\n }, 250);\n });\n }\n});\n\nmodule.exports = exportCountrySelector;\n","'use strict';\n\nvar base = require('./carousels');\n\nvar einsteinObserver = require('einstein/components/einstein').einsteinObserver;\nvar baseProductTileCarousels = base.productTileCarousels;\nvar baseCartRecCarousels = base.cartRecProductCarousels;\n\n/**\n * Add einstein observer to product tile carousel slots inside an element with an einstein-slot class\n */\nbase.productTileCarousels = function () {\n einsteinObserver('.einstein:not(.RecommendationsInsideCart), .tile-type-simple', baseProductTileCarousels);\n};\n\nbase.baseCartRecCarousels = function () {\n einsteinObserver('.einstein.RecommendationsInsideCart', baseCartRecCarousels);\n};\n\nmodule.exports = base;\n","'use strict';\n\n/**\n * appends params to a url\n * @param {string} data - data returned from the server's ajax call\n * @param {Object} button - button that was clicked for email sign-up\n */\nfunction displayMessage(data, button) {\n $.spinner().stop();\n var status;\n var container = button.parents('.email-container');\n if (!container.length) container = $(window);\n\n if (data.success) {\n status = 'alert-success';\n } else {\n status = 'alert-danger';\n $('form.email-signup input:visible', container).first().focus();\n if (!data.msg) {\n data.msg = 'Email signup could not be completed at this time. Please try again later.';\n }\n }\n\n if (!$('.email-signup-message', container).length) {\n $('.subscription-handlers', container).append(\n '
      '\n );\n }\n $('.email-signup-message', container)\n .append('
      '\n + data.msg\n + ''\n + '
      ');\n\n // set tabindex and focus\n $('.email-signup-alert', container)\n .attr('tabindex', 0)\n .focus()\n .on('closed.bs.alert', function () {\n $('.email-signup-message', container).remove();\n button.removeAttr('disabled');\n });\n}\n\nmodule.exports = function () {\n $('form.email-signup').on('submit', function (e) {\n e.preventDefault();\n $.spinner().start();\n var form = $(this);\n var button = $('.subscribe-email', form);\n var emailAddress = form.find('[name=dwfrm_mcsubscribe_email]').val();\n $.ajax({\n url: form.data('action'),\n type: 'post',\n dataType: 'json',\n data: form.serialize(),\n success: function (data) {\n displayMessage(data, button);\n\n $(document).trigger('email-sign-up', {\n emailAddress: emailAddress,\n hashedEmail: data.hashedEmail\n });\n },\n error: function (err) {\n displayMessage(err, button);\n }\n });\n });\n};\n","'use strict';\nvar base = require('base/components/focus');\nvar FOCUSABLE = 'a[href], area[href], object:not([disabled]), :input:not([disabled]), [tabindex]';\nvar FILTER = ':visible:not([tabindex=-1])';\n\nvar getFocusableElements = function (context) {\n return context ? $(context).find(FOCUSABLE).filter(FILTER) : $(FOCUSABLE).filter(FILTER);\n};\n\nvar focus = {\n trapModalFocus: function (selector) {\n var TABKEYCODE = 9;\n\n $('body').on('keydown', selector, function (e) {\n var isTabPressed = (e.key === 'Tab' || e.keyCode === TABKEYCODE);\n\n if (!isTabPressed) {\n return;\n }\n\n var focusableEls = getFocusableElements($(selector));\n var firstFocusableEl = focusableEls.first()[0];\n var lastFocusableEl = focusableEls.last()[0];\n\n if (e.shiftKey) /* shift tab keypress */ {\n if (document.activeElement === firstFocusableEl) {\n lastFocusableEl.focus();\n e.preventDefault();\n }\n } else if (document.activeElement === lastFocusableEl) {\n firstFocusableEl.focus();\n e.preventDefault();\n }\n });\n },\n\n focusFirstModalElementOnOpen: function () {\n $('body').on('shown.bs.modal', '.modal', function () {\n // Store identifier for currently focused element, so we can return focus later\n var focusID = 'modal-' + Date.now();\n $(document.activeElement).attr('data-opened-by', focusID);\n $(this).data('focus-id', focusID);\n\n getFocusableElements(this).first().focus();\n });\n },\n\n returnFocusOnModalClose: function () {\n $('body').on('hide.bs.modal', '.modal', function () {\n var focusID = $(this).data('focus-id');\n\n if (focusID) {\n $('[data-opened-by=\"' + focusID + '\"]').focus();\n }\n });\n },\n\n focusElement: function (index, context) {\n var focusableEls = getFocusableElements(context);\n focusableEls.eq(index).focus();\n },\n\n getFocusIndex: function (element, context) {\n var focusableEls = getFocusableElements(context);\n return focusableEls.index(element);\n }\n};\n\nmodule.exports = $.extend(base, focus);\n","'use strict';\n\nvar keyboardAccessibility = require('base/components/keyboardAccessibility');\nvar focus = require('org/components/focus');\n\nvar mobileHeader = $('.mobile-header-container');\n\nvar clearSelection = function (element) {\n // ADA Fix, remove tab-index from navigation categories that are not visible\n var tabs;\n if ($(element).parents('.menu-list').length) { // On Sub-Cat\n tabs = $(element).parents('.menu-list').first().find('.dropdown-item:not(.top-category) a');\n $(element).parents('.menu-list').removeAttr('tabindex');\n $(element).closest('.menu-column').find('.dropdown-item .menu-dropdown-link').first()\n .attr('tabindex', 0);\n $(element).parents('.dropdown-menu').find('.menu-link').first()\n .attr('tabindex', 0);\n $(element).parents('.dropdown-menu').find('.back button').attr('tabindex', 0);\n $(element).closest('.menu-column').siblings('.menu-column').each(function () {\n $(this).find('.dropdown-item .menu-dropdown-link').first().attr('tabindex', 0);\n var siblingTabs = $(this).find('.dropdown-item .menu-dropdown-link').first();\n if ($(siblingTabs).parent().nextAll('.dropdown-item').children('.menu-dropdown-link[tabindex]').length > 0) {\n $(siblingTabs).parent().nextAll().children()\n .attr('tabindex', 0);\n }\n });\n } else { // On Top-level\n tabs = $(element).parents('.dropdown-menu').find('.menu-dropdown-link[tabindex]');\n $(element).closest('.menu-item').find('.menu-link').first()\n .attr('tabindex', 0);\n $(element).closest('.menu-item').siblings().find('.menu-link')\n .attr('tabindex', 0);\n }\n\n $.each(tabs, function () {\n $(this).attr('tabindex', -1);\n });\n\n $(element).closest('.dropdown, .dropdown-item').children('.dropdown-menu, .menu-list').children('.top-category')\n .detach();\n $(element).closest('.dropdown.show, .dropdown-item.show').children('.nav-link').attr('aria-expanded', 'false');\n $(element).closest('.dropdown.show, .dropdown-item.show').children('.menu-link').attr('aria-expanded', 'false');\n $(element).closest('.dropdown.show, .dropdown-item.show').find('.menu-dropdown-link').attr('aria-expanded', 'false');\n $(element).closest('.dropdown.show, .dropdown-item.show').removeClass('show');\n $(element).closest('li').detach();\n};\n\nmodule.exports = function () {\n var isDesktop = function (element) {\n var elementToCheck = element ? $(element).parents('.menu-toggleable-left') : $('.menu-toggleable-left');\n return elementToCheck.css('position') !== 'fixed';\n };\n\n var setDropdownLinkTabindexes = function () {\n // ADA Fix, make all links tabbable\n if (isDesktop()) {\n $('.menu-dropdown-link[tabindex]').attr('tabindex', 0);\n } else {\n // ADA Fix, mobile menu screen reader attributes\n $('#sg-navbar-collapse').attr({\n role: 'dialog',\n 'aria-modal': 'true',\n 'aria-label': 'menu'\n });\n $('.dropdown-menu .menu-dropdown-link[tabindex]').attr('tabindex', -1);\n }\n };\n\n $('.header-banner .close').on('click', function () {\n $('.header-banner').addClass('hide');\n });\n\n var navMenu = document.getElementById('sg-navbar-collapse');\n var hideMobileMenuAfterTransitionEnd = function () {\n if (navMenu.classList.contains('in')) {\n navMenu.addEventListener('transitionend', function () {\n navMenu.classList.add('hide-mobile');\n }, {\n capture: false,\n once: true,\n passive: false\n });\n }\n };\n\n var ENTERKEYCODE = 13;\n var ESCAPEKEYCODE = 27;\n\n setDropdownLinkTabindexes();\n\n var showMobileNavbarSubMenu = function (eventElement) {\n $('.modal-background').show();\n // copy parent element into current UL\n var li = $('
    2. ');\n var link = eventElement.clone().removeClass('dropdown-toggle')\n .removeAttr('data-toggle')\n .removeAttr('aria-expanded')\n .attr('aria-haspopup', 'false');\n li.append(link);\n var closeMenu = $('
    3. ');\n closeMenu.append($('.close-menu').first().clone());\n // Prevent multiple drop-down menus from being created\n var menuList = eventElement.closest('.dropdown-item').find('> .menu-list');\n if (menuList.children('.dropdown-item.top-category').length === 0) {\n if (eventElement.hasClass('has-sub-cat')) {\n menuList\n .prepend(li)\n .prepend(closeMenu);\n // copy navigation menu into view\n eventElement.closest('.dropdown-item').addClass('show');\n } else {\n eventElement.parent().children('.dropdown-menu')\n .prepend(li)\n .prepend(closeMenu);\n // copy navigation menu into view\n eventElement.parent().addClass('show');\n mobileHeader.css('display', 'none'); // hide mobile header when not on first off-canvas pane\n }\n }\n\n // ADA The menu-dropdown links with sub-cats only need role=button at mobile, when you can click them to expand another menu level.\n $('h2 a.menu-dropdown-link.has-sub-cat').attr('role', 'button');\n\n // ADA Fixes\n // Set tab-index for each level so that only the current set of categories can be tabbed into\n var tabs = [];\n if (eventElement.hasClass('dropdown-toggle')) { // Top Level\n // Loop to only get first level\n $.each(eventElement.next().find('.menu-row').children('.menu-column'), function () {\n tabs.push($(this).find('.menu-dropdown-link[tabindex]').first());\n $(this).closest('.dropdown-menu').find('.back').attr('tabindex', 0);\n var siblingTabs = $(this).find('.menu-dropdown-link[tabindex]').first();\n if ($(siblingTabs).parent().nextAll('.dropdown-item').children('.menu-dropdown-link[tabindex]').length > 0) {\n tabs.push($(siblingTabs).parent().nextAll('.dropdown-item').children('.menu-dropdown-link[tabindex]'));\n }\n });\n\n // Set focus to back-button\n $('.back, .back button').removeAttr('tabindex');\n eventElement.next().find('.back > button').focus();\n } else { // Sub levels\n tabs = eventElement.parent().next().find('.dropdown-item:not(.top-category) a');\n\n // Make back button for INACTIVE submenu unreachable with Tab Key\n $('.back, .back button').attr('tabindex', -1);\n // Make back button for ACTIVE submenu reachable with Tab Key, then set focus on it\n eventElement.parent().next().find('.back button').attr('tabindex', 0)\n .focus();\n }\n\n $('.menu-dropdown-link, .menu-link').attr('tabindex', -1);\n $.each(tabs, function () {\n $(this).attr('tabindex', 0);\n });\n\n eventElement.attr('aria-expanded', 'true');\n link.attr('tabindex', 0);\n };\n\n var showNavbarSubmenu = function (eventElement) {\n $('.navbar-nav > li').each(function (index, navElement) {\n if (!$.contains(navElement, eventElement)) {\n $(navElement).find('.show').each(function () {\n clearSelection(navElement);\n });\n if ($(navElement).hasClass('show')) {\n $(navElement).removeClass('show');\n $(navElement).children('ul.dropdown-menu').removeClass('show');\n $(navElement).children('.nav-link').attr('aria-expanded', 'false');\n }\n }\n });\n // need to close all the dropdowns that are not direct parent of current dropdown\n $(eventElement).parent().addClass('show');\n var dropdownMenu = $(eventElement).siblings('.dropdown-menu');\n dropdownMenu.addClass('show');\n setTimeout(function () {\n if ((dropdownMenu).hasClass('show')) {\n dropdownMenu.addClass('trigger-active');\n }\n }, 150);\n $(eventElement).attr('aria-expanded', 'true');\n };\n\n var hideNavbarSubmenu = function (eventElement) {\n if (isDesktop(eventElement)) {\n $(eventElement).removeClass('show');\n var dropdownMenu = $(eventElement).children('.dropdown-menu');\n dropdownMenu.removeClass('trigger-active');\n setTimeout(function () {\n dropdownMenu.removeClass('show');\n }, 250);\n $(eventElement).children('.menu-link').attr('aria-expanded', 'false');\n $(eventElement).children('.nav-link').attr('aria-expanded', 'false');\n }\n };\n\n keyboardAccessibility('.main-menu .nav-link, .main-menu .dropdown-link',\n {\n 40: function (menuItem) { // down\n if (menuItem.hasClass('nav-item')) { // top level\n $('.navbar-nav .show').removeClass('show')\n .children('.dropdown-menu')\n .removeClass('show');\n menuItem.addClass('show').children('.dropdown-menu').addClass('show');\n $(this).attr('aria-expanded', 'true');\n menuItem.find('ul > li > a')\n .first()\n .focus();\n } else {\n menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');\n $(this).attr('aria-expanded', 'false');\n menuItem.next().children().first().focus();\n }\n },\n 39: function (menuItem) { // right\n if (menuItem.hasClass('nav-item')) { // top level\n menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');\n $(this).attr('aria-expanded', 'false');\n menuItem.next().children().first().focus();\n } else if (menuItem.hasClass('dropdown')) {\n menuItem.addClass('show').children('.dropdown-menu').addClass('show');\n $(this).attr('aria-expanded', 'true');\n menuItem.find('ul > li > a')\n .first()\n .focus();\n }\n },\n 38: function (menuItem) { // up\n if (menuItem.hasClass('nav-item')) { // top level\n menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');\n $(this).attr('aria-expanded', 'false');\n } else if (menuItem.prev().length === 0) {\n menuItem.parent().parent().removeClass('show')\n .children('.nav-link')\n .attr('aria-expanded', 'false');\n menuItem.parent().parent().children().first()\n .focus();\n } else {\n menuItem.prev().children().first().focus();\n }\n },\n 37: function (menuItem) { // left\n if (menuItem.hasClass('nav-item')) { // top level\n menuItem.removeClass('show').children('.dropdown-menu').removeClass('show');\n $(this).attr('aria-expanded', 'false');\n menuItem.prev().children().first().focus();\n } else {\n menuItem.closest('.show').removeClass('show')\n .closest('li.show').removeClass('show')\n .children()\n .first()\n .focus()\n .attr('aria-expanded', 'false');\n }\n },\n 27: function (menuItem) { // escape\n var parentMenu = menuItem.hasClass('show')\n ? menuItem\n : menuItem.closest('li.show');\n parentMenu.children('.show').removeClass('show');\n parentMenu.removeClass('show').children('.nav-link')\n .attr('aria-expanded', 'false');\n parentMenu.children().first().focus();\n }\n },\n function () {\n return $(this).parent();\n }\n );\n\n $('.menu-item.dropdown:not(.disabled) [data-toggle=\"dropdown\"]')\n .on('click', function (e) {\n setDropdownLinkTabindexes();\n if ($(this).siblings('.dropdown-menu').hasClass('show')) {\n e.preventDefault();\n hideNavbarSubmenu(this.parentElement);\n } else if (isDesktop(this)) {\n if ($(this).hasClass('dropdown-toggle')) {\n e.preventDefault();\n }\n showNavbarSubmenu(this);\n } else {\n e.preventDefault();\n showMobileNavbarSubMenu($(this));\n }\n })\n .on('keydown', function (e) {\n var isEnterPressed = (e.key === 'Enter' || e.keyCode === ENTERKEYCODE);\n\n if (!isEnterPressed || this !== document.activeElement) {\n return;\n }\n\n setDropdownLinkTabindexes();\n if ($(this).siblings('.dropdown-menu').hasClass('show')) {\n e.preventDefault();\n hideNavbarSubmenu(this.parentElement);\n } else if (isDesktop(this)) {\n if ($(this).hasClass('dropdown-toggle')) {\n e.preventDefault();\n }\n showNavbarSubmenu(this);\n } else {\n e.preventDefault();\n showMobileNavbarSubMenu($(this));\n }\n });\n\n $('body').on('keydown', function (e) {\n var isEscapePressed = (e.key === 'Escape' || e.keyCode === ESCAPEKEYCODE);\n\n if (isEscapePressed && $('.dropdown-menu.show.trigger-active').length > 0) {\n e.preventDefault();\n hideNavbarSubmenu($('.dropdown-menu.show.trigger-active').parent().get(0));\n }\n })\n .on('click', function (e) {\n var container = $('.dropdown-menu.show.trigger-active').parent();\n\n // if the target of the click isn't inside the open submenu, close it\n if (container.length > 0 && !container.is(e.target) && container.has(e.target).length === 0) {\n e.preventDefault();\n e.stopPropagation();\n hideNavbarSubmenu(container.get(0));\n }\n });\n\n $('.navbar>.close-menu>.close-button').on('click', function (e) {\n e.preventDefault();\n hideMobileMenuAfterTransitionEnd();\n $('.menu-toggleable-left').removeClass('in');\n $('.modal-background').hide();\n $('.navbar-toggler').focus();\n });\n\n $('.navbar-nav').on('click', '.back', function (e) {\n if (!isDesktop(this)) {\n e.preventDefault();\n clearSelection(this);\n\n if ($('.top-category').length) { // show/hide mobile header for devices with short heights\n mobileHeader.css('display', 'none');\n } else {\n mobileHeader.css('display', 'block');\n }\n\n // set focus on parent category\n const $mainMenu = $('.menu-item.dropdown').first();\n focus.focusElement(0, $mainMenu);\n }\n });\n\n $('.navbar-nav').on('click', '.close-button', function (e) {\n e.preventDefault();\n $('.navbar-nav').find('.top-category').detach();\n $('.navbar-nav').find('.nav-menu').detach();\n $('.navbar-nav').find('.show').removeClass('show');\n hideMobileMenuAfterTransitionEnd();\n $('.menu-toggleable-left').removeClass('in');\n $('.modal-background').hide();\n $('.navbar-toggler').focus();\n });\n\n $('.navbar-toggler').on('click', function (e) {\n e.preventDefault();\n setDropdownLinkTabindexes();\n const $mainMenu = $('.main-menu');\n $mainMenu.toggleClass('hide-mobile');\n\n // Timeout needed for css transition to apply properly.\n setTimeout(function () {\n $mainMenu.toggleClass('in');\n if ($mainMenu.hasClass('in')) {\n $mainMenu.find('.close-button button').focus();\n $('.modal-background').show();\n focus.trapModalFocus('.main-menu');\n focus.focusElement(0, $mainMenu);\n // Need to make sure the visible menu elements are tabbable, as setDropdownLinkTabindexes() just marked them all not-tabbable.\n if ($('.dropdown-item.show').length) {\n $('.dropdown-item.show .menu-dropdown-link[tabindex]', $mainMenu).attr('tabindex', 0);\n } else {\n $('.menu-item.show div.menu-list>.dropdown-item>h2>a.menu-dropdown-link[tabindex]', $mainMenu).attr('tabindex', 0);\n $('.menu-item.show div.menu-list>.dropdown-item>a.menu-dropdown-link[tabindex]', $mainMenu).attr('tabindex', 0);\n }\n } else {\n $('.modal-background').hide();\n $('.navbar-toggler').focus();\n }\n }, 20);\n });\n\n keyboardAccessibility('.navbar-header .user',\n {\n 40: function ($popover) { // down\n if ($popover.children('a').first().is(':focus')) {\n $popover.children('a').first().next().focus();\n } else {\n $popover.children('a').first().focus();\n }\n },\n 38: function ($popover) { // up\n if ($popover.children('a').first().is(':focus')) {\n $(this).focus();\n $popover.removeClass('show');\n } else {\n $popover.children('a').first().focus();\n }\n },\n 32: function ($popover) { // space\n $popover.toggleClass('show');\n $('.navbar-header .user').toggleClass('popover-visible');\n },\n 27: function ($popover) { // escape\n $(this).focus();\n $popover.removeClass('show');\n },\n 13: function ($popover) { // enter\n $popover.toggleClass('show');\n },\n 9: function ($popover) { // tab\n $popover.children('a').first().focus();\n $('.minicart-link, .reset-button').on('focus', function () {\n $popover.removeClass('show');\n $('.navbar-header .user').removeClass('popover-visible');\n $('#myaccount').attr('aria-expanded', false);\n });\n return;\n }\n },\n function () {\n var $popover = $('.user .popover');\n if (!($popover.hasClass('show')) && $('.navbar-header .user').hasClass('popover-visible')) {\n $('#myaccount').attr('aria-expanded', true);\n } else if (($popover.hasClass('show')) && !$('.navbar-header .user').hasClass('popover-visible')) {\n $('#myaccount').attr('aria-expanded', false);\n }\n return $popover;\n }\n );\n\n $('.navbar-header .user').on('click', function () {\n $(this).toggleClass('popover-visible');\n if ($(this).hasClass('popover-visible')) {\n $('.navbar-header .user .popover').addClass('show');\n $('#myaccount').attr('aria-expanded', true);\n $('#myaccount-popover').attr('tabindex', '0');\n $('#myaccount-popover').focus();\n } else {\n $('.navbar-header .user .popover').removeClass('show');\n $('#myaccount').attr('aria-expanded', false);\n $('#myaccount-popover').removeAttr('tabindex');\n }\n });\n\n $('.navbar-header .user').on('mouseenter', function () {\n if ($('.navbar-header .user .popover').length > 0) {\n $('.navbar-header .user .popover').addClass('show');\n $('#myaccount').attr('aria-expanded', 'true');\n }\n });\n\n $('.navbar-header .user').on('mouseleave', function () {\n if (!$(this).hasClass('popover-visible')) {\n $('.navbar-header .user .popover').removeClass('show');\n $('#myaccount').attr('aria-expanded', 'false');\n }\n });\n};\n","'use strict';\n\nvar cart = require('../cart/cart');\nvar utils = require('../util/utils');\nlet isMobile = utils.isMobile();\n\nvar updateMiniCart = true;\n\nmodule.exports = function () {\n cart();\n\n $('.minicart').on('count:update', function (event, count) {\n if (count && $.isNumeric(count.quantityTotal)) {\n $('.minicart .minicart-quantity').text(count.quantityTotal);\n $('.minicart .minicart-link').attr({\n 'aria-label': count.minicartCountOfItems,\n title: count.minicartCountOfItems\n });\n }\n });\n\n var miniCart = document.querySelector('.minicart');\n\n /**\n * A/B TEST minicart-redesign. DO NOT MODIFY showMinicartOverlay or #minicart-backdrop click handler UNTIL minicart-redesign A/B TEST CONCLUDES.\n * HANDLE DISPLAY OF TRANSPARENT OVERLAY WHEN MINICART IS OPENED.\n */\n function showMinicartOverlay() {\n var minicartBackdrop = '
      ';\n $('body').addClass('modal-open').append(minicartBackdrop);\n $('#minicart-backdrop').show();\n }\n $('body').on('click', '#minicart-backdrop, .minicart-close', function () {\n $('#minicart-backdrop').remove();\n $('body').removeClass('modal-open');\n });\n\n // ORDERGROOVE'S BUILT-IN EVENT; DETECTS WHEN OFFERS ARE READY TO BE RENDERED IN UI\n if (miniCart) {\n miniCart.addEventListener('og-receive-offer', ev => {\n // IF THE TARGET MINICART ITEM IS A SUBSCRIPTION OPT-IN, TRIGGER A REFRESH OF THE DISPLAY PRICE\n if (ev.target.__subscribed) { // eslint-disable-line no-underscore-dangle\n ev.target.querySelector('og-optin-button').click();\n }\n });\n }\n // show & hide mini cart on button click\n $('.minicart').on('touchstart click', function (e, addToCartData) {\n var url = $('.minicart').data('action-url');\n var count = parseInt($('.minicart .minicart-quantity').text(), 10);\n // A/B TEST: IF ABTestMinicartA is true, user is a participant in the minicart-redesign A/B Test\n var ABTestMinicartA = typeof (addToCartData) !== 'undefined' ? addToCartData.minicartVariantA : $('.minicart .popover.minicartVariantA').length > 0;\n if (ABTestMinicartA) $('.minicart').css('position', 'static');\n if (count !== 0 && $('.minicart .popover.show').length === 0) {\n if (!updateMiniCart) {\n $('#minicart-item-added-msg:not(.d-sm-none)').addClass('d-sm-none');\n $('.minicart-header.d-sm-none').removeClass('d-sm-none').addClass('d-sm-block');\n\n $('.minicart .popover').addClass('show');\n if (ABTestMinicartA) showMinicartOverlay();\n return;\n }\n\n $('.minicart .popover').addClass('show');\n if (ABTestMinicartA) {\n $('.minicart .popover').animate({\n width: isMobile ? '100vw' : '25rem',\n speed: 'slow'\n });\n showMinicartOverlay();\n }\n $('.minicart .popover').spinner().start();\n $.get(url, function (data) {\n $('.minicart .popover').empty();\n $('.minicart .popover').append(data);\n updateMiniCart = false;\n\n // A/B TEST: minicart-redesign Variant A modifications.\n // DO NOT MODIFY FOLLOWING CODE BLOCK UNTIL minicart-redesign A/B Test concludes.\n if (typeof (addToCartData) !== 'undefined' && addToCartData.minicartVariantA) {\n $('#minicart-item-added-msg.d-sm-none').removeClass('d-sm-none');\n $('.minicart-header.d-sm-block').removeClass('d-sm-block').addClass('d-sm-none');\n $('.minicart .popover:not(.minicartVariantA)').addClass('minicartVariantA');\n $('.minicart .estimated-total').removeClass('d-sm-flex');\n $('.minicart-close:first').focus();\n } else if (isMobile && addToCartData.pliUUID !== null) { // Mobile Minicart Experience: only show latest item added to cart\n var showLatestItem = '.minicart .product-summary .card:not(.uuid-' + addToCartData.pliUUID + ')';\n $(showLatestItem).hide();\n $('.minicart-close').focus();\n }\n $.spinner().stop();\n });\n } else if (count !== 0 && $('.minicart .popover.show').length !== 0 && !e.target.length) {\n // make sure we're not removing a product\n if (e.target.classList.contains('icon-icon-cart-dims')) {\n $('.minicart .popover').removeClass('show');\n }\n }\n });\n\n // only allow shopping cart btn redirect for mobile users\n $('.minicart-total').on('touchstart click', function (e) {\n if (!isMobile) {\n e.preventDefault();\n } else if (isMobile) {\n e.stopImmediatePropagation();\n window.location = '/cart';\n }\n });\n\n // close mini cart on click off\n $('body').on('touchstart click', function (e) {\n // TO DO: consolidate the list of target elements used to trigger minicart close to avoid need for so many conditions\n if ($('.minicartVariantA.show').length === 1 && $(e.target).is('og-optin-button, og-optout-button')) return;\n if (($('.minicart-close').has(e.target).length > 0 || e.target.id === 'minicart-continue-shopping')\n || $('.minicart').has(e.target).length <= 0) {\n $('.minicart .popover').removeClass('show');\n }\n });\n $('.minicart').on('mouseleave focusout', function (event) {\n if ((event.type === 'focusout' && $('.minicart').has(event.target).length > 0)\n || (event.type === 'mouseleave' && $(event.target).is('.minicart .quantity'))\n || $('body').hasClass('modal-open')) {\n event.stopPropagation();\n return;\n }\n });\n $('body').on('change', '.minicart .quantity', function () {\n if ($(this).parents('.bonus-product-line-item').length && $('.cart-page').length) {\n location.reload();\n }\n });\n $('body').on('product:afterAddToCart', function (e, data) { // eslint-disable-line no-unused-vars\n updateMiniCart = true;\n });\n $('body').on('cart:update', function () {\n updateMiniCart = true;\n });\n};\n","'use strict';\n\nmodule.exports = {\n initQuantitySelector: function () {\n var quantityModuleClass = '.quantity-module';\n var quantitySelectClass = '.quantity-select';\n var quantityInputClass = '.quantity-input';\n var quantityPlusClass = '.quantity-increase';\n var quantityMinusClass = '.quantity-decrease';\n\n var getQtyArray = function ($select) {\n var qtys = [];\n $select.find('option').each(function () {\n qtys.push($(this).val());\n });\n return qtys;\n };\n\n var qtySelectorInputHandler = function (e) {\n var $qty = $(this).parents(quantityModuleClass);\n var $select = $qty.find(quantitySelectClass);\n var $input = $qty.find(quantityInputClass);\n var $plus = $qty.find(quantityPlusClass);\n var $minus = $qty.find(quantityMinusClass);\n var quantities = getQtyArray($select);\n var currentIndex = quantities.indexOf($select.val());\n var newVal = null;\n var newIndex;\n\n // if the input field triggered the event but its value didn't change, EXIT\n if ($(this).is($input) && $input.data('previous-value').toString() === $input.val()) {\n return;\n }\n\n if ($(this).is($minus)) {\n // minus was clicked\n newIndex = currentIndex - 1;\n newVal = quantities[newIndex];\n } else if ($(this).is($plus)) {\n // plus was clicked\n newIndex = currentIndex + 1;\n newVal = quantities[newIndex];\n } else {\n // was updated via the input text field\n newVal = $(this).val();\n newIndex = quantities.indexOf(newVal);\n }\n if (typeof newVal === 'undefined') {\n return;\n }\n\n // attempt to change the value\n $(this).val(newVal);\n\n // update the value\n $(this).parents(quantityModuleClass).find(quantityInputClass).val(newVal);\n\n return $(this);\n };\n\n // init jQuery event handlers\n\n $(quantityModuleClass).on('change click', quantityPlusClass + ':not(.disabled), ' + quantityMinusClass + ':not(.disabled), ' + quantityInputClass, qtySelectorInputHandler);\n $(quantityModuleClass).on('change', quantitySelectClass, selectChangeHandler);\n }\n};\n","'use strict';\n\nvar debounce = require('lodash/debounce');\nvar minChars = 3;\nvar UP_KEY = 38;\nvar DOWN_KEY = 40;\nvar DIRECTION_DOWN = 1;\nvar DIRECTION_UP = -1;\n\n/**\n * Retrieves Suggestions element relative to scope\n *\n * @param {Object} scope - Search input field DOM element\n * @return {JQuery} - .suggestions-wrapper element\n */\nfunction getSuggestionsWrapper(scope) {\n return $(scope).siblings('.suggestions-wrapper');\n}\n\n/**\n * Determines whether DOM element is inside the .search-mobile class\n *\n * @param {Object} scope - DOM element, usually the input.search-field element\n * @return {boolean} - Whether DOM element is inside div.search-mobile\n */\nfunction isMobileSearch(scope) {\n return !!$(scope).closest('.search-mobile').length;\n}\n\n/**\n * Remove modal classes needed for mobile suggestions\n *\n */\nfunction clearModals() {\n $('body').removeClass('modal-open');\n $('header').siblings().attr('aria-hidden', 'false');\n $('.suggestions').removeClass('modal');\n $('.sticky-atc-container').css({\n visibility: 'visible'\n });\n}\n\n/**\n * Apply modal classes needed for mobile suggestions\n *\n * @param {Object} scope - Search input field DOM element\n */\nfunction applyModals(scope) {\n if (isMobileSearch(scope)) {\n $('body').addClass('modal-open');\n $('header').siblings().attr('aria-hidden', 'true');\n // Hide/show the sticky product nav when search suggestions open/close until pagebody scrolling is fixed/disabled\n $('.sticky-atc-container').css({\n visibility: 'hidden'\n });\n getSuggestionsWrapper(scope).find('.suggestions').addClass('modal');\n }\n}\n\n/**\n * Tear down Suggestions panel\n */\nfunction tearDownSuggestions() {\n $('input.search-field').val('');\n clearModals();\n $('.search-mobile .suggestions').unbind('scroll');\n $('.suggestions-wrapper').empty();\n}\n\n/**\n * Determines whether the \"More Content Below\" icon should be displayed\n *\n * @param {Object} scope - DOM element, usually the input.search-field element\n */\nfunction handleMoreContentBelowIcon(scope) {\n if (($(scope).scrollTop() + $(scope).innerHeight()) >= $(scope)[0].scrollHeight) {\n $('.more-below').fadeOut();\n } else {\n $('.more-below').fadeIn();\n }\n}\n\n/**\n * Positions Suggestions panel on page\n *\n * @param {Object} scope - DOM element, usually the input.search-field element\n */\nfunction positionSuggestions(scope) {\n var outerHeight;\n var $scope;\n var $suggestions;\n var top;\n\n if (isMobileSearch(scope)) {\n $scope = $(scope);\n top = $scope.offset().top;\n outerHeight = $scope.outerHeight();\n $suggestions = getSuggestionsWrapper(scope).find('.suggestions');\n $suggestions.css('top', top + outerHeight);\n\n handleMoreContentBelowIcon(scope);\n\n // Unfortunately, we have to bind this dynamically, as the live scroll event was not\n // properly detecting dynamic suggestions element's scroll event\n $suggestions.scroll(function () {\n handleMoreContentBelowIcon(this);\n });\n }\n}\n\n/**\n * Process Ajax response for Search Suggestions\n *\n * @param {Object|string} response - Empty object literal if null response or string with rendered\n * suggestions template contents\n */\nfunction processResponse(response) {\n var $suggestionsWrapper = getSuggestionsWrapper(this).empty();\n\n $.spinner().stop();\n\n if (!(typeof (response) === 'object')) {\n $suggestionsWrapper.append(response).show();\n positionSuggestions(this);\n\n if (isMobileSearch(this)) {\n applyModals(this);\n }\n\n // Trigger screen reader by setting aria-describedby with the new suggestion message.\n var suggestionsList = $('.suggestions .item');\n if ($(suggestionsList).length) {\n $('input.search-field').attr('aria-describedby', 'search-result-count');\n } else {\n $('input.search-field').removeAttr('aria-describedby');\n }\n } else {\n $suggestionsWrapper.hide();\n }\n}\n\n/**\n * Retrieve suggestions\n *\n * @param {Object} scope - Search field DOM element\n */\nfunction getSuggestions(scope) {\n var $suggestions = getSuggestionsWrapper(scope);\n var $form = $suggestions.parents('form');\n if ($(scope).val().length >= minChars) {\n $form.find('.suggestions-wrapper-content').removeClass('active'); // Remove the content suggestions\n $form.find('input.search-field').attr('aria-expanded', true);\n $.spinner().stop(); // Prevent multiple overlappying spinners\n $.spinner().start();\n $.ajax({\n context: scope,\n url: $suggestions.data('url') + encodeURIComponent($(scope).val()),\n method: 'GET',\n success: processResponse,\n error: function () {\n $.spinner().stop();\n }\n });\n } else {\n clearModals();\n $suggestions.empty();\n }\n}\n\n/**\n * Handle Search Suggestion Keyboard Arrow Keys\n *\n * @param {Integer} direction takes positive or negative number constant, DIRECTION_UP (-1) or DIRECTION_DOWN (+1)\n */\nfunction handleArrow(direction) {\n // get all li elements in the suggestions list\n var suggestionsList = $('.suggestions .item');\n if (suggestionsList.filter('.selected').length === 0) {\n suggestionsList.first().addClass('selected');\n $('input.search-field').each(function () {\n $(this).attr('aria-activedescendant', suggestionsList.first()[0].id);\n });\n } else {\n suggestionsList.each(function (index) {\n var idx = index + direction;\n if ($(this).hasClass('selected')) {\n $(this).removeClass('selected');\n $(this).removeAttr('aria-selected');\n if (suggestionsList.eq(idx).length !== 0) {\n suggestionsList.eq(idx).addClass('selected');\n suggestionsList.eq(idx).attr('aria-selected', true);\n $(this).removeProp('aria-selected');\n $('input.search-field').each(function () {\n $(this).attr('aria-activedescendant', suggestionsList.eq(idx)[0].id);\n });\n } else {\n suggestionsList.first().addClass('selected');\n suggestionsList.first().attr('aria-selected', true);\n $('input.search-field').each(function () {\n $(this).attr('aria-activedescendant', suggestionsList.first()[0].id);\n });\n }\n return false;\n }\n return true;\n });\n }\n}\n\nmodule.exports = function () {\n $('form[name=\"simpleSearch\"], form[name=\"parts-enter-model-number-form\"]').submit(function (e) {\n var suggestionsList = $(this).find('.suggestions .item');\n if (suggestionsList.filter('.selected').length !== 0) {\n e.preventDefault();\n suggestionsList.filter('.selected').find('a')[0].click();\n }\n });\n\n $('input.search-field, #parts-accessories-finder-model-number').each(function () {\n /**\n * Use debounce to avoid making an Ajax call on every single key press by waiting a few\n * hundred milliseconds before making the request. Without debounce, the user sees the\n * browser blink with every key press.\n */\n var debounceSuggestions = debounce(getSuggestions, 300);\n $(this).on('keyup focus', function (e) {\n // Capture Down/Up Arrow Key Events\n switch (e.which) {\n case DOWN_KEY:\n handleArrow(DIRECTION_DOWN);\n e.preventDefault(); // prevent moving the cursor\n break;\n case UP_KEY:\n handleArrow(DIRECTION_UP);\n e.preventDefault(); // prevent moving the cursor\n break;\n default:\n debounceSuggestions(this, e);\n }\n });\n });\n\n // ADA compliance for multiple instances of search template per page\n $('input.search-field, #search-label').each(function () {\n if ($(this).closest('.search-mobile').length > 0) {\n $(this).attr('id', $(this).attr('id') + '-mobile');\n $(this).next('label').attr('for', $(this).next('label').attr('for') + '-mobile');\n }\n });\n\n $('body').on('click', function (e) {\n if (!$('.suggestions').has(e.target).length && !$(e.target).hasClass('search-field')) {\n $('.suggestions').hide();\n $('.sticky-atc-container').css({\n visibility: 'visible'\n });\n }\n });\n\n // On focus of search input, show the close X, show the content suggestions after min characters input and set the fa toggle to close\n $('input.search-field').on('focus', function () {\n $('.reset-button').removeClass('d-none');\n if ($(this).val().length >= minChars) {\n $('.suggestions-wrapper-content').addClass('active');\n }\n });\n\n // In mobile search, on click of fa-close X, remove the ajax suggestions, clear the input value, remove the content suggestions and set the fa toggle to search\n $('body').on('click touchend', '.search-mobile button.fa-close', function (e) {\n e.preventDefault();\n $('.suggestions').hide();\n $('input.search-field').val('');\n $('.suggestions-wrapper-content').removeClass('active');\n $('.sticky-atc-container').css({\n visibility: 'visible'\n });\n tearDownSuggestions();\n });\n\n // In header and mobile search, clear the reset-button X, clear the input value, remove the content suggestions and the ajax suggestions\n $('.site-search .reset-button').on('click touchend', function () {\n $('.reset-button').addClass('d-none');\n $('input.search-field').val('');\n $('.suggestions-wrapper-content').removeClass('active');\n $('input.search-field').attr('aria-expanded', false);\n tearDownSuggestions();\n });\n\n // Triggering the focus out on mobile devices on click of reset-button X\n $('.site-search .reset-button').on('click touchend', function (e) {\n e.preventDefault();\n $('input.search-field').trigger('blur');\n });\n\n // Toggle List View and Grid View\n $('.grid-view-link').click(function () {\n $('.grid-container, .grid-view-link').toggleClass('list');\n var $toggle = $(this);\n var curAriaLabel = $toggle.attr('aria-label');\n var altAriaLabel = $toggle.data('alt-aria-label');\n $toggle.attr('aria-label', altAriaLabel);\n $toggle.data('alt-aria-label', curAriaLabel);\n });\n\n $('.site-search .fa-search').click(function () {\n if ($('input.search-field').val() !== '') {\n $('input.search-field').attr('value', $('input.search-field').val());\n $('form[name=\"simpleSearch\"]').submit();\n }\n });\n};\n","'use strict';\n\n/**\n * Show a spinner inside a given element\n * @param {element} $target - Element to block by the veil and spinner.\n * Pass body to block the whole page.\n */\nfunction addSpinner($target) {\n var $veil = $('
      ');\n $veil.append('
      ');\n if ($target.get(0).tagName === 'IMG') {\n $target.after($veil);\n $veil.css({ width: $target.width(), height: $target.height() });\n if ($target.parent().css('position') === 'static') {\n $target.parent().css('position', 'relative');\n }\n } else {\n $target.append($veil);\n if ($target.css('position') === 'static') {\n $target.parent().css('position', 'relative');\n $target.parent().addClass('veiled');\n }\n if ($target.get(0).tagName === 'BODY') {\n $veil.find('.spinner').css('position', 'fixed');\n }\n }\n $veil.click(function (e) {\n e.stopPropagation();\n });\n}\n\n/**\n * Remove existing spinner\n * @param {element} $veil - jQuery pointer to the veil element\n */\nfunction removeSpinner($veil) {\n if ($veil.parent().hasClass('veiled')) {\n $veil.parent().css('position', '');\n $veil.parent().removeClass('veiled');\n }\n $veil.off('click');\n $veil.remove();\n}\n\n// element level spinner:\n$.fn.spinner = function () {\n var $element = $(this);\n var Fn = function () {\n this.start = function () {\n if ($element.length) {\n addSpinner($element);\n }\n };\n this.stop = function () {\n if ($element.length) {\n var $veil = $('.veil');\n removeSpinner($veil);\n }\n };\n };\n return new Fn();\n};\n\n// page-level spinner:\n$.spinner = function () {\n var Fn = function () {\n this.start = function () {\n addSpinner($('body'));\n };\n this.stop = function () {\n removeSpinner($('.veil'));\n };\n };\n return new Fn();\n};\n","'use strict';\n\nvar util = require('../util/utils');\n\n/**\n * Reusable slick carousel configurations\n * @example - $('.product-carousel').slick(slickConfigs.pdp)\n */\n\nmodule.exports = {\n hero: {\n autoplay: true,\n accessibility: false,\n autoplaySpeed: 5000,\n easing: 'swing',\n infinite: true,\n speed: 800,\n dots: true,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1\n },\n\n productTiles: {\n infinite: false,\n speed: 300,\n dots: true,\n accessibility: false,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n lazyLoad: 'ondemand',\n mobileFirst: true,\n responsive: [\n {\n breakpoint: util.getViewports('lg'),\n settings: {\n slidesToShow: 4\n }\n },\n {\n breakpoint: util.getViewports('md'),\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1,\n centerPadding: '35px',\n arrows: false,\n dots: true\n }\n }\n ]\n },\n\n cartProductRecs: {\n infinite: false,\n speed: 300,\n dots: true,\n appendDots: $('.RecommendationsInsideCart'),\n accessibility: false,\n arrows: false,\n slidesToShow: 2,\n slidesToScroll: 1,\n lazyLoad: 'ondemand',\n mobileFirst: true,\n responsive: [\n {\n breakpoint: util.getViewports('xl'),\n settings: {\n slidesToShow: 4\n }\n },\n {\n breakpoint: util.getViewports('md'),\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1,\n centerPadding: '35px',\n arrows: false,\n dots: true\n }\n }\n ]\n },\n\n productTilesIntercept: {\n infinite: false,\n accessibility: false,\n lazyLoad: 'ondemand',\n mobileFirst: true,\n speed: 300,\n dots: true,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n responsive: [\n {\n breakpoint: util.getViewports('lg'),\n settings: {\n slidesToShow: 3,\n slidesToScroll: 3,\n arrows: false\n }\n },\n {\n breakpoint: util.getViewports('sm'),\n settings: {\n slidesToShow: 2,\n slidesToScroll: 2,\n arrows: false\n }\n }\n ]\n },\n\n quickview: {\n dots: true,\n accessibility: false,\n slidesToShow: 1,\n slidesToScroll: 1,\n infinite: true,\n speed: 400,\n lazyLoad: 'ondemand',\n arrows: false\n },\n\n pdp: {\n dots: false,\n accessibility: false,\n vertical: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n infinite: true,\n speed: 400,\n lazyLoad: 'anticipated',\n arrows: false,\n asNavFor: '.product-small-carousel',\n responsive: [{\n breakpoint: util.getViewports('th'),\n settings: {\n vertical: false,\n dots: true,\n lazyLoad: 'ondemand'\n }\n }]\n },\n\n headerPromo: {\n autoplay: false,\n accessibility: false,\n easing: 'linear',\n infinite: true,\n speed: 500,\n dots: false,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1\n },\n\n contentBlock: {\n autoplay: false,\n accessibility: false,\n easing: 'swing',\n infinite: false,\n speed: 1000,\n dots: false,\n arrows: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n mobileFirst: true,\n lazyLoad: 'ondemand',\n responsive: [\n {\n breakpoint: util.getViewports('lg'),\n settings: {\n slidesToShow: 5,\n slidesToScroll: 1,\n arrows: false\n }\n }\n ]\n },\n\n videoTiles: {\n infinite: true,\n accessibility: false,\n speed: 300,\n dots: true,\n arrows: false,\n slidesToShow: 1,\n slidesToScroll: 1,\n variableWidth: true,\n centerMode: true,\n mobileFirst: true,\n responsive: [\n {\n breakpoint: util.getViewports('sm'),\n settings: 'unslick'\n }\n ]\n },\n\n feature: {\n infinite: true,\n accessibility: false,\n speed: 300,\n dots: true,\n arrows: false,\n slidesToShow: 1,\n slidesToScroll: 1,\n centerMode: true,\n variableWidth: true,\n mobileFirst: true,\n responsive: [\n {\n breakpoint: util.getViewports('sm'),\n settings: 'unslick'\n }\n ]\n },\n\n partsTiles: {\n infinite: false,\n lazyLoad: 'ondemand',\n accessibility: false,\n speed: 300,\n dots: true,\n arrows: true,\n mobileFirst: true,\n slidesToShow: 1,\n slidesToScroll: 1,\n responsive: [\n {\n breakpoint: util.getViewports('lg'),\n settings: {\n slidesToShow: 4,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: util.getViewports('md'),\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1,\n variableWidth: true,\n centerMode: true,\n arrows: false,\n dots: true\n }\n }\n ]\n },\n\n blogTiles: {\n infinite: true,\n accessibility: false,\n speed: 300,\n dots: true,\n arrows: true,\n slidesToShow: 4,\n slidesToScroll: 1,\n swipeToSlide: true,\n responsive: [\n {\n breakpoint: 991,\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: 768,\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n }\n ]\n },\n\n blogArticle: {\n infinite: true,\n accessibility: false,\n speed: 600,\n dots: true,\n arrows: false,\n slidesToShow: 1,\n slidesToScroll: 1,\n variableWidth: true,\n centerMode: true,\n mobileFirst: true,\n responsive: [\n {\n breakpoint: util.getViewports('md'),\n settings: 'unslick'\n }\n ]\n }\n};\n","require('@babel/polyfill');\nwindow.jQuery = window.$ = require('jquery');\nvar processInclude = require('base/util');\nvar tagManager = require('int_googletags/tagManager');\n\n$(document).ready(function () {\n // init specific global components.\n if (window.pageContext && window.pageContext.ns) {\n tagManager.init(window.pageContext.ns);\n }\n processInclude(require('./components/quantitySelector'));\n processInclude(require('./components/menu'));\n processInclude(require('base/components/cookie'));\n processInclude(require('./components/consentTracking'));\n processInclude(require('./components/emailSignup'));\n processInclude(require('./components/backtotop'));\n processInclude(require('./components/miniCart'));\n processInclude(require('./components/collapsibleItem'));\n processInclude(require('./components/search'));\n processInclude(require('./components/clientSideValidation'));\n processInclude(require('./components/countrySelector'));\n processInclude(require('./components/carousels'));\n processInclude(require('lyonscg/components/tooltips'));\n processInclude(require('./components/einsteinObserver'));\n processInclude(require('plugin_blog/components/carousels'));\n processInclude(require('plugin_blog/components/video'));\n processInclude(require('pagedesigner/experience/carousels'));\n processInclude(require('pagedesigner/experience/heros'));\n processInclude(require('pagedesigner/experience/modal'));\n processInclude(require('pagedesigner/experience/tabs'));\n processInclude(require('pagedesigner/experience/pdProductTiles'));\n processInclude(require('pagedesigner/experience/stickyComponents'));\n processInclude(require('./components/ada'));\n processInclude(require('int_ordergroove/cart/cart'));\n processInclude(require('int_ordergroove_custom/optins'));\n});\n\nrequire('lyonscg/thirdParty/bootstrap');\nrequire('./components/spinner');\nrequire('svg4everybody')({\n polyfill: true // needed for IE11 support\n});\nrequire('slick-carousel');\nrequire('picturefill');\nrequire('lazysizes');\n\n// Missing forEach on NodeList for IE11\nif (window.NodeList && !NodeList.prototype.forEach) {\n NodeList.prototype.forEach = Array.prototype.forEach;\n}\n","'use strict';\n\nvar productBase = require('lyonscg/product/base');\nvar slickConfigs = require('../config/slickConfigs');\nvar imagesloaded = require('imagesloaded');\nvar zoomConfigs = require('lyonscg/config/zoomConfigs');\nvar utils = require('../util/utils');\nvar carouselsBonusProduct = require('../components/carousels');\nvar ada = require('../components/ada');\nlet isMobile = utils.isMobile();\nconst urlMinicartShow = $('.minicart').attr('data-action-url');\n\n/**\n * Disable PDP Zoom\n */\nfunction disableZoom() {\n $('.slide-link').trigger('zoom.destroy');\n}\n\n/**\n * Init PDP Zoom\n */\nfunction initZoom() {\n disableZoom();\n\n var isDesktop = utils.mediaBreakpointUp('lg');\n var $activeSlide = $('.product-carousel .slick-active');\n\n $activeSlide.each(function () {\n var $slider = $(this);\n var $image = $slider.find('.slide-link.zoom-hires');\n var url = $image.attr('href');\n\n if ($image.length > 0 && url && url !== 'null' && isDesktop) {\n var config = {\n url: url\n };\n config = $.extend({}, zoomConfigs, config);\n $image.zoom(config);\n }\n });\n}\n\n\n/**\n * If the current slide has a data-caption attribute, render the value within caption-container class element. Otherwise, render an empty string.\n * @param {jquery} $carousel - the carousel to update a caption on\n * @param {number} nextSlideIndex - the slick slider index of the next slide\n */\nfunction updateCaptionText($carousel, nextSlideIndex) {\n var $activeImage;\n var $caption = $carousel.parent().find('.caption-container .caption-text');\n\n if ($caption.length === 0) return;\n\n if (typeof (nextSlideIndex) !== 'undefined') {\n $activeImage = $carousel.find(`[data-slick-index=\"${nextSlideIndex}\"] .slide-img`);\n } else {\n $activeImage = $carousel.find('.slick-active .slide-img');\n }\n\n var captionText = $activeImage && $activeImage.attr('data-caption') ? $activeImage.attr('data-caption') : '';\n $caption.text(captionText);\n\n // If slick dots are visible, move caption to be before the dots\n if ($carousel.find('.slick-dots').length > 0 && $carousel.find('.caption-container').length === 0) {\n $carousel.find('.slick-dots').before($('.caption-container').last());\n }\n}\n\n/**\n * Init the product video in modal\n */\nfunction videoInModalInit() {\n $(document).on('ready', function () {\n window.oneTrustUtil = window.oneTrustUtil || [];\n window.oneTrustUtil.push(function () {\n window.sYoutubeHost = 'www.youtube-nocookie.com';\n if (oneTrustUtil.allowTargeting()) { // eslint-disable-line\n window.sYoutubeHost = 'www.youtube.com';\n var $ytContent = $('.youtube-content');\n if ($ytContent) {\n var srcURL = $ytContent.attr('src');\n if (srcURL.indexOf('www.youtube-nocookie.com') !== -1) {\n srcURL = srcURL.replace('www.youtube-nocookie.com', 'www.youtube.com');\n $ytContent.attr('src', srcURL);\n }\n }\n }\n });\n });\n\n $('body').on('click', '.pdp-video-link', (e) => {\n const $btn = $(e.currentTarget);\n const ytHost = window.sYoutubeHost ? window.sYoutubeHost : 'www.youtube-nocookie.com';\n // Traverse DOM based if PDP Video is a Feature Video or a Product Video Caro\n const $modal = $btn.hasClass('feature-video-link') ? $btn.parents('.feature-video').next('.product-video-modal') : $btn.parents('.video-wrapper').children('.product-video-modal');\n\n const $video = $modal.find('iframe');\n const $ytID = $modal.data('id');\n const $ytURL = 'https://' + ytHost + '/embed/' + $ytID + '?rel=0feature=oembed';\n\n $modal.on('shown.bs.modal', function () {\n $video.attr('src', $ytURL);\n });\n\n $modal.on('hidden.bs.modal', function () {\n $video.attr('src', '');\n });\n });\n}\n\n/**\n * Init the product carousel using a predefined slick configuration\n */\nfunction carouselInit() {\n // eslint-disable-next-line no-param-reassign\n var $productContainer = $('.product-detail');\n\n var $carousels = $productContainer.find('.product-carousel');\n var $carouselsSmall = $productContainer.find('.product-small-carousel');\n\n if ($carousels.length) {\n $carousels.each(function () {\n var $carousel = $(this);\n var $wrapper = $carousel.closest('.product-detail');\n if ($wrapper.hasClass('product-detail-bundle')) {\n $wrapper = $wrapper.find('.product-detail-bundle-wrapper');\n }\n\n $carousel.not('.slick-initialized').slick(slickConfigs.pdp);\n initZoom();\n\n $carousel.on('afterChange', initZoom.bind($wrapper));\n $carousel.on('init', function () {\n ada.accessbileSliders($carousel);\n initZoom();\n updateCaptionText($carousel);\n }).on('afterChange', function () {\n ada.accessbileSliders($carousel);\n initZoom();\n }).on('beforeChange', function (event, slick, currentSlide, nextSlide) {\n updateCaptionText($carousel, nextSlide);\n });\n $carousel.not('.slick-initialized').slick(slickConfigs.pdp);\n });\n }\n\n if ($carouselsSmall.length) {\n $carouselsSmall.each(function () {\n var $carousel = $(this);\n\n // Get previous product caro ID for slick asNavFor\n var prodCaroID = '#' + $carousel.siblings('.product-carousel').attr('id');\n\n // Get unique thumbnail slides to add aria-labels for ADA, exclude cloned slides\n var $prodCaroThumbUnique = $carousel.find('.slick-slide:not(.slick-cloned) .slide img');\n\n // Match unique slides to cloned slides by shared alt attributes. Set aria-labels that differentiate unique slides from cloned slides.\n if ($prodCaroThumbUnique.length > 0) {\n $prodCaroThumbUnique.each(function (index) {\n $(this).parent().attr('aria-label', 'Carousel slide image ' + (index + 1) + ' of ' + $prodCaroThumbUnique.length);\n var thumbnailLabel = $(this).parent().attr('aria-label');\n var thumbnailAlt = $(this).attr('alt');\n $('.product-small-carousel .slick-cloned .slide [alt=\"' + thumbnailAlt + '\"]').parent().attr('aria-label', 'Clone of ' + thumbnailLabel);\n });\n }\n // Init slick caro for product thumbnails on PDP\n $carousel.not('.slick-initialized').on('init', function () {\n ada.accessbileSliders($carousel);\n }).on('afterChange', function () {\n ada.accessbileSliders($carousel);\n }).slick({\n vertical: true,\n infinite: true,\n accessibility: false,\n speed: 400,\n lazyLoad: 'ondemand',\n dots: false,\n arrows: true,\n slidesToShow: 4,\n slidesToScroll: 1,\n focusOnSelect: true,\n asNavFor: prodCaroID,\n responsive: [{\n breakpoint: utils.getViewports('lg'),\n settings: {\n slidesToShow: 4,\n slidesToScroll: 1\n }\n }]\n });\n });\n }\n}\n\n\n/**\n * Retrieves the relevant pid value\n * @param {jquery} $el - DOM container for a given add to cart button\n * @return {string} - value to be used when adding product to cart\n */\nfunction getPidValue($el) {\n var pid;\n\n if ($('#quickViewModal').hasClass('show') && !$('.product-set').length) {\n pid = $($el).closest('.modal-content').find('.product-quickview').data('pid');\n } else if ($('.product-set-detail').length || $('.product-set').length) {\n pid = $($el).closest('.product-detail').find('.product-id').text();\n } else if ($('.storepage').length) {\n // page designer detail tile component\n pid = $($el).closest('.pd-product-detail').data('pid');\n } else {\n pid = $('.product-detail:not(\".bundle-item\")').data('pid');\n }\n\n return pid;\n}\n\n/**\n * Retrieve contextual quantity selector\n * @param {jquery} $el - DOM container for the relevant quantity\n * @return {jquery} - quantity selector DOM container\n */\nfunction getQuantitySelector($el) {\n let $selector = $('.quantity-select');\n if ($el && ($('.set-items').length || $('.storepage').length)) {\n $selector = $($el).closest('.product-detail').find('.quantity-select');\n }\n return $selector;\n}\n\n/**\n * Retrieves the value associated with the Quantity pull-down menu\n * @param {jquery} $el - DOM container for the relevant quantity\n * @return {string} - value found in the quantity input\n */\nfunction getQuantitySelected($el) {\n return getQuantitySelector($el).val();\n}\n\n/**\n * Process the attribute values for an attribute that has image swatches\n *\n * @param {Object} attr - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {Object[]} attr.values - Array of attribute value objects\n * @param {string} attr.values.value - Attribute coded value\n * @param {string} attr.values.url - URL to de/select an attribute value of the product\n * @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be\n * selected. If there is no variant that corresponds to a specific combination of attribute\n * values, an attribute may be disabled in the Product Detail Page\n * @param {jQuery} $productContainer - DOM container for a given product\n * @param {Object} msgs - object containing resource messages\n */\nfunction processSwatchValues(attr, $productContainer, msgs) {\n attr.values.forEach(function (attrValue) {\n var $attrValue = $productContainer.find('[data-attr=\"' + attr.id + '\"] [data-attr-value=\"' +\n attrValue.value + '\"]');\n var $swatchButton = $attrValue.parent();\n\n if (attrValue.selected) {\n $attrValue.addClass('selected');\n $attrValue.siblings('.selected-assistive-text').text(msgs.assistiveSelectedText);\n } else {\n $attrValue.removeClass('selected');\n $attrValue.siblings('.selected-assistive-text').empty();\n }\n\n if (attrValue.url) {\n $swatchButton.attr('data-url', attrValue.url);\n } else {\n $swatchButton.removeAttr('data-url');\n }\n\n // Disable if not selectable\n $attrValue.removeClass('selectable unselectable');\n\n $attrValue.addClass(attrValue.selectable ? 'selectable' : 'unselectable');\n });\n}\n\n/**\n * Process attribute values associated with an attribute that does not have image swatches\n *\n * @param {Object} attr - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {Object[]} attr.values - Array of attribute value objects\n * @param {string} attr.values.value - Attribute coded value\n * @param {string} attr.values.url - URL to de/select an attribute value of the product\n * @param {boolean} attr.values.isSelectable - Flag as to whether an attribute value can be\n * selected. If there is no variant that corresponds to a specific combination of attribute\n * values, an attribute may be disabled in the Product Detail Page\n * @param {jQuery} $productContainer - DOM container for a given product\n */\nfunction processNonSwatchValues(attr, $productContainer) {\n var $attr = '[data-attr=\"' + attr.id + '\"]';\n var $defaultOption = $productContainer.find($attr + ' .select-' + attr.id + ' option:first');\n $defaultOption.attr('value', attr.resetUrl);\n\n attr.values.forEach(function (attrValue) {\n var $attrValue = $productContainer\n .find($attr + ' [data-attr-value=\"' + attrValue.value + '\"]');\n $attrValue.attr('value', attrValue.url)\n .removeAttr('disabled');\n\n if (!attrValue.selectable) {\n $attrValue.attr('disabled', true);\n }\n });\n}\n\n/**\n * Routes the handling of attribute processing depending on whether the attribute has image\n * swatches or not\n *\n * @param {Object} attrs - Attribute\n * @param {string} attr.id - Attribute ID\n * @param {jQuery} $productContainer - DOM element for a given product\n * @param {Object} msgs - object containing resource messages\n */\nfunction updateAttrs(attrs, $productContainer, msgs) {\n // Currently, the only attribute type that has image swatches is Color.\n var attrsWithSwatches = ['color'];\n\n attrs.forEach(function (attr) {\n if (attrsWithSwatches.indexOf(attr.id) > -1) {\n processSwatchValues(attr, $productContainer, msgs);\n } else {\n processNonSwatchValues(attr, $productContainer);\n }\n });\n}\n\n/**\n * Retrieves url to use when adding a product to the cart\n *\n * @return {string} - The provided URL to use when adding a product to the cart\n */\nfunction getAddToCartUrl() {\n return $('.add-to-cart-url').val();\n}\n\n/**\n * Parses the html for a modal window\n * @param {string} html - representing the body and footer of the modal window\n *\n * @return {Object} - Object with properties body and footer.\n */\nfunction parseHtml(html) {\n var $html = $('
      ').append($.parseHTML(html));\n\n var body = $html.find('.choice-of-bonus-product');\n var footer = $html.find('.modal-footer').children();\n\n return { body: body, footer: footer };\n}\n\n/**\n * Retrieves url to use when adding a product to the cart\n *\n * @param {Object} data - data object used to fill in dynamic portions of the html\n */\nfunction chooseBonusProducts(data) {\n $('.modal-body').spinner().start();\n\n if ($('#chooseBonusProductModal').length !== 0) {\n $('#chooseBonusProductModal').remove();\n }\n var bonusUrl;\n if (data.bonusChoiceRuleBased) {\n bonusUrl = data.showProductsUrlRuleBased;\n } else {\n bonusUrl = data.showProductsUrlListBased;\n }\n\n // Overrides Base Bonus Product Modal\n var htmlString = ''\n + '
      '\n + '
      '\n + ''\n + '
      '\n + '
      '\n + ' ' + data.labels.selectprods + ''\n + ' '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      ';\n $('body').append(htmlString);\n $('.modal-body').spinner().start();\n\n // Overrides Base Bonus Product Modal\n $.ajax({\n url: bonusUrl,\n method: 'GET',\n dataType: 'json',\n success: function (response) {\n var parsedHtml = parseHtml(response.renderedTemplate);\n $('#chooseBonusProductModal .modal-body').empty();\n $('#chooseBonusProductModal .modal-header .close .sr-only').text(response.closeButtonText);\n $('#chooseBonusProductModal .modal-body').html(parsedHtml.body);\n $('#chooseBonusProductModal .modal-footer').html(parsedHtml.footer);\n $('#chooseBonusProductModal').modal('show');\n\n $('.bonus-product-item').each(function () {\n var bonusItem = $(this);\n imagesloaded(bonusItem).on('done', function () {\n carouselsBonusProduct.quickviewCarousel();\n });\n });\n\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n}\n\n/**\n * Scroll to active accordion header with class scollTo\n */\nfunction scrollToActiveAccordion() {\n $('html, body').animate({\n scrollTop: $('.scrollTo').offset().top\n }, 2000);\n}\n\n/**\n * Show add to cart message\n * @param {string} response - ajax response from clicking the add to cart button\n */\nfunction showAddToCartToast(response) {\n // show add to cart toast\n var messageType = response.error ? 'alert-danger' : 'alert-success';\n if ($('.add-to-cart-messages').length === 0) {\n $('body').append(\n '
      '\n );\n }\n\n $('.add-to-cart-messages').append(\n '
      '\n + response.message\n + ''\n + '
      '\n );\n\n // set tabindex and focus\n $('.add-to-basket-alert').attr('tabindex', 0);\n $('.add-to-basket-alert').focus();\n\n $('.add-to-basket-alert').on('closed.bs.alert', function () {\n $('.add-to-cart-messages').remove();\n });\n}\n\n/**\n * Updates the Mini-Cart quantity value after the customer has pressed the \"Add to Cart\" button\n * @param {string} response - ajax response from clicking the add to cart button\n */\nfunction handlePostCartAdd(response) {\n $('.minicart').trigger('count:update', response);\n\n if (response.newBonusDiscountLineItem && Object.keys(response.newBonusDiscountLineItem).length !== 0) {\n chooseBonusProducts(response.newBonusDiscountLineItem);\n } else if (response.recommendationInterceptData && Object.keys(response.recommendationInterceptData).length !== 0) {\n var recommendationIntercept = require('../cart/recommendationIntercept');\n var promise = recommendationIntercept.methods.showRecommendationIntercept(response.recommendationInterceptData);\n promise.fail(function (data) {\n showAddToCartToast(response);\n if (data && data.redirectUrl) {\n location.href = data.redirectUrl;\n }\n });\n } else {\n // A/B TEST minicart-redesign: SKIP DISPLAYING THE TOAST (EXCEPT /cart PAGE)\n if (response.minicartVariantA && window.pageContext.title !== 'Cart') return;\n if (!isMobile) { // eslint-disable-line no-lonely-if\n showAddToCartToast(response);\n }\n }\n}\n\n/**\n * Retrieves the bundle product item ID's for the Controller to replace bundle master product\n * items with their selected variants\n *\n * @param {jQuery} $addToCartButton - DOM element for add to cart button\n * @return {string[]} - List of selected bundle product item ID's\n */\nfunction getChildProducts($addToCartButton) {\n var childProducts = [];\n $addToCartButton.closest('.product-detail, .tile-add-to-cart').find('.bundle-item').each(function () {\n childProducts.push({\n pid: $(this).find('.product-id').first().text(),\n quantity: parseInt($(this).find('label.quantity').data('quantity'), 10)\n });\n });\n\n return childProducts.length ? JSON.stringify(childProducts) : [];\n}\n\n/**\n * For Checkbox promotion, add after product has been added to cart\n *\n * @param {jQuery} $container - DOM element for parent\n */\nfunction checkForCoupon($container) {\n var $safeContainer = $container || $(document);\n var checkbox = $safeContainer.find('#checkboxPromotion');\n var useCheckbox = checkbox ? checkbox.is(':checked') : false;\n\n if (checkbox && useCheckbox) {\n var url = $safeContainer.find('#checkbox-coupon-form').attr('action');\n var checkboxform = $safeContainer.find('#checkbox-coupon-form').serialize();\n var $couponLabel = $safeContainer.find('.callout-badge');\n $.ajax({\n url: url,\n method: 'GET',\n data: checkboxform,\n success: function () {\n $safeContainer.find('.checkboxPromotion').hide();\n $couponLabel.html($couponLabel.data('applied-title'));\n }\n });\n }\n}\n\n/**\n * Retrieve product options\n *\n * @param {jQuery} $productContainer - DOM element for current product\n * @return {string} - Product options and their selected values\n */\nfunction getOptions($productContainer) {\n var options = $productContainer\n .find('.product-option')\n .map(function () {\n var $elOption = $(this).find('.options-select');\n var urlValue = $elOption.val();\n var selectedValueId = $elOption.find('option[value=\"' + urlValue + '\"]')\n .data('value-id');\n return {\n optionId: $(this).data('option-id'),\n selectedValueId: selectedValueId\n };\n }).toArray();\n\n return JSON.stringify(options);\n}\n\n/**\n * Dynamically creates Slick.js carousel from response containing images. Overrides the default storefront Bootstrap carousel.\n * @param {Object[]} imgs - Array of large product images,along with related information\n * @param {jQuery} $productContainer - DOM element for a given product\n */\nfunction createCarousel(imgs, $productContainer) {\n var carousel = $productContainer.find('.product-carousel');\n var smallCarousel = $productContainer.find('.product-small-carousel');\n var accessibleZoomCarousel = $productContainer.find('.zoom-feature-img');\n var accessibleZoomThumbnails = $productContainer.find('.zoom-tile-nav');\n\n // REMOVE SLIDES FROM SLICK.JS PRODUCT CAROUSELS (LARGE, SMALL, AND ACCESSIBLE ZOOM), REMOVE ACCESSIBLE ZOOM NAV THUMBNAILS\n var largeCarouselSlideCount = $('.product-carousel').slick('getSlick').$slides;\n var smallCarouselSlideCount = $('.product-small-carousel').slick('getSlick').$slides;\n\n accessibleZoomCarousel.removeClass('slick-initialized').children().remove();\n accessibleZoomThumbnails.children().remove();\n\n for (let i = 0; i < largeCarouselSlideCount.length; i++) {\n $(carousel).slick('slickRemove', [0]);\n }\n for (let j = 0; j < smallCarouselSlideCount.length; j++) {\n $(smallCarousel).slick('slickRemove', [0]);\n }\n // REPLACE CAROUSEL SLIDES AND ACCESSIBLE ZOOM NAV THUMBNAILS W/ PARAM imgs\n for (let k = 0; k < imgs.length; k++) {\n $(carousel).slick('slickAdd', '
      \"'
      ');\n $(accessibleZoomCarousel).append('
      \"Expanded
      ');\n $(smallCarousel).slick('slickAdd', '
      ');\n accessibleZoomThumbnails.append('\"Thumbnail');\n }\n initZoom();\n}\n\n/**\n * Updates DOM using post-option selection Ajax response\n *\n * @param {OptionSelectionResponse} options - Ajax response options from selecting a product option\n * @param {jQuery} $productContainer - DOM element for current product\n */\nfunction updateOptions(options, $productContainer) {\n options.forEach(function (option) {\n var $optionEl = $productContainer.find('.product-option[data-option-id*=\"' + option.id\n + '\"]');\n option.values.forEach(function (value) {\n var valueEl = $optionEl.find('option[data-value-id*=\"' + value.id + '\"]');\n valueEl.val(value.url);\n });\n });\n}\n\n/**\n * Updates the quantity DOM elements post Ajax call\n * @param {UpdatedQuantity[]} quantities -\n * @param {jQuery} $productContainer - DOM container for a given product\n */\nfunction updateQuantities(quantities, $productContainer) {\n if (!($productContainer.parent('.bonus-product-item').length > 0)) {\n var optionsHtml = quantities.map(function (quantity) {\n var selected = quantity.selected ? ' selected ' : '';\n return '';\n }).join('');\n getQuantitySelector($productContainer).empty().html(optionsHtml);\n }\n}\n\n/**\n * Updates the availability status in the Product Detail Page\n *\n * @param {Object} response - Ajax response object after an\n * attribute value has been [de]selected\n * @param {jQuery} $productContainer - DOM element for a given product\n */\nfunction updateAvailability(response, $productContainer) {\n var availabilityValue = '';\n var availabilityMessages = response.product.availability.messages;\n if (!response.product.readyToOrder) {\n availabilityValue = '
    4. ' + response.resources.info_selectforstock + '
    5. ';\n } else {\n availabilityMessages.forEach(function (message) {\n availabilityValue += '
    6. ' + message + '
    7. ';\n });\n }\n\n $($productContainer).trigger('product:updateAvailability', {\n product: response.product,\n $productContainer: $productContainer,\n message: availabilityValue,\n resources: response.resources\n });\n}\n\n/**\n * Generates html for promotions section\n *\n * @param {array} promotions - list of promotions\n * @return {string} - Compiled HTML\n */\nfunction getPromotionsHtml(promotions) {\n if (!promotions) {\n return '';\n }\n\n var html = '';\n\n promotions.forEach(function (promotion) {\n html += '
      ' + promotion.calloutMsg +\n '
      ';\n });\n\n return html;\n}\n\n/**\n * Generates html for product attributes section\n *\n * @param {array} attributes - list of attributes\n * @return {string} - Compiled HTML\n */\nfunction getAttributesHtml(attributes) {\n if (!attributes) {\n return '';\n }\n\n var html = '';\n\n attributes.forEach(function (attributeGroup) {\n if (attributeGroup.ID === 'mainAttributes') {\n attributeGroup.attributes.forEach(function (attribute) {\n html += '
      ' + attribute.label + ': '\n + attribute.value + '
      ';\n });\n }\n });\n\n return html;\n}\n\n/**\n * Parses JSON from Ajax call made whenever an attribute value is [de]selected\n * @param {Object} response - response from Ajax call\n * @param {Object} response.product - Product object\n * @param {string} response.product.id - Product ID\n * @param {Object[]} response.product.variationAttributes - Product attributes\n * @param {Object[]} response.product.images - Product images\n * @param {boolean} response.product.hasRequiredAttrsSelected - Flag as to whether all required\n * attributes have been selected. Used partially to\n * determine whether the Add to Cart button can be enabled\n * @param {jQuery} $productContainer - DOM element for a given product.\n */\nfunction handleVariantResponse(response, $productContainer) {\n var isChoiceOfBonusProducts =\n $productContainer.parents('.choose-bonus-product-dialog').length > 0;\n var isVaraint;\n if (response.product.variationAttributes) {\n updateAttrs(response.product.variationAttributes, $productContainer, response.resources);\n isVaraint = response.product.productType === 'variant';\n if (isChoiceOfBonusProducts && isVaraint) {\n $productContainer.parent('.bonus-product-item')\n .data('pid', response.product.id);\n\n $productContainer.parent('.bonus-product-item')\n .data('ready-to-order', response.product.readyToOrder);\n }\n }\n\n // Update primary images\n var primaryImageUrls = response.product.images.large;\n createCarousel(primaryImageUrls, $productContainer);\n\n // Update pricing\n if (!isChoiceOfBonusProducts) {\n var $priceSelector = $('.prices .price', $productContainer).length\n ? $('.prices .price', $productContainer)\n : $('.prices .price');\n $priceSelector.replaceWith(response.product.price.html);\n }\n\n // Update promotions\n $('.promotions').empty().html(getPromotionsHtml(response.product.promotions));\n\n updateAvailability(response, $productContainer);\n\n if (isChoiceOfBonusProducts) {\n var $selectButton = $productContainer.find('.select-bonus-product');\n $selectButton.trigger('bonusproduct:updateSelectButton', {\n product: response.product, $productContainer: $productContainer\n });\n } else {\n // Enable \"Add to Cart\" button if all required attributes have been selected\n $('button.add-to-cart, button.add-to-cart-global, button.update-cart-product-global').trigger('product:updateAddToCart', {\n product: response.product, $productContainer: $productContainer\n }).trigger('product:statusUpdate', response.product);\n }\n\n // Update attributes\n $productContainer.find('.main-attributes').empty()\n .html(getAttributesHtml(response.product.attributes));\n}\n\n/**\n * updates the product view when a product attribute is selected or deselected or when\n * changing quantity\n * @param {string} selectedValueUrl - the Url for the selected variation value\n * @param {jQuery} $productContainer - DOM element for current product\n */\nfunction attributeSelect(selectedValueUrl, $productContainer) {\n if (selectedValueUrl) {\n $('body').trigger('product:beforeAttributeSelect',\n { url: selectedValueUrl, container: $productContainer });\n\n $.ajax({\n url: selectedValueUrl,\n method: 'GET',\n success: function (data) {\n handleVariantResponse(data, $productContainer);\n updateOptions(data.product.options, $productContainer);\n updateQuantities(data.product.quantities, $productContainer);\n $('body').trigger('product:afterAttributeSelect',\n { data: data, container: $productContainer });\n $.spinner().stop();\n },\n error: function () {\n $.spinner().stop();\n }\n });\n }\n initZoom();\n}\n\n$('body').on('hidden.bs.modal', '#recommendationInterceptModal', function () {\n location.reload();\n});\n\nvar exportBase = $.extend({}, productBase, {\n carouselInit: carouselInit,\n videoInModalInit: videoInModalInit,\n scrollToActiveAccordion: scrollToActiveAccordion,\n\n methods: {\n editBonusProducts: function (data) {\n chooseBonusProducts(data);\n }\n },\n\n selectBonusProduct: function () {\n $(document).on('click', '.select-bonus-product', function () {\n var $choiceOfBonusProduct = $(this).parents('.choice-of-bonus-product');\n var pid = $(this).data('pid');\n var maxPids = $('.choose-bonus-product-dialog').data('total-qty');\n var submittedQty = parseInt($(this).parents('.choice-of-bonus-product').find('.bonus-quantity-select').val(), 10) || 1;\n if (isNaN(submittedQty)) {\n submittedQty = 1;\n }\n var totalQty = 0;\n $.each($('#chooseBonusProductModal .selected-bonus-products .selected-pid'), function () {\n totalQty += $(this).data('qty');\n });\n totalQty += submittedQty;\n var optionID = $(this).parents('.choice-of-bonus-product').find('.product-option').data('option-id');\n var valueId = $(this).parents('.choice-of-bonus-product').find('.options-select option:selected').data('valueId');\n if (totalQty <= maxPids) {\n var selectedBonusProductHtml = ''\n + '
      '\n + '
      '\n + $choiceOfBonusProduct.find('.product-name').html()\n + '
      '\n + '
      '\n + '
      '\n ;\n $('#chooseBonusProductModal .selected-bonus-products').append(selectedBonusProductHtml);\n $('.pre-cart-products').html(totalQty);\n $('.selected-bonus-products .bonus-summary').removeClass('alert-danger');\n } else {\n $('.selected-bonus-products .bonus-summary').addClass('alert-danger');\n }\n });\n },\n\n addToCart: function () {\n $(document).on('click', 'button.add-to-cart, button.add-to-cart-global', function () {\n var addToCartUrl;\n var pid;\n var pidsObj;\n var setPids;\n var quantity;\n var pdRedirectUrl;\n\n $('body').trigger('product:beforeAddToCart', this);\n\n if ($('.set-items').length && $(this).hasClass('add-to-cart-global')) {\n setPids = [];\n\n $('.product-detail').each(function () {\n if (!$(this).hasClass('product-set-detail')) {\n setPids.push({\n pid: $(this).find('.product-id').text(),\n qty: $(this).find('.quantity-select').val(),\n options: getOptions($(this))\n });\n }\n });\n pidsObj = JSON.stringify(setPids);\n }\n\n if ($(this).hasClass('add-to-cart-tile')) {\n pid = $(this).data('pid');\n addToCartUrl = $(this).data('add-to-cart-url');\n quantity = 1;\n } else {\n pid = getPidValue($(this));\n addToCartUrl = getAddToCartUrl();\n quantity = getQuantitySelected($(this));\n }\n // page designer component redirect to cart configuration\n pdRedirectUrl = $(this).data('redirect-url') || null;\n\n var $productContainer = $(this).closest('.product-detail');\n if (!$productContainer.length) {\n $productContainer = $(this).closest('.quick-view-dialog').find('.product-detail');\n }\n\n var form = {\n pid: pid,\n pidsObj: pidsObj,\n childProducts: getChildProducts($(this)),\n quantity: quantity\n };\n\n if (!$('.bundle-item').length) {\n form.options = getOptions($productContainer);\n }\n\n $(this).trigger('updateAddToCartFormData', form);\n var $atc = $(this);\n if (addToCartUrl) {\n $.ajax({\n url: addToCartUrl,\n method: 'POST',\n data: form,\n success: function (data) {\n handlePostCartAdd(data);\n\n data.redirectUrl = pdRedirectUrl;\n $('body').trigger('product:afterAddToCart', data);\n\n var isProductTile = $atc.closest('.product-tile').length > 0;\n var parent = null;\n if (isProductTile) {\n parent = $atc.closest('.product-tile');\n checkForCoupon($(parent));\n } else {\n checkForCoupon();\n }\n\n $.spinner().stop();\n // page designer component configuration to redirect to cart\n if (pdRedirectUrl && !data.pliUUID) {\n window.location = pdRedirectUrl;\n }\n if ($atc.parents().find('#recommendationInterceptModal').length > 0) {\n $('#maincontent').load(location.href + ' #maincontent');\n }\n if (!(data.action === 'Cart-Show')) {\n // A/B TEST minicart-redesign: ON ADD TO CART FROM /cart PAGE, RELOAD THE PAGE INSTEAD OF TRIGGERING MINICART.\n if (data.minicartVariantA && window.pageContext.title === 'Cart') {\n location.reload();\n return;\n }\n $('.minicart').trigger('click', data);\n $('.minicart .popover').addClass('show');\n }\n },\n error: function (data) {\n showAddToCartToast(data.responseJSON);\n $.spinner().stop();\n },\n complete: function (data) {\n // A/B TEST minicart-redesign: HANDLE ADD TO CART FROM PRODUCT RECOMMENDATIONS INSIDE MINICART -> REFRESH MINICART DATA\n if ($atc.parents('.minicart-recommendations').length > 0 && data.responseJSON.minicartVariantA) {\n $('.minicart .popover').empty();\n $('.minicart .popover').spinner().start();\n $.get(urlMinicartShow, function (minicartData) {\n $('.minicart .popover').append(minicartData);\n $('.minicart .popover').spinner().stop();\n });\n return;\n }\n\n $('html, body').animate({\n scrollTop: !isMobile ? $('body').offset().top : scrollY\n }, 500);\n }\n });\n }\n });\n },\n\n selectAttribute: function () {\n $(document).on('change', 'select[class*=\"select-\"], .options-select', function (e) {\n e.preventDefault();\n\n var $productContainer = $(this).closest('.set-item');\n if (!$productContainer.length) {\n $productContainer = $(this).closest('.product-detail');\n }\n attributeSelect(e.currentTarget.value, $productContainer);\n });\n },\n updateCaptionText: updateCaptionText\n});\n\nmodule.exports = exportBase;\n","'use strict';\n\nvar utils = require('lyonscg/util/utils');\n\nmodule.exports = $.extend(utils, {\n /**\n * @desc Media breakpoints that are used throughout the Javascript\n */\n breakpoints: {\n xs: 0,\n sm: 576,\n md: 768,\n lg: 992,\n th: 1025,\n xl: 1440\n }\n});\n","'use strict';\nvar baseCarousel = require('lyonscg/components/carousels');\nvar slickConfigs = require('../config/slickConfigs');\n\nmodule.exports = $.extend({}, baseCarousel, {\n blogCarousels: function () {\n $('.blog-tile-caro').slick(slickConfigs.blogTiles);\n },\n categorySubFolder: function () {\n $('.category-subfolder-tile-caro').slick(slickConfigs.categorySubFolderTiles);\n },\n productRelatedContent: function () {\n $('.product-related-content-tile-caro').slick(slickConfigs.productRelatedContent);\n }\n});\n","'use strict';\n\n/**\n * Show iframe video in modal window\n * @param {string} videoID Youtube video ID\n * @param {string} linkTitle use link title as iframe title\n */\nfunction showVideo(videoID, linkTitle) {\n const ytHost = window.sYoutubeHost ? window.sYoutubeHost : 'www.youtube-nocookie.com';\n var htmlString = ''\n + '
      '\n + '
      role=\"document\"'\n + ''\n + '
      '\n + '
      '\n + ''\n + '
      '\n + '
      '\n + '
      '\n + ''\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      '\n + '
      ';\n\n $('body').append(htmlString);\n\n $('#VideoModal').on('hide.bs.modal', function () {\n $('#VideoModal').remove();\n });\n $('#VideoModal').modal('show');\n}\n\n/**\n * Initialize Video functionality\n */\nfunction video() {\n $('body').on('click', '.js-video-link', function (e) {\n var $link = $(this);\n var title = $link.data('iframetitle') || 'Product Video';\n\n if ($link.data('id')) {\n e.preventDefault();\n\n showVideo($link.data('id'), title);\n }\n });\n}\n\nmodule.exports = video;\n","'use strict';\nvar baseConfigs = require('lyonscg/config/slickConfigs');\n/**\n * Reusable Blog slick carousel configurations\n * @example - $('.product-carousel').slick(slickConfigs.pdp)\n */\nmodule.exports = $.extend({}, baseConfigs, {\n blogTiles: {\n infinite: true,\n speed: 300,\n dots: true,\n arrows: true,\n slidesToShow: 4,\n slidesToScroll: 1,\n swipeToSlide: true,\n responsive: [\n {\n breakpoint: 991,\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: 768,\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n }\n ]\n },\n categorySubFolderTiles: {\n infinite: true,\n speed: 300,\n dots: false,\n arrows: true,\n slidesToShow: 8,\n slidesToScroll: 1,\n swipeToSlide: true,\n responsive: [\n {\n breakpoint: 991,\n settings: {\n slidesToShow: 6,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: 768,\n settings: {\n slidesToShow: 4,\n slidesToScroll: 1\n }\n }\n ]\n },\n productRelatedContent: {\n infinite: true,\n speed: 300,\n dots: true,\n arrows: true,\n slidesToShow: 4,\n slidesToScroll: 1,\n swipeToSlide: true,\n responsive: [\n {\n breakpoint: 769,\n settings: {\n slidesToShow: 3,\n slidesToScroll: 1\n }\n },\n {\n breakpoint: 577,\n settings: {\n slidesToShow: 2,\n slidesToScroll: 1\n }\n }\n ]\n }\n});\n","'use strict';\n\n/**\n * einsteinObserver\n * Initialize an observer for adding a callback to Einstein DOM element injection.\n * @param {string} selector - the selector to observe for changes\n * @param {function} fn - the callback for when changes are found\n */\nvar einsteinObserver = function (selector, fn) {\n var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;\n var einsteinSelector = '[id^=\"cq_recomm_slot\"]';\n var elements = window.document.querySelectorAll(selector);\n var check = function (mutationList, observer) {\n if (mutationList.length) {\n // if new child detected, disconnect observer and call callback\n observer.disconnect();\n fn.call();\n }\n };\n\n // check each element for child\n elements.forEach(function (element) {\n var einsteinElement = element.querySelector(einsteinSelector);\n if (einsteinElement === null) {\n // not an einstein recommender slot\n if (element.childElementCount > 0) {\n // if child, call callback immediately\n fn.call();\n }\n } else if (einsteinElement.childElementCount > 0) {\n // if child, call callback immediately\n fn.call();\n } else {\n // if no child, attach observer and wait for child to be added\n var observer = new MutationObserver(check);\n observer.observe(einsteinElement, { childList: true });\n }\n });\n};\n\nmodule.exports = { einsteinObserver: einsteinObserver };\n","'use strict';\nvar utils = require('pagedesigner/util/utils');\nvar videoUtils = require('pagedesigner/util/videos');\nvar imagesLoaded = require('imagesloaded');\nvar _ = require('lodash');\nvar ada = require('org/components/ada');\n\n/**\n * positions the slider controls over the image in mobile\n * @param {Object} $slider - the carousel\n */\nfunction positionHeroControls($slider) {\n $slider.find('.slick-dots').css('top', '');\n if (utils.mediaBreakpointDown('md')) {\n let $elements = $slider.find('.text-block');\n if ($elements.length > 0) {\n let $tallestBlock = _.maxBy($elements, function (el) { return $(el).height(); });\n $slider.find('.slick-dots').css('top', ($tallestBlock.offsetHeight + 45) * -1);\n }\n }\n}\n\n/**\n * Inserts a play/pause button control on autoplay sliders\n * @param {Object} $slider - the carousel to modify\n */\nfunction insertPlayButton($slider) {\n if (!$slider || $slider.find('.caro-control').length > 0) {\n return;\n }\n $slider.find('.slick-dots li:last-of-type').after(\n '
    8. '\n );\n}\n\n/**\n * ensures slides and mobile content blocks have the same height\n * @param {Object} $slides - the elements to sync\n */\nfunction syncHelper($slides) {\n // only sync mobile content in heros\n utils.syncHeights($slides.find('.text-block.d-lg-none'));\n\n // all slides\n utils.syncHeights($slides);\n}\n\n/**\n * common carousel init\n * @param {Object} $slider - the elements to sync\n * @param {Object} configs - slider settings\n */\nfunction initSlider($slider, configs) {\n var sliderConfigs = configs || $slider.data('slick');\n // get first child of each slide to sync heights\n var $slides = $slider.find('.pd-caro-item > div');\n\n // hide dots where there is only one slide; known slick bug\n $slider.on('init', function () {\n if ($slider.find('.slick-dots li:not(.pause)').length === 1) {\n $slider.find('.slick-dots').hide();\n }\n syncHelper($slides);\n if ($slider.hasClass('pd-hero-caro')) {\n positionHeroControls($slider);\n // Video Slide handlers\n videoUtils.videoControlsInit($slider);\n }\n });\n\n $slider.on('init afterChange', function () {\n ada.accessbileSliders($slider);\n });\n\n imagesLoaded($slider).on('done', function () {\n // initialize the slider\n $slider.slick(sliderConfigs);\n\n // if this is an autoplay slider, add a play/pause button\n // PD TODO: add keyboard accessibility\n if ($slider.hasClass('autoplay')) {\n insertPlayButton($slider);\n }\n }).on('always', function () {\n // insurance for lazy loaded images\n $slider.slick(sliderConfigs);\n });\n\n utils.smartResize(function () {\n // re-init if we have 'unslicked' the slider\n if (!$slider.hasClass('slick-initialized')) {\n $slides.css('height', '');\n $slider.slick(sliderConfigs);\n } else {\n syncHelper($slides);\n }\n\n if ($slider.hasClass('pd-hero-caro')) {\n positionHeroControls($slider);\n }\n });\n}\n\nvar pdCarousels = {\n initEvents: function () {\n // events will be init'd on load\n $('body').on('click', '.toggle-button', function (e) {\n e.stopPropagation();\n var $btn = $(e.target);\n var $parentSlider = $btn.closest('.slick-slider');\n var isHero = $parentSlider.hasClass('pd-hero-caro');\n if ($btn.hasClass('isPaused')) {\n $parentSlider.slick('slickPlay');\n $btn.removeClass('isPaused').addClass('isPlaying');\n if (isHero) {\n $parentSlider.find('.slick-active button').css('animation-play-state', 'running');\n }\n } else {\n $parentSlider.slick('slickPause');\n $btn.removeClass('isPlaying').addClass('isPaused');\n if (isHero) {\n $parentSlider.find('.slick-active button').css('animation-play-state', 'paused');\n }\n }\n });\n },\n /**\n * @function\n * @desc Inits all sliders in Page Designer. Default Configs are overwritten via data attributes in the template.\n * In the case that this is an Einstein carousel, we use an observer to ensure the slider gets initialized\n */\n pdContentCaro: function () {\n $('.pd-content-caro').not('.slick-initialized').each(function (idx) {\n // find and initialize all of the content carousels\n var $slider = $(this);\n var sliderID = 'pd-content-caro-' + idx;\n\n // assign a unique ID for optional targeting\n $slider.attr('id', sliderID);\n\n // hide dots where there is only one slide; known slick bug\n $slider.on('init', function () {\n // check PI recommendations content if this is a Product Tile slider\n if ($slider.find('.product-tile-pd').length > 0) {\n utils.mutationObserver($slider, function () { pdCarousels.pdContentCaro(); });\n }\n\n // handle slider video card events\n if ($slider.find('[class$=\"videoComponent\"]').length > 0) {\n let $videoComponents = $('[class$=\"videoComponent\"]');\n $videoComponents.each(function () {\n let $videoComponent = $(this);\n videoUtils.initPosterImgEvents($videoComponent);\n });\n }\n });\n\n initSlider($slider);\n });\n },\n /**\n * @function\n * @desc Inits the Custom Hero Slider. Default Configs are overwritten via data attributes in the template.\n * In the case that this is an Einstein carousel, we use an observer to ensure the slider gets initialized\n */\n pdHeroCaro: function () {\n $('.pd-hero-caro').not('.slick-initialized').each(function (idx) {\n // find and initialize all of the hero carousels\n var $slider = $(this);\n var sliderID = 'pd-hero-caro-' + idx;\n\n // assign a unique ID for optional targeting\n $slider.attr('id', sliderID);\n\n initSlider($slider);\n });\n },\n /**\n * @function\n * @desc Inits the Product Detail Tile Use it With replenishment products slider\n */\n pdReplenishmentCaro: function () {\n var configs = {\n speed: 300,\n dots: true,\n arrows: false,\n slidesToShow: 2,\n slidesToScroll: 1,\n mobileFirst: true,\n infinite: false\n };\n\n $('.replenishment-product-caro').not('.slick-initialized').each(function (idx) {\n // find and initialize all of the hero carousels\n var $slider = $(this);\n var sliderID = 'replenishment-product-caro-' + idx;\n // assign a unique ID for optional targeting\n $slider.attr('id', sliderID);\n $slider.slick(configs);\n });\n }\n};\n\nmodule.exports = pdCarousels;\n","'use strict';\nvar videoUtils = require('../util/videos');\nvar utils = require('../util/utils');\n\nmodule.exports = {\n initEvents: function () {\n // events will be init'd on load\n var $inlineBtn = $('.video-inline-link');\n var $heroComponent = $('.vf-wrapper');\n $heroComponent.on('click', function (e) {\n var $currentVideo = $(e.currentTarget);\n if ($currentVideo.hasClass('paused')) {\n videoUtils.playPauseVideo($currentVideo, 'play');\n $currentVideo.removeClass('paused');\n $inlineBtn.removeClass('paused');\n $inlineBtn.addClass('playing');\n } else {\n videoUtils.playPauseVideo($currentVideo, 'pause');\n $currentVideo.addClass('paused');\n $inlineBtn.addClass('paused');\n $inlineBtn.removeClass('playing');\n }\n });\n\n // hide the poster image to reveal the inline video\n $inlineBtn.on('click', function (e) {\n var $parent = $(e.currentTarget).parent();\n var $posterImg = $parent.find('.photo-tile-container');\n var $vfWrapper = $parent.find('.vf-wrapper, .inline-video');\n $posterImg.addClass('disable-poster-image');\n $vfWrapper.trigger('click');\n });\n\n // IE11 fix for object-fit\n // will fix all images, not just heros\n utils.ieObjectFitFix();\n },\n initJumpLinks: function () {\n $('.jump-link').on('click', function (e) {\n e.preventDefault();\n var anchorTarget = $(e.currentTarget).attr('href');\n utils.scrollBrowser($(anchorTarget).offset().top - 40);\n });\n },\n initVideoComponents: function () {\n let $videoComponents = $('[class$=\"videoComponent\"]');\n $videoComponents.each(function () {\n let $videoComponent = $(this);\n videoUtils.initPosterImgEvents($videoComponent);\n });\n }\n};\n","const alert = require('base/components/errorNotification');\nvar videoUtils = require('../util/videos');\n\n/**\n * Init globally reusable modals for content links\n *\n * The function adds an event listener on the \".modal-link\" class in a button element.\n *\n * Required Attributes:\n * data-link=”contentAssetURL” - This will be the Page Link to the Content Asset or Partial Page.\n *\n */\n\n/**\n * Gets the modal content\n * @param {string} url - the get url\n * @param {string} pageSelector - the partial page container to use in the response html\n * @returns {Promise} - a promise resolved with the modal content\n */\nfunction getModalContent(url, pageSelector) {\n var selectorString = pageSelector || '.content-asset-container';\n return new Promise((resolve, reject) => {\n $.ajax({\n url,\n success: (html) => {\n var $content = $(html).find(selectorString);\n if ($content.length === 0) {\n $content = $(html).clone();\n }\n\n $content.removeClass('container');\n resolve($content.html());\n },\n error: (err) => reject(err)\n });\n });\n}\n\n/**\n * Creates the modal jQuery element\n * @param {string} title - the modal's title content\n * @param {string} body - the modal's body content\n * @param {Object} options - display options\n * @returns {jQuery} - a modal\n */\nfunction createModalElement(title, body, options) {\n var closeText = $('.modal-background').data('close');\n var html =\n `\n
      \n
      \n
      \n
      \n

      ${title}

      \n \n
      \n
      ${body || ''}
      \n \n
      \n
      \n
      \n `;\n\n return $(html);\n}\n\nmodule.exports = function () {\n $('body').on('click', '.modal-link', (e) => {\n e.preventDefault();\n var $link = $(e.currentTarget);\n var getUrl = $link.data('link');\n var title = $link.data('title') || '';\n var displayTitle = ($link.data('display-title') !== undefined) ? $link.data('display-title') : true;\n var displayFooter = ($link.data('display-footer') !== undefined) ? $link.data('display-footer') : true;\n var pageSelector = ($link.data('cid') !== undefined) ? '.' + $link.data('cid') : null;\n var modalSize = ($link.data('size') !== undefined) ? $link.data('size') : 'modal-lg';\n\n $.spinner().start();\n getModalContent(getUrl, pageSelector)\n .then((content) => {\n var displayOptions = {\n displayTitle,\n displayFooter,\n modalSize\n };\n var $modal = createModalElement(title, content, displayOptions);\n $('body').append($modal);\n $modal.on('show.bs.modal', function () { $modal.focus(); });\n $modal.on('shown.bs.modal', function () {\n $('.modal-header').trigger('focus');\n });\n $modal.on('hidden.bs.modal', function () {\n $link.trigger('focus');\n $modal.remove();\n });\n $modal.modal('show');\n }).catch(function (err) {\n window.console.error(err);\n alert($('.error-messaging'), $('.error-messaging').data('default-message'));\n })\n .finally(function () {\n $.spinner().stop();\n });\n });\n\n // global modal events\n $('.modal').on('show.bs.modal', function () {\n $(this).focus();\n }).on('shown.bs.modal', function () {\n $(this).find('.modal-header').trigger('focus');\n }).on('hidden.bs.modal', function (e) {\n $(e.currentTarget).trigger('focus');\n });\n\n $('body').on('click', '.video-modal-link, .text-video-link ', (e) => {\n e.preventDefault();\n var $link = $(e.currentTarget);\n $.spinner().start();\n var $modal = $('#' + $link.data('target'));\n $modal.addClass('pd-close');\n // append modal to the body so it works from absolutely positioned component regions\n // unbind and re-add namespaced click event in case of multiple watches\n $modal.appendTo('body').modal('show');\n $modal.off('shown.bs.modal.videos').on('shown.bs.modal.videos', function () {\n $('.modal-header').trigger('focus');\n // if triggered from inline poster image, just play the modal\n if ($link.hasClass('playNow')) {\n videoUtils.playPauseVideo($modal, 'play');\n } else if ($modal.find('.poster-img').length > 0) {\n videoUtils.initPosterImgEvents($modal);\n }\n });\n $modal.off('hide.bs.modal.videos').on('hide.bs.modal.videos', function () {\n videoUtils.playPauseVideo($modal, 'pause');\n videoUtils.resetPosterImage($modal);\n });\n $.spinner().stop();\n });\n};\n","'use strict';\nvar utils = require('pagedesigner/util/utils');\n\nvar synchTileElements = function () {\n let $tiles = $('.product-jump-tile, .einstein .product-tile');\n utils.syncHeights($tiles.find('.tile-heading > div'));\n utils.syncHeights($tiles.find('.pdp-link'));\n utils.syncHeights($tiles.find('.price-container')); // for OOS prods\n utils.syncHeights($tiles.find('.tile-desc'));\n utils.syncHeights($tiles.find('.promo-callout-container')); // for OOS prods\n utils.syncHeights($tiles.find('.tile-add-to-cart')); // for OOS prods\n\n // If tile Affirm has loaded, sync tile heights\n if ($tiles.find('.affirm-modal-trigger').length > 0) {\n utils.syncHeights($tiles.find('.tile-affirm')); // for OOS prods\n } else {\n // If tile Affirm has not loaded, use the inline height attribute until mutation is observed and a new height is set\n let affirmH = $tiles.find('.affirm-as-low-as').attr('height');\n let $affirmWrapper = $tiles.find('.tile-affirm');\n $affirmWrapper.each(function () {\n $(this).css('height', affirmH);\n });\n }\n // synch individual features\n for (var i = 1; i < 11; i++) {\n let $feature = $('.feature' + i.toString());\n if ($feature.length > 0) {\n utils.syncHeights($feature);\n }\n }\n // sync Product Family Chart Features\n let $chartTh = $('.chart-product-th');\n if ($chartTh.length > 0) utils.syncHeights($chartTh);\n let $chartImgContainer = $('.chart-product-th .image-container');\n if ($chartImgContainer.length > 0) utils.syncHeights($chartImgContainer);\n $('.family-chart-body').slick({\n mobileFirst: true,\n arrows: false,\n infinite: false,\n slidesToShow: 1,\n slidesToScroll: 1,\n centerMode: true,\n centerPadding: '0',\n appendDots: $('.family-chart-dots'),\n accessibility: false,\n dots: true,\n responsive: [\n {\n breakpoint: 480,\n settings: {\n slidesToShow: 2,\n centerMode: false,\n variableWidth: true\n }\n },\n {\n breakpoint: 921,\n settings: {\n slidesToShow: 3,\n centerMode: false,\n variableWidth: true\n }\n },\n {\n breakpoint: 1025,\n settings: {\n slidesToShow: 4,\n centerMode: false,\n variableWidth: true\n }\n },\n {\n breakpoint: 1026,\n settings: {\n slidesToShow: 5,\n centerMode: false,\n variableWidth: true\n }\n }\n ]\n });\n};\n\nvar pdProductTiles = {\n initTiles: function () {\n $(window).on('load', function () {\n // check for duplicates on same page\n let $powerreivews = $('[data-pwr-itemid]');\n let missedReviews = [];\n for (let i = 0; i < $powerreivews.length; i++) {\n let powerreivew = $powerreivews[i];\n if ($(powerreivew).find('.pr-category-snippet').length <= 0) {\n missedReviews.push(powerreivew);\n }\n }\n for (let j = 0; j < missedReviews.length; j++) {\n let $missedReview = $(missedReviews[j]);\n let $review = $('[data-pwr-itemid=' + $missedReview.data('pwr-itemid') + ']').find('.pr-category-snippet').closest('.p-w-r');\n let $powerreivew = $missedReview.find('.p-w-r');\n $powerreivew.html($review.children().clone());\n }\n });\n },\n initJumpTiles: function () {\n if ($('.product-jump-tile').length > 0) {\n // preemptive synching\n pdProductTiles.synchTileElements();\n $(window).on('resize', function () {\n pdProductTiles.synchTileElements();\n });\n\n // Sync heights of tile Power Reviews and Affirm element; the 3rd party scripts that inject these elements are deferred to improve performance\n let $powerreivewsEl = $('.product-jump-tile .p-w-r');\n utils.mutationObserver($powerreivewsEl, function () {\n pdProductTiles.synchTileElements();\n });\n let $affirmEl = $('.product-jump-tile .tile-affirm');\n utils.affirmObserver($affirmEl, function () {\n pdProductTiles.synchTileElements();\n });\n }\n },\n synchTileElements: synchTileElements,\n /**\n * @function\n * @desc Inits the Product Detail Tile Use it With replenishment products drawer handling\n */\n initDetailTiles: function () {\n var base = require('org/product/base');\n var carouselInit = require('pagedesigner/experience/carousels');\n base.carouselInit();\n carouselInit.pdReplenishmentCaro();\n // handle slider inside drawer\n let $drawerParent = $('.replenishment-products-slider');\n let $drawerContent = $drawerParent.find('.collapse');\n $drawerContent.each(function () {\n $(this).on('show.bs.collapse', function () {\n // hidden carousels\n var $slider = $(this).find('.slick-initialized');\n if ($slider.length > 0) {\n // hide the slider until it is init'd properly\n $slider.css('opacity', 0);\n // destroy and reinit slider\n $slider.slick('unslick');\n carouselInit.pdReplenishmentCaro();\n // show the slider\n $slider.css('opacity', 1);\n }\n });\n });\n }\n\n};\n\nmodule.exports = pdProductTiles;\n","'use strict';\nvar utils = require('pagedesigner/util/utils');\nvar _ = require('lodash');\nconst $header = $('.sticky-header');\nconst $stickyCTABanner = $('.sticky-banner');\nlet lastTop = 0;\n\n// When the user scrolls to the threshold value, the header slides up and sticky CTA banner slides down\n// When the user scrolls up on a given page on the site, the header will reappear.\n// Used this method: https://medium.com/@mariusc23/hide-header-on-scroll-down-show-on-scroll-up-67bbaae9a78c\n\n/**\n * handle scroll\n * @param {number} lastTop - last top position\n */\nfunction hasScrolled() {\n const scrollTriggerMobile = $stickyCTABanner.data('mobile-threshold') || 100;\n const scrollTriggerDesktop = $stickyCTABanner.data('desktop-threshold') || 100;\n\n // get top scroll position\n let scrollTop = $(window).scrollTop();\n\n // If we have not scrolled past the threshold, do nothing\n if (utils.mediaBreakpointDown('md') && Math.abs(lastTop - scrollTop) <= scrollTriggerMobile) {\n return;\n }\n\n if (utils.mediaBreakpointUp('lg') && Math.abs(lastTop - scrollTop) <= scrollTriggerDesktop) {\n return;\n }\n\n // If current position > last position AND scrolled past trigger threshold (viewport based)\n if (scrollTop > lastTop && ((utils.mediaBreakpointDown('md') && scrollTop > scrollTriggerMobile) || (utils.mediaBreakpointUp('lg') && scrollTop > scrollTriggerDesktop))) {\n // hide the nav, bring sticky buy bar up\n $header.addClass('move-up');\n $stickyCTABanner.removeClass('move-up');\n } else if (scrollTop < lastTop && $header.hasClass('move-up')) {\n // if the pages is scrolled up, reset the banner\n $header.removeClass('move-up');\n $stickyCTABanner.addClass('move-up');\n }\n\n lastTop = scrollTop;\n}\n/**\n * Update positioning of mobile ATC\n */\nfunction updateMobileAtc() {\n var $stickyAtcContainer = $('.sticky-atc-container');\n if (window.scrollY > 75) {\n $($stickyAtcContainer).css({\n top: 0,\n position: 'fixed'\n });\n $('#nav-display-control .icon-icon-chevron-up-white:visible').click();\n } else {\n $($stickyAtcContainer).css({\n position: 'relative'\n });\n }\n}\n/**\n * Toggle visibility of product navigation\n */\nfunction toggleProductNav() {\n $('.product-nav').slideToggle(400);\n $('#nav-display-control .icon-icon-chevron-down-white, #nav-display-control .icon-icon-chevron-up-white').toggle();\n if ($('#nav-display-control .icon-icon-chevron-up-white:visible').length > 0) {\n $('#nav-display-control').attr('aria-label', 'Contract product navigation menu');\n } else {\n $('#nav-display-control').attr('aria-label', 'Expand product navigation menu');\n }\n}\n\nvar pdStickyComponents = {\n /**\n * @function\n * @desc Makes sure the page has proper top padding\n */\n initStickyHeader: function () {\n let headerHeight = $header.outerHeight();\n $header.closest('.storepage').css('padding-top', headerHeight);\n },\n /**\n * @function\n * @desc Inits the Sticky CTA Payment Banner\n */\n initBanner: function () {\n $(window).on('scroll', _.throttle(function () {\n hasScrolled(lastTop);\n }, 250));\n\n // set strike through price color\n let $strikePrice = $stickyCTABanner.find('.strike-through');\n if ($strikePrice.length > 0) {\n let strikeColor = $('[data-strike-color]').data('strike-color');\n $strikePrice.find('.value').addClass(strikeColor);\n }\n },\n /**\n * Calculate position of mobile sticky atc below header, account for mobile search open/close\n */\n stickyAtcEvents: function () {\n document.addEventListener('scroll', function () {\n updateMobileAtc();\n });\n $('.search-glass-interactive').click(function () {\n updateMobileAtc();\n });\n $('#nav-display-control').click(function () {\n toggleProductNav();\n });\n }\n\n};\n\nmodule.exports = pdStickyComponents;\n","'use strict';\nvar processInclude = require('base/util');\n\nmodule.exports = {\n initEvents: function () {\n // events will be init'd on load\n let $tabParent = $('.tabbed-layout');\n let $tabEls = $tabParent.find('a[data-toggle=\"tab\"]');\n $tabEls.each(function () {\n $(this).on('show.bs.tab', function (e) {\n let $tabID = $(e.target).attr('href');\n let $tabContent = $tabParent.find($tabID);\n\n // hidden carousels\n var $slider = $tabContent.find('.slick-initialized');\n if ($slider.length > 0) {\n // hide the slider until it is init'd properly\n $slider.css('opacity', 0);\n // destroy and reinit slider\n $slider.slick('unslick');\n processInclude(require('pagedesigner/experience/carousels'));\n // show the slider\n $slider.css('opacity', 1);\n }\n });\n });\n }\n};\n","'use strict';\n\nvar baseUtils = require('org/util/utils');\nvar _ = require('lodash');\n\nvar utils = $.extend({}, baseUtils, {\n /**\n * @function\n * @desc Syncs the heights of all elements passed\n * @param {jQuery} $elements - elements to sync\n */\n syncHeights: function ($elements) {\n // clear any inline height styles\n _.each($elements, function (el) {\n $(el).css('height', '');\n });\n var $tallestEl = _.maxBy($elements, function (el) { return $(el).height(); });\n _.each($elements, function (el) {\n $(el).css('height', $tallestEl.offsetHeight);\n });\n },\n\n /**\n * @function\n * @description Executes a callback function when the user has stopped resizing the screen.\n * It will only execute on changes to page width.\n * @param {function} callback - Callback function for the resize event\n */\n smartResize: function (callback) {\n var windowWidth = $(window).innerWidth();\n\n $(window).resize(_.debounce(function () {\n if (windowWidth !== $(window).innerWidth()) {\n callback();\n windowWidth = $(window).innerWidth();\n }\n }, 100));\n },\n\n /**\n * mutationObserver - Einstein Observer\n * Initialize an observer for adding a callback to Einstein DOM element injection.\n * @param {jQuery} $elements - jQuery element(s) to observe for changes\n * @param {function} fn - the callback for when changes are found\n */\n mutationObserver: function ($elements, fn) {\n var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;\n var einsteinSelector = '[id^=\"cq_recomm_slot\"]';\n var check = function (mutationList, observer) {\n if (mutationList.length) {\n // if new child detected, disconnect observer and call callback\n observer.disconnect();\n fn.call();\n }\n };\n\n // check each element for child\n $elements.each(function (index, element) {\n var einsteinElement = element.querySelector(einsteinSelector);\n if (einsteinElement === null) {\n // not an einstein recommender slot\n if (element.childElementCount > 0) {\n // if child, call callback immediately\n fn.call();\n } // TO DO: THERE'S NO CONDITION HERE TO HANDLE NON-EINSTEIN ELEMENTS WHERE CHILD ELEMENTS ARE INSERTED AFTER A DELAY\n } else if (einsteinElement.childElementCount > 0) {\n // if child, call callback immediately\n fn.call();\n } else {\n // if no child, attach observer and wait for child to be added\n var observer = new MutationObserver(check);\n observer.observe(einsteinElement, { childList: true });\n }\n });\n },\n\n /**\n * affirmObserver - Affirm Observer\n * Initialize an observer for adding a callback to Affirm DOM element injection.\n * @param {jQuery} $elements - jQuery element(s) to observe for changes\n * @param {function} fn - the callback for when changes are found\n */\n affirmObserver: function ($elements, fn) {\n // eslint-disable-next-line require-jsdoc\n function affirmMutations(mutations, observer) {\n for (let mutation of mutations) {\n if (mutation.target.classList.contains('affirm-as-low-as')) observer.disconnect();\n fn.call();\n }\n }\n $elements.each(function (index, element) {\n var observer = new MutationObserver(affirmMutations);\n observer.observe(element, { characterData: true, subtree: true, childList: true });\n });\n },\n\n /**\n * @function\n * @description adds values to resource strings\n * @returns {string} s - the string\n * Taken from SiteGenesis\n */\n stringFormat: function () {\n var s = arguments[0];\n var i;\n var len = arguments.length - 1;\n for (i = 0; i < len; i++) {\n var reg = new RegExp('\\\\{' + i + '\\\\}', 'gm');\n s = s.replace(reg, arguments[i + 1]);\n }\n return s;\n },\n\n /**\n * Checks if an element is in viewport\n * https://stackoverflow.com/a/26831113\n * @param {jQuery} $el - the target element\n * @returns {boolean} - if the element in viewport\n */\n isInViewport: function ($el) {\n var elHeight = $el.outerHeight();\n var windowHeight = $(window).height();\n var relativeSize = $el[0].getBoundingClientRect();\n var top = relativeSize.top;\n var bottom = relativeSize.bottom;\n\n var pxInViewport = Math.max(0, (top > 0) ? Math.min(elHeight, windowHeight - top) :\n Math.min(bottom, windowHeight));\n\n return (pxInViewport > 0);\n },\n\n /**\n * IE11 fallback for object-fit CSS usage\n */\n ieObjectFitFix: function () {\n /**\n * Get the browser\n * @return {boolean} browser check\n */\n function msieversion() {\n var ua = window.navigator.userAgent;\n var msie = ua.indexOf('MSIE ');\n\n // If Internet Explorer, return version number\n if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv:11\\./)) {\n return true;\n }\n return false;\n }\n\n if (msieversion()) {\n $('.article-feature-card').addClass('object-fit-fix');\n $('.photo-tile-figure').addClass('object-fit-fix');\n $('.video-component').addClass('object-fit-fix');\n }\n }\n});\n\nmodule.exports = utils;\n","'use strict';\n\n/**\n * POST commands to YouTube or Vimeo API\n * @param {string} player - Player selector\n * @param {string} command - Command to player\n */\nvar postMessageToPlayer = function (player, command) {\n if (player == null || command == null) return;\n var content = player.contentWindow;\n content.postMessage(JSON.stringify(command), '*');\n};\n\n/**\n * Play or Pause video when the slide is changing\n * @param {string} slide - the slide to handle\n * @param {string} control - Video host\n */\nvar playPauseVideo = function (slide, control) {\n var $currentSlide = $(slide);\n var $videoComponent = $($currentSlide).find('.video-component');\n // only handle Video slides\n if ($videoComponent.length === 0) {\n return;\n }\n // get the video slide type\n var slideType = $($currentSlide).find('.video-component').attr('class').split(' ')[0].trim();\n\n var videoCommand = {\n vimeo: {\n play: function () {\n var player = $currentSlide.find('iframe').get(0);\n var startTime = $currentSlide.data('video-start');\n if ((startTime != null && startTime > 0) && !$currentSlide.hasClass('started')) {\n $currentSlide.addClass('started');\n postMessageToPlayer(player, {\n method: 'setCurrentTime',\n value: startTime\n });\n }\n postMessageToPlayer(player, {\n method: 'play',\n value: 1\n });\n $videoComponent.removeClass('paused');\n },\n pause: function () {\n var player = $currentSlide.find('iframe').get(0);\n postMessageToPlayer(player, {\n method: 'pause',\n value: 1\n });\n $videoComponent.addClass('paused');\n }\n },\n youtube: {\n play: function () {\n var player = $currentSlide.find('iframe').get(0);\n postMessageToPlayer(player, {\n event: 'command',\n func: 'playVideo'\n });\n $videoComponent.removeClass('paused');\n },\n pause: function () {\n var player = $currentSlide.find('iframe').get(0);\n postMessageToPlayer(player, {\n event: 'command',\n func: 'pauseVideo'\n });\n $videoComponent.addClass('paused');\n }\n },\n video: {\n play: function () {\n var video = $currentSlide.find('video').get(0);\n if (video != null) {\n video.play();\n }\n $videoComponent.removeClass('paused');\n },\n pause: function () {\n var video = $currentSlide.find('video').get(0);\n if (video != null) {\n video.pause();\n }\n $videoComponent.addClass('paused');\n }\n }\n };\n\n if (slideType !== null && slideType in videoCommand) {\n videoCommand[slideType][control]();\n }\n};\n\n/**\n * Initialize video controls for slick slider videos\n * @param {string} slideWrapper - Slick slider to target\n */\nvar videoControlsInit = function (slideWrapper) {\n // Initialize\n // auto pause all slides in case any videos with sound are included.\n var $filteredSlides = $(slideWrapper).find('.slick-slide').filter(':not(.slick-current)');\n $filteredSlides.each(function (idx) {\n playPauseVideo($filteredSlides[idx], 'pause');\n });\n\n // play the first slide\n var $currentSlide = slideWrapper.find('.slick-current');\n playPauseVideo($currentSlide, 'play');\n\n slideWrapper.on('click', function (slick) {\n $currentSlide = $(slick.currentTarget);\n if ($currentSlide.find('.video-component').hasClass('paused')) {\n playPauseVideo($currentSlide, 'play');\n } else {\n playPauseVideo($currentSlide, 'pause');\n }\n });\n slideWrapper.on('beforeChange', function (event, slick) {\n var slickBefore = $(slick.$slides[slick.currentSlide]);\n playPauseVideo(slickBefore, 'pause');\n });\n slideWrapper.on('afterChange', function (event, slick) {\n var slickAfter = $(slick.$slides[slick.currentSlide]);\n playPauseVideo(slickAfter, 'play');\n });\n};\n\n/**\n * @param {Object} $container - the active video container (could be a modal)\n * handles poster image events\n */\nfunction initPosterImgEvents($container) {\n // hide the poster image to reveal the inline video\n // bind and unbind namespaced events to in case multiple watches\n let $videoComponent = $container.find('.video-component');\n let $posterImg = $videoComponent.find('.poster-img');\n $posterImg.off('click.poster').on('click.poster', function () {\n $posterImg.addClass('disable-poster-image');\n $videoComponent.removeClass('showButton');\n playPauseVideo($container, 'play');\n });\n}\n\n/**\n * @param {Object} $container - the active video container (could be a modal)\n * Puts the poster image back in place\n */\nfunction resetPosterImage($container) {\n // hide the poster image to reveal the inline video\n let $videoComponent = $container.find('.video-component');\n let $posterImg = $videoComponent.find('.poster-img');\n $posterImg.removeClass('disable-poster-image');\n $videoComponent.addClass('showButton');\n}\n\n\nvar videoUtils = {\n postMessageToPlayer: postMessageToPlayer,\n playPauseVideo: playPauseVideo,\n videoControlsInit: videoControlsInit,\n initPosterImgEvents: initPosterImgEvents,\n resetPosterImage: resetPosterImage\n};\n\nmodule.exports = videoUtils;\n","\"use strict\";\n\nrequire(\"core-js/es6\");\n\nrequire(\"core-js/fn/array/includes\");\n\nrequire(\"core-js/fn/string/pad-start\");\n\nrequire(\"core-js/fn/string/pad-end\");\n\nrequire(\"core-js/fn/symbol/async-iterator\");\n\nrequire(\"core-js/fn/object/get-own-property-descriptors\");\n\nrequire(\"core-js/fn/object/values\");\n\nrequire(\"core-js/fn/object/entries\");\n\nrequire(\"core-js/fn/promise/finally\");\n\nrequire(\"core-js/web\");\n\nrequire(\"regenerator-runtime/runtime\");\n\nif (global._babelPolyfill && typeof console !== \"undefined\" && console.warn) {\n console.warn(\"@babel/polyfill is loaded more than once on this page. This is probably not desirable/intended \" + \"and may have consequences if different versions of the polyfills are applied sequentially. \" + \"If you do need to load the polyfill more than once, use @babel/polyfill/noConflict \" + \"instead to bypass the warning.\");\n}\n\nglobal._babelPolyfill = true;","/**\n * Copyright (c) 2014-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n!(function(global) {\n \"use strict\";\n\n var Op = Object.prototype;\n var hasOwn = Op.hasOwnProperty;\n var undefined; // More compressible than void 0.\n var $Symbol = typeof Symbol === \"function\" ? Symbol : {};\n var iteratorSymbol = $Symbol.iterator || \"@@iterator\";\n var asyncIteratorSymbol = $Symbol.asyncIterator || \"@@asyncIterator\";\n var toStringTagSymbol = $Symbol.toStringTag || \"@@toStringTag\";\n\n var inModule = typeof module === \"object\";\n var runtime = global.regeneratorRuntime;\n if (runtime) {\n if (inModule) {\n // If regeneratorRuntime is defined globally and we're in a module,\n // make the exports object identical to regeneratorRuntime.\n module.exports = runtime;\n }\n // Don't bother evaluating the rest of this file if the runtime was\n // already defined globally.\n return;\n }\n\n // Define the runtime globally (as expected by generated code) as either\n // module.exports (if we're in a module) or a new, empty object.\n runtime = global.regeneratorRuntime = inModule ? module.exports : {};\n\n function wrap(innerFn, outerFn, self, tryLocsList) {\n // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.\n var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;\n var generator = Object.create(protoGenerator.prototype);\n var context = new Context(tryLocsList || []);\n\n // The ._invoke method unifies the implementations of the .next,\n // .throw, and .return methods.\n generator._invoke = makeInvokeMethod(innerFn, self, context);\n\n return generator;\n }\n runtime.wrap = wrap;\n\n // Try/catch helper to minimize deoptimizations. Returns a completion\n // record like context.tryEntries[i].completion. This interface could\n // have been (and was previously) designed to take a closure to be\n // invoked without arguments, but in all the cases we care about we\n // already have an existing method we want to call, so there's no need\n // to create a new function object. We can even get away with assuming\n // the method takes exactly one argument, since that happens to be true\n // in every case, so we don't have to touch the arguments object. The\n // only additional allocation required is the completion record, which\n // has a stable shape and so hopefully should be cheap to allocate.\n function tryCatch(fn, obj, arg) {\n try {\n return { type: \"normal\", arg: fn.call(obj, arg) };\n } catch (err) {\n return { type: \"throw\", arg: err };\n }\n }\n\n var GenStateSuspendedStart = \"suspendedStart\";\n var GenStateSuspendedYield = \"suspendedYield\";\n var GenStateExecuting = \"executing\";\n var GenStateCompleted = \"completed\";\n\n // Returning this object from the innerFn has the same effect as\n // breaking out of the dispatch switch statement.\n var ContinueSentinel = {};\n\n // Dummy constructor functions that we use as the .constructor and\n // .constructor.prototype properties for functions that return Generator\n // objects. For full spec compliance, you may wish to configure your\n // minifier not to mangle the names of these two functions.\n function Generator() {}\n function GeneratorFunction() {}\n function GeneratorFunctionPrototype() {}\n\n // This is a polyfill for %IteratorPrototype% for environments that\n // don't natively support it.\n var IteratorPrototype = {};\n IteratorPrototype[iteratorSymbol] = function () {\n return this;\n };\n\n var getProto = Object.getPrototypeOf;\n var NativeIteratorPrototype = getProto && getProto(getProto(values([])));\n if (NativeIteratorPrototype &&\n NativeIteratorPrototype !== Op &&\n hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {\n // This environment has a native %IteratorPrototype%; use it instead\n // of the polyfill.\n IteratorPrototype = NativeIteratorPrototype;\n }\n\n var Gp = GeneratorFunctionPrototype.prototype =\n Generator.prototype = Object.create(IteratorPrototype);\n GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;\n GeneratorFunctionPrototype.constructor = GeneratorFunction;\n GeneratorFunctionPrototype[toStringTagSymbol] =\n GeneratorFunction.displayName = \"GeneratorFunction\";\n\n // Helper for defining the .next, .throw, and .return methods of the\n // Iterator interface in terms of a single ._invoke method.\n function defineIteratorMethods(prototype) {\n [\"next\", \"throw\", \"return\"].forEach(function(method) {\n prototype[method] = function(arg) {\n return this._invoke(method, arg);\n };\n });\n }\n\n runtime.isGeneratorFunction = function(genFun) {\n var ctor = typeof genFun === \"function\" && genFun.constructor;\n return ctor\n ? ctor === GeneratorFunction ||\n // For the native GeneratorFunction constructor, the best we can\n // do is to check its .name property.\n (ctor.displayName || ctor.name) === \"GeneratorFunction\"\n : false;\n };\n\n runtime.mark = function(genFun) {\n if (Object.setPrototypeOf) {\n Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);\n } else {\n genFun.__proto__ = GeneratorFunctionPrototype;\n if (!(toStringTagSymbol in genFun)) {\n genFun[toStringTagSymbol] = \"GeneratorFunction\";\n }\n }\n genFun.prototype = Object.create(Gp);\n return genFun;\n };\n\n // Within the body of any async function, `await x` is transformed to\n // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test\n // `hasOwn.call(value, \"__await\")` to determine if the yielded value is\n // meant to be awaited.\n runtime.awrap = function(arg) {\n return { __await: arg };\n };\n\n function AsyncIterator(generator) {\n function invoke(method, arg, resolve, reject) {\n var record = tryCatch(generator[method], generator, arg);\n if (record.type === \"throw\") {\n reject(record.arg);\n } else {\n var result = record.arg;\n var value = result.value;\n if (value &&\n typeof value === \"object\" &&\n hasOwn.call(value, \"__await\")) {\n return Promise.resolve(value.__await).then(function(value) {\n invoke(\"next\", value, resolve, reject);\n }, function(err) {\n invoke(\"throw\", err, resolve, reject);\n });\n }\n\n return Promise.resolve(value).then(function(unwrapped) {\n // When a yielded Promise is resolved, its final value becomes\n // the .value of the Promise<{value,done}> result for the\n // current iteration.\n result.value = unwrapped;\n resolve(result);\n }, function(error) {\n // If a rejected Promise was yielded, throw the rejection back\n // into the async generator function so it can be handled there.\n return invoke(\"throw\", error, resolve, reject);\n });\n }\n }\n\n var previousPromise;\n\n function enqueue(method, arg) {\n function callInvokeWithMethodAndArg() {\n return new Promise(function(resolve, reject) {\n invoke(method, arg, resolve, reject);\n });\n }\n\n return previousPromise =\n // If enqueue has been called before, then we want to wait until\n // all previous Promises have been resolved before calling invoke,\n // so that results are always delivered in the correct order. If\n // enqueue has not been called before, then it is important to\n // call invoke immediately, without waiting on a callback to fire,\n // so that the async generator function has the opportunity to do\n // any necessary setup in a predictable way. This predictability\n // is why the Promise constructor synchronously invokes its\n // executor callback, and why async functions synchronously\n // execute code before the first await. Since we implement simple\n // async functions in terms of async generators, it is especially\n // important to get this right, even though it requires care.\n previousPromise ? previousPromise.then(\n callInvokeWithMethodAndArg,\n // Avoid propagating failures to Promises returned by later\n // invocations of the iterator.\n callInvokeWithMethodAndArg\n ) : callInvokeWithMethodAndArg();\n }\n\n // Define the unified helper method that is used to implement .next,\n // .throw, and .return (see defineIteratorMethods).\n this._invoke = enqueue;\n }\n\n defineIteratorMethods(AsyncIterator.prototype);\n AsyncIterator.prototype[asyncIteratorSymbol] = function () {\n return this;\n };\n runtime.AsyncIterator = AsyncIterator;\n\n // Note that simple async functions are implemented on top of\n // AsyncIterator objects; they just return a Promise for the value of\n // the final result produced by the iterator.\n runtime.async = function(innerFn, outerFn, self, tryLocsList) {\n var iter = new AsyncIterator(\n wrap(innerFn, outerFn, self, tryLocsList)\n );\n\n return runtime.isGeneratorFunction(outerFn)\n ? iter // If outerFn is a generator, return the full iterator.\n : iter.next().then(function(result) {\n return result.done ? result.value : iter.next();\n });\n };\n\n function makeInvokeMethod(innerFn, self, context) {\n var state = GenStateSuspendedStart;\n\n return function invoke(method, arg) {\n if (state === GenStateExecuting) {\n throw new Error(\"Generator is already running\");\n }\n\n if (state === GenStateCompleted) {\n if (method === \"throw\") {\n throw arg;\n }\n\n // Be forgiving, per 25.3.3.3.3 of the spec:\n // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume\n return doneResult();\n }\n\n context.method = method;\n context.arg = arg;\n\n while (true) {\n var delegate = context.delegate;\n if (delegate) {\n var delegateResult = maybeInvokeDelegate(delegate, context);\n if (delegateResult) {\n if (delegateResult === ContinueSentinel) continue;\n return delegateResult;\n }\n }\n\n if (context.method === \"next\") {\n // Setting context._sent for legacy support of Babel's\n // function.sent implementation.\n context.sent = context._sent = context.arg;\n\n } else if (context.method === \"throw\") {\n if (state === GenStateSuspendedStart) {\n state = GenStateCompleted;\n throw context.arg;\n }\n\n context.dispatchException(context.arg);\n\n } else if (context.method === \"return\") {\n context.abrupt(\"return\", context.arg);\n }\n\n state = GenStateExecuting;\n\n var record = tryCatch(innerFn, self, context);\n if (record.type === \"normal\") {\n // If an exception is thrown from innerFn, we leave state ===\n // GenStateExecuting and loop back for another invocation.\n state = context.done\n ? GenStateCompleted\n : GenStateSuspendedYield;\n\n if (record.arg === ContinueSentinel) {\n continue;\n }\n\n return {\n value: record.arg,\n done: context.done\n };\n\n } else if (record.type === \"throw\") {\n state = GenStateCompleted;\n // Dispatch the exception by looping back around to the\n // context.dispatchException(context.arg) call above.\n context.method = \"throw\";\n context.arg = record.arg;\n }\n }\n };\n }\n\n // Call delegate.iterator[context.method](context.arg) and handle the\n // result, either by returning a { value, done } result from the\n // delegate iterator, or by modifying context.method and context.arg,\n // setting context.delegate to null, and returning the ContinueSentinel.\n function maybeInvokeDelegate(delegate, context) {\n var method = delegate.iterator[context.method];\n if (method === undefined) {\n // A .throw or .return when the delegate iterator has no .throw\n // method always terminates the yield* loop.\n context.delegate = null;\n\n if (context.method === \"throw\") {\n if (delegate.iterator.return) {\n // If the delegate iterator has a return method, give it a\n // chance to clean up.\n context.method = \"return\";\n context.arg = undefined;\n maybeInvokeDelegate(delegate, context);\n\n if (context.method === \"throw\") {\n // If maybeInvokeDelegate(context) changed context.method from\n // \"return\" to \"throw\", let that override the TypeError below.\n return ContinueSentinel;\n }\n }\n\n context.method = \"throw\";\n context.arg = new TypeError(\n \"The iterator does not provide a 'throw' method\");\n }\n\n return ContinueSentinel;\n }\n\n var record = tryCatch(method, delegate.iterator, context.arg);\n\n if (record.type === \"throw\") {\n context.method = \"throw\";\n context.arg = record.arg;\n context.delegate = null;\n return ContinueSentinel;\n }\n\n var info = record.arg;\n\n if (! info) {\n context.method = \"throw\";\n context.arg = new TypeError(\"iterator result is not an object\");\n context.delegate = null;\n return ContinueSentinel;\n }\n\n if (info.done) {\n // Assign the result of the finished delegate to the temporary\n // variable specified by delegate.resultName (see delegateYield).\n context[delegate.resultName] = info.value;\n\n // Resume execution at the desired location (see delegateYield).\n context.next = delegate.nextLoc;\n\n // If context.method was \"throw\" but the delegate handled the\n // exception, let the outer generator proceed normally. If\n // context.method was \"next\", forget context.arg since it has been\n // \"consumed\" by the delegate iterator. If context.method was\n // \"return\", allow the original .return call to continue in the\n // outer generator.\n if (context.method !== \"return\") {\n context.method = \"next\";\n context.arg = undefined;\n }\n\n } else {\n // Re-yield the result returned by the delegate method.\n return info;\n }\n\n // The delegate iterator is finished, so forget it and continue with\n // the outer generator.\n context.delegate = null;\n return ContinueSentinel;\n }\n\n // Define Generator.prototype.{next,throw,return} in terms of the\n // unified ._invoke helper method.\n defineIteratorMethods(Gp);\n\n Gp[toStringTagSymbol] = \"Generator\";\n\n // A Generator should always return itself as the iterator object when the\n // @@iterator function is called on it. Some browsers' implementations of the\n // iterator prototype chain incorrectly implement this, causing the Generator\n // object to not be returned from this call. This ensures that doesn't happen.\n // See https://github.com/facebook/regenerator/issues/274 for more details.\n Gp[iteratorSymbol] = function() {\n return this;\n };\n\n Gp.toString = function() {\n return \"[object Generator]\";\n };\n\n function pushTryEntry(locs) {\n var entry = { tryLoc: locs[0] };\n\n if (1 in locs) {\n entry.catchLoc = locs[1];\n }\n\n if (2 in locs) {\n entry.finallyLoc = locs[2];\n entry.afterLoc = locs[3];\n }\n\n this.tryEntries.push(entry);\n }\n\n function resetTryEntry(entry) {\n var record = entry.completion || {};\n record.type = \"normal\";\n delete record.arg;\n entry.completion = record;\n }\n\n function Context(tryLocsList) {\n // The root entry object (effectively a try statement without a catch\n // or a finally block) gives us a place to store values thrown from\n // locations where there is no enclosing try statement.\n this.tryEntries = [{ tryLoc: \"root\" }];\n tryLocsList.forEach(pushTryEntry, this);\n this.reset(true);\n }\n\n runtime.keys = function(object) {\n var keys = [];\n for (var key in object) {\n keys.push(key);\n }\n keys.reverse();\n\n // Rather than returning an object with a next method, we keep\n // things simple and return the next function itself.\n return function next() {\n while (keys.length) {\n var key = keys.pop();\n if (key in object) {\n next.value = key;\n next.done = false;\n return next;\n }\n }\n\n // To avoid creating an additional object, we just hang the .value\n // and .done properties off the next function object itself. This\n // also ensures that the minifier will not anonymize the function.\n next.done = true;\n return next;\n };\n };\n\n function values(iterable) {\n if (iterable) {\n var iteratorMethod = iterable[iteratorSymbol];\n if (iteratorMethod) {\n return iteratorMethod.call(iterable);\n }\n\n if (typeof iterable.next === \"function\") {\n return iterable;\n }\n\n if (!isNaN(iterable.length)) {\n var i = -1, next = function next() {\n while (++i < iterable.length) {\n if (hasOwn.call(iterable, i)) {\n next.value = iterable[i];\n next.done = false;\n return next;\n }\n }\n\n next.value = undefined;\n next.done = true;\n\n return next;\n };\n\n return next.next = next;\n }\n }\n\n // Return an iterator with no values.\n return { next: doneResult };\n }\n runtime.values = values;\n\n function doneResult() {\n return { value: undefined, done: true };\n }\n\n Context.prototype = {\n constructor: Context,\n\n reset: function(skipTempReset) {\n this.prev = 0;\n this.next = 0;\n // Resetting context._sent for legacy support of Babel's\n // function.sent implementation.\n this.sent = this._sent = undefined;\n this.done = false;\n this.delegate = null;\n\n this.method = \"next\";\n this.arg = undefined;\n\n this.tryEntries.forEach(resetTryEntry);\n\n if (!skipTempReset) {\n for (var name in this) {\n // Not sure about the optimal order of these conditions:\n if (name.charAt(0) === \"t\" &&\n hasOwn.call(this, name) &&\n !isNaN(+name.slice(1))) {\n this[name] = undefined;\n }\n }\n }\n },\n\n stop: function() {\n this.done = true;\n\n var rootEntry = this.tryEntries[0];\n var rootRecord = rootEntry.completion;\n if (rootRecord.type === \"throw\") {\n throw rootRecord.arg;\n }\n\n return this.rval;\n },\n\n dispatchException: function(exception) {\n if (this.done) {\n throw exception;\n }\n\n var context = this;\n function handle(loc, caught) {\n record.type = \"throw\";\n record.arg = exception;\n context.next = loc;\n\n if (caught) {\n // If the dispatched exception was caught by a catch block,\n // then let that catch block handle the exception normally.\n context.method = \"next\";\n context.arg = undefined;\n }\n\n return !! caught;\n }\n\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n var record = entry.completion;\n\n if (entry.tryLoc === \"root\") {\n // Exception thrown outside of any try block that could handle\n // it, so set the completion value of the entire function to\n // throw the exception.\n return handle(\"end\");\n }\n\n if (entry.tryLoc <= this.prev) {\n var hasCatch = hasOwn.call(entry, \"catchLoc\");\n var hasFinally = hasOwn.call(entry, \"finallyLoc\");\n\n if (hasCatch && hasFinally) {\n if (this.prev < entry.catchLoc) {\n return handle(entry.catchLoc, true);\n } else if (this.prev < entry.finallyLoc) {\n return handle(entry.finallyLoc);\n }\n\n } else if (hasCatch) {\n if (this.prev < entry.catchLoc) {\n return handle(entry.catchLoc, true);\n }\n\n } else if (hasFinally) {\n if (this.prev < entry.finallyLoc) {\n return handle(entry.finallyLoc);\n }\n\n } else {\n throw new Error(\"try statement without catch or finally\");\n }\n }\n }\n },\n\n abrupt: function(type, arg) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.tryLoc <= this.prev &&\n hasOwn.call(entry, \"finallyLoc\") &&\n this.prev < entry.finallyLoc) {\n var finallyEntry = entry;\n break;\n }\n }\n\n if (finallyEntry &&\n (type === \"break\" ||\n type === \"continue\") &&\n finallyEntry.tryLoc <= arg &&\n arg <= finallyEntry.finallyLoc) {\n // Ignore the finally entry if control is not jumping to a\n // location outside the try/catch block.\n finallyEntry = null;\n }\n\n var record = finallyEntry ? finallyEntry.completion : {};\n record.type = type;\n record.arg = arg;\n\n if (finallyEntry) {\n this.method = \"next\";\n this.next = finallyEntry.finallyLoc;\n return ContinueSentinel;\n }\n\n return this.complete(record);\n },\n\n complete: function(record, afterLoc) {\n if (record.type === \"throw\") {\n throw record.arg;\n }\n\n if (record.type === \"break\" ||\n record.type === \"continue\") {\n this.next = record.arg;\n } else if (record.type === \"return\") {\n this.rval = this.arg = record.arg;\n this.method = \"return\";\n this.next = \"end\";\n } else if (record.type === \"normal\" && afterLoc) {\n this.next = afterLoc;\n }\n\n return ContinueSentinel;\n },\n\n finish: function(finallyLoc) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.finallyLoc === finallyLoc) {\n this.complete(entry.completion, entry.afterLoc);\n resetTryEntry(entry);\n return ContinueSentinel;\n }\n }\n },\n\n \"catch\": function(tryLoc) {\n for (var i = this.tryEntries.length - 1; i >= 0; --i) {\n var entry = this.tryEntries[i];\n if (entry.tryLoc === tryLoc) {\n var record = entry.completion;\n if (record.type === \"throw\") {\n var thrown = record.arg;\n resetTryEntry(entry);\n }\n return thrown;\n }\n }\n\n // The context.catch method must only be called with a location\n // argument that corresponds to a known catch block.\n throw new Error(\"illegal catch attempt\");\n },\n\n delegateYield: function(iterable, resultName, nextLoc) {\n this.delegate = {\n iterator: values(iterable),\n resultName: resultName,\n nextLoc: nextLoc\n };\n\n if (this.method === \"next\") {\n // Deliberately forget the last sent value so that we don't\n // accidentally pass it on to the delegate.\n this.arg = undefined;\n }\n\n return ContinueSentinel;\n }\n };\n})(\n // In sloppy mode, unbound `this` refers to the global object, fallback to\n // Function constructor if we're in global strict mode. That is sadly a form\n // of indirect eval which violates Content Security Policy.\n (function() {\n return this || (typeof self === \"object\" && self);\n })() || Function(\"return this\")()\n);\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.6.2): alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * Constants\n */\n\nconst NAME = 'alert'\nconst VERSION = '4.6.2'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst CLASS_NAME_ALERT = 'alert'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\n\nconst EVENT_CLOSE = `close${EVENT_KEY}`\nconst EVENT_CLOSED = `closed${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst SELECTOR_DISMISS = '[data-dismiss=\"alert\"]'\n\n/**\n * Class definition\n */\n\nclass Alert {\n constructor(element) {\n this._element = element\n }\n\n // Getters\n static get VERSION() {\n return VERSION\n }\n\n // Public\n close(element) {\n let rootElement = this._element\n if (element) {\n rootElement = this._getRootElement(element)\n }\n\n const customEvent = this._triggerCloseEvent(rootElement)\n\n if (customEvent.isDefaultPrevented()) {\n return\n }\n\n this._removeElement(rootElement)\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n this._element = null\n }\n\n // Private\n _getRootElement(element) {\n const selector = Util.getSelectorFromElement(element)\n let parent = false\n\n if (selector) {\n parent = document.querySelector(selector)\n }\n\n if (!parent) {\n parent = $(element).closest(`.${CLASS_NAME_ALERT}`)[0]\n }\n\n return parent\n }\n\n _triggerCloseEvent(element) {\n const closeEvent = $.Event(EVENT_CLOSE)\n\n $(element).trigger(closeEvent)\n return closeEvent\n }\n\n _removeElement(element) {\n $(element).removeClass(CLASS_NAME_SHOW)\n\n if (!$(element).hasClass(CLASS_NAME_FADE)) {\n this._destroyElement(element)\n return\n }\n\n const transitionDuration = Util.getTransitionDurationFromElement(element)\n\n $(element)\n .one(Util.TRANSITION_END, event => this._destroyElement(element, event))\n .emulateTransitionEnd(transitionDuration)\n }\n\n _destroyElement(element) {\n $(element)\n .detach()\n .trigger(EVENT_CLOSED)\n .remove()\n }\n\n // Static\n static _jQueryInterface(config) {\n return this.each(function () {\n const $element = $(this)\n let data = $element.data(DATA_KEY)\n\n if (!data) {\n data = new Alert(this)\n $element.data(DATA_KEY, data)\n }\n\n if (config === 'close') {\n data[config](this)\n }\n })\n }\n\n static _handleDismiss(alertInstance) {\n return function (event) {\n if (event) {\n event.preventDefault()\n }\n\n alertInstance.close(this)\n }\n }\n}\n\n/**\n * Data API implementation\n */\n\n$(document).on(\n EVENT_CLICK_DATA_API,\n SELECTOR_DISMISS,\n Alert._handleDismiss(new Alert())\n)\n\n/**\n * jQuery\n */\n\n$.fn[NAME] = Alert._jQueryInterface\n$.fn[NAME].Constructor = Alert\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Alert._jQueryInterface\n}\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.6.2): carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * Constants\n */\n\nconst NAME = 'carousel'\nconst VERSION = '4.6.2'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key\nconst ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\nconst SWIPE_THRESHOLD = 40\n\nconst CLASS_NAME_CAROUSEL = 'carousel'\nconst CLASS_NAME_ACTIVE = 'active'\nconst CLASS_NAME_SLIDE = 'slide'\nconst CLASS_NAME_RIGHT = 'carousel-item-right'\nconst CLASS_NAME_LEFT = 'carousel-item-left'\nconst CLASS_NAME_NEXT = 'carousel-item-next'\nconst CLASS_NAME_PREV = 'carousel-item-prev'\nconst CLASS_NAME_POINTER_EVENT = 'pointer-event'\n\nconst DIRECTION_NEXT = 'next'\nconst DIRECTION_PREV = 'prev'\nconst DIRECTION_LEFT = 'left'\nconst DIRECTION_RIGHT = 'right'\n\nconst EVENT_SLIDE = `slide${EVENT_KEY}`\nconst EVENT_SLID = `slid${EVENT_KEY}`\nconst EVENT_KEYDOWN = `keydown${EVENT_KEY}`\nconst EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`\nconst EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY}`\nconst EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`\nconst EVENT_TOUCHMOVE = `touchmove${EVENT_KEY}`\nconst EVENT_TOUCHEND = `touchend${EVENT_KEY}`\nconst EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`\nconst EVENT_POINTERUP = `pointerup${EVENT_KEY}`\nconst EVENT_DRAG_START = `dragstart${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst SELECTOR_ACTIVE = '.active'\nconst SELECTOR_ACTIVE_ITEM = '.active.carousel-item'\nconst SELECTOR_ITEM = '.carousel-item'\nconst SELECTOR_ITEM_IMG = '.carousel-item img'\nconst SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'\nconst SELECTOR_INDICATORS = '.carousel-indicators'\nconst SELECTOR_DATA_SLIDE = '[data-slide], [data-slide-to]'\nconst SELECTOR_DATA_RIDE = '[data-ride=\"carousel\"]'\n\nconst Default = {\n interval: 5000,\n keyboard: true,\n slide: false,\n pause: 'hover',\n wrap: true,\n touch: true\n}\n\nconst DefaultType = {\n interval: '(number|boolean)',\n keyboard: 'boolean',\n slide: '(boolean|string)',\n pause: '(string|boolean)',\n wrap: 'boolean',\n touch: 'boolean'\n}\n\nconst PointerType = {\n TOUCH: 'touch',\n PEN: 'pen'\n}\n\n/**\n * Class definition\n */\n\nclass Carousel {\n constructor(element, config) {\n this._items = null\n this._interval = null\n this._activeElement = null\n this._isPaused = false\n this._isSliding = false\n this.touchTimeout = null\n this.touchStartX = 0\n this.touchDeltaX = 0\n\n this._config = this._getConfig(config)\n this._element = element\n this._indicatorsElement = this._element.querySelector(SELECTOR_INDICATORS)\n this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent)\n\n this._addEventListeners()\n }\n\n // Getters\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n next() {\n if (!this._isSliding) {\n this._slide(DIRECTION_NEXT)\n }\n }\n\n nextWhenVisible() {\n const $element = $(this._element)\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden &&\n ($element.is(':visible') && $element.css('visibility') !== 'hidden')) {\n this.next()\n }\n }\n\n prev() {\n if (!this._isSliding) {\n this._slide(DIRECTION_PREV)\n }\n }\n\n pause(event) {\n if (!event) {\n this._isPaused = true\n }\n\n if (this._element.querySelector(SELECTOR_NEXT_PREV)) {\n Util.triggerTransitionEnd(this._element)\n this.cycle(true)\n }\n\n clearInterval(this._interval)\n this._interval = null\n }\n\n cycle(event) {\n if (!event) {\n this._isPaused = false\n }\n\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n\n if (this._config.interval && !this._isPaused) {\n this._updateInterval()\n\n this._interval = setInterval(\n (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),\n this._config.interval\n )\n }\n }\n\n to(index) {\n this._activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM)\n\n const activeIndex = this._getItemIndex(this._activeElement)\n\n if (index > this._items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n $(this._element).one(EVENT_SLID, () => this.to(index))\n return\n }\n\n if (activeIndex === index) {\n this.pause()\n this.cycle()\n return\n }\n\n const direction = index > activeIndex ?\n DIRECTION_NEXT :\n DIRECTION_PREV\n\n this._slide(direction, this._items[index])\n }\n\n dispose() {\n $(this._element).off(EVENT_KEY)\n $.removeData(this._element, DATA_KEY)\n\n this._items = null\n this._config = null\n this._element = null\n this._interval = null\n this._isPaused = null\n this._isSliding = null\n this._activeElement = null\n this._indicatorsElement = null\n }\n\n // Private\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _handleSwipe() {\n const absDeltax = Math.abs(this.touchDeltaX)\n\n if (absDeltax <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltax / this.touchDeltaX\n\n this.touchDeltaX = 0\n\n // swipe left\n if (direction > 0) {\n this.prev()\n }\n\n // swipe right\n if (direction < 0) {\n this.next()\n }\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n $(this._element).on(EVENT_KEYDOWN, event => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n $(this._element)\n .on(EVENT_MOUSEENTER, event => this.pause(event))\n .on(EVENT_MOUSELEAVE, event => this.cycle(event))\n }\n\n if (this._config.touch) {\n this._addTouchEventListeners()\n }\n }\n\n _addTouchEventListeners() {\n if (!this._touchSupported) {\n return\n }\n\n const start = event => {\n if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {\n this.touchStartX = event.originalEvent.clientX\n } else if (!this._pointerEvent) {\n this.touchStartX = event.originalEvent.touches[0].clientX\n }\n }\n\n const move = event => {\n // ensure swiping with one touch and not pinching\n this.touchDeltaX = event.originalEvent.touches && event.originalEvent.touches.length > 1 ?\n 0 :\n event.originalEvent.touches[0].clientX - this.touchStartX\n }\n\n const end = event => {\n if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {\n this.touchDeltaX = event.originalEvent.clientX - this.touchStartX\n }\n\n this._handleSwipe()\n if (this._config.pause === 'hover') {\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n\n this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n }\n\n $(this._element.querySelectorAll(SELECTOR_ITEM_IMG))\n .on(EVENT_DRAG_START, e => e.preventDefault())\n\n if (this._pointerEvent) {\n $(this._element).on(EVENT_POINTERDOWN, event => start(event))\n $(this._element).on(EVENT_POINTERUP, event => end(event))\n\n this._element.classList.add(CLASS_NAME_POINTER_EVENT)\n } else {\n $(this._element).on(EVENT_TOUCHSTART, event => start(event))\n $(this._element).on(EVENT_TOUCHMOVE, event => move(event))\n $(this._element).on(EVENT_TOUCHEND, event => end(event))\n }\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n switch (event.which) {\n case ARROW_LEFT_KEYCODE:\n event.preventDefault()\n this.prev()\n break\n case ARROW_RIGHT_KEYCODE:\n event.preventDefault()\n this.next()\n break\n default:\n }\n }\n\n _getItemIndex(element) {\n this._items = element && element.parentNode ?\n [].slice.call(element.parentNode.querySelectorAll(SELECTOR_ITEM)) :\n []\n return this._items.indexOf(element)\n }\n\n _getItemByDirection(direction, activeElement) {\n const isNextDirection = direction === DIRECTION_NEXT\n const isPrevDirection = direction === DIRECTION_PREV\n const activeIndex = this._getItemIndex(activeElement)\n const lastItemIndex = this._items.length - 1\n const isGoingToWrap = isPrevDirection && activeIndex === 0 ||\n isNextDirection && activeIndex === lastItemIndex\n\n if (isGoingToWrap && !this._config.wrap) {\n return activeElement\n }\n\n const delta = direction === DIRECTION_PREV ? -1 : 1\n const itemIndex = (activeIndex + delta) % this._items.length\n\n return itemIndex === -1 ?\n this._items[this._items.length - 1] : this._items[itemIndex]\n }\n\n _triggerSlideEvent(relatedTarget, eventDirectionName) {\n const targetIndex = this._getItemIndex(relatedTarget)\n const fromIndex = this._getItemIndex(this._element.querySelector(SELECTOR_ACTIVE_ITEM))\n const slideEvent = $.Event(EVENT_SLIDE, {\n relatedTarget,\n direction: eventDirectionName,\n from: fromIndex,\n to: targetIndex\n })\n\n $(this._element).trigger(slideEvent)\n\n return slideEvent\n }\n\n _setActiveIndicatorElement(element) {\n if (this._indicatorsElement) {\n const indicators = [].slice.call(this._indicatorsElement.querySelectorAll(SELECTOR_ACTIVE))\n $(indicators).removeClass(CLASS_NAME_ACTIVE)\n\n const nextIndicator = this._indicatorsElement.children[\n this._getItemIndex(element)\n ]\n\n if (nextIndicator) {\n $(nextIndicator).addClass(CLASS_NAME_ACTIVE)\n }\n }\n }\n\n _updateInterval() {\n const element = this._activeElement || this._element.querySelector(SELECTOR_ACTIVE_ITEM)\n\n if (!element) {\n return\n }\n\n const elementInterval = parseInt(element.getAttribute('data-interval'), 10)\n\n if (elementInterval) {\n this._config.defaultInterval = this._config.defaultInterval || this._config.interval\n this._config.interval = elementInterval\n } else {\n this._config.interval = this._config.defaultInterval || this._config.interval\n }\n }\n\n _slide(direction, element) {\n const activeElement = this._element.querySelector(SELECTOR_ACTIVE_ITEM)\n const activeElementIndex = this._getItemIndex(activeElement)\n const nextElement = element || activeElement &&\n this._getItemByDirection(direction, activeElement)\n const nextElementIndex = this._getItemIndex(nextElement)\n const isCycling = Boolean(this._interval)\n\n let directionalClassName\n let orderClassName\n let eventDirectionName\n\n if (direction === DIRECTION_NEXT) {\n directionalClassName = CLASS_NAME_LEFT\n orderClassName = CLASS_NAME_NEXT\n eventDirectionName = DIRECTION_LEFT\n } else {\n directionalClassName = CLASS_NAME_RIGHT\n orderClassName = CLASS_NAME_PREV\n eventDirectionName = DIRECTION_RIGHT\n }\n\n if (nextElement && $(nextElement).hasClass(CLASS_NAME_ACTIVE)) {\n this._isSliding = false\n return\n }\n\n const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)\n if (slideEvent.isDefaultPrevented()) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n return\n }\n\n this._isSliding = true\n\n if (isCycling) {\n this.pause()\n }\n\n this._setActiveIndicatorElement(nextElement)\n this._activeElement = nextElement\n\n const slidEvent = $.Event(EVENT_SLID, {\n relatedTarget: nextElement,\n direction: eventDirectionName,\n from: activeElementIndex,\n to: nextElementIndex\n })\n\n if ($(this._element).hasClass(CLASS_NAME_SLIDE)) {\n $(nextElement).addClass(orderClassName)\n\n Util.reflow(nextElement)\n\n $(activeElement).addClass(directionalClassName)\n $(nextElement).addClass(directionalClassName)\n\n const transitionDuration = Util.getTransitionDurationFromElement(activeElement)\n\n $(activeElement)\n .one(Util.TRANSITION_END, () => {\n $(nextElement)\n .removeClass(`${directionalClassName} ${orderClassName}`)\n .addClass(CLASS_NAME_ACTIVE)\n\n $(activeElement).removeClass(`${CLASS_NAME_ACTIVE} ${orderClassName} ${directionalClassName}`)\n\n this._isSliding = false\n\n setTimeout(() => $(this._element).trigger(slidEvent), 0)\n })\n .emulateTransitionEnd(transitionDuration)\n } else {\n $(activeElement).removeClass(CLASS_NAME_ACTIVE)\n $(nextElement).addClass(CLASS_NAME_ACTIVE)\n\n this._isSliding = false\n $(this._element).trigger(slidEvent)\n }\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n // Static\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n let _config = {\n ...Default,\n ...$(this).data()\n }\n\n if (typeof config === 'object') {\n _config = {\n ..._config,\n ...config\n }\n }\n\n const action = typeof config === 'string' ? config : _config.slide\n\n if (!data) {\n data = new Carousel(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'number') {\n data.to(config)\n } else if (typeof action === 'string') {\n if (typeof data[action] === 'undefined') {\n throw new TypeError(`No method named \"${action}\"`)\n }\n\n data[action]()\n } else if (_config.interval && _config.ride) {\n data.pause()\n data.cycle()\n }\n })\n }\n\n static _dataApiClickHandler(event) {\n const selector = Util.getSelectorFromElement(this)\n\n if (!selector) {\n return\n }\n\n const target = $(selector)[0]\n\n if (!target || !$(target).hasClass(CLASS_NAME_CAROUSEL)) {\n return\n }\n\n const config = {\n ...$(target).data(),\n ...$(this).data()\n }\n const slideIndex = this.getAttribute('data-slide-to')\n\n if (slideIndex) {\n config.interval = false\n }\n\n Carousel._jQueryInterface.call($(target), config)\n\n if (slideIndex) {\n $(target).data(DATA_KEY).to(slideIndex)\n }\n\n event.preventDefault()\n }\n}\n\n/**\n * Data API implementation\n */\n\n$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_SLIDE, Carousel._dataApiClickHandler)\n\n$(window).on(EVENT_LOAD_DATA_API, () => {\n const carousels = [].slice.call(document.querySelectorAll(SELECTOR_DATA_RIDE))\n for (let i = 0, len = carousels.length; i < len; i++) {\n const $carousel = $(carousels[i])\n Carousel._jQueryInterface.call($carousel, $carousel.data())\n }\n})\n\n/**\n * jQuery\n */\n\n$.fn[NAME] = Carousel._jQueryInterface\n$.fn[NAME].Constructor = Carousel\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Carousel._jQueryInterface\n}\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.6.2): collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * Constants\n */\n\nconst NAME = 'collapse'\nconst VERSION = '4.6.2'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_COLLAPSE = 'collapse'\nconst CLASS_NAME_COLLAPSING = 'collapsing'\nconst CLASS_NAME_COLLAPSED = 'collapsed'\n\nconst DIMENSION_WIDTH = 'width'\nconst DIMENSION_HEIGHT = 'height'\n\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst SELECTOR_ACTIVES = '.show, .collapsing'\nconst SELECTOR_DATA_TOGGLE = '[data-toggle=\"collapse\"]'\n\nconst Default = {\n toggle: true,\n parent: ''\n}\n\nconst DefaultType = {\n toggle: 'boolean',\n parent: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass Collapse {\n constructor(element, config) {\n this._isTransitioning = false\n this._element = element\n this._config = this._getConfig(config)\n this._triggerArray = [].slice.call(document.querySelectorAll(\n `[data-toggle=\"collapse\"][href=\"#${element.id}\"],` +\n `[data-toggle=\"collapse\"][data-target=\"#${element.id}\"]`\n ))\n\n const toggleList = [].slice.call(document.querySelectorAll(SELECTOR_DATA_TOGGLE))\n for (let i = 0, len = toggleList.length; i < len; i++) {\n const elem = toggleList[i]\n const selector = Util.getSelectorFromElement(elem)\n const filterElement = [].slice.call(document.querySelectorAll(selector))\n .filter(foundElem => foundElem === element)\n\n if (selector !== null && filterElement.length > 0) {\n this._selector = selector\n this._triggerArray.push(elem)\n }\n }\n\n this._parent = this._config.parent ? this._getParent() : null\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._element, this._triggerArray)\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n toggle() {\n if ($(this._element).hasClass(CLASS_NAME_SHOW)) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning ||\n $(this._element).hasClass(CLASS_NAME_SHOW)) {\n return\n }\n\n let actives\n let activesData\n\n if (this._parent) {\n actives = [].slice.call(this._parent.querySelectorAll(SELECTOR_ACTIVES))\n .filter(elem => {\n if (typeof this._config.parent === 'string') {\n return elem.getAttribute('data-parent') === this._config.parent\n }\n\n return elem.classList.contains(CLASS_NAME_COLLAPSE)\n })\n\n if (actives.length === 0) {\n actives = null\n }\n }\n\n if (actives) {\n activesData = $(actives).not(this._selector).data(DATA_KEY)\n if (activesData && activesData._isTransitioning) {\n return\n }\n }\n\n const startEvent = $.Event(EVENT_SHOW)\n $(this._element).trigger(startEvent)\n if (startEvent.isDefaultPrevented()) {\n return\n }\n\n if (actives) {\n Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide')\n if (!activesData) {\n $(actives).data(DATA_KEY, null)\n }\n }\n\n const dimension = this._getDimension()\n\n $(this._element)\n .removeClass(CLASS_NAME_COLLAPSE)\n .addClass(CLASS_NAME_COLLAPSING)\n\n this._element.style[dimension] = 0\n\n if (this._triggerArray.length) {\n $(this._triggerArray)\n .removeClass(CLASS_NAME_COLLAPSED)\n .attr('aria-expanded', true)\n }\n\n this.setTransitioning(true)\n\n const complete = () => {\n $(this._element)\n .removeClass(CLASS_NAME_COLLAPSING)\n .addClass(`${CLASS_NAME_COLLAPSE} ${CLASS_NAME_SHOW}`)\n\n this._element.style[dimension] = ''\n\n this.setTransitioning(false)\n\n $(this._element).trigger(EVENT_SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning ||\n !$(this._element).hasClass(CLASS_NAME_SHOW)) {\n return\n }\n\n const startEvent = $.Event(EVENT_HIDE)\n $(this._element).trigger(startEvent)\n if (startEvent.isDefaultPrevented()) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n Util.reflow(this._element)\n\n $(this._element)\n .addClass(CLASS_NAME_COLLAPSING)\n .removeClass(`${CLASS_NAME_COLLAPSE} ${CLASS_NAME_SHOW}`)\n\n const triggerArrayLength = this._triggerArray.length\n if (triggerArrayLength > 0) {\n for (let i = 0; i < triggerArrayLength; i++) {\n const trigger = this._triggerArray[i]\n const selector = Util.getSelectorFromElement(trigger)\n\n if (selector !== null) {\n const $elem = $([].slice.call(document.querySelectorAll(selector)))\n if (!$elem.hasClass(CLASS_NAME_SHOW)) {\n $(trigger).addClass(CLASS_NAME_COLLAPSED)\n .attr('aria-expanded', false)\n }\n }\n }\n }\n\n this.setTransitioning(true)\n\n const complete = () => {\n this.setTransitioning(false)\n $(this._element)\n .removeClass(CLASS_NAME_COLLAPSING)\n .addClass(CLASS_NAME_COLLAPSE)\n .trigger(EVENT_HIDDEN)\n }\n\n this._element.style[dimension] = ''\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n }\n\n setTransitioning(isTransitioning) {\n this._isTransitioning = isTransitioning\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n\n this._config = null\n this._parent = null\n this._element = null\n this._triggerArray = null\n this._isTransitioning = null\n }\n\n // Private\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n config.toggle = Boolean(config.toggle) // Coerce string values\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _getDimension() {\n const hasWidth = $(this._element).hasClass(DIMENSION_WIDTH)\n return hasWidth ? DIMENSION_WIDTH : DIMENSION_HEIGHT\n }\n\n _getParent() {\n let parent\n\n if (Util.isElement(this._config.parent)) {\n parent = this._config.parent\n\n // It's a jQuery object\n if (typeof this._config.parent.jquery !== 'undefined') {\n parent = this._config.parent[0]\n }\n } else {\n parent = document.querySelector(this._config.parent)\n }\n\n const selector = `[data-toggle=\"collapse\"][data-parent=\"${this._config.parent}\"]`\n const children = [].slice.call(parent.querySelectorAll(selector))\n\n $(children).each((i, element) => {\n this._addAriaAndCollapsedClass(\n Collapse._getTargetFromElement(element),\n [element]\n )\n })\n\n return parent\n }\n\n _addAriaAndCollapsedClass(element, triggerArray) {\n const isOpen = $(element).hasClass(CLASS_NAME_SHOW)\n\n if (triggerArray.length) {\n $(triggerArray)\n .toggleClass(CLASS_NAME_COLLAPSED, !isOpen)\n .attr('aria-expanded', isOpen)\n }\n }\n\n // Static\n static _getTargetFromElement(element) {\n const selector = Util.getSelectorFromElement(element)\n return selector ? document.querySelector(selector) : null\n }\n\n static _jQueryInterface(config) {\n return this.each(function () {\n const $element = $(this)\n let data = $element.data(DATA_KEY)\n const _config = {\n ...Default,\n ...$element.data(),\n ...(typeof config === 'object' && config ? config : {})\n }\n\n if (!data && _config.toggle && typeof config === 'string' && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n if (!data) {\n data = new Collapse(this, _config)\n $element.data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config]()\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\n$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.currentTarget.tagName === 'A') {\n event.preventDefault()\n }\n\n const $trigger = $(this)\n const selector = Util.getSelectorFromElement(this)\n const selectors = [].slice.call(document.querySelectorAll(selector))\n\n $(selectors).each(function () {\n const $target = $(this)\n const data = $target.data(DATA_KEY)\n const config = data ? 'toggle' : $trigger.data()\n Collapse._jQueryInterface.call($target, config)\n })\n})\n\n/**\n * jQuery\n */\n\n$.fn[NAME] = Collapse._jQueryInterface\n$.fn[NAME].Constructor = Collapse\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Collapse._jQueryInterface\n}\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.6.2): modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * Constants\n */\n\nconst NAME = 'modal'\nconst VERSION = '4.6.2'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key\n\nconst CLASS_NAME_SCROLLABLE = 'modal-dialog-scrollable'\nconst CLASS_NAME_SCROLLBAR_MEASURER = 'modal-scrollbar-measure'\nconst CLASS_NAME_BACKDROP = 'modal-backdrop'\nconst CLASS_NAME_OPEN = 'modal-open'\nconst CLASS_NAME_FADE = 'fade'\nconst CLASS_NAME_SHOW = 'show'\nconst CLASS_NAME_STATIC = 'modal-static'\n\nconst EVENT_HIDE = `hide${EVENT_KEY}`\nconst EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`\nconst EVENT_HIDDEN = `hidden${EVENT_KEY}`\nconst EVENT_SHOW = `show${EVENT_KEY}`\nconst EVENT_SHOWN = `shown${EVENT_KEY}`\nconst EVENT_FOCUSIN = `focusin${EVENT_KEY}`\nconst EVENT_RESIZE = `resize${EVENT_KEY}`\nconst EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`\nconst EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY}`\nconst EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY}`\nconst EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`\n\nconst SELECTOR_DIALOG = '.modal-dialog'\nconst SELECTOR_MODAL_BODY = '.modal-body'\nconst SELECTOR_DATA_TOGGLE = '[data-toggle=\"modal\"]'\nconst SELECTOR_DATA_DISMISS = '[data-dismiss=\"modal\"]'\nconst SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'\nconst SELECTOR_STICKY_CONTENT = '.sticky-top'\n\nconst Default = {\n backdrop: true,\n keyboard: true,\n focus: true,\n show: true\n}\n\nconst DefaultType = {\n backdrop: '(boolean|string)',\n keyboard: 'boolean',\n focus: 'boolean',\n show: 'boolean'\n}\n\n/**\n * Class definition\n */\n\nclass Modal {\n constructor(element, config) {\n this._config = this._getConfig(config)\n this._element = element\n this._dialog = element.querySelector(SELECTOR_DIALOG)\n this._backdrop = null\n this._isShown = false\n this._isBodyOverflowing = false\n this._ignoreBackdropClick = false\n this._isTransitioning = false\n this._scrollbarWidth = 0\n }\n\n // Getters\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n const showEvent = $.Event(EVENT_SHOW, {\n relatedTarget\n })\n\n $(this._element).trigger(showEvent)\n\n if (showEvent.isDefaultPrevented()) {\n return\n }\n\n this._isShown = true\n\n if ($(this._element).hasClass(CLASS_NAME_FADE)) {\n this._isTransitioning = true\n }\n\n this._checkScrollbar()\n this._setScrollbar()\n\n this._adjustDialog()\n\n this._setEscapeEvent()\n this._setResizeEvent()\n\n $(this._element).on(\n EVENT_CLICK_DISMISS,\n SELECTOR_DATA_DISMISS,\n event => this.hide(event)\n )\n\n $(this._dialog).on(EVENT_MOUSEDOWN_DISMISS, () => {\n $(this._element).one(EVENT_MOUSEUP_DISMISS, event => {\n if ($(event.target).is(this._element)) {\n this._ignoreBackdropClick = true\n }\n })\n })\n\n this._showBackdrop(() => this._showElement(relatedTarget))\n }\n\n hide(event) {\n if (event) {\n event.preventDefault()\n }\n\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = $.Event(EVENT_HIDE)\n\n $(this._element).trigger(hideEvent)\n\n if (!this._isShown || hideEvent.isDefaultPrevented()) {\n return\n }\n\n this._isShown = false\n const transition = $(this._element).hasClass(CLASS_NAME_FADE)\n\n if (transition) {\n this._isTransitioning = true\n }\n\n this._setEscapeEvent()\n this._setResizeEvent()\n\n $(document).off(EVENT_FOCUSIN)\n\n $(this._element).removeClass(CLASS_NAME_SHOW)\n\n $(this._element).off(EVENT_CLICK_DISMISS)\n $(this._dialog).off(EVENT_MOUSEDOWN_DISMISS)\n\n if (transition) {\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, event => this._hideModal(event))\n .emulateTransitionEnd(transitionDuration)\n } else {\n this._hideModal()\n }\n }\n\n dispose() {\n [window, this._element, this._dialog]\n .forEach(htmlElement => $(htmlElement).off(EVENT_KEY))\n\n /**\n * `document` has 2 events `EVENT_FOCUSIN` and `EVENT_CLICK_DATA_API`\n * Do not move `document` in `htmlElements` array\n * It will remove `EVENT_CLICK_DATA_API` event that should remain\n */\n $(document).off(EVENT_FOCUSIN)\n\n $.removeData(this._element, DATA_KEY)\n\n this._config = null\n this._element = null\n this._dialog = null\n this._backdrop = null\n this._isShown = null\n this._isBodyOverflowing = null\n this._ignoreBackdropClick = null\n this._isTransitioning = null\n this._scrollbarWidth = null\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _triggerBackdropTransition() {\n const hideEventPrevented = $.Event(EVENT_HIDE_PREVENTED)\n\n $(this._element).trigger(hideEventPrevented)\n if (hideEventPrevented.isDefaultPrevented()) {\n return\n }\n\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n\n if (!isModalOverflowing) {\n this._element.style.overflowY = 'hidden'\n }\n\n this._element.classList.add(CLASS_NAME_STATIC)\n\n const modalTransitionDuration = Util.getTransitionDurationFromElement(this._dialog)\n $(this._element).off(Util.TRANSITION_END)\n\n $(this._element).one(Util.TRANSITION_END, () => {\n this._element.classList.remove(CLASS_NAME_STATIC)\n if (!isModalOverflowing) {\n $(this._element).one(Util.TRANSITION_END, () => {\n this._element.style.overflowY = ''\n })\n .emulateTransitionEnd(this._element, modalTransitionDuration)\n }\n })\n .emulateTransitionEnd(modalTransitionDuration)\n this._element.focus()\n }\n\n _showElement(relatedTarget) {\n const transition = $(this._element).hasClass(CLASS_NAME_FADE)\n const modalBody = this._dialog ? this._dialog.querySelector(SELECTOR_MODAL_BODY) : null\n\n if (!this._element.parentNode ||\n this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {\n // Don't move modal's DOM position\n document.body.appendChild(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.setAttribute('role', 'dialog')\n\n if ($(this._dialog).hasClass(CLASS_NAME_SCROLLABLE) && modalBody) {\n modalBody.scrollTop = 0\n } else {\n this._element.scrollTop = 0\n }\n\n if (transition) {\n Util.reflow(this._element)\n }\n\n $(this._element).addClass(CLASS_NAME_SHOW)\n\n if (this._config.focus) {\n this._enforceFocus()\n }\n\n const shownEvent = $.Event(EVENT_SHOWN, {\n relatedTarget\n })\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._element.focus()\n }\n\n this._isTransitioning = false\n $(this._element).trigger(shownEvent)\n }\n\n if (transition) {\n const transitionDuration = Util.getTransitionDurationFromElement(this._dialog)\n\n $(this._dialog)\n .one(Util.TRANSITION_END, transitionComplete)\n .emulateTransitionEnd(transitionDuration)\n } else {\n transitionComplete()\n }\n }\n\n _enforceFocus() {\n $(document)\n .off(EVENT_FOCUSIN) // Guard against infinite focus loop\n .on(EVENT_FOCUSIN, event => {\n if (document !== event.target &&\n this._element !== event.target &&\n $(this._element).has(event.target).length === 0) {\n this._element.focus()\n }\n })\n }\n\n _setEscapeEvent() {\n if (this._isShown) {\n $(this._element).on(EVENT_KEYDOWN_DISMISS, event => {\n if (this._config.keyboard && event.which === ESCAPE_KEYCODE) {\n event.preventDefault()\n this.hide()\n } else if (!this._config.keyboard && event.which === ESCAPE_KEYCODE) {\n this._triggerBackdropTransition()\n }\n })\n } else if (!this._isShown) {\n $(this._element).off(EVENT_KEYDOWN_DISMISS)\n }\n }\n\n _setResizeEvent() {\n if (this._isShown) {\n $(window).on(EVENT_RESIZE, event => this.handleUpdate(event))\n } else {\n $(window).off(EVENT_RESIZE)\n }\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._element.removeAttribute('role')\n this._isTransitioning = false\n this._showBackdrop(() => {\n $(document.body).removeClass(CLASS_NAME_OPEN)\n this._resetAdjustments()\n this._resetScrollbar()\n $(this._element).trigger(EVENT_HIDDEN)\n })\n }\n\n _removeBackdrop() {\n if (this._backdrop) {\n $(this._backdrop).remove()\n this._backdrop = null\n }\n }\n\n _showBackdrop(callback) {\n const animate = $(this._element).hasClass(CLASS_NAME_FADE) ?\n CLASS_NAME_FADE : ''\n\n if (this._isShown && this._config.backdrop) {\n this._backdrop = document.createElement('div')\n this._backdrop.className = CLASS_NAME_BACKDROP\n\n if (animate) {\n this._backdrop.classList.add(animate)\n }\n\n $(this._backdrop).appendTo(document.body)\n\n $(this._element).on(EVENT_CLICK_DISMISS, event => {\n if (this._ignoreBackdropClick) {\n this._ignoreBackdropClick = false\n return\n }\n\n if (event.target !== event.currentTarget) {\n return\n }\n\n if (this._config.backdrop === 'static') {\n this._triggerBackdropTransition()\n } else {\n this.hide()\n }\n })\n\n if (animate) {\n Util.reflow(this._backdrop)\n }\n\n $(this._backdrop).addClass(CLASS_NAME_SHOW)\n\n if (!callback) {\n return\n }\n\n if (!animate) {\n callback()\n return\n }\n\n const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)\n\n $(this._backdrop)\n .one(Util.TRANSITION_END, callback)\n .emulateTransitionEnd(backdropTransitionDuration)\n } else if (!this._isShown && this._backdrop) {\n $(this._backdrop).removeClass(CLASS_NAME_SHOW)\n\n const callbackRemove = () => {\n this._removeBackdrop()\n if (callback) {\n callback()\n }\n }\n\n if ($(this._element).hasClass(CLASS_NAME_FADE)) {\n const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)\n\n $(this._backdrop)\n .one(Util.TRANSITION_END, callbackRemove)\n .emulateTransitionEnd(backdropTransitionDuration)\n } else {\n callbackRemove()\n }\n } else if (callback) {\n callback()\n }\n }\n\n // ----------------------------------------------------------------------\n // the following methods are used to handle overflowing modals\n // todo (fat): these should probably be refactored out of modal.js\n // ----------------------------------------------------------------------\n\n _adjustDialog() {\n const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight\n\n if (!this._isBodyOverflowing && isModalOverflowing) {\n this._element.style.paddingLeft = `${this._scrollbarWidth}px`\n }\n\n if (this._isBodyOverflowing && !isModalOverflowing) {\n this._element.style.paddingRight = `${this._scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n _checkScrollbar() {\n const rect = document.body.getBoundingClientRect()\n this._isBodyOverflowing = Math.round(rect.left + rect.right) < window.innerWidth\n this._scrollbarWidth = this._getScrollbarWidth()\n }\n\n _setScrollbar() {\n if (this._isBodyOverflowing) {\n // Note: DOMNode.style.paddingRight returns the actual value or '' if not set\n // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set\n const fixedContent = [].slice.call(document.querySelectorAll(SELECTOR_FIXED_CONTENT))\n const stickyContent = [].slice.call(document.querySelectorAll(SELECTOR_STICKY_CONTENT))\n\n // Adjust fixed content padding\n $(fixedContent).each((index, element) => {\n const actualPadding = element.style.paddingRight\n const calculatedPadding = $(element).css('padding-right')\n $(element)\n .data('padding-right', actualPadding)\n .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`)\n })\n\n // Adjust sticky content margin\n $(stickyContent).each((index, element) => {\n const actualMargin = element.style.marginRight\n const calculatedMargin = $(element).css('margin-right')\n $(element)\n .data('margin-right', actualMargin)\n .css('margin-right', `${parseFloat(calculatedMargin) - this._scrollbarWidth}px`)\n })\n\n // Adjust body padding\n const actualPadding = document.body.style.paddingRight\n const calculatedPadding = $(document.body).css('padding-right')\n $(document.body)\n .data('padding-right', actualPadding)\n .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`)\n }\n\n $(document.body).addClass(CLASS_NAME_OPEN)\n }\n\n _resetScrollbar() {\n // Restore fixed content padding\n const fixedContent = [].slice.call(document.querySelectorAll(SELECTOR_FIXED_CONTENT))\n $(fixedContent).each((index, element) => {\n const padding = $(element).data('padding-right')\n $(element).removeData('padding-right')\n element.style.paddingRight = padding ? padding : ''\n })\n\n // Restore sticky content\n const elements = [].slice.call(document.querySelectorAll(`${SELECTOR_STICKY_CONTENT}`))\n $(elements).each((index, element) => {\n const margin = $(element).data('margin-right')\n if (typeof margin !== 'undefined') {\n $(element).css('margin-right', margin).removeData('margin-right')\n }\n })\n\n // Restore body padding\n const padding = $(document.body).data('padding-right')\n $(document.body).removeData('padding-right')\n document.body.style.paddingRight = padding ? padding : ''\n }\n\n _getScrollbarWidth() { // thx d.walsh\n const scrollDiv = document.createElement('div')\n scrollDiv.className = CLASS_NAME_SCROLLBAR_MEASURER\n document.body.appendChild(scrollDiv)\n const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth\n document.body.removeChild(scrollDiv)\n return scrollbarWidth\n }\n\n // Static\n static _jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = {\n ...Default,\n ...$(this).data(),\n ...(typeof config === 'object' && config ? config : {})\n }\n\n if (!data) {\n data = new Modal(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n\n data[config](relatedTarget)\n } else if (_config.show) {\n data.show(relatedTarget)\n }\n })\n }\n}\n\n/**\n * Data API implementation\n */\n\n$(document).on(EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {\n let target\n const selector = Util.getSelectorFromElement(this)\n\n if (selector) {\n target = document.querySelector(selector)\n }\n\n const config = $(target).data(DATA_KEY) ?\n 'toggle' : {\n ...$(target).data(),\n ...$(this).data()\n }\n\n if (this.tagName === 'A' || this.tagName === 'AREA') {\n event.preventDefault()\n }\n\n const $target = $(target).one(EVENT_SHOW, showEvent => {\n if (showEvent.isDefaultPrevented()) {\n // Only register focus restorer if modal will actually get shown\n return\n }\n\n $target.one(EVENT_HIDDEN, () => {\n if ($(this).is(':visible')) {\n this.focus()\n }\n })\n })\n\n Modal._jQueryInterface.call($(target), config, this)\n})\n\n/**\n * jQuery\n */\n\n$.fn[NAME] = Modal._jQueryInterface\n$.fn[NAME].Constructor = Modal\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Modal._jQueryInterface\n}\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.6.2): scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * Constants\n */\n\nconst NAME = 'scrollspy'\nconst VERSION = '4.6.2'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'\nconst CLASS_NAME_ACTIVE = 'active'\n\nconst EVENT_ACTIVATE = `activate${EVENT_KEY}`\nconst EVENT_SCROLL = `scroll${EVENT_KEY}`\nconst EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`\n\nconst METHOD_OFFSET = 'offset'\nconst METHOD_POSITION = 'position'\n\nconst SELECTOR_DATA_SPY = '[data-spy=\"scroll\"]'\nconst SELECTOR_NAV_LIST_GROUP = '.nav, .list-group'\nconst SELECTOR_NAV_LINKS = '.nav-link'\nconst SELECTOR_NAV_ITEMS = '.nav-item'\nconst SELECTOR_LIST_ITEMS = '.list-group-item'\nconst SELECTOR_DROPDOWN = '.dropdown'\nconst SELECTOR_DROPDOWN_ITEMS = '.dropdown-item'\nconst SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle'\n\nconst Default = {\n offset: 10,\n method: 'auto',\n target: ''\n}\n\nconst DefaultType = {\n offset: 'number',\n method: 'string',\n target: '(string|element)'\n}\n\n/**\n * Class definition\n */\n\nclass ScrollSpy {\n constructor(element, config) {\n this._element = element\n this._scrollElement = element.tagName === 'BODY' ? window : element\n this._config = this._getConfig(config)\n this._selector = `${this._config.target} ${SELECTOR_NAV_LINKS},` +\n `${this._config.target} ${SELECTOR_LIST_ITEMS},` +\n `${this._config.target} ${SELECTOR_DROPDOWN_ITEMS}`\n this._offsets = []\n this._targets = []\n this._activeTarget = null\n this._scrollHeight = 0\n\n $(this._scrollElement).on(EVENT_SCROLL, event => this._process(event))\n\n this.refresh()\n this._process()\n }\n\n // Getters\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n refresh() {\n const autoMethod = this._scrollElement === this._scrollElement.window ?\n METHOD_OFFSET : METHOD_POSITION\n\n const offsetMethod = this._config.method === 'auto' ?\n autoMethod : this._config.method\n\n const offsetBase = offsetMethod === METHOD_POSITION ?\n this._getScrollTop() : 0\n\n this._offsets = []\n this._targets = []\n\n this._scrollHeight = this._getScrollHeight()\n\n const targets = [].slice.call(document.querySelectorAll(this._selector))\n\n targets\n .map(element => {\n let target\n const targetSelector = Util.getSelectorFromElement(element)\n\n if (targetSelector) {\n target = document.querySelector(targetSelector)\n }\n\n if (target) {\n const targetBCR = target.getBoundingClientRect()\n if (targetBCR.width || targetBCR.height) {\n // TODO (fat): remove sketch reliance on jQuery position/offset\n return [\n $(target)[offsetMethod]().top + offsetBase,\n targetSelector\n ]\n }\n }\n\n return null\n })\n .filter(Boolean)\n .sort((a, b) => a[0] - b[0])\n .forEach(item => {\n this._offsets.push(item[0])\n this._targets.push(item[1])\n })\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n $(this._scrollElement).off(EVENT_KEY)\n\n this._element = null\n this._scrollElement = null\n this._config = null\n this._selector = null\n this._offsets = null\n this._targets = null\n this._activeTarget = null\n this._scrollHeight = null\n }\n\n // Private\n _getConfig(config) {\n config = {\n ...Default,\n ...(typeof config === 'object' && config ? config : {})\n }\n\n if (typeof config.target !== 'string' && Util.isElement(config.target)) {\n let id = $(config.target).attr('id')\n if (!id) {\n id = Util.getUID(NAME)\n $(config.target).attr('id', id)\n }\n\n config.target = `#${id}`\n }\n\n Util.typeCheckConfig(NAME, config, DefaultType)\n\n return config\n }\n\n _getScrollTop() {\n return this._scrollElement === window ?\n this._scrollElement.pageYOffset : this._scrollElement.scrollTop\n }\n\n _getScrollHeight() {\n return this._scrollElement.scrollHeight || Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight\n )\n }\n\n _getOffsetHeight() {\n return this._scrollElement === window ?\n window.innerHeight : this._scrollElement.getBoundingClientRect().height\n }\n\n _process() {\n const scrollTop = this._getScrollTop() + this._config.offset\n const scrollHeight = this._getScrollHeight()\n const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight()\n\n if (this._scrollHeight !== scrollHeight) {\n this.refresh()\n }\n\n if (scrollTop >= maxScroll) {\n const target = this._targets[this._targets.length - 1]\n\n if (this._activeTarget !== target) {\n this._activate(target)\n }\n\n return\n }\n\n if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {\n this._activeTarget = null\n this._clear()\n return\n }\n\n for (let i = this._offsets.length; i--;) {\n const isActiveTarget = this._activeTarget !== this._targets[i] &&\n scrollTop >= this._offsets[i] &&\n (typeof this._offsets[i + 1] === 'undefined' ||\n scrollTop < this._offsets[i + 1])\n\n if (isActiveTarget) {\n this._activate(this._targets[i])\n }\n }\n }\n\n _activate(target) {\n this._activeTarget = target\n\n this._clear()\n\n const queries = this._selector\n .split(',')\n .map(selector => `${selector}[data-target=\"${target}\"],${selector}[href=\"${target}\"]`)\n\n const $link = $([].slice.call(document.querySelectorAll(queries.join(','))))\n\n if ($link.hasClass(CLASS_NAME_DROPDOWN_ITEM)) {\n $link.closest(SELECTOR_DROPDOWN)\n .find(SELECTOR_DROPDOWN_TOGGLE)\n .addClass(CLASS_NAME_ACTIVE)\n $link.addClass(CLASS_NAME_ACTIVE)\n } else {\n // Set triggered link as active\n $link.addClass(CLASS_NAME_ACTIVE)\n // Set triggered links parents as active\n // With both