HEX
Server: Apache/2.4.57 (Debian)
System: Linux web-server-k8s-e92jnr3j-6f99bff6b6-rp2wg 6.1.0-22-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.94-1 (2024-06-21) x86_64
User: apache (48)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,
Upload Files
File: /var/www/sites/1250.info/wp-content/themes/savoy/assets/js/dev/nm-core.js
(function($) {
	
	'use strict';
	
	if (!$.nmThemeExtensions)
		$.nmThemeExtensions = {};
	
	function NmTheme() {
		// Initialize scripts
		this.init();
	};
	
	
	NmTheme.prototype = {
	
		/**
		 *	Initialize
		 */
		init: function() {
			var self = this;
            
            // CSS Classes
            self.classHeaderFixed = 'header-on-scroll';
            self.classMobileMenuOpen = 'mobile-menu-open';
            self.classSearchOpen = 'header-search-open';
            self.classWidgetPanelOpen = 'widget-panel-open';

            // Page elements
            self.$window = $(window);
            self.$document = $(document);
            self.$html = $('html');
            self.$body = $('body');

            // Page includes element
            self.$pageIncludes = $('#nm-page-includes');

            // Page overlay
            self.$pageOverlay = $('#nm-page-overlay');

            // Header
            self.$topBar = $('#nm-top-bar');
            self.$header = $('#nm-header');
            self.$headerPlaceholder = $('#nm-header-placeholder');
            self.headerScrollTolerance = 0;

            // Mobile menu
            self.$mobileMenuBtn = $('#nm-mobile-menu-button');
            self.$mobileMenu = $('#nm-mobile-menu');
            self.$mobileMenuScroller = self.$mobileMenu.children('.nm-mobile-menu-scroll');
            self.$mobileMenuLi = self.$mobileMenu.find('ul li.menu-item');

            // Widget panel
            self.$widgetPanel = $('#nm-widget-panel');
            self.widgetPanelAnimSpeed = 250;

            // Slide panels animation speed
            self.panelsAnimSpeed = 200;

            // Shop
            self.$shopWrap = $('#nm-shop');
            self.isShop = (self.$shopWrap.length) ? true : false;
            self.shopCustomSelect = (nm_wp_vars.shopCustomSelect != '0') ? true : false;

            // Search
            self.searchEnabled = (nm_wp_vars.shopSearch !== '0') ? true : false;
            
            // Browser check
            self.isChromium = !!window.chrome; // Chromium engine (multiple browsers)
            self.isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
            
            /* Page-load transition */
            if (nm_wp_vars.pageLoadTransition != '0') {
                self.isIos = navigator.userAgent.match(/iPad/i) || navigator.userAgent.match(/iPhone/i);
                if (!self.isIos) {
                    self.$window.on('beforeunload', function(e) {
                        $('#nm-page-load-overlay').addClass('nm-loader'); // Show preloader animation
                        self.$html.removeClass('nm-page-loaded');
                    });
                }
                // Hide page-load overlay - Note: Using the "pageshow" event so the overlay is hidden when the browser "back" button is used (only seems to be needed in Safari though)
                if ('onpagehide' in window) {
                    window.addEventListener('pageshow', function() {
                        setTimeout(function() { self.$html.addClass('nm-page-loaded'); }, 150);
                    }, false);
                } else {
                    setTimeout(function() { self.$html.addClass('nm-page-loaded'); }, 150);
                }
            }
            
			// Remove the CSS transition preload class
			self.$body.removeClass('nm-preload');
			
            // Check for touch device (modernizr)
			self.isTouch = (self.$html.hasClass('touch')) ? true : false;
			
            // Add "has-hover" class - Makes it possible to add :hover selectors for non-touch devices only
            if (self.isTouch) {
                if (nm_wp_vars.touchHover != '0') { self.$html.addClass('has-hover'); }
            } else {
                self.$html.addClass('has-hover');
            }
            
			// Fixed header
			self.headerIsFixed = (self.$body.hasClass('header-fixed')) ? true : false;
			
			// History (browser "back" button): Init
			var enablePushState = (nm_wp_vars.pushStateMobile != '0' || self.$html.hasClass('no-touch')) ? true : false;
            if (enablePushState && self.$html.hasClass('history')) {
				self.hasPushState = true;
				window.history.replaceState({nmShop: true}, '', window.location.href);
			} else {
				self.hasPushState = false;
			}
			
            // Scrollbar
            self.setScrollbarWidth();
            
			// Init header
			self.headerCheckPlaceholderHeight(); // Make sure the header and header-placeholder has the same height
			if (self.headerIsFixed) {
				self.headerSetScrollTolerance();
				self.mobileMenuPrep();
			}
            
            // Init widget panel
			self.widgetPanelPrep();
			
			// Check for old IE browser (IE10 or below)
			var ua = window.navigator.userAgent,
            	msie = ua.indexOf('MSIE ');
			if (msie > 0) {
				self.$html.addClass('nm-old-ie');
			}
            
            // Shop - Infinite load: Snapback cache
            if (nm_wp_vars.infloadSnapbackCache != '0' && ! self.isTouch && ! self.isFirefox) {
                self.shopInfloadSnapbackCache();
            }
            
			// Load extension scripts
			self.loadExtension();
			
			self.bind();
			self.initPageIncludes();
            
			
			// "Add to cart" redirect: Show cart panel
			if (self.$body.hasClass('nm-added-to-cart')) {
				self.$body.removeClass('nm-added-to-cart')
				
                self.$window.on('load', function() {
					// Is widget/cart panel enabled?
					if (self.$widgetPanel.length) {
                        // Show cart panel
                        self.widgetPanelShow(true, true); // Args: showLoader, addingToCart
                        // Hide cart panel "loader" overlay
                        setTimeout(function() { self.widgetPanelCartHideLoader(); }, 1000);
                    }
				});
			}
		},
		
		
		/**
		 *	Extensions: Load scripts
		 */
		loadExtension: function() {
			var self = this;
            
			// Extension: Shop
			if ($.nmThemeExtensions.shop) {
				$.nmThemeExtensions.shop.call(self);
			}
            
            // Extension: Search
            if (self.searchEnabled && $.nmThemeExtensions.search) {
                $.nmThemeExtensions.search.call(self);
            }
				
			// Extension: Shop - Single product
			if ($.nmThemeExtensions.singleProduct) {
				$.nmThemeExtensions.singleProduct.call(self);
			}
				
			// Extension: Shop - Cart
			if ($.nmThemeExtensions.cart) {
				$.nmThemeExtensions.cart.call(self);
			}
			
			// Extension: Shop - Checkout
			if ($.nmThemeExtensions.checkout) {
				$.nmThemeExtensions.checkout.call(self);
			}
            
            // Extension: Blog
			if ($.nmThemeExtensions.blog) {
				$.nmThemeExtensions.blog.call(self);
			}
		},
		
        
		/**
		 *  Helper: Calculate scrollbar width
		 */
		setScrollbarWidth: function() {
			// From Magnific Popup v1.0.0
			var self = this,
				scrollDiv = document.createElement('div');
			scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
			document.body.appendChild(scrollDiv);
			self.scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
			document.body.removeChild(scrollDiv);
			// /Magnific Popup
		},
        
        
        /**
		 *	Helper: Is page vertically scrollable?
		 */
        pageIsScrollable: function() {
            return document.body.scrollHeight > document.body.clientHeight;
            //jQuery alt: return self.$body.height() > self.$window.height();
        },
        
        
        /**
		 *  Helper: Get parameter from current page URL
		 */
        urlGetParameter: function(param) {
            var url = decodeURIComponent(window.location.search.substring(1)),
                urlVars = url.split('&'),
                paramName, i;

            for (i = 0; i < urlVars.length; i++) {
                paramName = urlVars[i].split('=');
                if (paramName[0] === param) {
                    return paramName[1] === undefined ? true : paramName[1];
                }
            }
        },
		
		
		/**
		 *  Helper: Add/update a key-value pair in the URL query parameters 
		 */
		updateUrlParameter: function(uri, key, value) {
			// Remove #hash before operating on the uri
			var i = uri.indexOf('#'),
				hash = i === -1 ? '' : uri.substr(i);
			uri = (i === -1) ? uri : uri.substr(0, i);
			
			var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i"),
				separator = (uri.indexOf('?') !== -1) ? "&" : "?";
			
			if (uri.match(re)) {
				uri = uri.replace(re, '$1' + key + "=" + value + '$2');
			} else {
				uri = uri + separator + key + "=" + value;
			}
			
			return uri + hash; // Append #hash
		},
		
		
		/**
		 *	Helper: Set browser history "pushState" (AJAX url)
		 */
		setPushState: function(pageUrl) {
			var self = this;
			
			// Set browser "pushState"
			if (self.hasPushState) {
				window.history.pushState({nmShop: true}, '', pageUrl);
			}
		},
		
		
		/**
		 *	Header: Check/set placeholder height
		 */
		headerCheckPlaceholderHeight: function() {
			var self = this;
			
            if (nm_wp_vars.headerPlaceholderSetHeight == 0) {
                console.log('NM: Header placeholder height NOT set');
                return false;
            }
            
			// Make sure the header is not fixed/floated
			if (self.$body.hasClass(self.classHeaderFixed)) {
				return;
			}
			
            var headerHeight = Math.round(self.$header.innerHeight()),
				headerPlaceholderHeight = Math.round(parseInt(self.$headerPlaceholder.css('height')));
            
            // Is there a height difference of more than 1 pixel between the header and header-placeholder?
            if (Math.abs(headerHeight - headerPlaceholderHeight) > 1) {
                self.$headerPlaceholder.css('height', headerHeight+'px');
			}
		},
		
		
		/**
		 *	Header: Set scroll tolerance
		 */
		headerSetScrollTolerance: function() {
			var self = this;
			
			self.headerScrollTolerance = (self.$topBar.length && self.$topBar.is(':visible')) ? self.$topBar.outerHeight(true) : 0;
		},
        
        
        /**
		 *	Header: Toggle fixed class
		 */
        headerToggleFixedClass: function(self) {
            if (self.$document.scrollTop() > self.headerScrollTolerance) {
                if (!self.$body.hasClass(self.classHeaderFixed)) {
                    self.$body.addClass(self.classHeaderFixed);
                }
            } else {
                if (self.$body.hasClass(self.classHeaderFixed)) {
                    self.$body.removeClass(self.classHeaderFixed);
                }
            }
        },
		
		
		/**
		 *	Bind scripts
		 */
		bind: function() {
			var self = this;
			
            
			/* Bind: Window resize */
			var timer = null;
            self.$window.on('resize', function() {
				if (timer) { clearTimeout(timer); }
				timer = setTimeout(function() {
					// Make sure the header and header-placeholder has the same height
					self.headerCheckPlaceholderHeight();
																	
					if (self.headerIsFixed) {
						self.headerSetScrollTolerance();
						self.mobileMenuPrep();
					}
				}, 250);
			});
            
            
            /* Media query matching */
            var _hideMobileMenu = function(mediaQuery) {
                if (mediaQuery.matches && self.$body.hasClass(self.classMobileMenuOpen)) {
                    self.pageOverlayHide();
                }
            },
            _hideHeaderSearch = function(mediaQuery) {
                if (mediaQuery.matches && self.$body.hasClass(self.classSearchOpen)) {
                    self.pageOverlayHide();
                }
            },            
            breakpointMobileMenu = window.matchMedia('(min-width: 992px)'),
            breakpointHeaderSearch = window.matchMedia('(max-width: 991px)');
            // Use "addEventListener" when available ("addListener" is deprecated)
            try {
                breakpointMobileMenu.addEventListener('change', _hideMobileMenu);
                breakpointHeaderSearch.addEventListener('change', _hideHeaderSearch);
            } catch(err1) {
                try {
                    breakpointMobileMenu.addListener(_hideMobileMenu);
                    breakpointHeaderSearch.addListener(_hideHeaderSearch);
                } catch(err2) {
                    console.error('NM: Media query matching - ' + err2);
                }
            }
            
            
            /* Bind: Mobile "orientationchange" event */
            if (self.isTouch) {
                self.$window.on('orientationchange', function() {
                    self.$body.addClass('touch-orientation-change');
                    setTimeout(function() { 
                        self.$body.removeClass('touch-orientation-change');
                    }, 500);
                });
            }
            
			
			/* Bind: Window scroll (Fixed header) */
			if (self.headerIsFixed) {
                self.$window.on('scroll.nmheader', function() {
                    self.headerToggleFixedClass(self);
                });
                
				self.$window.trigger('scroll');
			}
			
			
			/* Bind: Menus - Sub-menu hover (set position and "bridge" height) */
			var $topMenuItems = $('#nm-top-menu').children('.menu-item'),
				$mainMenuItems = $('#nm-main-menu-ul').children('.menu-item'),
                $secondaryMenuItems = $('#nm-right-menu-ul').children('.menu-item'),
                $menuItems = $().add($topMenuItems).add($mainMenuItems).add($secondaryMenuItems);
            
            $menuItems.on('mouseenter', function() {
                var $menuItem = $(this),
                    $subMenu = $menuItem.children('.sub-menu');
                
                if ($subMenu.length) {
                    // Sub-menu: Set position/offset (prevents menu from being positioned outside the browser window)
                    var windowWidth = self.$window.innerWidth(),
                        subMenuOffset = $subMenu.offset().left,
                        subMenuWidth = $subMenu.width(),
                        subMenuGap = windowWidth - (subMenuOffset + subMenuWidth);
                    if (subMenuGap < 0) {
                        $subMenu.css('left', (subMenuGap-33)+'px');
                    }
                    
                    // Header sub-menus: Set "bridge" height (prevents menu from closing when hovering outside its parent <li> element)
                    if (! $menuItem.hasClass('bridge-height-set')) {
                        var $headerMenuContainer = $menuItem.closest('nav');
                        if ($headerMenuContainer.length) {
                            $menuItem.addClass('bridge-height-set');
                            var menuBridgeHeight = Math.ceil(($headerMenuContainer.height() - $menuItem.height()) / 2);
                            $subMenu.children('.nm-sub-menu-bridge').css('height', (menuBridgeHeight + 1) + 'px');
                        }
                    }
                }
            }).on('mouseleave', function() {
                // Reset sub-menu position
                var $subMenu = $(this).children('.sub-menu');
                if ($subMenu.length) {
                    $subMenu.css('left', '');
                }
            });
			
            
            /* Bind: Header - Shop links */
            if (! self.isShop) {
                self.$header.on('click.nmHeaderShopRedirect', '.shop-redirect-link > a', function(e) {
                    e.preventDefault();
                    var url = $(this).attr('href');
                    window.location.href = url + '#shop';
                });
            }
			
            
			/* Bind: Mobile menu button */
			self.$mobileMenuBtn.on('click', function(e) {
				e.preventDefault();
				
				if (!self.$body.hasClass(self.classMobileMenuOpen)) {
					self.mobileMenuOpen();
				} else {
					self.mobileMenuClose(true); // Args: hideOverlay
				}
			});
			
			/* Function: Mobile menu - Toggle sub-menu */
			var _mobileMenuToggleSub = function($menu, $subMenu) {
                $menu.toggleClass('active');
				$subMenu.toggleClass('open');
			};
			
			/* Bind: Mobile menu list elements */
			self.$mobileMenuLi.on('click.nmMenuToggle', function(e) {
                e.stopPropagation(); // Prevent click event on parent menu link
                
                self.$document.trigger('nm_mobile_menu_toggle', [e, this]);
                
				var $this = $(this),
					$thisSubMenu = $this.children('ul');
                
                if ($thisSubMenu.length) {
                    // Prevent toggle when "nm-notoggle" class is added -and- the "plus" icon wasn't clicked
                    if ($this.hasClass('nm-notoggle') && ! $(e.target).hasClass('nm-menu-toggle')) { return; }
                    
                    e.preventDefault();
                    _mobileMenuToggleSub($this, $thisSubMenu);
				}
			});
			
			
			/* Bind: Widget panel */
			if (self.$widgetPanel.length) {
				self.widgetPanelBind();
			}
			
			
			/* Bind: Login/register popup */
			if (self.$pageIncludes.hasClass('login-popup')) {
                var nonceValuesUpdated = false;
                
				$('#nm-menu-account-btn').on('click.nmLoginShowPopup', function(e) {
					e.preventDefault();
					
                    // Checkout page fix: Make sure the login form is visible
                    $('#nm-login-wrap').children('.login').css('display', '');
                    
					$.magnificPopup.open({
						mainClass: 'nm-login-popup nm-mfp-fade-in',
						alignTop: true,
						closeMarkup: '<a class="mfp-close nm-font nm-font-close2"></a>',
						removalDelay: 180,
						items: {
							src: '#nm-login-popup-wrap',
							type: 'inline'
						},
						callbacks: {
                            open: function() {
                                if (nonceValuesUpdated) { return; }
                                nonceValuesUpdated = true;
                                
                                // Update popup "nonce" input values so the form can be submitted on cached pages
                                $.ajax({
                                    type: 'POST',
                                    //url: nm_wp_vars.ajaxUrl,
                                    //data: { action: 'nm_ajax_login_get_nonce_fields' },
                                    url: wc_add_to_cart_params.wc_ajax_url.toString().replace('%%endpoint%%', 'nm_ajax_login_get_nonces'),
                                    dataType: 'json',
                                    cache: false,
                                    headers: {'cache-control': 'no-cache'},
                                    success: function(noncesJson) {
                                        $('#woocommerce-login-nonce').attr('value', noncesJson.login);
                                        $('#woocommerce-register-nonce').attr('value', noncesJson.register);
                                    }
                                });
                            },
							close: function() {
								// Make sure the login form is displayed when the modal is re-opened
								$('#nm-login-wrap').addClass('inline fade-in slide-up');
								$('#nm-register-wrap').removeClass('inline fade-in slide-up');
							}
						}
					});
				});
			}
			
			
			/* Bind: Page overlay */
            self.$pageOverlay.on('click', function() {
				self.pageOverlayHide();
			});
            
            
            /* Bind: Shop attributes */
			if (nm_wp_vars.shopAttsSwapImage == '1') {
                // Enable on touch?
                var shopAttsSwapImageOnTouch = (self.isTouch && nm_wp_vars.shopAttsSwapImageOnTouch == '1') ? true : false;
                
                if ( shopAttsSwapImageOnTouch ) {
                    /* Bind: Shop attribute links */
                    self.$body.on('click', '.nm-shop-loop-attribute-link',/* { passive: true }, */function(e) {
                        var $link = $(this);
                        if (! $link.hasClass('selected')) {
                            e.preventDefault();
                            $link.parent().children('.selected').removeClass('selected');
                            $link.addClass('selected');
                            self.shopAttsSwapImage($link);
                        }
                    });
                } else {
                    /* Bind: Shop attribute links */
                    self.$body.on('mouseenter', '.nm-shop-loop-attribute-link', function() {
                        var $link = $(this);
                        self.shopAttsSwapImage($link);
                    });

                    if (nm_wp_vars.shopAttsSwapImageRevert == '1') {
                        /* Bind: Shop attributes container */
                        self.$body.on('mouseleave.nmShopImageRevert', '.nm-shop-loop-attributes', function() {
                            var $attsWrap = $(this);
                            self.shopAttsSwapImageRevert($attsWrap, false);
                        });

                        /* Bind: Page load */
                        self.$window.on('beforeunload', function(e) {
                            // Unbind "mouseleave.nmShopImageRevert" event when attribute link is clicked (page loads)
                            self.$body.off('mouseleave.nmShopImageRevert');
                        });
                    }
                }
            }
		},
		
        
        /**
		 *	Page overlay: Show
		 */
		pageOverlayShow: function() {
            var self = this;
            
            // Mobile menu
            if (self.$body.hasClass(self.classMobileMenuOpen)) {
                self.$pageOverlay.addClass('nm-mobile-menu-overlay');
            // Header search
            } else if (self.$body.hasClass(self.classSearchOpen)) {
                self.$pageOverlay.addClass('nm-header-search-overlay');
            // Widget panel
            } else if (self.$body.hasClass(self.classWidgetPanelOpen)) {
                self.$pageOverlay.addClass('nm-widget-panel-overlay');
            }
            
            self.$pageOverlay.addClass('show');
		},
        
        
        /**
		 *	Page overlay: Hide
		 */
		pageOverlayHide: function() {
            var self = this;
            
            // Mobile menu
            if (self.$body.hasClass(self.classMobileMenuOpen)) {
                self.mobileMenuClose(false); // Args: hideOverlay
            // Header search
            } else if (self.$body.hasClass(self.classSearchOpen)) {
                self.headerSearchTogglePanel();
            // Widget panel
            } else if (self.$body.hasClass(self.classWidgetPanelOpen)) {
                self.widgetPanelHide();
            }
            
            // Trigger "nm_page_overlay_hide" event
            self.$body.trigger('nm_page_overlay_hide');
            
            self.$pageOverlay.addClass('fade-out');
            setTimeout(function() {
                self.$pageOverlay.removeClass(); // Remove all classes from page-overlay element
            }, self.panelsAnimSpeed);
		},
        
		
		/**
		 *	Mobile menu: Prepare (add CSS)
		 */
		mobileMenuPrep: function() {
			var self = this,
				windowHeight = self.$window.height() - self.$header.outerHeight(true);
			
			self.$mobileMenuScroller.css({'max-height': windowHeight+'px', 'margin-right': '-'+self.scrollbarWidth+'px'});
		},
        
        
        /**
		 *	Mobile menu: Open
		 */
		mobileMenuOpen: function(hideOverlay) {
            var self = this,
                headerPosition = self.$header.outerHeight(true);
            
            self.$mobileMenuScroller.css('margin-top', headerPosition+'px');
            
            self.$body.addClass(self.classMobileMenuOpen);
            self.pageOverlayShow();
        },
        
        
        /**
		 *	Mobile menu: Close
		 */
		mobileMenuClose: function(hideOverlay) {
            var self = this;
            
            self.$body.removeClass(self.classMobileMenuOpen);
            
            if (hideOverlay) {
                self.pageOverlayHide();
            }

            // Hide open menus (first level only)
            setTimeout(function() {
                $('#nm-mobile-menu-main-ul').children('.active').removeClass('active').children('ul').removeClass('open');
                $('#nm-mobile-menu-secondary-ul').children('.active').removeClass('active').children('ul').removeClass('open');
            }, 250);
        },
        
        
		/**
		 *	Widget panel: Prepare
		 */
		widgetPanelPrep: function() {
			var self = this;
            
            // Cart panel: Hide scrollbar
            self.widgetPanelCartHideScrollbar();
            
            // Cart panel: Set Ajax state
            self.cartPanelAjax = null;
            
            if (nm_wp_vars.cartPanelQtyArrows != '0') {
                // Cart panel: Bind quantity-input buttons
                self.quantityInputsBindButtons(self.$widgetPanel);

                // Cart panel - Quantity inputs: Bind "blur" event
                self.$widgetPanel.on('blur', 'input.qty', function() {
                    var $quantityInput = $(this),
                        currentVal = parseFloat($quantityInput.val()),
                        max	= parseFloat($quantityInput.attr('max'));

                    // Validate input values
                    if (currentVal === '' || currentVal === 'NaN') { currentVal = 0; }
                    if (max === 'NaN') { max = ''; }

                    // Make sure the value is not higher than the max value
                    if (currentVal > max) { 
                        $quantityInput.val(max);
                        currentVal = max;
                    };

                    // Is the quantity value more than 0?
                    if (currentVal > 0) {
                        self.widgetPanelCartUpdate($quantityInput);
                    }
                });

                // Cart panel - Quantity inputs: Bind "nm_qty_change" event
                self.$document.on('nm_qty_change', function(event, quantityInput) {
                    // Is the widget-panel open?
                    if (self.$body.hasClass(self.classWidgetPanelOpen)) {
                        self.widgetPanelCartUpdate($(quantityInput));
                    }
                });
            }
		},
        
        
		/**
		 *	Widget panel: Bind
		 */
		widgetPanelBind: function() {
			var self = this;
			
			// Touch event handling
			if (self.isTouch) {
				//if (self.headerIsFixed) { // Allow page overlay "touchmove" event if header is not fixed/floating
                // Bind: Page overlay "touchmove" event
                self.$pageOverlay.on('touchmove', function(e) {
                    e.preventDefault(); // Prevent default touch event
                });
				//}
				
				// Bind: Widget panel "touchmove" event
				self.$widgetPanel.on('touchmove', function(e) {				
					e.stopPropagation(); // Prevent event propagation (bubbling)
				});
			}
			
			/* Bind: "Cart" buttons */
			$('#nm-menu-cart-btn, #nm-mobile-menu-cart-btn').on('click.nmAtc', function(e) {
				e.preventDefault();										
				
				// Close the mobile menu first					
				if (self.$body.hasClass(self.classMobileMenuOpen)) {
					var $this = $(this);
                    self.pageOverlayHide();
					setTimeout(function() {
						$this.trigger('click'); // Trigger this function again
					}, self.panelsAnimSpeed);
				} else {
				    self.widgetPanelShow();
                }
			});
			
			/* Bind: "Close" button */
			$('#nm-widget-panel-close').on('click.nmWidgetPanelClose', function(e) {
				e.preventDefault();
                self.pageOverlayHide();
			});
            
            /* Bind: "Continue shopping" button */
			self.$widgetPanel.on('click.nmCartPanelClose', '#nm-cart-panel-continue', function(e) {
				e.preventDefault();
                self.pageOverlayHide();
			});
		},
        
        
		/**
		 *	Widget panel: Show
		 */
		widgetPanelShow: function(showLoader, addingToCart) {
			var self = this;
            
            // Show widget/cart panel on add-to-cart?
            if (addingToCart && nm_wp_vars.cartPanelShowOnAtc == '0') {
                self.shopShowNotices();
                return;
            }
            
			if (showLoader) {
                self.widgetPanelCartShowLoader();
			}
			
            self.$body.addClass('widget-panel-opening '+self.classWidgetPanelOpen);
            self.pageOverlayShow();
            
            setTimeout(function() {
                self.$body.removeClass('widget-panel-opening');
            }, self.widgetPanelAnimSpeed);
		},
        
        
        /**
		 *	Widget panel: Hide
		 */
		widgetPanelHide: function() {
			var self = this;
			
            self.$body.addClass('widget-panel-closing');
            self.$body.removeClass(self.classWidgetPanelOpen);
            
            setTimeout(function() {
                self.$body.removeClass('widget-panel-closing');
            }, self.widgetPanelAnimSpeed);
		},
		
        
        /**
		 *	Widget panel: Cart - Show loader
		 */
		widgetPanelCartShowLoader: function() {
			$('#nm-cart-panel-loader').addClass('show');
		},
        
		
		/**
		 *	Widget panel: Cart - Hide loader
		 */
		widgetPanelCartHideLoader: function() {
            var self = this;
            
			$('#nm-cart-panel-loader').addClass('fade-out');
			setTimeout(function() {
                $('#nm-cart-panel-loader').removeClass('fade-out show');
            }, 200);
		},
        
        
        /**
		 *	Widget panel: Cart - Hide scrollbar
		 */
		widgetPanelCartHideScrollbar: function() {
            var self = this;
            self.$widgetPanel.children('.nm-widget-panel-inner').css('marginRight', '-'+self.scrollbarWidth+'px');
        },
		
		
        /**
		 *	Widget panel: Cart - Update quantity
         *
         *  Note: Based on the "quantity_update" function in "../woocommerce/assets/js/frontend/cart.js"
		 */
        widgetPanelCartUpdate: function($quantityInput) {
            var self = this;
            
            // Is an Ajax request already running?
            if (self.cartPanelAjax) {
                self.cartPanelAjax.abort(); // Abort current Ajax request
            }
            
            // Show thumbnail loader
            $quantityInput.closest('li').addClass('loading');
            
            var $cartForm = $('#nm-cart-panel-form'), // The "#nm-cart-panel-form" element is placed in the "../footer.php" file
                $cartFormNonce = $cartForm.find('#_wpnonce'),
                data = {};
            
            if ( ! $cartFormNonce.length ) {
                console.log( 'NM - widgetPanelCartUpdate: Nonce field not found.' );
                return;
            }
            
            data['nm_cart_panel_update'] = '1';
			data['update_cart'] = '1';
            data[$quantityInput.attr('name')] = $quantityInput.val();
            data['_wpnonce'] = $cartFormNonce.val();
            
			// Make call to actual form post URL.
			self.cartPanelAjax = $.ajax({
				type:     'POST',
				url:      $cartForm.attr('action'),
                data:     data,
				dataType: 'html',
				error: function(XMLHttpRequest, textStatus, errorThrown) {
				    console.log('NM: AJAX error - widgetPanelCartUpdate() - ' + errorThrown);
                    
                    // Hide any visible thumbnail loaders (no need to hide on "success" since the cart panel is replaced)
                    $('#nm-cart-panel .cart_list').children('.loading').removeClass('loading');
                },
                success:  function(response) {
                    // Replace cart fragments
                    $(document.body).trigger('wc_fragment_refresh').trigger('updated_cart_totals');
				},
				complete: function() {
                    self.cartPanelAjax = null; // Reset Ajax state
                }
			});
        },
        
        
        /**
		 *	Shop: Replace fragments
		 */
        shopReplaceFragments: function(fragments) {
            var $fragment;
            $.each(fragments, function(selector, fragment) {
                $fragment = $(fragment);
                if ($fragment.length) {
                    $(selector).replaceWith($fragment);
                }
            });
        },
        
        
        /**
         *  Shop - Attributes: Swap product thumbnail
         */
        shopAttsSwapImage: function($link) {
            var attributeImgSrc = $link.data('attr-src');

            if (attributeImgSrc) {
                var $productWrap = $link.closest('.product'),
                    $productThumb = $productWrap.find('.attachment-woocommerce_thumbnail').first();

                $productThumb.attr('src', attributeImgSrc);
                $productThumb.attr('srcset', attributeImgSrc);
                $productWrap.addClass('nm-attr-image-set');
            } else {
                this.shopAttsSwapImageRevert($link, true);
            }
        },
        
        
        /**
         *  Shop - Attributes: Revert swapped product thumbnail
         */
        shopAttsSwapImageRevert: function($this, isAttrLink) {
            var $productWrap = $this.closest('.product');

            if ($productWrap.hasClass('nm-attr-image-set')) {
                var $attrWrap = (isAttrLink) ? $this.closest('.nm-shop-loop-attributes') : $this,
                    productThumbSrc = $attrWrap.data('thumb-src');

                if (productThumbSrc) {
                    var $productThumb = $attrWrap.closest('.product').find('.attachment-woocommerce_thumbnail').first(),
                        productThumbSrcset = $attrWrap.data('thumb-srcset');

                    $productThumb.attr('src', productThumbSrc);
                    $productThumb.attr('srcset', productThumbSrcset);
                    $productWrap.removeClass('nm-attr-image-set');
                }
            }
        },
        
        
        /**
		 *	Shop - Infinite load: Snapback cache for browser "back" button
		 */
        shopInfloadSnapbackCache: function() {
            var self = this;
            
            /* Bind: Track page loads when cache is saved
             * - Note: Run on every page (place above conditional below) */
            self.$window.on('beforeunload', function() {
                var pageViews = sessionStorage.getItem('pageCacheViews');
                
                if (pageViews) {
                    // Only update page-view count if cache is saved
                    var pageCache = sessionStorage.getItem('pageCache');
                    
                    if (pageCache && pageCache !== '{}') {
                        pageViews = parseInt(pageViews) + 1;
                        sessionStorage.setItem('pageCacheViews', pageViews);
                    }
                } else {
                    sessionStorage.setItem('pageCacheViews', 1);
                }
            });
            
            // Only need to run code below on shop catalog
            if (! $('#nm-shop-browse-wrap').length) {
                return false;
            }
            
            //console.log('NM: Snapback cache ACTIVE');
            
            var snapbackCache = SnapbackCache({bodySelector: '#nm-shop-browse-wrap'}),
                snapbackCacheLinks = nm_wp_vars.infloadSnapbackCacheLinks; // Comma separated list of Shop links that can be used to generate cache
            
            /* Bind: Product list links */
            self.$body.on('click', '#nm-shop-browse-wrap a', function() {
                var $link = $(this);
                
                if ($('#nm-shop-browse-wrap').hasClass('products-loaded') && $link.is(snapbackCacheLinks)) {
                    snapbackCache.cachePage();
                }
            });
        },
        
        
        /**
		 *	Quantity inputs: Bind buttons
		 */
		quantityInputsBindButtons: function($container) {
			var self = this,
                clickThrottle,
                clickThrottleTimeout = nm_wp_vars.cartPanelQtyThrottleTimeout;
            
			/* 
			 *	Bind buttons click event
			 *	Note: Modified code from WooCommerce core (v2.2.6)
			 */
			$container.off('click.nmQty').on('click.nmQty', '.nm-qty-plus, .nm-qty-minus', function() {
				if (clickThrottle) { clearTimeout(clickThrottle); }
                
                // Get elements and values
				var $this		= $(this),
					$qty		= $this.closest('.quantity').find('.qty'),
					currentVal	= parseFloat($qty.val()),
					max			= parseFloat($qty.attr('max')),
					min			= parseFloat($qty.attr('min')),
					step		= $qty.attr('step');
				
				// Format values
				if (!currentVal || currentVal === '' || currentVal === 'NaN') currentVal = 0;
				if (max === '' || max === 'NaN') max = '';
				if (min === '' || min === 'NaN') min = 0;
				if (step === 'any' || step === '' || step === undefined || parseFloat(step) === 'NaN') step = 1;
                
				// Change the value
				if ($this.hasClass('nm-qty-plus')) {
					if (max && (max == currentVal || currentVal > max)) {
						$qty.val(max);
					} else {
						$qty.val(currentVal + parseFloat(step));
                        clickThrottle = setTimeout(function() { self.quantityInputsTriggerEvents($qty); }, clickThrottleTimeout);
					}
				} else {
					if (min && (min == currentVal || currentVal < min)) {
						$qty.val(min);
					} else if (currentVal > 0) {
						$qty.val(currentVal - parseFloat(step));
                        clickThrottle = setTimeout(function() { self.quantityInputsTriggerEvents($qty); }, clickThrottleTimeout);
					}
				}
			});
		},
        
        
        /**
		 *    Quantity inputs: Trigger events
		 */
        quantityInputsTriggerEvents: function($qty) {
            var self = this;
            
            // Trigger quantity input "change" event
            $qty.trigger('change');

            // Trigger custom event
            self.$document.trigger('nm_qty_change', $qty);
        },
        
        
		/**
		 *	Initialize "page includes" elements
		 */
		initPageIncludes: function() {
			var self = this;
			
            /* VC element: Row - Full height */
            if (self.$pageIncludes.hasClass('row-full-height')) {
                var _rowSetFullHeight = function() {
                    var $row = $('.nm-row-full-height:first');

                    if ($row.length) {
                        var windowHeight = self.$window.height(),
                            rowOffsetTop = $row.offset().top,
                            rowFullHeight;
                        
                        // Set/calculate Row's viewpoint height (vh)
                        windowHeight > rowOffsetTop && (rowFullHeight = 100 - rowOffsetTop / (windowHeight / 100), $row.css('min-height', rowFullHeight+'vh'));
                    }
                }
                
                _rowSetFullHeight(); // Init
                
                /* Bind: Window "resize" event for changing Row height */
                var rowResizeTimer = null;
                self.$window.on('resize.nmRow', function() {
                    if (rowResizeTimer) { clearTimeout(rowResizeTimer); }
                    rowResizeTimer = setTimeout(function() { _rowSetFullHeight(); }, 250);
                });
            }
            
			/* VC element: Row - Video (YouTube) background */
			var rowVideoHide = (self.isTouch && nm_wp_vars.rowVideoOnTouch == 0) ? true : false; // Show video on touch?
            if (!rowVideoHide && self.$pageIncludes.hasClass('video-background')) {
				$('.nm-row-video').each(function() {
					var $row = $(this),
						youtubeUrl = $row.data('video-url');
					
					if (youtubeUrl) {
						var youtubeId = vcExtractYoutubeId(youtubeUrl); // Note: function located in: "nm-js_composer_front(.min).js"
						
						if (youtubeId) {
							insertYoutubeVideoAsBackground($row, youtubeId); // Note: function located in: "nm-js_composer_front(.min).js"
						}
					}
				});
			}
			
            self.$window.on('load', function() {
				
				/* Element: Banner */
				if (self.$pageIncludes.hasClass('banner')) {
                    self.elementBanner($('.nm-banner'));
				}
				
				/* Element: Banner slider */
				if (self.$pageIncludes.hasClass('banner-slider')) {
                    $('.nm-banner-slider').each(function() {
                        self.elementBannerSlider($(this));
                    });
				}
                
                /* Element: Product slider */
				if (self.$pageIncludes.hasClass('product-slider')) {
                    $('.nm-product-slider').each(function() {
                        self.elementProductSlider($(this));
                    });
				}
				
                /* Element: Product reviews slider */
				if (self.$pageIncludes.hasClass('product-reviews-slider')) {
                    $('.nm-product-reviews-slider').each(function() {
                        self.elementProductReviewsSlider($(this));
                    });
				}
                
				/* Element: Post slider */
				if (self.$pageIncludes.hasClass('post-slider')) {
                    $('.nm-post-slider').each(function() {
                        self.elementPostSlider($(this));
                    });
				}
				
				/* WP element: Gallery - popup */
                if (nm_wp_vars.wpGalleryPopup != '0' && self.$pageIncludes.hasClass('wp-gallery')) {
					$('.gallery').each(function() {
						$(this).magnificPopup({
							mainClass: 'nm-wp-gallery-popup nm-mfp-fade-in',
							closeMarkup: '<a class="mfp-close nm-font nm-font-close2"></a>',
							removalDelay: 180,
							delegate: '.gallery-icon > a', // Gallery item selector
							type: 'image',
							gallery: {
								enabled: true,
								arrowMarkup: '<a title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir% nm-font nm-font-angle-right"></a>'
							},
                            image: {
                                titleSrc: function(item) {
                                    // Get title from caption element
                                    var title = item.el.parent().next('.wp-caption-text').text();
                                    return title || '';
                                }
                            },
							closeBtnInside: false
						});
					});
				}
			
			});
			
			
			/* Element: Product categories */
			if (self.$pageIncludes.hasClass('product_categories')) {
                var self = this,
                    $categories = $('.nm-product-categories');

                self.elementProductCategoriesBindLinks($categories);

                if (self.$pageIncludes.hasClass('product_categories_masonry')) {
                    self.$window.on('load', function() {
                        for (var i = 0; i < $categories.length; i++) {
                            self.elementProductCategories($($categories[i]));
                        }
                    });
                }
			}
			
			/* Element: Lightbox */
            if (self.$pageIncludes.hasClass('lightbox')) {
                $('.nm-lightbox').each(function() {
                    self.elementLightbox($(this));
                });
			}
            
            /* Element: Elementor - Tabs */
            if (self.$pageIncludes.hasClass('elementor-tabs')) {
                $('.nm-elementor-tabs').each(function() {
                    self.elementElementorTabs($(this));
                });
            }
		},
        
        
        /**
		 *	Element: Banner
		 */
        elementBanner: function($banners) {
            var self = this;
            
            /* Bind: Banner shop links (AJAX) */
            if (self.isShop && self.filtersEnableAjax) {
                $banners.find('.nm-banner-shop-link').on('click.nmBannerAjax', function(e) {
                    e.preventDefault();
                    var shopUrl = $(this).attr('href');
                    if (shopUrl) {
                        self.shopExternalGetPage($(this).attr('href')); // Smooth-scroll to top, then load shop page
                    }
                });
            }
        },
        
        
        /**
		 *	Element: Banner - Add text animation class
		 */
        elementBannerAddAnimClass: function($slider, currentSlide) {
            // Make sure the slide has changed
            if ($slider.slideIndex != currentSlide) {
                $slider.slideIndex = currentSlide;

                // Remove animation class from previous banner
                if ($slider.$bannerContent) {
                    $slider.$bannerContent.removeClass($slider.bannerAnimation);
                }

                var $slideActive = ($slider.isSlick) ? $slider.find('.slick-track .slick-active') : $slider.children('.flickity-viewport').children('.flickity-slider').children('.is-selected'); // Note: Don't use "currentSlide" index to find the active element (Slick slider's "infinite" setting clones slides)
                $slider.$bannerContent = $slideActive.find('.nm-banner-text-inner');

                if ($slider.$bannerContent.length) {
                    $slider.bannerAnimation = $slider.$bannerContent.data('animate');
                    $slider.$bannerContent.addClass($slider.bannerAnimation);
                }
            }
        },
        
        
        /**
		 *	Element: Banner slider
		 */
        elementBannerSlider: function($slider) {
            var self = this;
            
            $slider.isSlick = ($slider.hasClass('plugin-slick')) ? true : false;

            // Wrap slider's banner elements in a "div" element
            $slider.children().wrap('<div class="nm-banner-slide"></div>');

            if ($slider.isSlick) {
                var slickOptions = {
                    arrows: false,
                    prevArrow: '<a class="slick-prev"><i class="nm-font nm-font-angle-thin-left"></i></a>',
                    nextArrow: '<a class="slick-next"><i class="nm-font nm-font-angle-thin-right"></i></a>',
                    dots: false,
                    edgeFriction: 0,
                    infinite: false,
                    pauseOnHover: false,
                    speed: 350,
                    touchThreshold: 30
                };
                slickOptions = $.extend(slickOptions, $slider.data()); // Extend default slider settings with data attribute settings

                // Slick slider: Event - Init
                $slider.on('init', function() {
                    self.$document.trigger('banner-slider-loaded');
                    self.elementBannerAddAnimClass($slider, 0);
                });

                // Slick slider: Event - After slide change
                $slider.on('afterChange', function(event, slick, currentSlide) {
                    self.elementBannerAddAnimClass($slider, currentSlide);
                });

                // Slick slider: Event - After position/size changes
                $slider.on('setPosition', function(event, slick) {
                    var $slider = slick.$slider,
                        $currentSlide = $(slick.$slides[slick.currentSlide]);
                    self.elementBannerSliderToggleLayoutClass($slider, $currentSlide);
                });

                // Slick slider: Initialize
                $slider.slick(slickOptions);
            } else {
                var sliderOptions = $.extend({}, $slider.data('options')), // Extend default slider options with data attribute options
                    sliderInstance;

                // Flickity: Single event - Initial slide select
                $slider.one('select.flickity', function() {
                    self.$document.trigger('banner-slider-loaded');
                    self.elementBannerAddAnimClass($slider, 0);
                });

                // Flickity: Event - Slide settled at end position
                $slider.on('settle.flickity', function() {
                    var currentSlide = sliderInstance.selectedIndex;
                    self.elementBannerAddAnimClass($slider, currentSlide);
                });

                // Flickity: Initialize
                $slider.flickity(sliderOptions);
                sliderInstance = $slider.data('flickity'); // Get slider instance

                // Flickity: Event: Slide select (keep below .flickity initialization)
                $slider.on('select.flickity', function() {
                    var $slider = $(this),
                        $currentSlide = (sliderInstance) ? $(sliderInstance.selectedElement) : $slider.find('.is-selected'); // In case the instance isn't available
                    self.elementBannerSliderToggleLayoutClass($slider, $currentSlide);
                });
                $slider.trigger('select.flickity'); // Trigger initial event

                // Flickity: Banner text "parallax" effect
                if ($slider.hasClass('has-text-parallax')) {
                    var $text = $slider.find('.nm-banner-text'),
                        x;
                    // Flickity: Event - Triggered when the slider moves
                    $slider.on('scroll.flickity', function(event, progress) {
                        sliderInstance.slides.forEach(function(slide, i) {
                            // Fix for "wrapAround" Flickity option - https://github.com/metafizzy/flickity/issues/468 - Note: This doesn't work with two slides
                            /*if (0 === i) {
                                x = Math.abs(sliderInstance.x) > sliderInstance.slidesWidth ? (sliderInstance.slidesWidth + sliderInstance.x + sliderInstance.slides[sliderInstance.slides.length - 1].outerWidth + slide.target) : slide.target + sliderInstance.x;
                            } else if (i === sliderInstance.slides.length - 1) {
                                x = Math.abs(sliderInstance.x) + sliderInstance.slides[i].outerWidth < sliderInstance.slidesWidth ? (slide.target - sliderInstance.slidesWidth + sliderInstance.x - sliderInstance.slides[i].outerWidth) : slide.target + sliderInstance.x;
                            } else {
                                x = slide.target + sliderInstance.x;
                            }

                            $text[i].style.transform = 'translate3d(' + x * (1/3) + 'px,0,0)';*/
                            // Note: Works with 2 slides, but not with the "wrapAround" option
                            x = (slide.target + sliderInstance.x) * 1/3;
                            $text[i].style.transform = 'translate3d(' + x + 'px,0,0)';
                        });
                    });
                }
            }
        },
        
        
        /**
		 *	Element: Banner slider - Toggle layout class
		 */
        elementBannerSliderToggleLayoutClass: function($slider, $currentSlide) {
            var $currentBanner = $currentSlide.children('.nm-banner');

            // Is the alternative text layout showing?
            if ($currentBanner.hasClass('alt-mobile-layout')) {
                if ($currentBanner.children('.nm-banner-content').css('position') != 'absolute') { // Content container has static/relative position when the alt. layout is showing
                    $slider.addClass('alt-mobile-layout-showing');
                } else {
                    $slider.removeClass('alt-mobile-layout-showing');
                }
            } else {
                $slider.removeClass('alt-mobile-layout-showing');
            }
        },
        
        
        /**
		 *	Element: Product slider
		 */
        elementProductSlider: function($sliderWrap) {
            var $slider = $sliderWrap.find('.nm-products:first'),
                sliderOptions = {
                    adaptiveHeight: true, // NOTE: Doesn't work with multiple slides
                    arrows: false,
                    prevArrow: '<a class="slick-prev"><i class="nm-font nm-font-angle-thin-left"></i></a>',
                    nextArrow: '<a class="slick-next"><i class="nm-font nm-font-angle-thin-right"></i></a>',
                    dots: true,
                    edgeFriction: 0,
                    infinite: false,
                    speed: 350,
                    touchThreshold: 30,
                    slidesToShow: 4,
                    slidesToScroll: 4,
                    responsive: [
                        {
                            breakpoint: 1024,
                            settings: {
                                slidesToShow: 3,
                                slidesToScroll: 3
                            }
                        },
                        {
                            breakpoint: 768,
                            settings: {
                                slidesToShow: 2,
                                slidesToScroll: 2
                            }
                        },
                        {
                            breakpoint: 518,
                            settings: {
                                slidesToShow: 1,
                                slidesToScroll: 1
                            }
                        }
                    ]
                };

            // Extend default slider settings with data attribute settings
            sliderOptions = $.extend(sliderOptions, $sliderWrap.data());

            // Responsive columns
            var colMobile = $sliderWrap.data('slides-to-show-mobile'),
                col_1024 = (parseInt(sliderOptions.slidesToShow) == 2) ? 2 : 3,
                col_768 = (parseInt(colMobile) > 2) ? colMobile : 2,
                col_518 = colMobile;

            // Set responsive columns
            sliderOptions.responsive[0].settings.slidesToShow = col_1024;
            sliderOptions.responsive[0].settings.slidesToScroll = col_1024;
            sliderOptions.responsive[1].settings.slidesToShow = col_768;
            sliderOptions.responsive[1].settings.slidesToScroll = col_768;
            sliderOptions.responsive[2].settings.slidesToShow = col_518;
            sliderOptions.responsive[2].settings.slidesToScroll = col_518;

            $slider.slick(sliderOptions);
        },
        
        
        /**
		 *	Element: Product reviews slider
		 */
        elementProductReviewsSlider: function($sliderWrap) {
            var self = this,
                $slider = $sliderWrap.find('.nm-product-reviews-ul'),
                sliderOptions = {
                    adaptiveHeight: true, // NOTE: Doesn't work with multiple slides
                    arrows: false,
                    prevArrow: '<a class="slick-prev"><i class="nm-font nm-font-angle-thin-left"></i></a>',
                    nextArrow: '<a class="slick-next"><i class="nm-font nm-font-angle-thin-right"></i></a>',
                    dots: true,
                    edgeFriction: 0,
                    infinite: false,
                    speed: 350,
                    touchThreshold: 30,
                    slidesToShow: 4,
                    slidesToScroll: 4,
                    responsive: [
                        {
                            breakpoint: 1024,
                            settings: {
                                slidesToShow: 3,
                                slidesToScroll: 3
                            }
                        },
                        {
                            breakpoint: 768,
                            settings: {
                                slidesToShow: 2,
                                slidesToScroll: 2
                            }
                        },
                        {
                            breakpoint: 518,
                            settings: {
                                slidesToShow: 1,
                                slidesToScroll: 1
                            }
                        }
                    ]
                };

            // Extend default slider settings with data attribute settings
            sliderOptions = $.extend(sliderOptions, $sliderWrap.data());
            
            if (sliderOptions.slidesToShow == 2) {
                // Max. two columns
                sliderOptions.responsive[0].settings.slidesToShow = 2;
                sliderOptions.responsive[0].settings.slidesToScroll = 2;
            }
            
            /* Function: Set slider height based on tallest slide */
            var _sliderSetHeight = function(slider) {
                // Make sure slider is visible (Elementor can trigger resize when dragging)
                if (! $(slider).is(':visible')) { return; }
                
                var activeSlides = [],
                    tallestSlide = 0;

                // Short timeout in order to get correct active slides
                setTimeout(function() {
                    $('.slick-track .slick-active', slider).each(function(item) {
                        activeSlides[item] = $(this).outerHeight();
                    });
                    
                    activeSlides.forEach(function(item) {
                        if (item > tallestSlide) {
                            tallestSlide = item;
                        }
                    });
                    
                    $('.slick-list', slider).css('height', Math.ceil(tallestSlide)+'px');
                }, 10);
            };
            
            // Bind slider init/change/resize events
            $slider.on('init', function(slick) {
                _sliderSetHeight(this);
            });
            $slider.on('beforeChange', function(slick, currentSlide, nextSlide) {
                _sliderSetHeight(this);
            });
            var sliderResizeTimer = null;
            self.$window.on('resize.reviewsSlider', function() {
                if (sliderResizeTimer) { clearTimeout(sliderResizeTimer); }
                sliderResizeTimer = setTimeout(function() { _sliderSetHeight($slider[0]); }, 250);
            });
            
            // Init slider
            $slider.slick(sliderOptions);
        },
        
        
        /**
		 *	Element: Post slider
		 */
        elementPostSlider: function($slider) {
            var sliderOptions = {
                    adaptiveHeight: true, // NOTE: Doesn't work with multiple slides
                    arrows: false,
                    prevArrow: '<a class="slick-prev"><i class="nm-font nm-font-angle-thin-left"></i></a>',
                    nextArrow: '<a class="slick-next"><i class="nm-font nm-font-angle-thin-right"></i></a>',
                    dots: true,
                    edgeFriction: 0,
                    infinite: false,
                    pauseOnHover: false,
                    speed: 350,
                    touchThreshold: 30,
                    slidesToShow: 4,
                    slidesToScroll: 4,
                    responsive: [
                        {
                            breakpoint: 1024,
                            settings: {
                                slidesToShow: 3,
                                slidesToScroll: 3
                            }
                        },
                        {
                            breakpoint: 768,
                            settings: {
                                slidesToShow: 2,
                                slidesToScroll: 2
                            }
                        },
                        {
                            breakpoint: 518,
                            settings: {
                                slidesToShow: 1,
                                slidesToScroll: 1
                            }
                        }
                    ]
                };

            // Extend default slider settings with data attribute settings
            sliderOptions = $.extend(sliderOptions, $slider.data());

            if (sliderOptions.slidesToShow == 2) {
                // Max. two columns
                sliderOptions.responsive[0].settings.slidesToShow = 2;
                sliderOptions.responsive[0].settings.slidesToScroll = 2;
            }

            $slider.slick(sliderOptions);
        },
        
        
        /**
		 *	Element: Product categories
		 */
        elementProductCategories: function($categories) {
            if ($categories.hasClass('masonry-enabled')) {
                var $categoriesUl = $categories.children('.woocommerce').children('ul');

                // Initialize Masonry
                $categoriesUl.masonry({
                    itemSelector: '.product-category',
                    gutter: 0,
                    //horizontalOrder: true,
                    initLayout: false // Disable initial layout
                });

                // Masonry event: "layoutComplete"
                $categoriesUl.masonry('on', 'layoutComplete', function() {
                    $categoriesUl.closest('.nm-product-categories').removeClass('nm-loader'); // Hide preloader
                    $categoriesUl.addClass('show');
                });

                // Trigger initial layout
                $categoriesUl.masonry();
            }
        },
        
        
        /**
		 *	Element: Product categories - Bind shop links
		 */
        elementProductCategoriesBindLinks: function($categories) {
            var self = this;
            
            if (self.isShop && self.filtersEnableAjax) {
                $categories.find('.product-category a').on('click', function(e) {
                    e.preventDefault();
                    // Load shop category page
                    self.shopExternalGetPage($(this).attr('href'));
                });
            }
        },
        
        
        /**
		 *	Element: Lightbox
		 */
        elementLightbox: function($lightbox) {
            $lightbox.on('click', function(e) {
                e.preventDefault();
                e.stopPropagation();

                var $this = $(this),
                    type = $this.data('mfp-type'),
                    lightboxOptions = {
                        mainClass: 'nm-wp-gallery-popup nm-mfp-zoom-in',
                        closeMarkup: '<a class="mfp-close nm-font nm-font-close2"></a>',
                        removalDelay: 180,
                        type: type,
                        closeBtnInside: false,
                        image: {
                            titleSrc: 'data-mfp-title'
                        }
                    };
                
                lightboxOptions.closeOnContentClick = (type == 'inline') ? false : true; // Disable "closeOnContentClick" for inline/HTML lightboxes
                
                $this.magnificPopup(lightboxOptions).magnificPopup('open');
            });
        },
        
        
        /**
		 *	Element: Elementor - Tabs
		 */
        elementElementorTabs: function($tabs) {
            var $tab, $tabActive;

            $tabs.children('.nm-elementor-tabs-wrapper').children('.nm-elementor-tab').on('click', function(e) {
                e.preventDefault();

                $tab = $(this);
                
                if ($tab.hasClass('nm-elementor-active')) { return; }
                
                $tabActive = $tab.closest('.nm-elementor-tabs-wrapper').children('.nm-elementor-active');
                
                // Change tab "active" class
                $tabActive.removeClass('nm-elementor-active');
                $tab.addClass('nm-elementor-active');

                // Change content "active" class
                $('#'+$tabActive.attr('aria-controls')).removeClass('nm-elementor-active');
                $('#'+$tab.attr('aria-controls')).addClass('nm-elementor-active');
            });
        }
        
	};
	
	
	// Add core script to $.nmTheme so it can be extended
	$.nmTheme = NmTheme.prototype;
    
    
    /**
     *  Document ready (".ready()" doesn't work with nested ".load()" functions in jQuery 3.0+)
     *
     *  Source: http://stackoverflow.com/questions/9899372/pure-javascript-equivalent-to-jquerys-ready-how-to-call-a-function-when-the/9899701#9899701
     */
    $.nmReady = function(fn) {
        // See if DOM is already available
        if (document.readyState === 'complete' || document.readyState === 'interactive') {
            // Call on next available tick
            setTimeout(fn, 1);
        } else {
            document.addEventListener('DOMContentLoaded', fn);
        }
    };
    
    
    $.nmReady(function() {
		// Initialize script
		$.nmThemeInstance = new NmTheme();
	});
	
})(jQuery);