/* Minification failed. Returning unminified contents.
(1909,142-149): run-time error JS1019: Can't have 'break' outside of loop: break n
(1908,451-458): run-time error JS1019: Can't have 'break' outside of loop: break n
 */
/**
 * Copyright 2010 Tim Down.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * jshashtable
 *
 * jshashtable is a JavaScript implementation of a hash table. It creates a single constructor function called Hashtable
 * in the global scope.
 *
 * Author: Tim Down <tim@timdown.co.uk>
 * Version: 2.1
 * Build date: 21 March 2010
 * Website: http://www.timdown.co.uk/jshashtable
 */

var Hashtable = (function() {
	var FUNCTION = "function";

	var arrayRemoveAt = (typeof Array.prototype.splice == FUNCTION) ?
		function(arr, idx) {
			arr.splice(idx, 1);
		} :

		function(arr, idx) {
			var itemsAfterDeleted, i, len;
			if (idx === arr.length - 1) {
				arr.length = idx;
			} else {
				itemsAfterDeleted = arr.slice(idx + 1);
				arr.length = idx;
				for (i = 0, len = itemsAfterDeleted.length; i < len; ++i) {
					arr[idx + i] = itemsAfterDeleted[i];
				}
			}
		};

	function hashObject(obj) {
		var hashCode;
		if (typeof obj == "string") {
			return obj;
		} else if (typeof obj.hashCode == FUNCTION) {
			// Check the hashCode method really has returned a string
			hashCode = obj.hashCode();
			return (typeof hashCode == "string") ? hashCode : hashObject(hashCode);
		} else if (typeof obj.toString == FUNCTION) {
			return obj.toString();
		} else {
			try {
				return String(obj);
			} catch (ex) {
				// For host objects (such as ActiveObjects in IE) that have no toString() method and throw an error when
				// passed to String()
				return Object.prototype.toString.call(obj);
			}
		}
	}

	function equals_fixedValueHasEquals(fixedValue, variableValue) {
		return fixedValue.equals(variableValue);
	}

	function equals_fixedValueNoEquals(fixedValue, variableValue) {
		return (typeof variableValue.equals == FUNCTION) ?
			   variableValue.equals(fixedValue) : (fixedValue === variableValue);
	}

	function createKeyValCheck(kvStr) {
		return function(kv) {
			if (kv === null) {
				throw new Error("null is not a valid " + kvStr);
			} else if (typeof kv == "undefined") {
				throw new Error(kvStr + " must not be undefined");
			}
		};
	}

	var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value");

	/*----------------------------------------------------------------------------------------------------------------*/

	function Bucket(hash, firstKey, firstValue, equalityFunction) {
        this[0] = hash;
		this.entries = [];
		this.addEntry(firstKey, firstValue);

		if (equalityFunction !== null) {
			this.getEqualityFunction = function() {
				return equalityFunction;
			};
		}
	}

	var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2;

	function createBucketSearcher(mode) {
		return function(key) {
			var i = this.entries.length, entry, equals = this.getEqualityFunction(key);
			while (i--) {
				entry = this.entries[i];
				if ( equals(key, entry[0]) ) {
					switch (mode) {
						case EXISTENCE:
							return true;
						case ENTRY:
							return entry;
						case ENTRY_INDEX_AND_VALUE:
							return [ i, entry[1] ];
					}
				}
			}
			return false;
		};
	}

	function createBucketLister(entryProperty) {
		return function(aggregatedArr) {
			var startIndex = aggregatedArr.length;
			for (var i = 0, len = this.entries.length; i < len; ++i) {
				aggregatedArr[startIndex + i] = this.entries[i][entryProperty];
			}
		};
	}

	Bucket.prototype = {
		getEqualityFunction: function(searchValue) {
			return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals;
		},

		getEntryForKey: createBucketSearcher(ENTRY),

		getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE),

		removeEntryForKey: function(key) {
			var result = this.getEntryAndIndexForKey(key);
			if (result) {
				arrayRemoveAt(this.entries, result[0]);
				return result[1];
			}
			return null;
		},

		addEntry: function(key, value) {
			this.entries[this.entries.length] = [key, value];
		},

		keys: createBucketLister(0),

		values: createBucketLister(1),

		getEntries: function(entries) {
			var startIndex = entries.length;
			for (var i = 0, len = this.entries.length; i < len; ++i) {
				// Clone the entry stored in the bucket before adding to array
				entries[startIndex + i] = this.entries[i].slice(0);
			}
		},

		containsKey: createBucketSearcher(EXISTENCE),

		containsValue: function(value) {
			var i = this.entries.length;
			while (i--) {
				if ( value === this.entries[i][1] ) {
					return true;
				}
			}
			return false;
		}
	};

	/*----------------------------------------------------------------------------------------------------------------*/

	// Supporting functions for searching hashtable buckets

	function searchBuckets(buckets, hash) {
		var i = buckets.length, bucket;
		while (i--) {
			bucket = buckets[i];
			if (hash === bucket[0]) {
				return i;
			}
		}
		return null;
	}

	function getBucketForHash(bucketsByHash, hash) {
		var bucket = bucketsByHash[hash];

		// Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype
		return ( bucket && (bucket instanceof Bucket) ) ? bucket : null;
	}

	/*----------------------------------------------------------------------------------------------------------------*/

	function Hashtable(hashingFunctionParam, equalityFunctionParam) {
		var that = this;
		var buckets = [];
		var bucketsByHash = {};

		var hashingFunction = (typeof hashingFunctionParam == FUNCTION) ? hashingFunctionParam : hashObject;
		var equalityFunction = (typeof equalityFunctionParam == FUNCTION) ? equalityFunctionParam : null;

		this.put = function(key, value) {
			checkKey(key);
			checkValue(value);
			var hash = hashingFunction(key), bucket, bucketEntry, oldValue = null;

			// Check if a bucket exists for the bucket key
			bucket = getBucketForHash(bucketsByHash, hash);
			if (bucket) {
				// Check this bucket to see if it already contains this key
				bucketEntry = bucket.getEntryForKey(key);
				if (bucketEntry) {
					// This bucket entry is the current mapping of key to value, so replace old value and we're done.
					oldValue = bucketEntry[1];
					bucketEntry[1] = value;
				} else {
					// The bucket does not contain an entry for this key, so add one
					bucket.addEntry(key, value);
				}
			} else {
				// No bucket exists for the key, so create one and put our key/value mapping in
				bucket = new Bucket(hash, key, value, equalityFunction);
				buckets[buckets.length] = bucket;
				bucketsByHash[hash] = bucket;
			}
			return oldValue;
		};

		this.get = function(key) {
			checkKey(key);

			var hash = hashingFunction(key);

			// Check if a bucket exists for the bucket key
			var bucket = getBucketForHash(bucketsByHash, hash);
			if (bucket) {
				// Check this bucket to see if it contains this key
				var bucketEntry = bucket.getEntryForKey(key);
				if (bucketEntry) {
					// This bucket entry is the current mapping of key to value, so return the value.
					return bucketEntry[1];
				}
			}
			return null;
		};

		this.containsKey = function(key) {
			checkKey(key);
			var bucketKey = hashingFunction(key);

			// Check if a bucket exists for the bucket key
			var bucket = getBucketForHash(bucketsByHash, bucketKey);

			return bucket ? bucket.containsKey(key) : false;
		};

		this.containsValue = function(value) {
			checkValue(value);
			var i = buckets.length;
			while (i--) {
				if (buckets[i].containsValue(value)) {
					return true;
				}
			}
			return false;
		};

		this.clear = function() {
			buckets.length = 0;
			bucketsByHash = {};
		};

		this.isEmpty = function() {
			return !buckets.length;
		};

		var createBucketAggregator = function(bucketFuncName) {
			return function() {
				var aggregated = [], i = buckets.length;
				while (i--) {
					buckets[i][bucketFuncName](aggregated);
				}
				return aggregated;
			};
		};

		this.keys = createBucketAggregator("keys");
		this.values = createBucketAggregator("values");
		this.entries = createBucketAggregator("getEntries");

		this.remove = function(key) {
			checkKey(key);

			var hash = hashingFunction(key), bucketIndex, oldValue = null;

			// Check if a bucket exists for the bucket key
			var bucket = getBucketForHash(bucketsByHash, hash);

			if (bucket) {
				// Remove entry from this bucket for this key
				oldValue = bucket.removeEntryForKey(key);
				if (oldValue !== null) {
					// Entry was removed, so check if bucket is empty
					if (!bucket.entries.length) {
						// Bucket is empty, so remove it from the bucket collections
						bucketIndex = searchBuckets(buckets, hash);
						arrayRemoveAt(buckets, bucketIndex);
						delete bucketsByHash[hash];
					}
				}
			}
			return oldValue;
		};

		this.size = function() {
			var total = 0, i = buckets.length;
			while (i--) {
				total += buckets[i].entries.length;
			}
			return total;
		};

		this.each = function(callback) {
			var entries = that.entries(), i = entries.length, entry;
			while (i--) {
				entry = entries[i];
				callback(entry[0], entry[1]);
			}
		};

		this.putAll = function(hashtable, conflictCallback) {
			var entries = hashtable.entries();
			var entry, key, value, thisValue, i = entries.length;
			var hasConflictCallback = (typeof conflictCallback == FUNCTION);
			while (i--) {
				entry = entries[i];
				key = entry[0];
				value = entry[1];

				// Check for a conflict. The default behaviour is to overwrite the value for an existing key
				if ( hasConflictCallback && (thisValue = that.get(key)) ) {
					value = conflictCallback(key, thisValue, value);
				}
				that.put(key, value);
			}
		};

		this.clone = function() {
			var clone = new Hashtable(hashingFunctionParam, equalityFunctionParam);
			clone.putAll(that);
			return clone;
		};
	}

	return Hashtable;
})();

/**
 * jquery.numberformatter - Formatting/Parsing Numbers in jQuery
 * 
 * Written by
 * Michael Abernethy (mike@abernethysoft.com),
 * Andrew Parry (aparry0@gmail.com)
 *
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * @author Michael Abernethy, Andrew Parry
 * @version 1.2.3-SNAPSHOT ($Id$)
 * 
 * Dependencies
 * 
 * jQuery (http://jquery.com)
 * jshashtable (http://www.timdown.co.uk/jshashtable)
 * 
 * Notes & Thanks
 * 
 * many thanks to advweb.nanasi.jp for his bug fixes
 * jsHashtable is now used also, so thanks to the author for that excellent little class.
 *
 * This plugin can be used to format numbers as text and parse text as Numbers
 * Because we live in an international world, we cannot assume that everyone
 * uses "," to divide thousands, and "." as a decimal point.
 *
 * As of 1.2 the way this plugin works has changed slightly, parsing text to a number
 * has 1 set of functions, formatting a number to text has it's own. Before things
 * were a little confusing, so I wanted to separate the 2 out more.
 *
 *
 * jQuery extension functions:
 *
 * formatNumber(options, writeBack, giveReturnValue) - Reads the value from the subject, parses to
 * a Javascript Number object, then formats back to text using the passed options and write back to
 * the subject.
 * 
 * parseNumber(options) - Parses the value in the subject to a Number object using the passed options
 * to decipher the actual number from the text, then writes the value as text back to the subject.
 * 
 * 
 * Generic functions:
 * 
 * formatNumber(numberString, options) - Takes a plain number as a string (e.g. '1002.0123') and returns
 * a string of the given format options.
 * 
 * parseNumber(numberString, options) - Takes a number as text that is formatted the same as the given
 * options then and returns it as a plain Number object.
 * 
 * To achieve the old way of combining parsing and formatting to keep say a input field always formatted
 * to a given format after it has lost focus you'd simply use a combination of the functions.
 * 
 * e.g.
 * $("#salary").blur(function(){
 * 		$(this).parseNumber({format:"#,###.00", locale:"us"});
 * 		$(this).formatNumber({format:"#,###.00", locale:"us"});
 * });
 *
 * The syntax for the formatting is:
 * 0 = Digit
 * # = Digit, zero shows as absent
 * . = Decimal separator
 * - = Negative sign
 * , = Grouping Separator
 * % = Percent (multiplies the number by 100)
 * 
 * For example, a format of "#,###.00" and text of 4500.20 will
 * display as "4.500,20" with a locale of "de", and "4,500.20" with a locale of "us"
 *
 *
 * As of now, the only acceptable locales are 
 * Arab Emirates -> "ae"
 * Australia -> "au"
 * Austria -> "at"
 * Brazil -> "br"
 * Canada -> "ca"
 * China -> "cn"
 * Czech -> "cz"
 * Denmark -> "dk"
 * Egypt -> "eg"
 * Finland -> "fi"
 * France  -> "fr"
 * Germany -> "de"
 * Greece -> "gr"
 * Great Britain -> "gb"
 * Hong Kong -> "hk"
 * India -> "in"
 * Israel -> "il"
 * Japan -> "jp"
 * Russia -> "ru"
 * South Korea -> "kr"
 * Spain -> "es"
 * Sweden -> "se"
 * Switzerland -> "ch"
 * Taiwan -> "tw"
 * Thailand -> "th"
 * United States -> "us"
 * Vietnam -> "vn"
 **/

(function(jQuery) {

	var nfLocales = new Hashtable();
	
	var nfLocalesLikeUS = [ 'ae','au','ca','cn','eg','gb','hk','il','in','jp','sk','th','tw','us' ];
	var nfLocalesLikeDE = [ 'at','br','de','dk','es','gr','it','nl','pt','tr','vn' ];
	var nfLocalesLikeFR = [ 'cz','fi','fr','ru','se','pl' ];
	var nfLocalesLikeCH = [ 'ch' ];
	
	var nfLocaleFormatting = [ [".", ","], [",", "."], [",", " "], [".", "'"] ]; 
	var nfAllLocales = [ nfLocalesLikeUS, nfLocalesLikeDE, nfLocalesLikeFR, nfLocalesLikeCH ]

	function FormatData(dec, group, neg) {
		this.dec = dec;
		this.group = group;
		this.neg = neg;
	};

	function init() {
		// write the arrays into the hashtable
		for (var localeGroupIdx = 0; localeGroupIdx < nfAllLocales.length; localeGroupIdx++) {
			localeGroup = nfAllLocales[localeGroupIdx];
			for (var i = 0; i < localeGroup.length; i++) {
				nfLocales.put(localeGroup[i], localeGroupIdx);
			}
		}
	};

	function formatCodes(locale, isFullLocale) {
		if (nfLocales.size() == 0)
			init();

         // default values
         var dec = ".";
         var group = ",";
         var neg = "-";
         
         if (isFullLocale == false) {
	         // Extract and convert to lower-case any language code from a real 'locale' formatted string, if not use as-is
	         // (To prevent locale format like : "fr_FR", "en_US", "de_DE", "fr_FR", "en-US", "de-DE")
	         if (locale.indexOf('_') != -1)
				locale = locale.split('_')[1].toLowerCase();
			 else if (locale.indexOf('-') != -1)
				locale = locale.split('-')[1].toLowerCase();
		}

		 // hashtable lookup to match locale with codes
		 var codesIndex = nfLocales.get(locale);
		 if (codesIndex) {
		 	var codes = nfLocaleFormatting[codesIndex];
			if (codes) {
				dec = codes[0];
				group = codes[1];
			}
		 }
		 return new FormatData(dec, group, neg);
    };
	
	
	/*	Formatting Methods	*/
	
	
	/**
	 * Formats anything containing a number in standard js number notation.
	 * 
	 * @param {Object}	options			The formatting options to use
	 * @param {Boolean}	writeBack		(true) If the output value should be written back to the subject
	 * @param {Boolean} giveReturnValue	(true) If the function should return the output string
	 */
	jQuery.fn.formatNumber = function(options, writeBack, giveReturnValue) {
	
		return this.each(function() {
			// enforce defaults
			if (writeBack == null)
				writeBack = true;
			if (giveReturnValue == null)
				giveReturnValue = true;
			
			// get text
			var text;
			if (jQuery(this).is(":input"))
				text = new String(jQuery(this).val());
			else
				text = new String(jQuery(this).text());

			// format
			var returnString = jQuery.formatNumber(text, options);
		
			// set formatted string back, only if a success
//			if (returnString) {
				if (writeBack) {
					if (jQuery(this).is(":input"))
						jQuery(this).val(returnString);
					else
						jQuery(this).text(returnString);
				}
				if (giveReturnValue)
					return returnString;
//			}
//			return '';
		});
	};
	
	/**
	 * First parses a string and reformats it with the given options.
	 * 
	 * @param {Object} numberString
	 * @param {Object} options
	 */
	jQuery.formatNumber = function(numberString, options){
		var options = jQuery.extend({}, jQuery.fn.formatNumber.defaults, options);
		var formatData = formatCodes(options.locale.toLowerCase(), options.isFullLocale);
		
		var dec = formatData.dec;
		var group = formatData.group;
		var neg = formatData.neg;
		
		var validFormat = "0#-,.";
		
		// strip all the invalid characters at the beginning and the end
		// of the format, and we'll stick them back on at the end
		// make a special case for the negative sign "-" though, so 
		// we can have formats like -$23.32
		var prefix = "";
		var negativeInFront = false;
		for (var i = 0; i < options.format.length; i++) {
			if (validFormat.indexOf(options.format.charAt(i)) == -1) 
				prefix = prefix + options.format.charAt(i);
			else 
				if (i == 0 && options.format.charAt(i) == '-') {
					negativeInFront = true;
					continue;
				}
				else 
					break;
		}
		var suffix = "";
		for (var i = options.format.length - 1; i >= 0; i--) {
			if (validFormat.indexOf(options.format.charAt(i)) == -1) 
				suffix = options.format.charAt(i) + suffix;
			else 
				break;
		}
		
		options.format = options.format.substring(prefix.length);
		options.format = options.format.substring(0, options.format.length - suffix.length);
		
		// now we need to convert it into a number
		//while (numberString.indexOf(group) > -1) 
		//	numberString = numberString.replace(group, '');
		//var number = new Number(numberString.replace(dec, ".").replace(neg, "-"));
		var number = new Number(numberString);
		
		return jQuery._formatNumber(number, options, suffix, prefix, negativeInFront);
	};
	
	/**
	 * Formats a Number object into a string, using the given formatting options
	 * 
	 * @param {Object} numberString
	 * @param {Object} options
	 */
	jQuery._formatNumber = function(number, options, suffix, prefix, negativeInFront) {
		var options = jQuery.extend({}, jQuery.fn.formatNumber.defaults, options);
		var formatData = formatCodes(options.locale.toLowerCase(), options.isFullLocale);
		
		var dec = formatData.dec;
		var group = formatData.group;
		var neg = formatData.neg;
		
		var forcedToZero = false;
		if (isNaN(number)) {
			if (options.nanForceZero == true) {
				number = 0;
				forcedToZero = true;
			} else 
				return null;
		}

		// special case for percentages
        if (suffix == "%")
        	number = number * 100;

		var returnString = "";
		if (options.format.indexOf(".") > -1) {
			var decimalPortion = dec;
			var decimalFormat = options.format.substring(options.format.lastIndexOf(".") + 1);
			
			// round or truncate number as needed
			if (options.round == true)
				number = new Number(number.toFixed(decimalFormat.length));
			else {
				var numStr = number.toString();
				numStr = numStr.substring(0, numStr.lastIndexOf('.') + decimalFormat.length + 1);
				number = new Number(numStr);
			}
			
			var decimalValue = number % 1;
			var decimalString = new String(decimalValue.toFixed(decimalFormat.length));
			decimalString = decimalString.substring(decimalString.lastIndexOf(".") + 1);
			
			for (var i = 0; i < decimalFormat.length; i++) {
				if (decimalFormat.charAt(i) == '#' && decimalString.charAt(i) != '0') {
                	decimalPortion += decimalString.charAt(i);
					continue;
				} else if (decimalFormat.charAt(i) == '#' && decimalString.charAt(i) == '0') {
					var notParsed = decimalString.substring(i);
					if (notParsed.match('[1-9]')) {
						decimalPortion += decimalString.charAt(i);
						continue;
					} else
						break;
				} else if (decimalFormat.charAt(i) == "0")
					decimalPortion += decimalString.charAt(i);
			}
			returnString += decimalPortion
         } else
			number = Math.round(number);

		var ones = Math.floor(number);
		if (number < 0)
			ones = Math.ceil(number);

		var onesFormat = "";
		if (options.format.indexOf(".") == -1)
			onesFormat = options.format;
		else
			onesFormat = options.format.substring(0, options.format.indexOf("."));

		var onePortion = "";
		if (!(ones == 0 && onesFormat.substr(onesFormat.length - 1) == '#') || forcedToZero) {
			// find how many digits are in the group
			var oneText = new String(Math.abs(ones));
			var groupLength = 9999;
			if (onesFormat.lastIndexOf(",") != -1)
				groupLength = onesFormat.length - onesFormat.lastIndexOf(",") - 1;
			var groupCount = 0;
			for (var i = oneText.length - 1; i > -1; i--) {
				onePortion = oneText.charAt(i) + onePortion;
				groupCount++;
				if (groupCount == groupLength && i != 0) {
					onePortion = group + onePortion;
					groupCount = 0;
				}
			}
			
			// account for any pre-data padding
			if (onesFormat.length > onePortion.length) {
				var padStart = onesFormat.indexOf('0');
				if (padStart != -1) {
					var padLen = onesFormat.length - padStart;
					
					// pad to left with 0's or group char
					var pos = onesFormat.length - onePortion.length - 1;
					while (onePortion.length < padLen) {
						var padChar = onesFormat.charAt(pos);
						// replace with real group char if needed
						if (padChar == ',')
							padChar = group;
						onePortion = padChar + onePortion;
						pos--;
					}
				}
			}
		}
		
		if (!onePortion && onesFormat.indexOf('0', onesFormat.length - 1) !== -1)
   			onePortion = '0';

		returnString = onePortion + returnString;

		// handle special case where negative is in front of the invalid characters
		if (number < 0 && negativeInFront && prefix.length > 0)
			prefix = neg + prefix;
		else if (number < 0)
			returnString = neg + returnString;
		
		if (!options.decimalSeparatorAlwaysShown) {
			if (returnString.lastIndexOf(dec) == returnString.length - 1) {
				returnString = returnString.substring(0, returnString.length - 1);
			}
		}
		returnString = prefix + returnString + suffix;
		return returnString;
	};


	/*	Parsing Methods	*/


	/**
	 * Parses a number of given format from the element and returns a Number object.
	 * @param {Object} options
	 */
	jQuery.fn.parseNumber = function(options, writeBack, giveReturnValue) {
		// enforce defaults
		if (writeBack == null)
			writeBack = true;
		if (giveReturnValue == null)
			giveReturnValue = true;
		
		// get text
		var text;
		if (jQuery(this).is(":input"))
			text = new String(jQuery(this).val());
		else
			text = new String(jQuery(this).text());
	
		// parse text
		var number = jQuery.parseNumber(text, options);
		
		if (number) {
			if (writeBack) {
				if (jQuery(this).is(":input"))
					jQuery(this).val(number.toString());
				else
					jQuery(this).text(number.toString());
			}
			if (giveReturnValue)
				return number;
		}
	};
	
	/**
	 * Parses a string of given format into a Number object.
	 * 
	 * @param {Object} string
	 * @param {Object} options
	 */
	jQuery.parseNumber = function(numberString, options) {
		var options = jQuery.extend({}, jQuery.fn.parseNumber.defaults, options);
		var formatData = formatCodes(options.locale.toLowerCase(), options.isFullLocale);

		var dec = formatData.dec;
		var group = formatData.group;
		var neg = formatData.neg;

		var valid = "1234567890.-";
		
		// now we need to convert it into a number
		while (numberString.indexOf(group)>-1)
			numberString = numberString.replace(group,'');
		numberString = numberString.replace(dec,".").replace(neg,"-");
		var validText = "";
		var hasPercent = false;
		if (numberString.charAt(numberString.length - 1) == "%" || options.isPercentage == true)
			hasPercent = true;
		for (var i=0; i<numberString.length; i++) {
			if (valid.indexOf(numberString.charAt(i))>-1)
				validText = validText + numberString.charAt(i);
		}
		var number = new Number(validText);
		if (hasPercent) {
			number = number / 100;
			var decimalPos = validText.indexOf('.');
			if (decimalPos != -1) {
				var decimalPoints = validText.length - decimalPos - 1;
				number = number.toFixed(decimalPoints + 2);
			} else {
				number = number.toFixed(validText.length - 1);
			}
		}

		return number;
	};

	jQuery.fn.parseNumber.defaults = {
		locale: "us",
		decimalSeparatorAlwaysShown: false,
		isPercentage: false,
		isFullLocale: false
	};

	jQuery.fn.formatNumber.defaults = {
		format: "#,###.00",
		locale: "us",
		decimalSeparatorAlwaysShown: false,
		nanForceZero: true,
		round: true,
		isFullLocale: false
	};
	
	Number.prototype.toFixed = function(precision) {
    	return jQuery._roundNumber(this, precision);
	};
	
	jQuery._roundNumber = function(number, decimalPlaces) {
		var power = Math.pow(10, decimalPlaces || 0);
    	var value = String(Math.round(number * power) / power);
    	
    	// ensure the decimal places are there
    	if (decimalPlaces > 0) {
    		var dp = value.indexOf(".");
    		if (dp == -1) {
    			value += '.';
    			dp = 0;
    		} else {
    			dp = value.length - (dp + 1);
    		}
    		
    		while (dp < decimalPlaces) {
    			value += '0';
    			dp++;
    		}
    	}
    	return value;
	};

 })(jQuery);
 
 // Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
  var cache = {};
  
  this.tmpl = function tmpl(str, data){
    // Figure out if we're getting a template, or if we need to
    // load the template - and be sure to cache the result.
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
      
      // Generate a reusable function that will serve as a template
      // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
        
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
        
        // Convert the template into pure JavaScript
        str
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');");
    
    // Provide some basic currying to the user
    return data ? fn( data ) : fn;
  };
})();

/**
 * jquery.dependClass - Attach class based on first class in list of current element
 * 
 * Written by
 * Egor Khmelev (hmelyoff@gmail.com)
 *
 * Licensed under the MIT (MIT-LICENSE.txt).
 *
 * @author Egor Khmelev
 * @version 0.1.0-BETA ($Id$)
 * 
 **/

(function($) {
	$.baseClass = function(obj){
	  obj = $(obj);
	  return obj.get(0).className.match(/([^ ]+)/)[1];
	};
	
	$.fn.addDependClass = function(className, delimiter){
		var options = {
		  delimiter: delimiter ? delimiter : '-'
		}
		return this.each(function(){
		  var baseClass = $.baseClass(this);
		  if(baseClass)
    		$(this).addClass(baseClass + options.delimiter + className);
		});
	};

	$.fn.removeDependClass = function(className, delimiter){
		var options = {
		  delimiter: delimiter ? delimiter : '-'
		}
		return this.each(function(){
		  var baseClass = $.baseClass(this);
		  if(baseClass)
    		$(this).removeClass(baseClass + options.delimiter + className);
		});
	};

	$.fn.toggleDependClass = function(className, delimiter){
		var options = {
		  delimiter: delimiter ? delimiter : '-'
		}
		return this.each(function(){
		  var baseClass = $.baseClass(this);
		  if(baseClass)
		    if($(this).is("." + baseClass + options.delimiter + className))
    		  $(this).removeClass(baseClass + options.delimiter + className);
    		else
    		  $(this).addClass(baseClass + options.delimiter + className);
		});
	};

})(jQuery);

/**
 * draggable - Class allows to make any element draggable
 * 
 * Written by
 * Egor Khmelev (hmelyoff@gmail.com)
 *
 * Licensed under the MIT (MIT-LICENSE.txt).
 *
 * @author Egor Khmelev
 * @version 0.1.0-BETA ($Id$)
 * 
 **/

(function( $ ){

  function Draggable(){
  	this._init.apply( this, arguments );
  };

	Draggable.prototype.oninit = function(){
	  
	};
	
	Draggable.prototype.events = function(){
	  
	};
	
	Draggable.prototype.onmousedown = function(){
		this.ptr.css({ position: "absolute" });
	};
	
	Draggable.prototype.onmousemove = function( evt, x, y ){
		this.ptr.css({ left: x, top: y });
	};
	
	Draggable.prototype.onmouseup = function(){
	  
	};

	Draggable.prototype.isDefault = {
		drag: false,
		clicked: false,
		toclick: true,
		mouseup: false
	};

	Draggable.prototype._init = function(){
		if( arguments.length > 0 ){
			this.ptr = $(arguments[0]);
			this.outer = $(".draggable-outer");

			this.is = {};
			$.extend( this.is, this.isDefault );

			var _offset = this.ptr.offset();
			this.d = {
				left: _offset.left,
				top: _offset.top,
				width: this.ptr.width(),
				height: this.ptr.height()
			};

			this.oninit.apply( this, arguments );

			this._events();
		}
	};
	
	Draggable.prototype._getPageCoords = function( event ){
	  if( event.targetTouches && event.targetTouches[0] ){
	    return { x: event.targetTouches[0].pageX, y: event.targetTouches[0].pageY };
	  } else
	    return { x: event.pageX, y: event.pageY };
	};
	
	Draggable.prototype._bindEvent = function( ptr, eventType, handler ){
	  var self = this;

	  if( this.supportTouches_ )
      ptr.get(0).addEventListener( this.events_[ eventType ], handler, false );
	  
	  else
	    ptr.bind( this.events_[ eventType ], handler );
	};
	
	Draggable.prototype._events = function(){
		var self = this;

	this.supportTouches_ = false;// 'ontouchend' in document;
    this.events_ = {
      "click": this.supportTouches_ ? "touchstart" : "click",
      "down": this.supportTouches_ ? "touchstart" : "mousedown",
      "move": this.supportTouches_ ? "touchmove" : "mousemove",
      "up"  : this.supportTouches_ ? "touchend" : "mouseup"
    };

    this._bindEvent( $( document ), "move", function( event ){
			if( self.is.drag ){
        event.stopPropagation();
        event.preventDefault();
				self._mousemove( event );
			}
		});
    this._bindEvent( $( document ), "down", function( event ){
			if( self.is.drag ){
        event.stopPropagation();
        event.preventDefault();
			}
		});
    this._bindEvent( $( document ), "up", function( event ){
			self._mouseup( event );
		});
		
    this._bindEvent( this.ptr, "down", function( event ){
			self._mousedown( event );
			return false;
		});
    this._bindEvent( this.ptr, "up", function( event ){
			self._mouseup( event );
		});
		
		this.ptr.find("a")
			.click(function(){
				self.is.clicked = true;

				if( !self.is.toclick ){
					self.is.toclick = true;
					return false;
				}
			})
			.mousedown(function( event ){
				self._mousedown( event );
				return false;
			});

		this.events();
	};
	
	Draggable.prototype._mousedown = function( evt ){
		this.is.drag = true;
		this.is.clicked = false;
		this.is.mouseup = false;

		var _offset = this.ptr.offset();
		var coords = this._getPageCoords( evt );
		this.cx = coords.x - _offset.left;
		this.cy = coords.y - _offset.top;

		$.extend(this.d, {
			left: _offset.left,
			top: _offset.top,
			width: this.ptr.width(),
			height: this.ptr.height()
		});

		if( this.outer && this.outer.get(0) ){
			this.outer.css({ height: Math.max(this.outer.height(), $(document.body).height()), overflow: "hidden" });
		}

		this.onmousedown( evt );
	};
	
	Draggable.prototype._mousemove = function( evt ){
		this.is.toclick = false;
		var coords = this._getPageCoords( evt );
		this.onmousemove( evt, coords.x - this.cx, coords.y - this.cy );
	};
	
	Draggable.prototype._mouseup = function( evt ){
		var oThis = this;

		if( this.is.drag ){
			this.is.drag = false;

			if( this.outer && this.outer.get(0) ){

				if( $.browser.mozilla ){
					this.outer.css({ overflow: "hidden" });
				} else {
					this.outer.css({ overflow: "visible" });
				}

        if( $.browser.msie && $.browser.version == '6.0' ){
         this.outer.css({ height: "100%" });
        } else {
         this.outer.css({ height: "auto" });
        }  
			}

			this.onmouseup( evt );
		}
	};
	
	window.Draggable = Draggable;

})( jQuery );

/**
 * jquery.slider - Slider ui control in jQuery
 * 
 * Written by
 * Egor Khmelev (hmelyoff@gmail.com)
 *
 * Licensed under the MIT (MIT-LICENSE.txt).
 *
 * @author Egor Khmelev
 * @version 1.1.0-RELEASE ($Id$)
 * 
 * Dependencies
 * 
 * jQuery (http://jquery.com)
 * jquery.numberformatter (http://code.google.com/p/jquery-numberformatter/)
 * tmpl (http://ejohn.org/blog/javascript-micro-templating/)
 * jquery.dependClass
 * draggable
 * 
 **/

(function( $ ) {
  
  function isArray( value ){
    if( typeof value == "undefined" ) return false;
    
    if (value instanceof Array || (!(value instanceof Object) &&
         (Object.prototype.toString.call((value)) == '[object Array]') ||
         typeof value.length == 'number' &&
         typeof value.splice != 'undefined' &&
         typeof value.propertyIsEnumerable != 'undefined' &&
         !value.propertyIsEnumerable('splice')
        )) {
      return true;
    }
    
    return false;
  }

	$.slider = function( node, settings ){
	  var jNode = $(node);
	  if( !jNode.data( "jslider" ) )
	    jNode.data( "jslider", new jSlider( node, settings ) );
	  
	  return jNode.data( "jslider" );
	};
	
	$.fn.slider = function( action, opt_value ){
	  var returnValue, args = arguments;
	  
	  function isDef( val ){
	    return val !== undefined;
	  };

	  function isDefAndNotNull( val ){
      return val != null;
	  };
	  
		this.each(function(){
		  var self = $.slider( this, action );
		  
		  // do actions
		  if( typeof action == "string" ){
		    switch( action ){
		      case "value":
		        if( isDef( args[ 1 ] ) && isDef( args[ 2 ] ) ){
		          var pointers = self.getPointers();
		          if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){
		            pointers[0].set( args[ 1 ] );
		            pointers[0].setIndexOver();
		          }
		          
		          if( isDefAndNotNull( pointers[1] ) && isDefAndNotNull( args[2] ) ){
		            pointers[1].set( args[ 2 ] );
		            pointers[1].setIndexOver();
		          }
		        }
		        
		        else if( isDef( args[ 1 ] ) ){
		          var pointers = self.getPointers();
		          if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){
		            pointers[0].set( args[ 1 ] );
		            pointers[0].setIndexOver();
		          }
		        }
		        
		        else
  		        returnValue = self.getValue();

		        break;

		      case "prc":
		        if( isDef( args[ 1 ] ) && isDef( args[ 2 ] ) ){
		          var pointers = self.getPointers();
		          if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){
		            pointers[0]._set( args[ 1 ] );
		            pointers[0].setIndexOver();
		          }

		          if( isDefAndNotNull( pointers[1] ) && isDefAndNotNull( args[2] ) ){
		            pointers[1]._set( args[ 2 ] );
		            pointers[1].setIndexOver();
		          }
		        }

		        else if( isDef( args[ 1 ] ) ){
		          var pointers = self.getPointers();
		          if( isDefAndNotNull( pointers[0] ) && isDefAndNotNull( args[1] ) ){
		            pointers[0]._set( args[ 1 ] );
		            pointers[0].setIndexOver();
		          }
		        }

		        else
  		        returnValue = self.getPrcValue();

		        break;

  		    case "calculatedValue":
  		      var value = self.getValue().split(";");
  		      returnValue = "";
  		      for (var i=0; i < value.length; i++) {
  		        returnValue += (i > 0 ? ";" : "") + self.nice( value[i] );
  		      };
  		      
  		      break;
  		      
  		    case "skin":
		        self.setSkin( args[1] );

  		      break;
		    };
		  
		  }
		  
		  // return actual object
		  else if( !action && !opt_value ){
		    if( !isArray( returnValue ) )
		      returnValue = [];

		    returnValue.push( self );
		  }
		});
		
		// flatten array just with one slider
		if( isArray( returnValue ) && returnValue.length == 1 )
		  returnValue = returnValue[ 0 ];
		
		return returnValue || this;
	};
  
  var OPTIONS = {

    settings: {
      from: 1,
      to: 10,
      step: 1,
      smooth: true,
      limits: true,
      round: 0,
      format: { format: "#,##0.##" },
      value: "5;7",
      dimension: ""
    },
    
    className: "jslider",
    selector: ".jslider-",

    template: tmpl(
      '<span class="<%=className%>">' +
        '<table><tr><td>' +
          '<div class="<%=className%>-bg">' +
            '<i class="l"></i><i class="f"></i><i class="r"></i>' +
            '<i class="v"></i>' +
          '</div>' +

          '<div class="<%=className%>-pointer"></div>' +
          '<div class="<%=className%>-pointer <%=className%>-pointer-to"></div>' +
        
          '<div class="<%=className%>-label"><span><%=settings.from%></span></div>' +
          '<div class="<%=className%>-label <%=className%>-label-to"><span><%=settings.to%></span><%=settings.dimension%></div>' +

          '<div class="<%=className%>-value"><span></span><%=settings.dimension%></div>' +
          '<div class="<%=className%>-value <%=className%>-value-to"><span></span><%=settings.dimension%></div>' +
          
          '<div class="<%=className%>-scale"><%=scale%></div>'+

        '</td></tr></table>' +
      '</span>'
    )
    
  };

  function jSlider(){
  	return this.init.apply( this, arguments );
  };

  jSlider.prototype.init = function( node, settings ){
    this.settings = $.extend(true, {}, OPTIONS.settings, settings ? settings : {});
    
    // obj.sliderHandler = this;
    this.inputNode = $( node ).hide();
    						
		this.settings.interval = this.settings.to-this.settings.from;
		this.settings.value = this.inputNode.attr("value");
		
		if( this.settings.calculate && $.isFunction( this.settings.calculate ) )
		  this.nice = this.settings.calculate;

		if( this.settings.onstatechange && $.isFunction( this.settings.onstatechange ) )
		  this.onstatechange = this.settings.onstatechange;

    this.is = {
      init: false
    };
		this.o = {};

    this.create();
  };
  
  jSlider.prototype.onstatechange = function(){
    
  };
  
  jSlider.prototype.create = function(){
    var $this = this;
    
    this.domNode = $( OPTIONS.template({
      className: OPTIONS.className,
      settings: {
        from: this.nice( this.settings.from ),
        to: this.nice( this.settings.to ),
        dimension: this.settings.dimension
      },
      scale: this.generateScale()
    }) );
    
    this.inputNode.after( this.domNode );
    this.drawScale();
    
    // set skin class
    if( this.settings.skin && this.settings.skin.length > 0 )
      this.setSkin( this.settings.skin );

		this.sizes = {
		  domWidth: this.domNode.width(),
		  domOffset: this.domNode.offset()
		};

    // find some objects
    $.extend(this.o, {
      pointers: {},
      labels: {
        0: {
          o: this.domNode.find(OPTIONS.selector + "value").not(OPTIONS.selector + "value-to")
        },
        1: {
          o: this.domNode.find(OPTIONS.selector + "value").filter(OPTIONS.selector + "value-to")
        }
      },
      limits: {
        0: this.domNode.find(OPTIONS.selector + "label").not(OPTIONS.selector + "label-to"),
        1: this.domNode.find(OPTIONS.selector + "label").filter(OPTIONS.selector + "label-to")
      }
    });

    $.extend(this.o.labels[0], {
      value: this.o.labels[0].o.find("span")
    });

    $.extend(this.o.labels[1], {
      value: this.o.labels[1].o.find("span")
    });

    
    if( !$this.settings.value.split(";")[1] ){
      this.settings.single = true;
      this.domNode.addDependClass("single");
    }

    if( !$this.settings.limits )
      this.domNode.addDependClass("limitless");

    this.domNode.find(OPTIONS.selector + "pointer").each(function( i ){
      var value = $this.settings.value.split(";")[i];
      if( value ){
        $this.o.pointers[i] = new jSliderPointer( this, i, $this );

        var prev = $this.settings.value.split(";")[i-1];
        if( prev && new Number(value) < new Number(prev) ) value = prev;

        value = value < $this.settings.from ? $this.settings.from : value;
        value = value > $this.settings.to ? $this.settings.to : value;
      
        $this.o.pointers[i].set( value, true );
      }
    });
    
    this.o.value = this.domNode.find(".v");
    this.is.init = true;
    
    $.each(this.o.pointers, function(i){
      $this.redraw(this);
    });
    
    (function(self){
      $(window).resize(function(){
        self.onresize();
      });
    })(this);

  };
  
  jSlider.prototype.setSkin = function( skin ){
    if( this.skin_ )
      this.domNode.removeDependClass( this.skin_, "_" );

    this.domNode.addDependClass( this.skin_ = skin, "_" );
  };
  
  jSlider.prototype.setPointersIndex = function( i ){
    $.each(this.getPointers(), function(i){
      this.index( i );
    });
  };
  
  jSlider.prototype.getPointers = function(){
    return this.o.pointers;
  };
  
  jSlider.prototype.generateScale = function(){
    if( this.settings.scale && this.settings.scale.length > 0 ){
      var str = "";
      var s = this.settings.scale;
      var prc = Math.round((100/(s.length-1))*10)/10;
      for( var i=0; i < s.length; i++ ){
        str += '<span style="left: ' + i*prc + '%">' + ( s[i] != '|' ? '<ins>' + s[i] + '</ins>' : '' ) + '</span>';
      };
      return str;
    } else return "";

    return "";
  };
  
  jSlider.prototype.drawScale = function(){
    this.domNode.find(OPTIONS.selector + "scale span ins").each(function(){
      $(this).css({ marginLeft: -$(this).outerWidth()/2 });
    });
  };
  
  jSlider.prototype.onresize = function(){
    var self = this;
		this.sizes = {
		  domWidth: this.domNode.width(),
		  domOffset: this.domNode.offset()
		};

    $.each(this.o.pointers, function(i){
      self.redraw(this);
    });
  };
  
  jSlider.prototype.update = function(){
    this.onresize();
    this.drawScale();
  };
  
  jSlider.prototype.limits = function( x, pointer ){
	  // smooth
	  if( !this.settings.smooth ){
	    var step = this.settings.step*100 / ( this.settings.interval );
	    x = Math.round( x/step ) * step;
	  }
	  
	  var another = this.o.pointers[1-pointer.uid];
	  if( another && pointer.uid && x < another.value.prc ) x = another.value.prc;
	  if( another && !pointer.uid && x > another.value.prc ) x = another.value.prc;

    // base limit
	  if( x < 0 ) x = 0;
	  if( x > 100 ) x = 100;
	  
    return Math.round( x*10 ) / 10;
  };
  
  jSlider.prototype.redraw = function( pointer ){
    if( !this.is.init ) return false;
    
    this.setValue();
    
    // redraw range line
    if( this.o.pointers[0] && this.o.pointers[1] )
      this.o.value.css({ left: this.o.pointers[0].value.prc + "%", width: ( this.o.pointers[1].value.prc - this.o.pointers[0].value.prc ) + "%" });

    this.o.labels[pointer.uid].value.html(
      this.nice(
        pointer.value.origin
      )
    );
    
    // redraw position of labels
    this.redrawLabels( pointer );

  };
  
  jSlider.prototype.redrawLabels = function( pointer ){

    function setPosition( label, sizes, prc ){
  	  sizes.margin = -sizes.label/2;

      // left limit
      label_left = sizes.border + sizes.margin;
      if( label_left < 0 )
        sizes.margin -= label_left;

      // right limit
      if( sizes.border+sizes.label / 2 > self.sizes.domWidth ){
        sizes.margin = 0;
        sizes.right = true;
      } else
        sizes.right = false;
        
      label.o.css({ left: prc + "%", marginLeft: sizes.margin, right: "auto" });
      if( sizes.right ) label.o.css({ left: "auto", right: 0 });
      return sizes;
    }

    var self = this;
	  var label = this.o.labels[pointer.uid];
	  var prc = pointer.value.prc;

		this.sizes = {
			domWidth: this.domNode.width(),
			domOffset: this.domNode.offset()
		};

	  var sizes = {
	    label: label.o.outerWidth(),
	    right: false,
	    border: ( prc * this.sizes.domWidth ) / 100
	  };

    if( !this.settings.single ){
      // glue if near;
      var another = this.o.pointers[1-pointer.uid];
    	var another_label = this.o.labels[another.uid];

      switch( pointer.uid ){
        case 0:
          if( sizes.border+sizes.label / 2 > another_label.o.offset().left-this.sizes.domOffset.left ){
            another_label.o.css({ visibility: "hidden" });
        	  another_label.value.html( this.nice( another.value.origin ) );

          	label.o.css({ visibility: "visible" });

          	prc = ( another.value.prc - prc ) / 2 + prc;
          	if( another.value.prc != pointer.value.prc ){
          	  label.value.html( this.nice(pointer.value.origin) + "&nbsp;&ndash;&nbsp;" + this.nice(another.value.origin) );
            	sizes.label = label.o.outerWidth();
            	sizes.border = ( prc * this.sizes.domWidth ) / 100;
            }
          } else {
          	another_label.o.css({ visibility: "visible" });
          }
          break;

        case 1:
          if( sizes.border - sizes.label / 2 < another_label.o.offset().left - this.sizes.domOffset.left + another_label.o.outerWidth() ){
            another_label.o.css({ visibility: "hidden" });
        	  another_label.value.html( this.nice(another.value.origin) );

          	label.o.css({ visibility: "visible" });

          	prc = ( prc - another.value.prc ) / 2 + another.value.prc;
          	if( another.value.prc != pointer.value.prc ){
          	  label.value.html( this.nice(another.value.origin) + "&nbsp;&ndash;&nbsp;" + this.nice(pointer.value.origin) );
            	sizes.label = label.o.outerWidth();
            	sizes.border = ( prc * this.sizes.domWidth ) / 100;
            }
          } else {
            another_label.o.css({ visibility: "visible" });
          }
          break;
      }
    }

    sizes = setPosition( label, sizes, prc );
    
    /* draw second label */
    if( another_label ){
      var sizes = {
  	    label: another_label.o.outerWidth(),
  	    right: false,
  	    border: ( another.value.prc * this.sizes.domWidth ) / 100
  	  };
      sizes = setPosition( another_label, sizes, another.value.prc );
    }
	  
    this.redrawLimits();
  };
  
  jSlider.prototype.redrawLimits = function(){
	  if( this.settings.limits ){

      var limits = [ true, true ];

      for( key in this.o.pointers ){

        if( !this.settings.single || key == 0 ){
        
      	  var pointer = this.o.pointers[key];
          var label = this.o.labels[pointer.uid];
          var label_left = label.o.offset().left - this.sizes.domOffset.left;

      	  var limit = this.o.limits[0];
          if( label_left < limit.outerWidth() )
            limits[0] = false;

      	  var limit = this.o.limits[1];
      	  if( label_left + label.o.outerWidth() > this.sizes.domWidth - limit.outerWidth() )
      	    limits[1] = false;
      	}

      };

      for( var i=0; i < limits.length; i++ ){
        if( limits[i] )
          this.o.limits[i].fadeIn("fast");
        else
          this.o.limits[i].fadeOut("fast");
      };

	  }
  };
  
  jSlider.prototype.setValue = function(){
    var value = this.getValue();
    this.inputNode.attr( "value", value );
    this.onstatechange.call( this, value );
  };

  jSlider.prototype.getValue = function(){
    if(!this.is.init) return false;
    var $this = this;
    
    var value = "";
    $.each( this.o.pointers, function(i){
      if( this.value.prc != undefined && !isNaN(this.value.prc) ) value += (i > 0 ? ";" : "") + $this.prcToValue( this.value.prc );
    });
    return value;
  };

  jSlider.prototype.getPrcValue = function(){
    if(!this.is.init) return false;
    var $this = this;
    
    var value = "";
    $.each( this.o.pointers, function(i){
      if( this.value.prc != undefined && !isNaN(this.value.prc) ) value += (i > 0 ? ";" : "") + this.value.prc;
    });
    return value;
  };
  
  jSlider.prototype.prcToValue = function( prc ){

	  if( this.settings.heterogeneity && this.settings.heterogeneity.length > 0 ){
  	  var h = this.settings.heterogeneity;

  	  var _start = 0;
  	  var _from = this.settings.from;

  	  for( var i=0; i <= h.length; i++ ){
  	    if( h[i] ) var v = h[i].split("/");
  	    else       var v = [100, this.settings.to];
  	    
  	    v[0] = new Number(v[0]);
  	    v[1] = new Number(v[1]);
  	      
  	    if( prc >= _start && prc <= v[0] ) {
  	      var value = _from + ( (prc-_start) * (v[1]-_from) ) / (v[0]-_start);
  	    }

  	    _start = v[0];
  	    _from = v[1];
  	  };

	  } else {
      var value = this.settings.from + ( prc * this.settings.interval ) / 100;
	  }

    return this.round( value );
  };
  
	jSlider.prototype.valueToPrc = function( value, pointer ){  	  
	  if( this.settings.heterogeneity && this.settings.heterogeneity.length > 0 ){
  	  var h = this.settings.heterogeneity;

  	  var _start = 0;
  	  var _from = this.settings.from;

  	  for (var i=0; i <= h.length; i++) {
  	    if(h[i]) var v = h[i].split("/");
  	    else     var v = [100, this.settings.to];
  	    v[0] = new Number(v[0]); v[1] = new Number(v[1]);
  	      
  	    if(value >= _from && value <= v[1]){
  	      var prc = pointer.limits(_start + (value-_from)*(v[0]-_start)/(v[1]-_from));
  	    }

  	    _start = v[0]; _from = v[1];
  	  };

	  } else {
  	  var prc = pointer.limits((value-this.settings.from)*100/this.settings.interval);
	  }

	  return prc;
	};
  
	jSlider.prototype.round = function( value ){
    value = Math.round( value / this.settings.step ) * this.settings.step;
		if( this.settings.round ) value = Math.round( value * Math.pow(10, this.settings.round) ) / Math.pow(10, this.settings.round);
		else value = Math.round( value );
		return value;
	};
	
	jSlider.prototype.nice = function( value ){
		value = value.toString().replace(/,/gi, ".").replace(/ /gi, "");;

		if( $.formatNumber ){
		  return $.formatNumber( new Number(value), this.settings.format || {} ).replace( /-/gi, "&minus;" );
		}
		  
		else {
		  return new Number(value);
		}
	};

  
  function jSliderPointer(){
  	Draggable.apply( this, arguments );
  }
  jSliderPointer.prototype = new Draggable();
  
  jSliderPointer.prototype.oninit = function( ptr, id, _constructor ){
    this.uid = id;
    this.parent = _constructor;
    this.value = {};
    this.settings = this.parent.settings;
  };
  
  jSliderPointer.prototype.onmousedown = function(evt){
	  this._parent = {
	    offset: this.parent.domNode.offset(),
	    width: this.parent.domNode.width()
	  };
	  this.ptr.addDependClass("hover");
	  this.setIndexOver();
	};

	jSliderPointer.prototype.onmousemove = function( evt, x ){
	  var coords = this._getPageCoords( evt );
	  this._set( this.calc( coords.x ) );
	};
	
	jSliderPointer.prototype.onmouseup = function( evt ){
	  if( this.parent.settings.callback && $.isFunction(this.parent.settings.callback) )
	    this.parent.settings.callback.call( this.parent, this.parent.getValue() );
	    
	  this.ptr.removeDependClass("hover");
	};
	
	jSliderPointer.prototype.setIndexOver = function(){
	  this.parent.setPointersIndex( 1 );
	  this.index( 2 );
	};
	
	jSliderPointer.prototype.index = function( i ){
	  this.ptr.css({ zIndex: i });
	};
	
	jSliderPointer.prototype.limits = function( x ){
	  return this.parent.limits( x, this );
	};
	
	jSliderPointer.prototype.calc = function(coords){
	  var x = this.limits(((coords-this._parent.offset.left)*100)/this._parent.width);
	  return x;
	};

	jSliderPointer.prototype.set = function( value, opt_origin ){
	  this.value.origin = this.parent.round(value);
	  this._set( this.parent.valueToPrc( value, this ), opt_origin );
	};
	
	jSliderPointer.prototype._set = function( prc, opt_origin ){
	  if( !opt_origin )
	    this.value.origin = this.parent.prcToValue(prc);

	  this.value.prc = prc;
		this.ptr.css({ left: prc + "%" });
	  this.parent.redraw(this);
	};
  
})(jQuery);
;
/**
 * @license
 * Lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
 */
;(function(){function n(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function t(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u<i;){var o=n[u];t(e,o,r(o),n)}return e}function r(n,t){for(var r=-1,e=null==n?0:n.length;++r<e&&false!==t(n[r],r,n););return n}function e(n,t){for(var r=null==n?0:n.length;r--&&false!==t(n[r],r,n););return n}function u(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(!t(n[r],r,n))return false;
return true}function i(n,t){for(var r=-1,e=null==n?0:n.length,u=0,i=[];++r<e;){var o=n[r];t(o,r,n)&&(i[u++]=o)}return i}function o(n,t){return!(null==n||!n.length)&&-1<v(n,t,0)}function f(n,t,r){for(var e=-1,u=null==n?0:n.length;++e<u;)if(r(t,n[e]))return true;return false}function c(n,t){for(var r=-1,e=null==n?0:n.length,u=Array(e);++r<e;)u[r]=t(n[r],r,n);return u}function a(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];return n}function l(n,t,r,e){var u=-1,i=null==n?0:n.length;for(e&&i&&(r=n[++u]);++u<i;)r=t(r,n[u],u,n);
return r}function s(n,t,r,e){var u=null==n?0:n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r}function h(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(t(n[r],r,n))return true;return false}function p(n,t,r){var e;return r(n,function(n,r,u){if(t(n,r,u))return e=r,false}),e}function _(n,t,r,e){var u=n.length;for(r+=e?1:-1;e?r--:++r<u;)if(t(n[r],r,n))return r;return-1}function v(n,t,r){if(t===t)n:{--r;for(var e=n.length;++r<e;)if(n[r]===t){n=r;break n}n=-1}else n=_(n,d,r);return n}function g(n,t,r,e){
--r;for(var u=n.length;++r<u;)if(e(n[r],t))return r;return-1}function d(n){return n!==n}function y(n,t){var r=null==n?0:n.length;return r?m(n,t)/r:F}function b(n){return function(t){return null==t?T:t[n]}}function x(n){return function(t){return null==n?T:n[t]}}function j(n,t,r,e,u){return u(n,function(n,u,i){r=e?(e=false,n):t(r,n,u,i)}),r}function w(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;return n}function m(n,t){for(var r,e=-1,u=n.length;++e<u;){var i=t(n[e]);i!==T&&(r=r===T?i:r+i)}return r;
}function A(n,t){for(var r=-1,e=Array(n);++r<n;)e[r]=t(r);return e}function E(n,t){return c(t,function(t){return[t,n[t]]})}function k(n){return function(t){return n(t)}}function S(n,t){return c(t,function(t){return n[t]})}function O(n,t){return n.has(t)}function I(n,t){for(var r=-1,e=n.length;++r<e&&-1<v(t,n[r],0););return r}function R(n,t){for(var r=n.length;r--&&-1<v(t,n[r],0););return r}function z(n){return"\\"+Un[n]}function W(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n];
}),r}function B(n,t){return function(r){return n(t(r))}}function L(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r];o!==t&&"__lodash_placeholder__"!==o||(n[r]="__lodash_placeholder__",i[u++]=r)}return i}function U(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=n}),r}function C(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=[n,n]}),r}function D(n){if(Rn.test(n)){for(var t=On.lastIndex=0;On.test(n);)++t;n=t}else n=Qn(n);return n}function M(n){return Rn.test(n)?n.match(On)||[]:n.split("");
}var T,$=1/0,F=NaN,N=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],P=/\b__p\+='';/g,Z=/\b(__p\+=)''\+/g,q=/(__e\(.*?\)|\b__t\))\+'';/g,V=/&(?:amp|lt|gt|quot|#39);/g,K=/[&<>"']/g,G=RegExp(V.source),H=RegExp(K.source),J=/<%-([\s\S]+?)%>/g,Y=/<%([\s\S]+?)%>/g,Q=/<%=([\s\S]+?)%>/g,X=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,nn=/^\w*$/,tn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,rn=/[\\^$.*+?()[\]{}|]/g,en=RegExp(rn.source),un=/^\s+|\s+$/g,on=/^\s+/,fn=/\s+$/,cn=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,an=/\{\n\/\* \[wrapped with (.+)\] \*/,ln=/,? & /,sn=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,hn=/\\(\\)?/g,pn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,_n=/\w*$/,vn=/^[-+]0x[0-9a-f]+$/i,gn=/^0b[01]+$/i,dn=/^\[object .+?Constructor\]$/,yn=/^0o[0-7]+$/i,bn=/^(?:0|[1-9]\d*)$/,xn=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,jn=/($^)/,wn=/['\n\r\u2028\u2029\\]/g,mn="[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?)*",An="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+mn,En="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]?|[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",kn=RegExp("['\u2019]","g"),Sn=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g"),On=RegExp("\\ud83c[\\udffb-\\udfff](?=\\ud83c[\\udffb-\\udfff])|"+En+mn,"g"),In=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:d|ll|m|re|s|t|ve))?|[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?|\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])|\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])|\\d+",An].join("|"),"g"),Rn=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]"),zn=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Wn="Array Buffer DataView Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Promise RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Bn={};
Bn["[object Float32Array]"]=Bn["[object Float64Array]"]=Bn["[object Int8Array]"]=Bn["[object Int16Array]"]=Bn["[object Int32Array]"]=Bn["[object Uint8Array]"]=Bn["[object Uint8ClampedArray]"]=Bn["[object Uint16Array]"]=Bn["[object Uint32Array]"]=true,Bn["[object Arguments]"]=Bn["[object Array]"]=Bn["[object ArrayBuffer]"]=Bn["[object Boolean]"]=Bn["[object DataView]"]=Bn["[object Date]"]=Bn["[object Error]"]=Bn["[object Function]"]=Bn["[object Map]"]=Bn["[object Number]"]=Bn["[object Object]"]=Bn["[object RegExp]"]=Bn["[object Set]"]=Bn["[object String]"]=Bn["[object WeakMap]"]=false;
var Ln={};Ln["[object Arguments]"]=Ln["[object Array]"]=Ln["[object ArrayBuffer]"]=Ln["[object DataView]"]=Ln["[object Boolean]"]=Ln["[object Date]"]=Ln["[object Float32Array]"]=Ln["[object Float64Array]"]=Ln["[object Int8Array]"]=Ln["[object Int16Array]"]=Ln["[object Int32Array]"]=Ln["[object Map]"]=Ln["[object Number]"]=Ln["[object Object]"]=Ln["[object RegExp]"]=Ln["[object Set]"]=Ln["[object String]"]=Ln["[object Symbol]"]=Ln["[object Uint8Array]"]=Ln["[object Uint8ClampedArray]"]=Ln["[object Uint16Array]"]=Ln["[object Uint32Array]"]=true,
Ln["[object Error]"]=Ln["[object Function]"]=Ln["[object WeakMap]"]=false;var Un={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Cn=parseFloat,Dn=parseInt,Mn=typeof global=="object"&&global&&global.Object===Object&&global,Tn=typeof self=="object"&&self&&self.Object===Object&&self,$n=Mn||Tn||Function("return this")(),Fn=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Nn=Fn&&typeof module=="object"&&module&&!module.nodeType&&module,Pn=Nn&&Nn.exports===Fn,Zn=Pn&&Mn.process,qn=function(){
try{var n=Nn&&Nn.f&&Nn.f("util").types;return n?n:Zn&&Zn.binding&&Zn.binding("util")}catch(n){}}(),Vn=qn&&qn.isArrayBuffer,Kn=qn&&qn.isDate,Gn=qn&&qn.isMap,Hn=qn&&qn.isRegExp,Jn=qn&&qn.isSet,Yn=qn&&qn.isTypedArray,Qn=b("length"),Xn=x({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I",
"\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C",
"\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i",
"\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r",
"\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij",
"\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"}),nt=x({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}),tt=x({"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'"}),rt=function x(mn){function An(n){if(yu(n)&&!ff(n)&&!(n instanceof Un)){if(n instanceof On)return n;if(oi.call(n,"__wrapped__"))return Fe(n)}return new On(n)}function En(){}function On(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=T}function Un(n){this.__wrapped__=n,
this.__actions__=[],this.__dir__=1,this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Mn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Tn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Fn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Nn(n){var t=-1,r=null==n?0:n.length;for(this.__data__=new Fn;++t<r;)this.add(n[t]);
}function Zn(n){this.size=(this.__data__=new Tn(n)).size}function qn(n,t){var r,e=ff(n),u=!e&&of(n),i=!e&&!u&&af(n),o=!e&&!u&&!i&&_f(n),u=(e=e||u||i||o)?A(n.length,ni):[],f=u.length;for(r in n)!t&&!oi.call(n,r)||e&&("length"==r||i&&("offset"==r||"parent"==r)||o&&("buffer"==r||"byteLength"==r||"byteOffset"==r)||Se(r,f))||u.push(r);return u}function Qn(n){var t=n.length;return t?n[ir(0,t-1)]:T}function et(n,t){return De(Ur(n),pt(t,0,n.length))}function ut(n){return De(Ur(n))}function it(n,t,r){(r===T||lu(n[t],r))&&(r!==T||t in n)||st(n,t,r);
}function ot(n,t,r){var e=n[t];oi.call(n,t)&&lu(e,r)&&(r!==T||t in n)||st(n,t,r)}function ft(n,t){for(var r=n.length;r--;)if(lu(n[r][0],t))return r;return-1}function ct(n,t,r,e){return uo(n,function(n,u,i){t(e,n,r(n),i)}),e}function at(n,t){return n&&Cr(t,Wu(t),n)}function lt(n,t){return n&&Cr(t,Bu(t),n)}function st(n,t,r){"__proto__"==t&&Ai?Ai(n,t,{configurable:true,enumerable:true,value:r,writable:true}):n[t]=r}function ht(n,t){for(var r=-1,e=t.length,u=Ku(e),i=null==n;++r<e;)u[r]=i?T:Ru(n,t[r]);return u;
}function pt(n,t,r){return n===n&&(r!==T&&(n=n<=r?n:r),t!==T&&(n=n>=t?n:t)),n}function _t(n,t,e,u,i,o){var f,c=1&t,a=2&t,l=4&t;if(e&&(f=i?e(n,u,i,o):e(n)),f!==T)return f;if(!du(n))return n;if(u=ff(n)){if(f=me(n),!c)return Ur(n,f)}else{var s=vo(n),h="[object Function]"==s||"[object GeneratorFunction]"==s;if(af(n))return Ir(n,c);if("[object Object]"==s||"[object Arguments]"==s||h&&!i){if(f=a||h?{}:Ae(n),!c)return a?Mr(n,lt(f,n)):Dr(n,at(f,n))}else{if(!Ln[s])return i?n:{};f=Ee(n,s,c)}}if(o||(o=new Zn),
i=o.get(n))return i;o.set(n,f),pf(n)?n.forEach(function(r){f.add(_t(r,t,e,r,n,o))}):sf(n)&&n.forEach(function(r,u){f.set(u,_t(r,t,e,u,n,o))});var a=l?a?ve:_e:a?Bu:Wu,p=u?T:a(n);return r(p||n,function(r,u){p&&(u=r,r=n[u]),ot(f,u,_t(r,t,e,u,n,o))}),f}function vt(n){var t=Wu(n);return function(r){return gt(r,n,t)}}function gt(n,t,r){var e=r.length;if(null==n)return!e;for(n=Qu(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===T&&!(u in n)||!i(o))return false}return true}function dt(n,t,r){if(typeof n!="function")throw new ti("Expected a function");
return bo(function(){n.apply(T,r)},t)}function yt(n,t,r,e){var u=-1,i=o,a=true,l=n.length,s=[],h=t.length;if(!l)return s;r&&(t=c(t,k(r))),e?(i=f,a=false):200<=t.length&&(i=O,a=false,t=new Nn(t));n:for(;++u<l;){var p=n[u],_=null==r?p:r(p),p=e||0!==p?p:0;if(a&&_===_){for(var v=h;v--;)if(t[v]===_)continue n;s.push(p)}else i(t,_,e)||s.push(p)}return s}function bt(n,t){var r=true;return uo(n,function(n,e,u){return r=!!t(n,e,u)}),r}function xt(n,t,r){for(var e=-1,u=n.length;++e<u;){var i=n[e],o=t(i);if(null!=o&&(f===T?o===o&&!wu(o):r(o,f)))var f=o,c=i;
}return c}function jt(n,t){var r=[];return uo(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function wt(n,t,r,e,u){var i=-1,o=n.length;for(r||(r=ke),u||(u=[]);++i<o;){var f=n[i];0<t&&r(f)?1<t?wt(f,t-1,r,e,u):a(u,f):e||(u[u.length]=f)}return u}function mt(n,t){return n&&oo(n,t,Wu)}function At(n,t){return n&&fo(n,t,Wu)}function Et(n,t){return i(t,function(t){return _u(n[t])})}function kt(n,t){t=Sr(t,n);for(var r=0,e=t.length;null!=n&&r<e;)n=n[Me(t[r++])];return r&&r==e?n:T}function St(n,t,r){return t=t(n),
ff(n)?t:a(t,r(n))}function Ot(n){if(null==n)n=n===T?"[object Undefined]":"[object Null]";else if(mi&&mi in Qu(n)){var t=oi.call(n,mi),r=n[mi];try{n[mi]=T;var e=true}catch(n){}var u=ai.call(n);e&&(t?n[mi]=r:delete n[mi]),n=u}else n=ai.call(n);return n}function It(n,t){return n>t}function Rt(n,t){return null!=n&&oi.call(n,t)}function zt(n,t){return null!=n&&t in Qu(n)}function Wt(n,t,r){for(var e=r?f:o,u=n[0].length,i=n.length,a=i,l=Ku(i),s=1/0,h=[];a--;){var p=n[a];a&&t&&(p=c(p,k(t))),s=Ci(p.length,s),
l[a]=!r&&(t||120<=u&&120<=p.length)?new Nn(a&&p):T}var p=n[0],_=-1,v=l[0];n:for(;++_<u&&h.length<s;){var g=p[_],d=t?t(g):g,g=r||0!==g?g:0;if(v?!O(v,d):!e(h,d,r)){for(a=i;--a;){var y=l[a];if(y?!O(y,d):!e(n[a],d,r))continue n}v&&v.push(d),h.push(g)}}return h}function Bt(n,t,r){var e={};return mt(n,function(n,u,i){t(e,r(n),u,i)}),e}function Lt(t,r,e){return r=Sr(r,t),t=2>r.length?t:kt(t,hr(r,0,-1)),r=null==t?t:t[Me(Ve(r))],null==r?T:n(r,t,e)}function Ut(n){return yu(n)&&"[object Arguments]"==Ot(n)}function Ct(n){
return yu(n)&&"[object ArrayBuffer]"==Ot(n)}function Dt(n){return yu(n)&&"[object Date]"==Ot(n)}function Mt(n,t,r,e,u){if(n===t)t=true;else if(null==n||null==t||!yu(n)&&!yu(t))t=n!==n&&t!==t;else n:{var i=ff(n),o=ff(t),f=i?"[object Array]":vo(n),c=o?"[object Array]":vo(t),f="[object Arguments]"==f?"[object Object]":f,c="[object Arguments]"==c?"[object Object]":c,a="[object Object]"==f,o="[object Object]"==c;if((c=f==c)&&af(n)){if(!af(t)){t=false;break n}i=true,a=false}if(c&&!a)u||(u=new Zn),t=i||_f(n)?se(n,t,r,e,Mt,u):he(n,t,f,r,e,Mt,u);else{
if(!(1&r)&&(i=a&&oi.call(n,"__wrapped__"),f=o&&oi.call(t,"__wrapped__"),i||f)){n=i?n.value():n,t=f?t.value():t,u||(u=new Zn),t=Mt(n,t,r,e,u);break n}if(c)t:if(u||(u=new Zn),i=1&r,f=_e(n),o=f.length,c=_e(t).length,o==c||i){for(a=o;a--;){var l=f[a];if(!(i?l in t:oi.call(t,l))){t=false;break t}}if((c=u.get(n))&&u.get(t))t=c==t;else{c=true,u.set(n,t),u.set(t,n);for(var s=i;++a<o;){var l=f[a],h=n[l],p=t[l];if(e)var _=i?e(p,h,l,t,n,u):e(h,p,l,n,t,u);if(_===T?h!==p&&!Mt(h,p,r,e,u):!_){c=false;break}s||(s="constructor"==l);
}c&&!s&&(r=n.constructor,e=t.constructor,r!=e&&"constructor"in n&&"constructor"in t&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(c=false)),u.delete(n),u.delete(t),t=c}}else t=false;else t=false}}return t}function Tt(n){return yu(n)&&"[object Map]"==vo(n)}function $t(n,t,r,e){var u=r.length,i=u,o=!e;if(null==n)return!i;for(n=Qu(n);u--;){var f=r[u];if(o&&f[2]?f[1]!==n[f[0]]:!(f[0]in n))return false}for(;++u<i;){var f=r[u],c=f[0],a=n[c],l=f[1];if(o&&f[2]){if(a===T&&!(c in n))return false;
}else{if(f=new Zn,e)var s=e(a,l,c,n,t,f);if(s===T?!Mt(l,a,3,e,f):!s)return false}}return true}function Ft(n){return!(!du(n)||ci&&ci in n)&&(_u(n)?hi:dn).test(Te(n))}function Nt(n){return yu(n)&&"[object RegExp]"==Ot(n)}function Pt(n){return yu(n)&&"[object Set]"==vo(n)}function Zt(n){return yu(n)&&gu(n.length)&&!!Bn[Ot(n)]}function qt(n){return typeof n=="function"?n:null==n?$u:typeof n=="object"?ff(n)?Jt(n[0],n[1]):Ht(n):Zu(n)}function Vt(n){if(!ze(n))return Li(n);var t,r=[];for(t in Qu(n))oi.call(n,t)&&"constructor"!=t&&r.push(t);
return r}function Kt(n,t){return n<t}function Gt(n,t){var r=-1,e=su(n)?Ku(n.length):[];return uo(n,function(n,u,i){e[++r]=t(n,u,i)}),e}function Ht(n){var t=xe(n);return 1==t.length&&t[0][2]?We(t[0][0],t[0][1]):function(r){return r===n||$t(r,n,t)}}function Jt(n,t){return Ie(n)&&t===t&&!du(t)?We(Me(n),t):function(r){var e=Ru(r,n);return e===T&&e===t?zu(r,n):Mt(t,e,3)}}function Yt(n,t,r,e,u){n!==t&&oo(t,function(i,o){if(u||(u=new Zn),du(i)){var f=u,c=Le(n,o),a=Le(t,o),l=f.get(a);if(l)it(n,o,l);else{
var l=e?e(c,a,o+"",n,t,f):T,s=l===T;if(s){var h=ff(a),p=!h&&af(a),_=!h&&!p&&_f(a),l=a;h||p||_?ff(c)?l=c:hu(c)?l=Ur(c):p?(s=false,l=Ir(a,true)):_?(s=false,l=zr(a,true)):l=[]:xu(a)||of(a)?(l=c,of(c)?l=Ou(c):du(c)&&!_u(c)||(l=Ae(a))):s=false}s&&(f.set(a,l),Yt(l,a,r,e,f),f.delete(a)),it(n,o,l)}}else f=e?e(Le(n,o),i,o+"",n,t,u):T,f===T&&(f=i),it(n,o,f)},Bu)}function Qt(n,t){var r=n.length;if(r)return t+=0>t?r:0,Se(t,r)?n[t]:T}function Xt(n,t,r){var e=-1;return t=c(t.length?t:[$u],k(ye())),n=Gt(n,function(n){return{
a:c(t,function(t){return t(n)}),b:++e,c:n}}),w(n,function(n,t){var e;n:{e=-1;for(var u=n.a,i=t.a,o=u.length,f=r.length;++e<o;){var c=Wr(u[e],i[e]);if(c){e=e>=f?c:c*("desc"==r[e]?-1:1);break n}}e=n.b-t.b}return e})}function nr(n,t){return tr(n,t,function(t,r){return zu(n,r)})}function tr(n,t,r){for(var e=-1,u=t.length,i={};++e<u;){var o=t[e],f=kt(n,o);r(f,o)&&lr(i,Sr(o,n),f)}return i}function rr(n){return function(t){return kt(t,n)}}function er(n,t,r,e){var u=e?g:v,i=-1,o=t.length,f=n;for(n===t&&(t=Ur(t)),
r&&(f=c(n,k(r)));++i<o;)for(var a=0,l=t[i],l=r?r(l):l;-1<(a=u(f,l,a,e));)f!==n&&xi.call(f,a,1),xi.call(n,a,1);return n}function ur(n,t){for(var r=n?t.length:0,e=r-1;r--;){var u=t[r];if(r==e||u!==i){var i=u;Se(u)?xi.call(n,u,1):xr(n,u)}}}function ir(n,t){return n+Ii(Ti()*(t-n+1))}function or(n,t){var r="";if(!n||1>t||9007199254740991<t)return r;do t%2&&(r+=n),(t=Ii(t/2))&&(n+=n);while(t);return r}function fr(n,t){return xo(Be(n,t,$u),n+"")}function cr(n){return Qn(Uu(n))}function ar(n,t){var r=Uu(n);
return De(r,pt(t,0,r.length))}function lr(n,t,r,e){if(!du(n))return n;t=Sr(t,n);for(var u=-1,i=t.length,o=i-1,f=n;null!=f&&++u<i;){var c=Me(t[u]),a=r;if(u!=o){var l=f[c],a=e?e(l,c,f):T;a===T&&(a=du(l)?l:Se(t[u+1])?[]:{})}ot(f,c,a),f=f[c]}return n}function sr(n){return De(Uu(n))}function hr(n,t,r){var e=-1,u=n.length;for(0>t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Ku(u);++e<u;)r[e]=n[e+t];return r}function pr(n,t){var r;return uo(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}
function _r(n,t,r){var e=0,u=null==n?e:n.length;if(typeof t=="number"&&t===t&&2147483647>=u){for(;e<u;){var i=e+u>>>1,o=n[i];null!==o&&!wu(o)&&(r?o<=t:o<t)?e=i+1:u=i}return u}return vr(n,t,$u,r)}function vr(n,t,r,e){t=r(t);for(var u=0,i=null==n?0:n.length,o=t!==t,f=null===t,c=wu(t),a=t===T;u<i;){var l=Ii((u+i)/2),s=r(n[l]),h=s!==T,p=null===s,_=s===s,v=wu(s);(o?e||_:a?_&&(e||h):f?_&&h&&(e||!p):c?_&&h&&!p&&(e||!v):p||v?0:e?s<=t:s<t)?u=l+1:i=l}return Ci(i,4294967294)}function gr(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){
var o=n[r],f=t?t(o):o;if(!r||!lu(f,c)){var c=f;i[u++]=0===o?0:o}}return i}function dr(n){return typeof n=="number"?n:wu(n)?F:+n}function yr(n){if(typeof n=="string")return n;if(ff(n))return c(n,yr)+"";if(wu(n))return ro?ro.call(n):"";var t=n+"";return"0"==t&&1/n==-$?"-0":t}function br(n,t,r){var e=-1,u=o,i=n.length,c=true,a=[],l=a;if(r)c=false,u=f;else if(200<=i){if(u=t?null:so(n))return U(u);c=false,u=O,l=new Nn}else l=t?[]:a;n:for(;++e<i;){var s=n[e],h=t?t(s):s,s=r||0!==s?s:0;if(c&&h===h){for(var p=l.length;p--;)if(l[p]===h)continue n;
t&&l.push(h),a.push(s)}else u(l,h,r)||(l!==a&&l.push(h),a.push(s))}return a}function xr(n,t){return t=Sr(t,n),n=2>t.length?n:kt(n,hr(t,0,-1)),null==n||delete n[Me(Ve(t))]}function jr(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++i<u)&&t(n[i],i,n););return r?hr(n,e?0:i,e?i+1:u):hr(n,e?i+1:0,e?u:i)}function wr(n,t){var r=n;return r instanceof Un&&(r=r.value()),l(t,function(n,t){return t.func.apply(t.thisArg,a([n],t.args))},r)}function mr(n,t,r){var e=n.length;if(2>e)return e?br(n[0]):[];for(var u=-1,i=Ku(e);++u<e;)for(var o=n[u],f=-1;++f<e;)f!=u&&(i[u]=yt(i[u]||o,n[f],t,r));
return br(wt(i,1),t,r)}function Ar(n,t,r){for(var e=-1,u=n.length,i=t.length,o={};++e<u;)r(o,n[e],e<i?t[e]:T);return o}function Er(n){return hu(n)?n:[]}function kr(n){return typeof n=="function"?n:$u}function Sr(n,t){return ff(n)?n:Ie(n,t)?[n]:jo(Iu(n))}function Or(n,t,r){var e=n.length;return r=r===T?e:r,!t&&r>=e?n:hr(n,t,r)}function Ir(n,t){if(t)return n.slice();var r=n.length,r=gi?gi(r):new n.constructor(r);return n.copy(r),r}function Rr(n){var t=new n.constructor(n.byteLength);return new vi(t).set(new vi(n)),
t}function zr(n,t){return new n.constructor(t?Rr(n.buffer):n.buffer,n.byteOffset,n.length)}function Wr(n,t){if(n!==t){var r=n!==T,e=null===n,u=n===n,i=wu(n),o=t!==T,f=null===t,c=t===t,a=wu(t);if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&n<t||a&&r&&u&&!e&&!i||f&&r&&u||!o&&u||!c)return-1}return 0}function Br(n,t,r,e){var u=-1,i=n.length,o=r.length,f=-1,c=t.length,a=Ui(i-o,0),l=Ku(c+a);for(e=!e;++f<c;)l[f]=t[f];for(;++u<o;)(e||u<i)&&(l[r[u]]=n[u]);for(;a--;)l[f++]=n[u++];
return l}function Lr(n,t,r,e){var u=-1,i=n.length,o=-1,f=r.length,c=-1,a=t.length,l=Ui(i-f,0),s=Ku(l+a);for(e=!e;++u<l;)s[u]=n[u];for(l=u;++c<a;)s[l+c]=t[c];for(;++o<f;)(e||u<i)&&(s[l+r[o]]=n[u++]);return s}function Ur(n,t){var r=-1,e=n.length;for(t||(t=Ku(e));++r<e;)t[r]=n[r];return t}function Cr(n,t,r,e){var u=!r;r||(r={});for(var i=-1,o=t.length;++i<o;){var f=t[i],c=e?e(r[f],n[f],f,r,n):T;c===T&&(c=n[f]),u?st(r,f,c):ot(r,f,c)}return r}function Dr(n,t){return Cr(n,po(n),t)}function Mr(n,t){return Cr(n,_o(n),t);
}function Tr(n,r){return function(e,u){var i=ff(e)?t:ct,o=r?r():{};return i(e,n,ye(u,2),o)}}function $r(n){return fr(function(t,r){var e=-1,u=r.length,i=1<u?r[u-1]:T,o=2<u?r[2]:T,i=3<n.length&&typeof i=="function"?(u--,i):T;for(o&&Oe(r[0],r[1],o)&&(i=3>u?T:i,u=1),t=Qu(t);++e<u;)(o=r[e])&&n(t,o,e,i);return t})}function Fr(n,t){return function(r,e){if(null==r)return r;if(!su(r))return n(r,e);for(var u=r.length,i=t?u:-1,o=Qu(r);(t?i--:++i<u)&&false!==e(o[i],i,o););return r}}function Nr(n){return function(t,r,e){
var u=-1,i=Qu(t);e=e(t);for(var o=e.length;o--;){var f=e[n?o:++u];if(false===r(i[f],f,i))break}return t}}function Pr(n,t,r){function e(){return(this&&this!==$n&&this instanceof e?i:n).apply(u?r:this,arguments)}var u=1&t,i=Vr(n);return e}function Zr(n){return function(t){t=Iu(t);var r=Rn.test(t)?M(t):T,e=r?r[0]:t.charAt(0);return t=r?Or(r,1).join(""):t.slice(1),e[n]()+t}}function qr(n){return function(t){return l(Mu(Du(t).replace(kn,"")),n,"")}}function Vr(n){return function(){var t=arguments;switch(t.length){
case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:return new n(t[0],t[1],t[2]);case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=eo(n.prototype),t=n.apply(r,t);return du(t)?t:r}}function Kr(t,r,e){function u(){for(var o=arguments.length,f=Ku(o),c=o,a=de(u);c--;)f[c]=arguments[c];return c=3>o&&f[0]!==a&&f[o-1]!==a?[]:L(f,a),
o-=c.length,o<e?ue(t,r,Jr,u.placeholder,T,f,c,T,T,e-o):n(this&&this!==$n&&this instanceof u?i:t,this,f)}var i=Vr(t);return u}function Gr(n){return function(t,r,e){var u=Qu(t);if(!su(t)){var i=ye(r,3);t=Wu(t),r=function(n){return i(u[n],n,u)}}return r=n(t,r,e),-1<r?u[i?t[r]:r]:T}}function Hr(n){return pe(function(t){var r=t.length,e=r,u=On.prototype.thru;for(n&&t.reverse();e--;){var i=t[e];if(typeof i!="function")throw new ti("Expected a function");if(u&&!o&&"wrapper"==ge(i))var o=new On([],true)}for(e=o?e:r;++e<r;)var i=t[e],u=ge(i),f="wrapper"==u?ho(i):T,o=f&&Re(f[0])&&424==f[1]&&!f[4].length&&1==f[9]?o[ge(f[0])].apply(o,f[3]):1==i.length&&Re(i)?o[u]():o.thru(i);
return function(){var n=arguments,e=n[0];if(o&&1==n.length&&ff(e))return o.plant(e).value();for(var u=0,n=r?t[u].apply(this,n):e;++u<r;)n=t[u].call(this,n);return n}})}function Jr(n,t,r,e,u,i,o,f,c,a){function l(){for(var d=arguments.length,y=Ku(d),b=d;b--;)y[b]=arguments[b];if(_){var x,j=de(l),b=y.length;for(x=0;b--;)y[b]===j&&++x}if(e&&(y=Br(y,e,u,_)),i&&(y=Lr(y,i,o,_)),d-=x,_&&d<a)return j=L(y,j),ue(n,t,Jr,l.placeholder,r,y,j,f,c,a-d);if(j=h?r:this,b=p?j[n]:n,d=y.length,f){x=y.length;for(var w=Ci(f.length,x),m=Ur(y);w--;){
var A=f[w];y[w]=Se(A,x)?m[A]:T}}else v&&1<d&&y.reverse();return s&&c<d&&(y.length=c),this&&this!==$n&&this instanceof l&&(b=g||Vr(b)),b.apply(j,y)}var s=128&t,h=1&t,p=2&t,_=24&t,v=512&t,g=p?T:Vr(n);return l}function Yr(n,t){return function(r,e){return Bt(r,n,t(e))}}function Qr(n,t){return function(r,e){var u;if(r===T&&e===T)return t;if(r!==T&&(u=r),e!==T){if(u===T)return e;typeof r=="string"||typeof e=="string"?(r=yr(r),e=yr(e)):(r=dr(r),e=dr(e)),u=n(r,e)}return u}}function Xr(t){return pe(function(r){
return r=c(r,k(ye())),fr(function(e){var u=this;return t(r,function(t){return n(t,u,e)})})})}function ne(n,t){t=t===T?" ":yr(t);var r=t.length;return 2>r?r?or(t,n):t:(r=or(t,Oi(n/D(t))),Rn.test(t)?Or(M(r),0,n).join(""):r.slice(0,n))}function te(t,r,e,u){function i(){for(var r=-1,c=arguments.length,a=-1,l=u.length,s=Ku(l+c),h=this&&this!==$n&&this instanceof i?f:t;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++r];return n(h,o?e:this,s)}var o=1&r,f=Vr(t);return i}function re(n){return function(t,r,e){
e&&typeof e!="number"&&Oe(t,r,e)&&(r=e=T),t=Au(t),r===T?(r=t,t=0):r=Au(r),e=e===T?t<r?1:-1:Au(e);var u=-1;r=Ui(Oi((r-t)/(e||1)),0);for(var i=Ku(r);r--;)i[n?r:++u]=t,t+=e;return i}}function ee(n){return function(t,r){return typeof t=="string"&&typeof r=="string"||(t=Su(t),r=Su(r)),n(t,r)}}function ue(n,t,r,e,u,i,o,f,c,a){var l=8&t,s=l?o:T;o=l?T:o;var h=l?i:T;return i=l?T:i,t=(t|(l?32:64))&~(l?64:32),4&t||(t&=-4),u=[n,t,u,h,s,i,o,f,c,a],r=r.apply(T,u),Re(n)&&yo(r,u),r.placeholder=e,Ue(r,n,t)}function ie(n){
var t=Yu[n];return function(n,r){if(n=Su(n),(r=null==r?0:Ci(Eu(r),292))&&Wi(n)){var e=(Iu(n)+"e").split("e"),e=t(e[0]+"e"+(+e[1]+r)),e=(Iu(e)+"e").split("e");return+(e[0]+"e"+(+e[1]-r))}return t(n)}}function oe(n){return function(t){var r=vo(t);return"[object Map]"==r?W(t):"[object Set]"==r?C(t):E(t,n(t))}}function fe(n,t,r,e,u,i,o,f){var c=2&t;if(!c&&typeof n!="function")throw new ti("Expected a function");var a=e?e.length:0;if(a||(t&=-97,e=u=T),o=o===T?o:Ui(Eu(o),0),f=f===T?f:Eu(f),a-=u?u.length:0,
64&t){var l=e,s=u;e=u=T}var h=c?T:ho(n);return i=[n,t,r,e,u,l,s,i,o,f],h&&(r=i[1],n=h[1],t=r|n,e=128==n&&8==r||128==n&&256==r&&i[7].length<=h[8]||384==n&&h[7].length<=h[8]&&8==r,131>t||e)&&(1&n&&(i[2]=h[2],t|=1&r?0:4),(r=h[3])&&(e=i[3],i[3]=e?Br(e,r,h[4]):r,i[4]=e?L(i[3],"__lodash_placeholder__"):h[4]),(r=h[5])&&(e=i[5],i[5]=e?Lr(e,r,h[6]):r,i[6]=e?L(i[5],"__lodash_placeholder__"):h[6]),(r=h[7])&&(i[7]=r),128&n&&(i[8]=null==i[8]?h[8]:Ci(i[8],h[8])),null==i[9]&&(i[9]=h[9]),i[0]=h[0],i[1]=t),n=i[0],
t=i[1],r=i[2],e=i[3],u=i[4],f=i[9]=i[9]===T?c?0:n.length:Ui(i[9]-a,0),!f&&24&t&&(t&=-25),Ue((h?co:yo)(t&&1!=t?8==t||16==t?Kr(n,t,f):32!=t&&33!=t||u.length?Jr.apply(T,i):te(n,t,r,e):Pr(n,t,r),i),n,t)}function ce(n,t,r,e){return n===T||lu(n,ei[r])&&!oi.call(e,r)?t:n}function ae(n,t,r,e,u,i){return du(n)&&du(t)&&(i.set(t,n),Yt(n,t,T,ae,i),i.delete(t)),n}function le(n){return xu(n)?T:n}function se(n,t,r,e,u,i){var o=1&r,f=n.length,c=t.length;if(f!=c&&!(o&&c>f))return false;if((c=i.get(n))&&i.get(t))return c==t;
var c=-1,a=true,l=2&r?new Nn:T;for(i.set(n,t),i.set(t,n);++c<f;){var s=n[c],p=t[c];if(e)var _=o?e(p,s,c,t,n,i):e(s,p,c,n,t,i);if(_!==T){if(_)continue;a=false;break}if(l){if(!h(t,function(n,t){if(!O(l,t)&&(s===n||u(s,n,r,e,i)))return l.push(t)})){a=false;break}}else if(s!==p&&!u(s,p,r,e,i)){a=false;break}}return i.delete(n),i.delete(t),a}function he(n,t,r,e,u,i,o){switch(r){case"[object DataView]":if(n.byteLength!=t.byteLength||n.byteOffset!=t.byteOffset)break;n=n.buffer,t=t.buffer;case"[object ArrayBuffer]":
if(n.byteLength!=t.byteLength||!i(new vi(n),new vi(t)))break;return true;case"[object Boolean]":case"[object Date]":case"[object Number]":return lu(+n,+t);case"[object Error]":return n.name==t.name&&n.message==t.message;case"[object RegExp]":case"[object String]":return n==t+"";case"[object Map]":var f=W;case"[object Set]":if(f||(f=U),n.size!=t.size&&!(1&e))break;return(r=o.get(n))?r==t:(e|=2,o.set(n,t),t=se(f(n),f(t),e,u,i,o),o.delete(n),t);case"[object Symbol]":if(to)return to.call(n)==to.call(t)}
return false}function pe(n){return xo(Be(n,T,Ze),n+"")}function _e(n){return St(n,Wu,po)}function ve(n){return St(n,Bu,_o)}function ge(n){for(var t=n.name+"",r=Gi[t],e=oi.call(Gi,t)?r.length:0;e--;){var u=r[e],i=u.func;if(null==i||i==n)return u.name}return t}function de(n){return(oi.call(An,"placeholder")?An:n).placeholder}function ye(){var n=An.iteratee||Fu,n=n===Fu?qt:n;return arguments.length?n(arguments[0],arguments[1]):n}function be(n,t){var r=n.__data__,e=typeof t;return("string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t)?r[typeof t=="string"?"string":"hash"]:r.map;
}function xe(n){for(var t=Wu(n),r=t.length;r--;){var e=t[r],u=n[e];t[r]=[e,u,u===u&&!du(u)]}return t}function je(n,t){var r=null==n?T:n[t];return Ft(r)?r:T}function we(n,t,r){t=Sr(t,n);for(var e=-1,u=t.length,i=false;++e<u;){var o=Me(t[e]);if(!(i=null!=n&&r(n,o)))break;n=n[o]}return i||++e!=u?i:(u=null==n?0:n.length,!!u&&gu(u)&&Se(o,u)&&(ff(n)||of(n)))}function me(n){var t=n.length,r=new n.constructor(t);return t&&"string"==typeof n[0]&&oi.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function Ae(n){
return typeof n.constructor!="function"||ze(n)?{}:eo(di(n))}function Ee(n,t,r){var e=n.constructor;switch(t){case"[object ArrayBuffer]":return Rr(n);case"[object Boolean]":case"[object Date]":return new e(+n);case"[object DataView]":return t=r?Rr(n.buffer):n.buffer,new n.constructor(t,n.byteOffset,n.byteLength);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":
case"[object Uint16Array]":case"[object Uint32Array]":return zr(n,r);case"[object Map]":return new e;case"[object Number]":case"[object String]":return new e(n);case"[object RegExp]":return t=new n.constructor(n.source,_n.exec(n)),t.lastIndex=n.lastIndex,t;case"[object Set]":return new e;case"[object Symbol]":return to?Qu(to.call(n)):{}}}function ke(n){return ff(n)||of(n)||!!(ji&&n&&n[ji])}function Se(n,t){var r=typeof n;return t=null==t?9007199254740991:t,!!t&&("number"==r||"symbol"!=r&&bn.test(n))&&-1<n&&0==n%1&&n<t;
}function Oe(n,t,r){if(!du(r))return false;var e=typeof t;return!!("number"==e?su(r)&&Se(t,r.length):"string"==e&&t in r)&&lu(r[t],n)}function Ie(n,t){if(ff(n))return false;var r=typeof n;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=n&&!wu(n))||(nn.test(n)||!X.test(n)||null!=t&&n in Qu(t))}function Re(n){var t=ge(n),r=An[t];return typeof r=="function"&&t in Un.prototype&&(n===r||(t=ho(r),!!t&&n===t[0]))}function ze(n){var t=n&&n.constructor;return n===(typeof t=="function"&&t.prototype||ei)}function We(n,t){
return function(r){return null!=r&&(r[n]===t&&(t!==T||n in Qu(r)))}}function Be(t,r,e){return r=Ui(r===T?t.length-1:r,0),function(){for(var u=arguments,i=-1,o=Ui(u.length-r,0),f=Ku(o);++i<o;)f[i]=u[r+i];for(i=-1,o=Ku(r+1);++i<r;)o[i]=u[i];return o[r]=e(f),n(t,this,o)}}function Le(n,t){if(("constructor"!==t||"function"!=typeof n[t])&&"__proto__"!=t)return n[t]}function Ue(n,t,r){var e=t+"";t=xo;var u,i=$e;return u=(u=e.match(an))?u[1].split(ln):[],r=i(u,r),(i=r.length)&&(u=i-1,r[u]=(1<i?"& ":"")+r[u],
r=r.join(2<i?", ":" "),e=e.replace(cn,"{\n/* [wrapped with "+r+"] */\n")),t(n,e)}function Ce(n){var t=0,r=0;return function(){var e=Di(),u=16-(e-r);if(r=e,0<u){if(800<=++t)return arguments[0]}else t=0;return n.apply(T,arguments)}}function De(n,t){var r=-1,e=n.length,u=e-1;for(t=t===T?e:t;++r<t;){var e=ir(r,u),i=n[e];n[e]=n[r],n[r]=i}return n.length=t,n}function Me(n){if(typeof n=="string"||wu(n))return n;var t=n+"";return"0"==t&&1/n==-$?"-0":t}function Te(n){if(null!=n){try{return ii.call(n)}catch(n){}
return n+""}return""}function $e(n,t){return r(N,function(r){var e="_."+r[0];t&r[1]&&!o(n,e)&&n.push(e)}),n.sort()}function Fe(n){if(n instanceof Un)return n.clone();var t=new On(n.__wrapped__,n.__chain__);return t.__actions__=Ur(n.__actions__),t.__index__=n.__index__,t.__values__=n.__values__,t}function Ne(n,t,r){var e=null==n?0:n.length;return e?(r=null==r?0:Eu(r),0>r&&(r=Ui(e+r,0)),_(n,ye(t,3),r)):-1}function Pe(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;return r!==T&&(u=Eu(r),u=0>r?Ui(e+u,0):Ci(u,e-1)),
_(n,ye(t,3),u,true)}function Ze(n){return(null==n?0:n.length)?wt(n,1):[]}function qe(n){return n&&n.length?n[0]:T}function Ve(n){var t=null==n?0:n.length;return t?n[t-1]:T}function Ke(n,t){return n&&n.length&&t&&t.length?er(n,t):n}function Ge(n){return null==n?n:$i.call(n)}function He(n){if(!n||!n.length)return[];var t=0;return n=i(n,function(n){if(hu(n))return t=Ui(n.length,t),true}),A(t,function(t){return c(n,b(t))})}function Je(t,r){if(!t||!t.length)return[];var e=He(t);return null==r?e:c(e,function(t){
return n(r,T,t)})}function Ye(n){return n=An(n),n.__chain__=true,n}function Qe(n,t){return t(n)}function Xe(){return this}function nu(n,t){return(ff(n)?r:uo)(n,ye(t,3))}function tu(n,t){return(ff(n)?e:io)(n,ye(t,3))}function ru(n,t){return(ff(n)?c:Gt)(n,ye(t,3))}function eu(n,t,r){return t=r?T:t,t=n&&null==t?n.length:t,fe(n,128,T,T,T,T,t)}function uu(n,t){var r;if(typeof t!="function")throw new ti("Expected a function");return n=Eu(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=T),
r}}function iu(n,t,r){return t=r?T:t,n=fe(n,8,T,T,T,T,T,t),n.placeholder=iu.placeholder,n}function ou(n,t,r){return t=r?T:t,n=fe(n,16,T,T,T,T,T,t),n.placeholder=ou.placeholder,n}function fu(n,t,r){function e(t){var r=c,e=a;return c=a=T,_=t,s=n.apply(e,r)}function u(n){var r=n-p;return n-=_,p===T||r>=t||0>r||g&&n>=l}function i(){var n=Go();if(u(n))return o(n);var r,e=bo;r=n-_,n=t-(n-p),r=g?Ci(n,l-r):n,h=e(i,r)}function o(n){return h=T,d&&c?e(n):(c=a=T,s)}function f(){var n=Go(),r=u(n);if(c=arguments,
a=this,p=n,r){if(h===T)return _=n=p,h=bo(i,t),v?e(n):s;if(g)return lo(h),h=bo(i,t),e(p)}return h===T&&(h=bo(i,t)),s}var c,a,l,s,h,p,_=0,v=false,g=false,d=true;if(typeof n!="function")throw new ti("Expected a function");return t=Su(t)||0,du(r)&&(v=!!r.leading,l=(g="maxWait"in r)?Ui(Su(r.maxWait)||0,t):l,d="trailing"in r?!!r.trailing:d),f.cancel=function(){h!==T&&lo(h),_=0,c=p=a=h=T},f.flush=function(){return h===T?s:o(Go())},f}function cu(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;
return i.has(u)?i.get(u):(e=n.apply(this,e),r.cache=i.set(u,e)||i,e)}if(typeof n!="function"||null!=t&&typeof t!="function")throw new ti("Expected a function");return r.cache=new(cu.Cache||Fn),r}function au(n){if(typeof n!="function")throw new ti("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function lu(n,t){return n===t||n!==n&&t!==t;
}function su(n){return null!=n&&gu(n.length)&&!_u(n)}function hu(n){return yu(n)&&su(n)}function pu(n){if(!yu(n))return false;var t=Ot(n);return"[object Error]"==t||"[object DOMException]"==t||typeof n.message=="string"&&typeof n.name=="string"&&!xu(n)}function _u(n){return!!du(n)&&(n=Ot(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n)}function vu(n){return typeof n=="number"&&n==Eu(n)}function gu(n){return typeof n=="number"&&-1<n&&0==n%1&&9007199254740991>=n;
}function du(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function yu(n){return null!=n&&typeof n=="object"}function bu(n){return typeof n=="number"||yu(n)&&"[object Number]"==Ot(n)}function xu(n){return!(!yu(n)||"[object Object]"!=Ot(n))&&(n=di(n),null===n||(n=oi.call(n,"constructor")&&n.constructor,typeof n=="function"&&n instanceof n&&ii.call(n)==li))}function ju(n){return typeof n=="string"||!ff(n)&&yu(n)&&"[object String]"==Ot(n)}function wu(n){return typeof n=="symbol"||yu(n)&&"[object Symbol]"==Ot(n);
}function mu(n){if(!n)return[];if(su(n))return ju(n)?M(n):Ur(n);if(wi&&n[wi]){n=n[wi]();for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}return t=vo(n),("[object Map]"==t?W:"[object Set]"==t?U:Uu)(n)}function Au(n){return n?(n=Su(n),n===$||n===-$?1.7976931348623157e308*(0>n?-1:1):n===n?n:0):0===n?n:0}function Eu(n){n=Au(n);var t=n%1;return n===n?t?n-t:n:0}function ku(n){return n?pt(Eu(n),0,4294967295):0}function Su(n){if(typeof n=="number")return n;if(wu(n))return F;if(du(n)&&(n=typeof n.valueOf=="function"?n.valueOf():n,
n=du(n)?n+"":n),typeof n!="string")return 0===n?n:+n;n=n.replace(un,"");var t=gn.test(n);return t||yn.test(n)?Dn(n.slice(2),t?2:8):vn.test(n)?F:+n}function Ou(n){return Cr(n,Bu(n))}function Iu(n){return null==n?"":yr(n)}function Ru(n,t,r){return n=null==n?T:kt(n,t),n===T?r:n}function zu(n,t){return null!=n&&we(n,t,zt)}function Wu(n){return su(n)?qn(n):Vt(n)}function Bu(n){if(su(n))n=qn(n,true);else if(du(n)){var t,r=ze(n),e=[];for(t in n)("constructor"!=t||!r&&oi.call(n,t))&&e.push(t);n=e}else{if(t=[],
null!=n)for(r in Qu(n))t.push(r);n=t}return n}function Lu(n,t){if(null==n)return{};var r=c(ve(n),function(n){return[n]});return t=ye(t),tr(n,r,function(n,r){return t(n,r[0])})}function Uu(n){return null==n?[]:S(n,Wu(n))}function Cu(n){return $f(Iu(n).toLowerCase())}function Du(n){return(n=Iu(n))&&n.replace(xn,Xn).replace(Sn,"")}function Mu(n,t,r){return n=Iu(n),t=r?T:t,t===T?zn.test(n)?n.match(In)||[]:n.match(sn)||[]:n.match(t)||[]}function Tu(n){return function(){return n}}function $u(n){return n;
}function Fu(n){return qt(typeof n=="function"?n:_t(n,1))}function Nu(n,t,e){var u=Wu(t),i=Et(t,u);null!=e||du(t)&&(i.length||!u.length)||(e=t,t=n,n=this,i=Et(t,Wu(t)));var o=!(du(e)&&"chain"in e&&!e.chain),f=_u(n);return r(i,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;if(o||t){var r=n(this.__wrapped__);return(r.__actions__=Ur(this.__actions__)).push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,a([this.value()],arguments))})}),n}function Pu(){}
function Zu(n){return Ie(n)?b(Me(n)):rr(n)}function qu(){return[]}function Vu(){return false}mn=null==mn?$n:rt.defaults($n.Object(),mn,rt.pick($n,Wn));var Ku=mn.Array,Gu=mn.Date,Hu=mn.Error,Ju=mn.Function,Yu=mn.Math,Qu=mn.Object,Xu=mn.RegExp,ni=mn.String,ti=mn.TypeError,ri=Ku.prototype,ei=Qu.prototype,ui=mn["__core-js_shared__"],ii=Ju.prototype.toString,oi=ei.hasOwnProperty,fi=0,ci=function(){var n=/[^.]+$/.exec(ui&&ui.keys&&ui.keys.IE_PROTO||"");return n?"Symbol(src)_1."+n:""}(),ai=ei.toString,li=ii.call(Qu),si=$n._,hi=Xu("^"+ii.call(oi).replace(rn,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),pi=Pn?mn.Buffer:T,_i=mn.Symbol,vi=mn.Uint8Array,gi=pi?pi.g:T,di=B(Qu.getPrototypeOf,Qu),yi=Qu.create,bi=ei.propertyIsEnumerable,xi=ri.splice,ji=_i?_i.isConcatSpreadable:T,wi=_i?_i.iterator:T,mi=_i?_i.toStringTag:T,Ai=function(){
try{var n=je(Qu,"defineProperty");return n({},"",{}),n}catch(n){}}(),Ei=mn.clearTimeout!==$n.clearTimeout&&mn.clearTimeout,ki=Gu&&Gu.now!==$n.Date.now&&Gu.now,Si=mn.setTimeout!==$n.setTimeout&&mn.setTimeout,Oi=Yu.ceil,Ii=Yu.floor,Ri=Qu.getOwnPropertySymbols,zi=pi?pi.isBuffer:T,Wi=mn.isFinite,Bi=ri.join,Li=B(Qu.keys,Qu),Ui=Yu.max,Ci=Yu.min,Di=Gu.now,Mi=mn.parseInt,Ti=Yu.random,$i=ri.reverse,Fi=je(mn,"DataView"),Ni=je(mn,"Map"),Pi=je(mn,"Promise"),Zi=je(mn,"Set"),qi=je(mn,"WeakMap"),Vi=je(Qu,"create"),Ki=qi&&new qi,Gi={},Hi=Te(Fi),Ji=Te(Ni),Yi=Te(Pi),Qi=Te(Zi),Xi=Te(qi),no=_i?_i.prototype:T,to=no?no.valueOf:T,ro=no?no.toString:T,eo=function(){
function n(){}return function(t){return du(t)?yi?yi(t):(n.prototype=t,t=new n,n.prototype=T,t):{}}}();An.templateSettings={escape:J,evaluate:Y,interpolate:Q,variable:"",imports:{_:An}},An.prototype=En.prototype,An.prototype.constructor=An,On.prototype=eo(En.prototype),On.prototype.constructor=On,Un.prototype=eo(En.prototype),Un.prototype.constructor=Un,Mn.prototype.clear=function(){this.__data__=Vi?Vi(null):{},this.size=0},Mn.prototype.delete=function(n){return n=this.has(n)&&delete this.__data__[n],
this.size-=n?1:0,n},Mn.prototype.get=function(n){var t=this.__data__;return Vi?(n=t[n],"__lodash_hash_undefined__"===n?T:n):oi.call(t,n)?t[n]:T},Mn.prototype.has=function(n){var t=this.__data__;return Vi?t[n]!==T:oi.call(t,n)},Mn.prototype.set=function(n,t){var r=this.__data__;return this.size+=this.has(n)?0:1,r[n]=Vi&&t===T?"__lodash_hash_undefined__":t,this},Tn.prototype.clear=function(){this.__data__=[],this.size=0},Tn.prototype.delete=function(n){var t=this.__data__;return n=ft(t,n),!(0>n)&&(n==t.length-1?t.pop():xi.call(t,n,1),
--this.size,true)},Tn.prototype.get=function(n){var t=this.__data__;return n=ft(t,n),0>n?T:t[n][1]},Tn.prototype.has=function(n){return-1<ft(this.__data__,n)},Tn.prototype.set=function(n,t){var r=this.__data__,e=ft(r,n);return 0>e?(++this.size,r.push([n,t])):r[e][1]=t,this},Fn.prototype.clear=function(){this.size=0,this.__data__={hash:new Mn,map:new(Ni||Tn),string:new Mn}},Fn.prototype.delete=function(n){return n=be(this,n).delete(n),this.size-=n?1:0,n},Fn.prototype.get=function(n){return be(this,n).get(n);
},Fn.prototype.has=function(n){return be(this,n).has(n)},Fn.prototype.set=function(n,t){var r=be(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this},Nn.prototype.add=Nn.prototype.push=function(n){return this.__data__.set(n,"__lodash_hash_undefined__"),this},Nn.prototype.has=function(n){return this.__data__.has(n)},Zn.prototype.clear=function(){this.__data__=new Tn,this.size=0},Zn.prototype.delete=function(n){var t=this.__data__;return n=t.delete(n),this.size=t.size,n},Zn.prototype.get=function(n){
return this.__data__.get(n)},Zn.prototype.has=function(n){return this.__data__.has(n)},Zn.prototype.set=function(n,t){var r=this.__data__;if(r instanceof Tn){var e=r.__data__;if(!Ni||199>e.length)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new Fn(e)}return r.set(n,t),this.size=r.size,this};var uo=Fr(mt),io=Fr(At,true),oo=Nr(),fo=Nr(true),co=Ki?function(n,t){return Ki.set(n,t),n}:$u,ao=Ai?function(n,t){return Ai(n,"toString",{configurable:true,enumerable:false,value:Tu(t),writable:true})}:$u,lo=Ei||function(n){
return $n.clearTimeout(n)},so=Zi&&1/U(new Zi([,-0]))[1]==$?function(n){return new Zi(n)}:Pu,ho=Ki?function(n){return Ki.get(n)}:Pu,po=Ri?function(n){return null==n?[]:(n=Qu(n),i(Ri(n),function(t){return bi.call(n,t)}))}:qu,_o=Ri?function(n){for(var t=[];n;)a(t,po(n)),n=di(n);return t}:qu,vo=Ot;(Fi&&"[object DataView]"!=vo(new Fi(new ArrayBuffer(1)))||Ni&&"[object Map]"!=vo(new Ni)||Pi&&"[object Promise]"!=vo(Pi.resolve())||Zi&&"[object Set]"!=vo(new Zi)||qi&&"[object WeakMap]"!=vo(new qi))&&(vo=function(n){
var t=Ot(n);if(n=(n="[object Object]"==t?n.constructor:T)?Te(n):"")switch(n){case Hi:return"[object DataView]";case Ji:return"[object Map]";case Yi:return"[object Promise]";case Qi:return"[object Set]";case Xi:return"[object WeakMap]"}return t});var go=ui?_u:Vu,yo=Ce(co),bo=Si||function(n,t){return $n.setTimeout(n,t)},xo=Ce(ao),jo=function(n){n=cu(n,function(n){return 500===t.size&&t.clear(),n});var t=n.cache;return n}(function(n){var t=[];return 46===n.charCodeAt(0)&&t.push(""),n.replace(tn,function(n,r,e,u){
t.push(e?u.replace(hn,"$1"):r||n)}),t}),wo=fr(function(n,t){return hu(n)?yt(n,wt(t,1,hu,true)):[]}),mo=fr(function(n,t){var r=Ve(t);return hu(r)&&(r=T),hu(n)?yt(n,wt(t,1,hu,true),ye(r,2)):[]}),Ao=fr(function(n,t){var r=Ve(t);return hu(r)&&(r=T),hu(n)?yt(n,wt(t,1,hu,true),T,r):[]}),Eo=fr(function(n){var t=c(n,Er);return t.length&&t[0]===n[0]?Wt(t):[]}),ko=fr(function(n){var t=Ve(n),r=c(n,Er);return t===Ve(r)?t=T:r.pop(),r.length&&r[0]===n[0]?Wt(r,ye(t,2)):[]}),So=fr(function(n){var t=Ve(n),r=c(n,Er);return(t=typeof t=="function"?t:T)&&r.pop(),
r.length&&r[0]===n[0]?Wt(r,T,t):[]}),Oo=fr(Ke),Io=pe(function(n,t){var r=null==n?0:n.length,e=ht(n,t);return ur(n,c(t,function(n){return Se(n,r)?+n:n}).sort(Wr)),e}),Ro=fr(function(n){return br(wt(n,1,hu,true))}),zo=fr(function(n){var t=Ve(n);return hu(t)&&(t=T),br(wt(n,1,hu,true),ye(t,2))}),Wo=fr(function(n){var t=Ve(n),t=typeof t=="function"?t:T;return br(wt(n,1,hu,true),T,t)}),Bo=fr(function(n,t){return hu(n)?yt(n,t):[]}),Lo=fr(function(n){return mr(i(n,hu))}),Uo=fr(function(n){var t=Ve(n);return hu(t)&&(t=T),
mr(i(n,hu),ye(t,2))}),Co=fr(function(n){var t=Ve(n),t=typeof t=="function"?t:T;return mr(i(n,hu),T,t)}),Do=fr(He),Mo=fr(function(n){var t=n.length,t=1<t?n[t-1]:T,t=typeof t=="function"?(n.pop(),t):T;return Je(n,t)}),To=pe(function(n){function t(t){return ht(t,n)}var r=n.length,e=r?n[0]:0,u=this.__wrapped__;return!(1<r||this.__actions__.length)&&u instanceof Un&&Se(e)?(u=u.slice(e,+e+(r?1:0)),u.__actions__.push({func:Qe,args:[t],thisArg:T}),new On(u,this.__chain__).thru(function(n){return r&&!n.length&&n.push(T),
n})):this.thru(t)}),$o=Tr(function(n,t,r){oi.call(n,r)?++n[r]:st(n,r,1)}),Fo=Gr(Ne),No=Gr(Pe),Po=Tr(function(n,t,r){oi.call(n,r)?n[r].push(t):st(n,r,[t])}),Zo=fr(function(t,r,e){var u=-1,i=typeof r=="function",o=su(t)?Ku(t.length):[];return uo(t,function(t){o[++u]=i?n(r,t,e):Lt(t,r,e)}),o}),qo=Tr(function(n,t,r){st(n,r,t)}),Vo=Tr(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),Ko=fr(function(n,t){if(null==n)return[];var r=t.length;return 1<r&&Oe(n,t[0],t[1])?t=[]:2<r&&Oe(t[0],t[1],t[2])&&(t=[t[0]]),
Xt(n,wt(t,1),[])}),Go=ki||function(){return $n.Date.now()},Ho=fr(function(n,t,r){var e=1;if(r.length)var u=L(r,de(Ho)),e=32|e;return fe(n,e,t,r,u)}),Jo=fr(function(n,t,r){var e=3;if(r.length)var u=L(r,de(Jo)),e=32|e;return fe(t,e,n,r,u)}),Yo=fr(function(n,t){return dt(n,1,t)}),Qo=fr(function(n,t,r){return dt(n,Su(t)||0,r)});cu.Cache=Fn;var Xo=fr(function(t,r){r=1==r.length&&ff(r[0])?c(r[0],k(ye())):c(wt(r,1),k(ye()));var e=r.length;return fr(function(u){for(var i=-1,o=Ci(u.length,e);++i<o;)u[i]=r[i].call(this,u[i]);
return n(t,this,u)})}),nf=fr(function(n,t){return fe(n,32,T,t,L(t,de(nf)))}),tf=fr(function(n,t){return fe(n,64,T,t,L(t,de(tf)))}),rf=pe(function(n,t){return fe(n,256,T,T,T,t)}),ef=ee(It),uf=ee(function(n,t){return n>=t}),of=Ut(function(){return arguments}())?Ut:function(n){return yu(n)&&oi.call(n,"callee")&&!bi.call(n,"callee")},ff=Ku.isArray,cf=Vn?k(Vn):Ct,af=zi||Vu,lf=Kn?k(Kn):Dt,sf=Gn?k(Gn):Tt,hf=Hn?k(Hn):Nt,pf=Jn?k(Jn):Pt,_f=Yn?k(Yn):Zt,vf=ee(Kt),gf=ee(function(n,t){return n<=t}),df=$r(function(n,t){
if(ze(t)||su(t))Cr(t,Wu(t),n);else for(var r in t)oi.call(t,r)&&ot(n,r,t[r])}),yf=$r(function(n,t){Cr(t,Bu(t),n)}),bf=$r(function(n,t,r,e){Cr(t,Bu(t),n,e)}),xf=$r(function(n,t,r,e){Cr(t,Wu(t),n,e)}),jf=pe(ht),wf=fr(function(n,t){n=Qu(n);var r=-1,e=t.length,u=2<e?t[2]:T;for(u&&Oe(t[0],t[1],u)&&(e=1);++r<e;)for(var u=t[r],i=Bu(u),o=-1,f=i.length;++o<f;){var c=i[o],a=n[c];(a===T||lu(a,ei[c])&&!oi.call(n,c))&&(n[c]=u[c])}return n}),mf=fr(function(t){return t.push(T,ae),n(Of,T,t)}),Af=Yr(function(n,t,r){
null!=t&&typeof t.toString!="function"&&(t=ai.call(t)),n[t]=r},Tu($u)),Ef=Yr(function(n,t,r){null!=t&&typeof t.toString!="function"&&(t=ai.call(t)),oi.call(n,t)?n[t].push(r):n[t]=[r]},ye),kf=fr(Lt),Sf=$r(function(n,t,r){Yt(n,t,r)}),Of=$r(function(n,t,r,e){Yt(n,t,r,e)}),If=pe(function(n,t){var r={};if(null==n)return r;var e=false;t=c(t,function(t){return t=Sr(t,n),e||(e=1<t.length),t}),Cr(n,ve(n),r),e&&(r=_t(r,7,le));for(var u=t.length;u--;)xr(r,t[u]);return r}),Rf=pe(function(n,t){return null==n?{}:nr(n,t);
}),zf=oe(Wu),Wf=oe(Bu),Bf=qr(function(n,t,r){return t=t.toLowerCase(),n+(r?Cu(t):t)}),Lf=qr(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Uf=qr(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Cf=Zr("toLowerCase"),Df=qr(function(n,t,r){return n+(r?"_":"")+t.toLowerCase()}),Mf=qr(function(n,t,r){return n+(r?" ":"")+$f(t)}),Tf=qr(function(n,t,r){return n+(r?" ":"")+t.toUpperCase()}),$f=Zr("toUpperCase"),Ff=fr(function(t,r){try{return n(t,T,r)}catch(n){return pu(n)?n:new Hu(n)}}),Nf=pe(function(n,t){
return r(t,function(t){t=Me(t),st(n,t,Ho(n[t],n))}),n}),Pf=Hr(),Zf=Hr(true),qf=fr(function(n,t){return function(r){return Lt(r,n,t)}}),Vf=fr(function(n,t){return function(r){return Lt(n,r,t)}}),Kf=Xr(c),Gf=Xr(u),Hf=Xr(h),Jf=re(),Yf=re(true),Qf=Qr(function(n,t){return n+t},0),Xf=ie("ceil"),nc=Qr(function(n,t){return n/t},1),tc=ie("floor"),rc=Qr(function(n,t){return n*t},1),ec=ie("round"),uc=Qr(function(n,t){return n-t},0);return An.after=function(n,t){if(typeof t!="function")throw new ti("Expected a function");
return n=Eu(n),function(){if(1>--n)return t.apply(this,arguments)}},An.ary=eu,An.assign=df,An.assignIn=yf,An.assignInWith=bf,An.assignWith=xf,An.at=jf,An.before=uu,An.bind=Ho,An.bindAll=Nf,An.bindKey=Jo,An.castArray=function(){if(!arguments.length)return[];var n=arguments[0];return ff(n)?n:[n]},An.chain=Ye,An.chunk=function(n,t,r){if(t=(r?Oe(n,t,r):t===T)?1:Ui(Eu(t),0),r=null==n?0:n.length,!r||1>t)return[];for(var e=0,u=0,i=Ku(Oi(r/t));e<r;)i[u++]=hr(n,e,e+=t);return i},An.compact=function(n){for(var t=-1,r=null==n?0:n.length,e=0,u=[];++t<r;){
var i=n[t];i&&(u[e++]=i)}return u},An.concat=function(){var n=arguments.length;if(!n)return[];for(var t=Ku(n-1),r=arguments[0];n--;)t[n-1]=arguments[n];return a(ff(r)?Ur(r):[r],wt(t,1))},An.cond=function(t){var r=null==t?0:t.length,e=ye();return t=r?c(t,function(n){if("function"!=typeof n[1])throw new ti("Expected a function");return[e(n[0]),n[1]]}):[],fr(function(e){for(var u=-1;++u<r;){var i=t[u];if(n(i[0],this,e))return n(i[1],this,e)}})},An.conforms=function(n){return vt(_t(n,1))},An.constant=Tu,
An.countBy=$o,An.create=function(n,t){var r=eo(n);return null==t?r:at(r,t)},An.curry=iu,An.curryRight=ou,An.debounce=fu,An.defaults=wf,An.defaultsDeep=mf,An.defer=Yo,An.delay=Qo,An.difference=wo,An.differenceBy=mo,An.differenceWith=Ao,An.drop=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===T?1:Eu(t),hr(n,0>t?0:t,e)):[]},An.dropRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===T?1:Eu(t),t=e-t,hr(n,0,0>t?0:t)):[]},An.dropRightWhile=function(n,t){return n&&n.length?jr(n,ye(t,3),true,true):[];
},An.dropWhile=function(n,t){return n&&n.length?jr(n,ye(t,3),true):[]},An.fill=function(n,t,r,e){var u=null==n?0:n.length;if(!u)return[];for(r&&typeof r!="number"&&Oe(n,t,r)&&(r=0,e=u),u=n.length,r=Eu(r),0>r&&(r=-r>u?0:u+r),e=e===T||e>u?u:Eu(e),0>e&&(e+=u),e=r>e?0:ku(e);r<e;)n[r++]=t;return n},An.filter=function(n,t){return(ff(n)?i:jt)(n,ye(t,3))},An.flatMap=function(n,t){return wt(ru(n,t),1)},An.flatMapDeep=function(n,t){return wt(ru(n,t),$)},An.flatMapDepth=function(n,t,r){return r=r===T?1:Eu(r),
wt(ru(n,t),r)},An.flatten=Ze,An.flattenDeep=function(n){return(null==n?0:n.length)?wt(n,$):[]},An.flattenDepth=function(n,t){return null!=n&&n.length?(t=t===T?1:Eu(t),wt(n,t)):[]},An.flip=function(n){return fe(n,512)},An.flow=Pf,An.flowRight=Zf,An.fromPairs=function(n){for(var t=-1,r=null==n?0:n.length,e={};++t<r;){var u=n[t];e[u[0]]=u[1]}return e},An.functions=function(n){return null==n?[]:Et(n,Wu(n))},An.functionsIn=function(n){return null==n?[]:Et(n,Bu(n))},An.groupBy=Po,An.initial=function(n){
return(null==n?0:n.length)?hr(n,0,-1):[]},An.intersection=Eo,An.intersectionBy=ko,An.intersectionWith=So,An.invert=Af,An.invertBy=Ef,An.invokeMap=Zo,An.iteratee=Fu,An.keyBy=qo,An.keys=Wu,An.keysIn=Bu,An.map=ru,An.mapKeys=function(n,t){var r={};return t=ye(t,3),mt(n,function(n,e,u){st(r,t(n,e,u),n)}),r},An.mapValues=function(n,t){var r={};return t=ye(t,3),mt(n,function(n,e,u){st(r,e,t(n,e,u))}),r},An.matches=function(n){return Ht(_t(n,1))},An.matchesProperty=function(n,t){return Jt(n,_t(t,1))},An.memoize=cu,
An.merge=Sf,An.mergeWith=Of,An.method=qf,An.methodOf=Vf,An.mixin=Nu,An.negate=au,An.nthArg=function(n){return n=Eu(n),fr(function(t){return Qt(t,n)})},An.omit=If,An.omitBy=function(n,t){return Lu(n,au(ye(t)))},An.once=function(n){return uu(2,n)},An.orderBy=function(n,t,r,e){return null==n?[]:(ff(t)||(t=null==t?[]:[t]),r=e?T:r,ff(r)||(r=null==r?[]:[r]),Xt(n,t,r))},An.over=Kf,An.overArgs=Xo,An.overEvery=Gf,An.overSome=Hf,An.partial=nf,An.partialRight=tf,An.partition=Vo,An.pick=Rf,An.pickBy=Lu,An.property=Zu,
An.propertyOf=function(n){return function(t){return null==n?T:kt(n,t)}},An.pull=Oo,An.pullAll=Ke,An.pullAllBy=function(n,t,r){return n&&n.length&&t&&t.length?er(n,t,ye(r,2)):n},An.pullAllWith=function(n,t,r){return n&&n.length&&t&&t.length?er(n,t,T,r):n},An.pullAt=Io,An.range=Jf,An.rangeRight=Yf,An.rearg=rf,An.reject=function(n,t){return(ff(n)?i:jt)(n,au(ye(t,3)))},An.remove=function(n,t){var r=[];if(!n||!n.length)return r;var e=-1,u=[],i=n.length;for(t=ye(t,3);++e<i;){var o=n[e];t(o,e,n)&&(r.push(o),
u.push(e))}return ur(n,u),r},An.rest=function(n,t){if(typeof n!="function")throw new ti("Expected a function");return t=t===T?t:Eu(t),fr(n,t)},An.reverse=Ge,An.sampleSize=function(n,t,r){return t=(r?Oe(n,t,r):t===T)?1:Eu(t),(ff(n)?et:ar)(n,t)},An.set=function(n,t,r){return null==n?n:lr(n,t,r)},An.setWith=function(n,t,r,e){return e=typeof e=="function"?e:T,null==n?n:lr(n,t,r,e)},An.shuffle=function(n){return(ff(n)?ut:sr)(n)},An.slice=function(n,t,r){var e=null==n?0:n.length;return e?(r&&typeof r!="number"&&Oe(n,t,r)?(t=0,
r=e):(t=null==t?0:Eu(t),r=r===T?e:Eu(r)),hr(n,t,r)):[]},An.sortBy=Ko,An.sortedUniq=function(n){return n&&n.length?gr(n):[]},An.sortedUniqBy=function(n,t){return n&&n.length?gr(n,ye(t,2)):[]},An.split=function(n,t,r){return r&&typeof r!="number"&&Oe(n,t,r)&&(t=r=T),r=r===T?4294967295:r>>>0,r?(n=Iu(n))&&(typeof t=="string"||null!=t&&!hf(t))&&(t=yr(t),!t&&Rn.test(n))?Or(M(n),0,r):n.split(t,r):[]},An.spread=function(t,r){if(typeof t!="function")throw new ti("Expected a function");return r=null==r?0:Ui(Eu(r),0),
fr(function(e){var u=e[r];return e=Or(e,0,r),u&&a(e,u),n(t,this,e)})},An.tail=function(n){var t=null==n?0:n.length;return t?hr(n,1,t):[]},An.take=function(n,t,r){return n&&n.length?(t=r||t===T?1:Eu(t),hr(n,0,0>t?0:t)):[]},An.takeRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===T?1:Eu(t),t=e-t,hr(n,0>t?0:t,e)):[]},An.takeRightWhile=function(n,t){return n&&n.length?jr(n,ye(t,3),false,true):[]},An.takeWhile=function(n,t){return n&&n.length?jr(n,ye(t,3)):[]},An.tap=function(n,t){return t(n),
n},An.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new ti("Expected a function");return du(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),fu(n,t,{leading:e,maxWait:t,trailing:u})},An.thru=Qe,An.toArray=mu,An.toPairs=zf,An.toPairsIn=Wf,An.toPath=function(n){return ff(n)?c(n,Me):wu(n)?[n]:Ur(jo(Iu(n)))},An.toPlainObject=Ou,An.transform=function(n,t,e){var u=ff(n),i=u||af(n)||_f(n);if(t=ye(t,4),null==e){var o=n&&n.constructor;e=i?u?new o:[]:du(n)&&_u(o)?eo(di(n)):{};
}return(i?r:mt)(n,function(n,r,u){return t(e,n,r,u)}),e},An.unary=function(n){return eu(n,1)},An.union=Ro,An.unionBy=zo,An.unionWith=Wo,An.uniq=function(n){return n&&n.length?br(n):[]},An.uniqBy=function(n,t){return n&&n.length?br(n,ye(t,2)):[]},An.uniqWith=function(n,t){return t=typeof t=="function"?t:T,n&&n.length?br(n,T,t):[]},An.unset=function(n,t){return null==n||xr(n,t)},An.unzip=He,An.unzipWith=Je,An.update=function(n,t,r){return null==n?n:lr(n,t,kr(r)(kt(n,t)),void 0)},An.updateWith=function(n,t,r,e){
return e=typeof e=="function"?e:T,null!=n&&(n=lr(n,t,kr(r)(kt(n,t)),e)),n},An.values=Uu,An.valuesIn=function(n){return null==n?[]:S(n,Bu(n))},An.without=Bo,An.words=Mu,An.wrap=function(n,t){return nf(kr(t),n)},An.xor=Lo,An.xorBy=Uo,An.xorWith=Co,An.zip=Do,An.zipObject=function(n,t){return Ar(n||[],t||[],ot)},An.zipObjectDeep=function(n,t){return Ar(n||[],t||[],lr)},An.zipWith=Mo,An.entries=zf,An.entriesIn=Wf,An.extend=yf,An.extendWith=bf,Nu(An,An),An.add=Qf,An.attempt=Ff,An.camelCase=Bf,An.capitalize=Cu,
An.ceil=Xf,An.clamp=function(n,t,r){return r===T&&(r=t,t=T),r!==T&&(r=Su(r),r=r===r?r:0),t!==T&&(t=Su(t),t=t===t?t:0),pt(Su(n),t,r)},An.clone=function(n){return _t(n,4)},An.cloneDeep=function(n){return _t(n,5)},An.cloneDeepWith=function(n,t){return t=typeof t=="function"?t:T,_t(n,5,t)},An.cloneWith=function(n,t){return t=typeof t=="function"?t:T,_t(n,4,t)},An.conformsTo=function(n,t){return null==t||gt(n,t,Wu(t))},An.deburr=Du,An.defaultTo=function(n,t){return null==n||n!==n?t:n},An.divide=nc,An.endsWith=function(n,t,r){
n=Iu(n),t=yr(t);var e=n.length,e=r=r===T?e:pt(Eu(r),0,e);return r-=t.length,0<=r&&n.slice(r,e)==t},An.eq=lu,An.escape=function(n){return(n=Iu(n))&&H.test(n)?n.replace(K,nt):n},An.escapeRegExp=function(n){return(n=Iu(n))&&en.test(n)?n.replace(rn,"\\$&"):n},An.every=function(n,t,r){var e=ff(n)?u:bt;return r&&Oe(n,t,r)&&(t=T),e(n,ye(t,3))},An.find=Fo,An.findIndex=Ne,An.findKey=function(n,t){return p(n,ye(t,3),mt)},An.findLast=No,An.findLastIndex=Pe,An.findLastKey=function(n,t){return p(n,ye(t,3),At);
},An.floor=tc,An.forEach=nu,An.forEachRight=tu,An.forIn=function(n,t){return null==n?n:oo(n,ye(t,3),Bu)},An.forInRight=function(n,t){return null==n?n:fo(n,ye(t,3),Bu)},An.forOwn=function(n,t){return n&&mt(n,ye(t,3))},An.forOwnRight=function(n,t){return n&&At(n,ye(t,3))},An.get=Ru,An.gt=ef,An.gte=uf,An.has=function(n,t){return null!=n&&we(n,t,Rt)},An.hasIn=zu,An.head=qe,An.identity=$u,An.includes=function(n,t,r,e){return n=su(n)?n:Uu(n),r=r&&!e?Eu(r):0,e=n.length,0>r&&(r=Ui(e+r,0)),ju(n)?r<=e&&-1<n.indexOf(t,r):!!e&&-1<v(n,t,r);
},An.indexOf=function(n,t,r){var e=null==n?0:n.length;return e?(r=null==r?0:Eu(r),0>r&&(r=Ui(e+r,0)),v(n,t,r)):-1},An.inRange=function(n,t,r){return t=Au(t),r===T?(r=t,t=0):r=Au(r),n=Su(n),n>=Ci(t,r)&&n<Ui(t,r)},An.invoke=kf,An.isArguments=of,An.isArray=ff,An.isArrayBuffer=cf,An.isArrayLike=su,An.isArrayLikeObject=hu,An.isBoolean=function(n){return true===n||false===n||yu(n)&&"[object Boolean]"==Ot(n)},An.isBuffer=af,An.isDate=lf,An.isElement=function(n){return yu(n)&&1===n.nodeType&&!xu(n)},An.isEmpty=function(n){
if(null==n)return true;if(su(n)&&(ff(n)||typeof n=="string"||typeof n.splice=="function"||af(n)||_f(n)||of(n)))return!n.length;var t=vo(n);if("[object Map]"==t||"[object Set]"==t)return!n.size;if(ze(n))return!Vt(n).length;for(var r in n)if(oi.call(n,r))return false;return true},An.isEqual=function(n,t){return Mt(n,t)},An.isEqualWith=function(n,t,r){var e=(r=typeof r=="function"?r:T)?r(n,t):T;return e===T?Mt(n,t,T,r):!!e},An.isError=pu,An.isFinite=function(n){return typeof n=="number"&&Wi(n)},An.isFunction=_u,
An.isInteger=vu,An.isLength=gu,An.isMap=sf,An.isMatch=function(n,t){return n===t||$t(n,t,xe(t))},An.isMatchWith=function(n,t,r){return r=typeof r=="function"?r:T,$t(n,t,xe(t),r)},An.isNaN=function(n){return bu(n)&&n!=+n},An.isNative=function(n){if(go(n))throw new Hu("Unsupported core-js use. Try https://npms.io/search?q=ponyfill.");return Ft(n)},An.isNil=function(n){return null==n},An.isNull=function(n){return null===n},An.isNumber=bu,An.isObject=du,An.isObjectLike=yu,An.isPlainObject=xu,An.isRegExp=hf,
An.isSafeInteger=function(n){return vu(n)&&-9007199254740991<=n&&9007199254740991>=n},An.isSet=pf,An.isString=ju,An.isSymbol=wu,An.isTypedArray=_f,An.isUndefined=function(n){return n===T},An.isWeakMap=function(n){return yu(n)&&"[object WeakMap]"==vo(n)},An.isWeakSet=function(n){return yu(n)&&"[object WeakSet]"==Ot(n)},An.join=function(n,t){return null==n?"":Bi.call(n,t)},An.kebabCase=Lf,An.last=Ve,An.lastIndexOf=function(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e;if(r!==T&&(u=Eu(r),u=0>u?Ui(e+u,0):Ci(u,e-1)),
t===t){for(r=u+1;r--&&n[r]!==t;);n=r}else n=_(n,d,u,true);return n},An.lowerCase=Uf,An.lowerFirst=Cf,An.lt=vf,An.lte=gf,An.max=function(n){return n&&n.length?xt(n,$u,It):T},An.maxBy=function(n,t){return n&&n.length?xt(n,ye(t,2),It):T},An.mean=function(n){return y(n,$u)},An.meanBy=function(n,t){return y(n,ye(t,2))},An.min=function(n){return n&&n.length?xt(n,$u,Kt):T},An.minBy=function(n,t){return n&&n.length?xt(n,ye(t,2),Kt):T},An.stubArray=qu,An.stubFalse=Vu,An.stubObject=function(){return{}},An.stubString=function(){
return""},An.stubTrue=function(){return true},An.multiply=rc,An.nth=function(n,t){return n&&n.length?Qt(n,Eu(t)):T},An.noConflict=function(){return $n._===this&&($n._=si),this},An.noop=Pu,An.now=Go,An.pad=function(n,t,r){n=Iu(n);var e=(t=Eu(t))?D(n):0;return!t||e>=t?n:(t=(t-e)/2,ne(Ii(t),r)+n+ne(Oi(t),r))},An.padEnd=function(n,t,r){n=Iu(n);var e=(t=Eu(t))?D(n):0;return t&&e<t?n+ne(t-e,r):n},An.padStart=function(n,t,r){n=Iu(n);var e=(t=Eu(t))?D(n):0;return t&&e<t?ne(t-e,r)+n:n},An.parseInt=function(n,t,r){
return r||null==t?t=0:t&&(t=+t),Mi(Iu(n).replace(on,""),t||0)},An.random=function(n,t,r){if(r&&typeof r!="boolean"&&Oe(n,t,r)&&(t=r=T),r===T&&(typeof t=="boolean"?(r=t,t=T):typeof n=="boolean"&&(r=n,n=T)),n===T&&t===T?(n=0,t=1):(n=Au(n),t===T?(t=n,n=0):t=Au(t)),n>t){var e=n;n=t,t=e}return r||n%1||t%1?(r=Ti(),Ci(n+r*(t-n+Cn("1e-"+((r+"").length-1))),t)):ir(n,t)},An.reduce=function(n,t,r){var e=ff(n)?l:j,u=3>arguments.length;return e(n,ye(t,4),r,u,uo)},An.reduceRight=function(n,t,r){var e=ff(n)?s:j,u=3>arguments.length;
return e(n,ye(t,4),r,u,io)},An.repeat=function(n,t,r){return t=(r?Oe(n,t,r):t===T)?1:Eu(t),or(Iu(n),t)},An.replace=function(){var n=arguments,t=Iu(n[0]);return 3>n.length?t:t.replace(n[1],n[2])},An.result=function(n,t,r){t=Sr(t,n);var e=-1,u=t.length;for(u||(u=1,n=T);++e<u;){var i=null==n?T:n[Me(t[e])];i===T&&(e=u,i=r),n=_u(i)?i.call(n):i}return n},An.round=ec,An.runInContext=x,An.sample=function(n){return(ff(n)?Qn:cr)(n)},An.size=function(n){if(null==n)return 0;if(su(n))return ju(n)?D(n):n.length;
var t=vo(n);return"[object Map]"==t||"[object Set]"==t?n.size:Vt(n).length},An.snakeCase=Df,An.some=function(n,t,r){var e=ff(n)?h:pr;return r&&Oe(n,t,r)&&(t=T),e(n,ye(t,3))},An.sortedIndex=function(n,t){return _r(n,t)},An.sortedIndexBy=function(n,t,r){return vr(n,t,ye(r,2))},An.sortedIndexOf=function(n,t){var r=null==n?0:n.length;if(r){var e=_r(n,t);if(e<r&&lu(n[e],t))return e}return-1},An.sortedLastIndex=function(n,t){return _r(n,t,true)},An.sortedLastIndexBy=function(n,t,r){return vr(n,t,ye(r,2),true);
},An.sortedLastIndexOf=function(n,t){if(null==n?0:n.length){var r=_r(n,t,true)-1;if(lu(n[r],t))return r}return-1},An.startCase=Mf,An.startsWith=function(n,t,r){return n=Iu(n),r=null==r?0:pt(Eu(r),0,n.length),t=yr(t),n.slice(r,r+t.length)==t},An.subtract=uc,An.sum=function(n){return n&&n.length?m(n,$u):0},An.sumBy=function(n,t){return n&&n.length?m(n,ye(t,2)):0},An.template=function(n,t,r){var e=An.templateSettings;r&&Oe(n,t,r)&&(t=T),n=Iu(n),t=bf({},t,e,ce),r=bf({},t.imports,e.imports,ce);var u,i,o=Wu(r),f=S(r,o),c=0;
r=t.interpolate||jn;var a="__p+='";r=Xu((t.escape||jn).source+"|"+r.source+"|"+(r===Q?pn:jn).source+"|"+(t.evaluate||jn).source+"|$","g");var l=oi.call(t,"sourceURL")?"//# sourceURL="+(t.sourceURL+"").replace(/[\r\n]/g," ")+"\n":"";if(n.replace(r,function(t,r,e,o,f,l){return e||(e=o),a+=n.slice(c,l).replace(wn,z),r&&(u=true,a+="'+__e("+r+")+'"),f&&(i=true,a+="';"+f+";\n__p+='"),e&&(a+="'+((__t=("+e+"))==null?'':__t)+'"),c=l+t.length,t}),a+="';",(t=oi.call(t,"variable")&&t.variable)||(a="with(obj){"+a+"}"),
a=(i?a.replace(P,""):a).replace(Z,"$1").replace(q,"$1;"),a="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(u?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+a+"return __p}",t=Ff(function(){return Ju(o,l+"return "+a).apply(T,f)}),t.source=a,pu(t))throw t;return t},An.times=function(n,t){if(n=Eu(n),1>n||9007199254740991<n)return[];var r=4294967295,e=Ci(n,4294967295);for(t=ye(t),n-=4294967295,e=A(e,t);++r<n;)t(r);return e},An.toFinite=Au,
An.toInteger=Eu,An.toLength=ku,An.toLower=function(n){return Iu(n).toLowerCase()},An.toNumber=Su,An.toSafeInteger=function(n){return n?pt(Eu(n),-9007199254740991,9007199254740991):0===n?n:0},An.toString=Iu,An.toUpper=function(n){return Iu(n).toUpperCase()},An.trim=function(n,t,r){return(n=Iu(n))&&(r||t===T)?n.replace(un,""):n&&(t=yr(t))?(n=M(n),r=M(t),t=I(n,r),r=R(n,r)+1,Or(n,t,r).join("")):n},An.trimEnd=function(n,t,r){return(n=Iu(n))&&(r||t===T)?n.replace(fn,""):n&&(t=yr(t))?(n=M(n),t=R(n,M(t))+1,
Or(n,0,t).join("")):n},An.trimStart=function(n,t,r){return(n=Iu(n))&&(r||t===T)?n.replace(on,""):n&&(t=yr(t))?(n=M(n),t=I(n,M(t)),Or(n,t).join("")):n},An.truncate=function(n,t){var r=30,e="...";if(du(t))var u="separator"in t?t.separator:u,r="length"in t?Eu(t.length):r,e="omission"in t?yr(t.omission):e;n=Iu(n);var i=n.length;if(Rn.test(n))var o=M(n),i=o.length;if(r>=i)return n;if(i=r-D(e),1>i)return e;if(r=o?Or(o,0,i).join(""):n.slice(0,i),u===T)return r+e;if(o&&(i+=r.length-i),hf(u)){if(n.slice(i).search(u)){
var f=r;for(u.global||(u=Xu(u.source,Iu(_n.exec(u))+"g")),u.lastIndex=0;o=u.exec(f);)var c=o.index;r=r.slice(0,c===T?i:c)}}else n.indexOf(yr(u),i)!=i&&(u=r.lastIndexOf(u),-1<u&&(r=r.slice(0,u)));return r+e},An.unescape=function(n){return(n=Iu(n))&&G.test(n)?n.replace(V,tt):n},An.uniqueId=function(n){var t=++fi;return Iu(n)+t},An.upperCase=Tf,An.upperFirst=$f,An.each=nu,An.eachRight=tu,An.first=qe,Nu(An,function(){var n={};return mt(An,function(t,r){oi.call(An.prototype,r)||(n[r]=t)}),n}(),{chain:false
}),An.VERSION="4.17.15",r("bind bindKey curry curryRight partial partialRight".split(" "),function(n){An[n].placeholder=An}),r(["drop","take"],function(n,t){Un.prototype[n]=function(r){r=r===T?1:Ui(Eu(r),0);var e=this.__filtered__&&!t?new Un(this):this.clone();return e.__filtered__?e.__takeCount__=Ci(r,e.__takeCount__):e.__views__.push({size:Ci(r,4294967295),type:n+(0>e.__dir__?"Right":"")}),e},Un.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),r(["filter","map","takeWhile"],function(n,t){
var r=t+1,e=1==r||3==r;Un.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:ye(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),r(["head","last"],function(n,t){var r="take"+(t?"Right":"");Un.prototype[n]=function(){return this[r](1).value()[0]}}),r(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Un.prototype[n]=function(){return this.__filtered__?new Un(this):this[r](1)}}),Un.prototype.compact=function(){return this.filter($u)},Un.prototype.find=function(n){
return this.filter(n).head()},Un.prototype.findLast=function(n){return this.reverse().find(n)},Un.prototype.invokeMap=fr(function(n,t){return typeof n=="function"?new Un(this):this.map(function(r){return Lt(r,n,t)})}),Un.prototype.reject=function(n){return this.filter(au(ye(n)))},Un.prototype.slice=function(n,t){n=Eu(n);var r=this;return r.__filtered__&&(0<n||0>t)?new Un(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==T&&(t=Eu(t),r=0>t?r.dropRight(-t):r.take(t-n)),r)},Un.prototype.takeRightWhile=function(n){
return this.reverse().takeWhile(n).reverse()},Un.prototype.toArray=function(){return this.take(4294967295)},mt(Un.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=An[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t);u&&(An.prototype[t]=function(){function t(n){return n=u.apply(An,a([n],f)),e&&h?n[0]:n}var o=this.__wrapped__,f=e?[1]:arguments,c=o instanceof Un,l=f[0],s=c||ff(o);s&&r&&typeof l=="function"&&1!=l.length&&(c=s=false);var h=this.__chain__,p=!!this.__actions__.length,l=i&&!h,c=c&&!p;
return!i&&s?(o=c?o:new Un(this),o=n.apply(o,f),o.__actions__.push({func:Qe,args:[t],thisArg:T}),new On(o,h)):l&&c?n.apply(this,f):(o=this.thru(t),l?e?o.value()[0]:o.value():o)})}),r("pop push shift sort splice unshift".split(" "),function(n){var t=ri[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);An.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(ff(u)?u:[],n)}return this[r](function(r){return t.apply(ff(r)?r:[],n)});
}}),mt(Un.prototype,function(n,t){var r=An[t];if(r){var e=r.name+"";oi.call(Gi,e)||(Gi[e]=[]),Gi[e].push({name:t,func:r})}}),Gi[Jr(T,2).name]=[{name:"wrapper",func:T}],Un.prototype.clone=function(){var n=new Un(this.__wrapped__);return n.__actions__=Ur(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Ur(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Ur(this.__views__),n},Un.prototype.reverse=function(){if(this.__filtered__){var n=new Un(this);
n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},Un.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=ff(t),u=0>r,i=e?t.length:0;n=i;for(var o=this.__views__,f=0,c=-1,a=o.length;++c<a;){var l=o[c],s=l.size;switch(l.type){case"drop":f+=s;break;case"dropRight":n-=s;break;case"take":n=Ci(n,f+s);break;case"takeRight":f=Ui(f,n-s)}}if(n={start:f,end:n},o=n.start,f=n.end,n=f-o,o=u?f:o-1,f=this.__iteratees__,c=f.length,a=0,l=Ci(n,this.__takeCount__),!e||!u&&i==n&&l==n)return wr(t,this.__actions__);
e=[];n:for(;n--&&a<l;){for(o+=r,u=-1,i=t[o];++u<c;){var h=f[u],s=h.type,h=(0,h.iteratee)(i);if(2==s)i=h;else if(!h){if(1==s)continue n;break n}}e[a++]=i}return e},An.prototype.at=To,An.prototype.chain=function(){return Ye(this)},An.prototype.commit=function(){return new On(this.value(),this.__chain__)},An.prototype.next=function(){this.__values__===T&&(this.__values__=mu(this.value()));var n=this.__index__>=this.__values__.length;return{done:n,value:n?T:this.__values__[this.__index__++]}},An.prototype.plant=function(n){
for(var t,r=this;r instanceof En;){var e=Fe(r);e.__index__=0,e.__values__=T,t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},An.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Un?(this.__actions__.length&&(n=new Un(this)),n=n.reverse(),n.__actions__.push({func:Qe,args:[Ge],thisArg:T}),new On(n,this.__chain__)):this.thru(Ge)},An.prototype.toJSON=An.prototype.valueOf=An.prototype.value=function(){return wr(this.__wrapped__,this.__actions__)},An.prototype.first=An.prototype.head,
wi&&(An.prototype[wi]=Xe),An}();typeof define=="function"&&typeof define.amd=="object"&&define.amd?($n._=rt, define(function(){return rt})):Nn?((Nn.exports=rt)._=rt,Fn._=rt):$n._=rt}).call(this);;
const isLayout2024 = document.documentElement.classList.contains("layout-2024");

function debounce(func, wait) {
    let timeout;
    return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
    };
}

const isFilterEmpty = function(){
    return document.querySelectorAll('.filters a.filter.selected').length === 0;
}

/**
 * update category cell divider class and hide the cell if condition meet
 * getScreenWidth function use to support the old browser to get screen width
 *
 * @param {String} type: To identify calling from which function
 * @param {Booleon} isShowAll: To control hide or show the cell
 * @access Public.
 */

const updateCategoryCells = function (type,isShowAll) {
    // console.log(`update cell trigger:${type} showall:${isShowAll}`)
    // Get all category-cell elements
    const categoryCells = document.querySelectorAll('#category-grid .category-cell');
    let allGroup = [];
    let currentGroup = [];
    let maxCellsPerGroup = 6;
    let maxCellsPerRow = 2;
  
    function getScreenWidth() {
        return window.innerWidth || document.documentElement.clientWidth;
    }

    if(getScreenWidth() >= 768){
        maxCellsPerGroup = 9;
        maxCellsPerRow = 3;
    }

    // adjust image cell size to 1 if only have 1 item on the last row
    function adjustImgCellSize(categoryGroup){
        categoryGroup.forEach(subcatGroup => {
            const CellGroup = subcatGroup.currentGroup;
            // if cell img exist on this subcat group and span 2 slot
            if(CellGroup[0].classList.contains("category-cell-img")){
                // If maxCellsPerRow is 2, set data-size to 2
                if (maxCellsPerRow == 2) {
                    CellGroup[0].setAttribute("data-size", "2");
                } else {
                    // Calculate total items (including img cell spanning two columns)
                    const totalItems = [...CellGroup].length + 1; // img cell spans two columns, so it takes two slots

                    // Calculate remaining items
                    const remainingItems = totalItems % maxCellsPerRow;
                    // console.log(`total${totalItems} % maxcellperrow${maxCellsPerRow} = remain${remainingItems}`)

                    // If subcat last row showing only 1 item, img cel use size 2 instead of 1
                    CellGroup[0].setAttribute("data-size", remainingItems == 1 ? "1" : "2");
                }
            }
        });
    }
  
    function isLastCell(index){
        return index == categoryCells.length-1;
    }
  
    categoryCells.forEach((cell, index) => {
        const subcat = cell.getAttribute('data-subcat');
        const firstIndex = subcat.split(',')[0];

        // check if is divider cell or is pdt cell
        if (cell.classList.contains('series-divider')) {
            //if is series divider cell
            //also mean this is the last cell of the subcat group

            allGroup.push({firstIndex,currentGroup}) // collect group
                
            if(!isShowAll){
                //reset divider hidden status
                cell.classList.remove('hidden');
            }
  
            // Reset group tracking for the next group
            currentGroup = [];
        } else if (subcat.startsWith(firstIndex)) {
            // if is pdt cell
            
            // if this pdt cell hidden and filter is empty, remove hidden
            if(isFilterEmpty() && cell.classList.contains('hidden')){
                cell.classList.remove('hidden');
            }

            currentGroup.push(cell); //collect cell

            //coz last subcat dun have divider, collect the last subcat here
            if(isLastCell(index)){
                allGroup.push({firstIndex,currentGroup}) // collect group
            }
        }
    });

    adjustImgCellSize(allGroup);
  
  }

  // sortCategoryCells obj
  // use flex order css to sort the cell '#category-grid .category-cell'  
  // argument: sort, order
  // if no any filter selected, order need to restart after .viewall
  // argument sort option:popular|newest|price
  // argument order option:asc|desc
  // popular will be default, which mean just need to remove the order, no need sort chking option
  // newest need to check the attribute 'data-newflag' number from .category-cell, from biggest number to lowest number, no need sort chking option
  // price need to check the attribute 'data-price' number  from .category-cell, only price need to check sort direction
  const sortCategoryCells = function (sort, order) {
    // collect cell from category grid
    const categoryCells = Array.from(document.querySelectorAll('#category-grid .category-cell'));
    let sortedCells = [];
    let cellPrice = "";

    //toggle all divider from '#category-grid .category-cell.series-divider' using class hidden
    const toggleDivider = function(action){
        const dividerCells = Array.from(document.querySelectorAll('#category-grid .category-cell.series-divider'));
        if(action == 'hidden'){
            dividerCells.forEach(cell => cell.classList.add('hidden'));
        }else{
            dividerCells.forEach(cell => cell.classList.remove('hidden'));
        }
    };

    //remove the extra space
    if(document.querySelector("#category-grid > .clearfix")){
        document.querySelector("#category-grid > .clearfix").classList.add('hidden');
    }

    if (sort === 'popular') {
        //remove order css from all the elem inside category cell
        categoryCells.forEach(cell => cell.style.order = '');

        //only when popular called and filter empty, reset the cell to default pattern
        if(isFilterEmpty()) {
            updateCategoryCells("sortby_click",false);
            toggleDivider('show');
        }
    } else if (sort === 'newest') {
        // Sort categoryCells based on data-newflag and update the order
        sortedCells = [...categoryCells].sort((a, b) => {
            return order === 'asc'
                ? a.getAttribute('data-newflag') - b.getAttribute('data-newflag')
                : b.getAttribute('data-newflag') - a.getAttribute('data-newflag');
        });
    
        sortedCells.forEach((cell, index) => {
            cell.style.order = index + 1;
        });
    
        toggleDivider('hidden');
    } else if (sort === 'price') {
        // Sort categoryCells based on data-price and update the order
        sortedCells = [...categoryCells].sort((a, b) => {
            return order === 'asc'
                ? a.getAttribute('data-price') - b.getAttribute('data-price')
                : b.getAttribute('data-price') - a.getAttribute('data-price');
        });
    
        sortedCells.forEach((cell, index) => {
            cellPrice = cell.getAttribute('data-price');
            
            if (cellPrice == null || cellPrice === '') {
                //move the no price cell to last position
                cell.style.order = sortedCells.length + 1;
            }else{
                cell.style.order = index + 1;
            }
        });
    
        toggleDivider('hidden');
    }

  }

  if(isLayout2024){
    /**
     * Sets up a window resize listener that triggers an update function 
     * only when the window width crosses specified breakpoints.
     *
     * and values are objects with 'min' and 'max' properties defining the width range.
     * @param {Function} update - The function to call when the window width crosses from one 
     * breakpoint range to another.
     *
     * setupResponsiveUpdate(update);
     */
    function setupResponsiveUpdate(update) {
        const breakpoints = {
            1: { min: 0, max: 767 },
            2: { min: 768, max: Infinity }
        };

        let currentBreakpoint = getBreakpoint(window.innerWidth || document.documentElement.clientWidth);
    
        function getBreakpoint(width) {
            return Object.keys(breakpoints).find(key => 
                width >= breakpoints[key].min && width <= breakpoints[key].max
            );
        }
    
        function handleResize() {
            // console.log("resize triggered");
            const newBreakpoint = getBreakpoint(window.innerWidth || document.documentElement.clientWidth);
            // console.log(`newBreakpoint ${newBreakpoint} currentBreakpoint ${currentBreakpoint}`)
            if (newBreakpoint !== currentBreakpoint) {
                // console.log(`Breakpoint changed from ${currentBreakpoint} to ${newBreakpoint}`);
                currentBreakpoint = newBreakpoint;
                update();
                // console.log("breakpoint change, update")
            }
        }

        // Listens for changes in the window size, typically triggered by resizing the browser window.
        window.addEventListener('resize', handleResize);

        // Listens for changes in device orientation, such as when switching between portrait and landscape modes.
        window.addEventListener('orientationchange', handleResize);

        handleResize(); // init
    }
    
    setupResponsiveUpdate(function(){
        if(isFilterEmpty()){
            // to check the extra review cell and hidden if need
            debounce(updateCategoryCells("window_resize",false), 200);
        }
    });
  }

/**
 * The grid.
 * /!\ This is a modified version of the grid library bellow.
 * For this particular use, cell sizes are given in classes (e.g. width-x2) rather than data attribute.
 * The second change is that this version handles different image src per cell. to provide in data attributes (e.g. data-src-x2).
 *
 * @author    Antoni Andre. http://antoniandre.me
 * @see       http://github.com/antoniandre/grid
 * @version   1.0
 * @since     2016-10-21
 * @license   MIT
 */
var thegrid = function (options) {
    /**
     * Private vars.
     * We don't want to mess them up from outside the class.
     *
     * @access private.
     */
    var self = this, grid, cells, cellsNum, cellWidth, cellHeight, updateImgSrc, sorting, hasBreakpoints, breakpointsArray,
        defaults =
        {
            grid: $('.thegrid'),// The container of the cells.
            // This var will be removed after init() to avoid circular error due to recursion.
            // It is stored in public var 'grid' instead.
            cells: $('.cell'),// The cells to render / place / filter.
            cellsPerRow: 7,
            cellHeight: 100,// Cell height in pixels.
            // You can also define a function like so:
            // function(gh){return ((gh.gridWidth / gh.options.cellsPerRow) * 262 / 400) + 100};
            // But be aware that it will be much more costy (you may opt for throttling).

            //========= Animations =========//
            // By default the cells animation will be handled via CSS transitions as it is known to be smoother and
            // of better performances. however if you want some fancy easing effects (e.g. elastic or bounce) you may
            // want to switch to javascript animations. If so just turn animationPlatform to 'js' and provide the easing
            // speed and curve that you want.
            // You can also override the default CSS transitions in a custom CSS like:
            //     .thegrid.transitions .cell {
            //         -webkit-transition: .3s ease-in-out;
            //         -o-transition: .3s ease-in-out;
            //         transition: .3s ease-in-out;
            //     }
            animationPlatform: 'css',// 'css' or 'js'.
            animationEasing: 'swing',// jQuery built-in easings are 'swing', 'linear'. the jquery.easing plug-in can add many more.
            animationSpeed: 500,
            animationDelay: 0,

            updateGridHeight: true,// On each render.

            //========= Responsiveness =========//
            // Only if breakpoints are set the redraw() function is triggered on each resize event.
            // You can throttle the redraw to alight the resizing event.
            // (while resizing the redraw() function is exec every throttlingDelay milliseconds)
            throttling: false,
            throttlingDelay: 300,// in ms.

            //========= Breakpoints =========//
            // (from desktop to mobile).
            // Use it to allow a different behavior according to the device screen width.
            // Example of use:
            //     breakpoints:
            //     {
            //         1199:
            //         {
            //             cellsPerRow: 5
            //         },
            //         767:
            //         {
            //             cellsPerRow: 4
            //         },
            //         479:
            //         {
            //             cellsPerRow: 3
            //         },
            //     }
            breakpoints: {},

            //========= Sorting =========//
            // If you want to apply sorting on The Grid cells.
            // Each criterion that you use for sorting can apply a numeric or string sorting.
            // This allows to sort naturally: numeric to [1, 3, 11], or string to ['1a', '11a', '3a'].
            // Note that for performance concerns this is set as a parameter and not as a native guess.
            // Example of use:
            //     sortingCriteria:
            //     {
            //         name: {type: 'string'},
            //         price: {type: 'int'},
            //     }
            sortingCriteria: {}
        },

        /**
         * Internal matrix class to handle all the calculations.
         * Represent the grid as a matrix like:
         * [0 0 0 0
         *  0 0 0 0
         *  0 0 0 0
         *  0 0 0 0].
         * Don't forget the optimization is very important as we trigger the redraw() function on resize event.
         * The inject() method will inject the given cell at a given position providing cell dimension in units.
         *
         * @access Private.
         */
        gridMatrix =
        {
            matrix: [],// The matrix in use for placing cells.
            cols: 0,// Cache the cols number for optimization as used few times in this class.
            rows: 0,// Cache the rows number for optimization as used few times in this class.
            emptyCells: [],// Index the empty cells for faster results.

            /**
             * Creates the matrix with the specified dimensions.
             * And fill each cell with Null to represent cell availability.
             *
             * @access Public.
             * @return {Object} The current instance.
             */
            init: function () {
                this.emptyCells = [];
                this.cols = self.options.cellsPerRow;
                this.rows = Math.ceil(cellsNum / this.cols);
                this.matrix = matrix(this.cols, this.rows, null);

                // First index the whole matrix cells in this.emptyCells array as every cell is empty.
                // Indexing the empty cells for optimization.
                for (var row = 0; row < this.rows; row++) {
                    // Creating decimal format to benefit from the simple numeric sorting.
                    for (var col = 0; col < self.options.cellsPerRow; col++) this.emptyCells.push(row + "." + col);
                }
                return this;
            },

            /**
             * Adds an empty cell to the quick index array - for optimization.
             *
             * @access Public.
             * @param {String} position: The [x, y] coordinates in matrix.
             */
            addEmptyCell: function (position) {
                this.emptyCells.push(position[1] + '.' + position[0]);
                this.emptyCells.sort(function (a, b) { return a - b });
            },

            /**
             * Removes an empty cell.
             *
             * @access Public.
             * @param {String} position: The [x, y] coordinates in matrix.
             */
            removeEmptyCell: function (position) {
                // IE8-:
                // for (var cell = 0, l = this.emptyCells.length; cell < l; cell++) {
                //     if (this.emptyCells[cell] === position[1] + ',' + position[0]) this.emptyCells.splice(cell, 1);
                // }
                // Modern browsers:
                var index = this.emptyCells.indexOf(position[1] + '.' + position[0]);
                if (index > -1) this.emptyCells.splice(index, 1);
                this.emptyCells.sort(function (a, b) { return a - b });
            },

            /**
             * Inject the cell content in matrix.
             * The matrix here won't contain any HTML it will only hold the given DOM cell index.
             *
             * @access Public.
             * @param {String} cellContent: The cell content
             * @param {String} dimension: The dimension
             * @param {String} position: The [x, y] coordinates in matrix.
             * @return {Array} The [x, y] coordinates in matrix.
             */
            inject: function (cellContent, dimension, position) {
                // Store the dimension (in units) of the cell we want to inject in matrix.
                var dim = { w: dimension[0], h: dimension[1] },
                    fillCellsWidth = dim.w,
                    fillCellsHeight = dim.h;

                // At what matrix position do we inject the cell content.
                if (position === undefined) position = this.getNextEmptyCell();

                // Inject when a matrix position where cell content is fitting is found.
                while (!this.canFit(dimension, position)) {
                    position = this.getNextCell(position);
                }
                // Now that a place is found in matrix to fit the content fill the needed matrix cells.
                var x = position[0], y = position[1];
                while (fillCellsWidth > 0) {
                    this.matrix[y][x] = cellContent;
                    this.removeEmptyCell([x, y]);
                    fillCellsHeight = dim.h - 1;

                    while (fillCellsHeight > 0) {
                        y++;
                        this.matrix[y][x] = cellContent;
                        this.removeEmptyCell([x, y]);
                        fillCellsHeight--;
                    }
                    fillCellsWidth--;

                    x++;
                }

                return position;
            },

            /**
             * Gets the next empty cell.
             *
             * @access Public.
             * @return {Array} The next empty cell [x, y] coordinates in matrix.
             */
            getNextEmptyCell: function () {
                if (!this.emptyCells.length) return this.addNewRow();

                var yx = this.emptyCells[0].split('.');
                // console.log('getNextEmptyCell', this.emptyCells, 'returning first empty cell: [x:'+yx[1]+', y:'+yx[0]+']', [parseInt(yx[1]), parseInt(yx[0])]);
                return [parseInt(yx[1]), parseInt(yx[0])];

            },

            /**
             * Gets the next cell whether it is empty or not.
             *
             * @access Public.
             * @param {Array} position: The [x, y] coordinates in matrix.
             * @return {Array} The next cell [x, y] coordinates in matrix.
             */
            getNextCell: function (position) {
                if (position === undefined) position = [0, 0];
                // console.log('getNextCell returns next existing cell position:', this.matrix[position[1]][position[0] + 1] !== undefined ? [position[0] + 1, position[1]] : [0, position[1] + 1])
                // : [0, position[1] + 1])

                // Look for a cell on the right of the current one, if none take the first one of the following row.
                return this.matrix[position[1]][position[0] + 1] !== undefined ? [position[0] + 1, position[1]] : [0, position[1] + 1];
            },

            /**
             * Adds a new row to the matrix.
             *
             * @access Public.
             * @return {Array} The [x, y] coordinates in matrix of the first cell of the new row.
             */
            addNewRow: function () {
                this.matrix.push(matrix(this.cols, 1, null)[0]);
                this.rows++;
                for (var i = 0; i < this.cols; i++) this.addEmptyCell([i, this.rows - 1]);

                return [0, this.rows - 1];
            },

            /**
             * Determines if cell is empty at the given position.
             *
             * @access Public.
             * @param {Array} position: The [x, y] coordinates in matrix.
             * @return {Boolean} True if cell is empty, False otherwise.
             */
            isCellEmpty: function (position) {
                if (this.matrix[position[1]] === undefined) {
                    this.addNewRow();
                    return true;
                }

                return this.matrix[position[1]][position[0]] === null;
            },

            /**
             * Determines the ability to fit the given cell in matrix at a given position.
             *
             * @access Public.
             * @param {Array} dimension: The dimension (in units) of the cell you want to put in matrix [width, height].
             * @param {Array} position: The [x, y] coordinates where to place the cell in matrix.
             * @return {Boolean} True if able to fit, False otherwise.
             */
            canFit: function (dimension, position) {
                var w = dimension[0],
                    h = dimension[1],
                    x = position[0],
                    y = position[1],
                    canFitWidth = 0,
                    canFitHeight = 0;

                // If the cell exists (different of undefined) and is empty.
                for (var i = x; i < x + w; i++) if (this.isCellEmpty([i, y]) === true) { canFitWidth++; }
                for (var j = y; j < y + h; j++) if (this.isCellEmpty([x, j]) === true) { canFitHeight++; }

                // console.log('cell with dimension [w: '+w+', h: '+h+'] can fit at position [x:'+x+', y:'+y+']', (canFitWidth >= w && canFitHeight >= h))
                return canFitWidth >= w && canFitHeight >= h;
            }
        };

    // Public vars.
    self.throttleTimer = null;
    // Uncomment for debugging.
    // self.gridMatrix = gridMatrix;


    /**
     * Loop through each DOM element in the 'cells' collection and place them in the created matrix according to availability.
     * Called internally by init() and redraw().
     *
     * @access Private.
     */
    var fillMatrix = function () {
        // Create the matrix to the specified dimensions.
        gridMatrix.init(self.options.cellsPerRow, Math.ceil(cellsNum / self.options.cellsPerRow), null);

        cells.each(function (i, cell) {
            cell = $(cell);
            // var unitWidth = Math.min(cell.data('width'), self.options.cellsPerRow) || 1,
            //     unitHeight = cell.data('height') || 1,
            var unitWidth = Math.min(cell.hasClass('width-x4') ? 4 : (cell.hasClass('width-x2') ? 2 : 1), self.options.cellsPerRow),
                unitHeight = cell.hasClass('height-x2') ? 2 : 1,
                matrixPosition = gridMatrix.inject(i, [unitWidth, unitHeight]);

            // Remember the matrix position in DOM pure js element itself.
            cell[0].matrixPosition = { x: matrixPosition[0], y: matrixPosition[1] };
        });

        return this;
    };

    /**
     * Render the grid.
     * Called internally by init() and redraw().
     *
     * @access Private.
     */
    var render = function () {
        // On each render, call the cellHeight function if it is defined and update the cell height.
        if (typeof self.options.cellHeight === 'function') {
            self.gridWidth = gridWidth = grid.width();
            cellHeight = self.options.cellHeight(self);
        }
        cells.each(function (i, cell) {
            cell = $(cell);
            // If reduce screen until a cell width doesn't fit grid width then constrain it to the grid width.
            // var unitWidth = cell.data('width') || 1,
            //     unitHeight = cell.data('height') || 1,
            var unitWidth = Math.min(cell.hasClass('width-x4') ? 4 : (cell.hasClass('width-x2') ? 2 : 1), self.options.cellsPerRow),
                unitHeight = cell.hasClass('height-x2') ? 2 : 1,
                newCss = (isLayout2024) ? {} : {
                    position: 'absolute',
                    width: (cellWidth * Math.min(unitWidth, self.options.cellsPerRow)) + '%',
                    height: cellHeight * unitHeight,
                    top: cell[0].matrixPosition.y * cellHeight,
                    left: (cell[0].matrixPosition.x * 100 / self.options.cellsPerRow) + '%',
                };

            if ($('body').innerWidth() < 375) {
                newCss.position = 'initial'
                newCss.top = 'auto'
                newCss.left = 'auto'
                if (cell.hasClass('category-banner')) {
                    newCss.height = 'auto'
                }
            }

            setTimeout(function () {
                self.options.animationPlatform === 'js' ? cell.stop(true, true).animate(newCss, self.options.animationSpeed, self.options.animationEasing)
                    : cell.css(newCss);
            }, self.options.animationDelay);

            var img = cell.find('img');
            img.attr('src', img.attr('data-src-x' + Math.max(unitWidth, unitHeight)));
        });

        // After the cells positionning the grid height might have changed. Update if updateGridHeight is set to true.
        if ($('body').innerWidth() < 375) {
            $('#category-grid').height('auto')
        } else {
            const newHeight = gridMatrix.rows * cellHeight;
            if (self.options.updateGridHeight) {
                grid.stop(true, true)
                    .animate({ height: newHeight > 0 ? newHeight : 'auto' }, { duration: self.options.animationSpeed, easing: 'easeOutCirc' });
            }
        }

        return this;
    };


    /**
     * Update parameters.
     *
     * @access Public.
     * @param {Object} params: The parameters to update. Will be overriding the current parameters.
     * @return {Object} The current instance.
     */
    self.updateParams = function (params) {
        // Merge current settings and overriding new ones given as parameter.
        self.options = $.extend(self.options, params);

        cellWidth = 100 / self.options.cellsPerRow;
        cellHeight = self.options.cellHeight;

        return this;
    };


    /**
     * Show or hide the given collection of cells retaining the given order.
     *
     * @access Public.
     * @param {jQuery Collection or selector string} cellsToToggle: either a css selector to match a collection of cells
     *                                                              or a jQuery collection of cells to toggle.
     * @param {Boolean} hide: whether to show or hide the given collection of cells.
                              Default: false.
     * @param {Boolean} toggleAllOthers: whether to show or hide all the other cells that are not in the given collection.
                                         Default: false.
     * @return {Object} The current instance.
     */
    self.filter = function (cellsToToggle, hide, toggleAllOthers) {
        hide = hide !== undefined ? hide : false;
        toggleAllOthers = toggleAllOthers !== undefined ? toggleAllOthers : false;
        // In case you the provide collection is not a jQuery object.
        cellsToToggle = cellsToToggle instanceof jQuery ? cellsToToggle : $(cellsToToggle);
        cellsToShow = hide ? (toggleAllOthers ? $(self.options.cells).not(cellsToToggle) : null) : cellsToToggle;
        cellsToHide = hide ? cellsToToggle : (toggleAllOthers ? $(self.options.cells).not(cellsToToggle) : null);

        // If the given selection is to show, keep its specific order in case of prior sorting.
        if (isLayout2024) {
            if (cellsToShow) cellsToShow.removeClass('hidden');
            if (cellsToHide) cellsToHide.addClass('hidden');
        }else{
            if (cellsToShow) cellsToShow.css({ top: 0, left: 0 }).removeClass('hidden');
            if (cellsToHide) {
                cellsToHide.addClass('hidden');
                setTimeout(function () { cellsToHide.css({ top: 0, left: 0 }) }, self.options.animationSpeed);
            }
        }

        cells = cellsToShow;
        cellsNum = cells.length;

        // if (sorting) self.sort();// New feature.

        return this;
    };


    /**
     * Sort the grid cells according to the active sorting from options.sortingCriteria.
     *
     * @access Public.
     * @return {Object} The current instance.
     */
    /* New feature:
    self.sort = function()
    {
        if (!sorting) return;

        var sortedCells = null;
        for (var criterion in sorting) if (sorting[criterion].active)
        {
            sortedCells = cells.slice(0).sort(function(a, b)
            {
                var multiplier = sorting[criterion].order === 'asc' ? 1 : -1, ret;
                a = a.attributes["data-" + criterion].value || '';
                b = b.attributes["data-" + criterion].value || '';

                switch(sorting[criterion].type)
                {
                    case 'int':
                    case 'float':
                        a = parseFloat(a);
                        b = parseFloat(b);
                        ret = a - b;
                        break;
                    default:
                        ret = a < b ? -1 : (a > b) ? 1 : 0;
                        break;
                }

                return ret * multiplier;
            });
            cells = sortedCells || sorting.default;
        }

        return this;
    };*/
    self.sort = function (sortedCells) {
        cells = $(sortedCells).not('.hidden');
        cellsNum = cells.length;

        return this;
    };


    /**
     * Initialize the sorting feature if sortingCriteria are given in options.
     *
     * @access Private.
     * @return {Object} The current instance.
     */
    var initSort = function () {
        sorting = {};

        for (var criterion in self.options.sortingCriteria) {
            sorting[criterion] = self.options.sortingCriteria[criterion];
            sorting[criterion].sorted = cells;
        }
        // Clone the array of cells to make sure their order won't be affected by any later change of cells var.
        sorting.default = cells.slice(0);

        $('[data-sort]').on('change', function (e) {
            this.pos = ((this.pos || 0) + 1) % 3;

            var order = ['', 'asc', 'desc'][this.pos];
            criterion = $(this).data('sort');

            $(this).attr('data-order', order);
            sorting[criterion].order = order;
            sorting[criterion].active = this.pos;

            if(!isLayout2024) self.sort().redraw();
        });

        return this;
    }

    /**
     * Reset all the applied filters and go back to the original cells collection from the DOM.
     *
     * @access Public.
     * @return {Object} The current instance.
     */
    self.resetFilters = function () {
        cells = $(self.options.cells).removeClass('hidden');
        cellsNum = cells.length;

        // if (sorting) self.sort();// New feature.
        return this;
    }


    /**
     * Redraw function to:
     * 1 - recalculate the disposition of cells in grid according to new dimensions and options.
     * 2 - render the new grid disposition.
     * Then trigger the redraw event.
     *
     * @access Public.
     * @return {Object} The current instance.
     */
    self.redraw = function () {
        fillMatrix();
        render();

        grid.trigger('redraw');

        return this;
    };


    /**
     * Bind the grid related events on grid init.
     *
     * @access Private.
     * @return void.
     */
    var bindEvents = function () {
        if (hasBreakpoints || typeof self.options.cellHeight === 'function') {
            $(window).on('resize gridInit afterResize', function (e) {
                // On resize, if throttling is on, exit quickly for better performance.
                // So keep this first.
                if (self.options.throttling && e.type === 'resize') {
                    // Only trigger a custom 'afterResize' callback event and return.
                    // So that, we recalculate at most once every options.throttlingDelay seconds.
                    self.throttleTimer = setTimeout(function () {
                        clearTimeout(self.throttleTimer);
                        self.throttleTimer = null;
                        $(window).trigger('afterResize');
                    }, self.options.throttlingDelay);
                    return true;
                }

                // Watch breakpoints and apply new config if a set of breakpoints is defined.
                var breakpointChange = hasBreakpoints ? watchBreakpoints(e) : false;

                // Only redraw if current breakpoint has changed or if we want to recalculate height at each step.
                if (breakpointChange || typeof self.options.cellHeight === 'function') {
                    self.redraw();
                }
            });
        }
    };


    /**
     * On window resize, gridInit and afterResize events, watch if the screen width reaches a breakpoint
     * and update the configuration accordingly by applying the new breakpoint's config over the current one.
     *
     * @access Private.
     * @return {boolean} true if the breakpoint has changed, false otherwise.
     */
    var watchBreakpoints = function (e) {

        var params = {},
            screenWidth = this.innerWidth,
            tmpBp = [],
            currentBreakpoint,
            newBreakpoint,
            breakpointChange = false;

        updateImgSrc = false;

        // Best optimized code possible:
        // Way faster than for loop on given breakpoints.
        // Also faster than a series of if / else if.
        // The idea is to have an array of breakpoints and add the current screenWidth in it and ASC sort the array.
        // Then locate the position of the screenWidth in array and get the breakpoint just above to get its config.
        tmpBp = breakpointsArray.slice(0);// Clone array.
        tmpBp.push(screenWidth);// Add screenWidth to the array.

        // Sort NATURAL ASC and always put '++' (for init config) at the end (e.g. [479, 767, 790, 1199, "1199++"]).
        tmpBp.sort(function (a, b) { return (typeof a === 'string' && a.indexOf('++') > -1) || parseInt(a) - parseInt(b) });
        var i = tmpBp.indexOf(screenWidth);// Get the position of screenWidth in the breakpoints array.

        // If the index is the last one (initial and widest screen config), breakpointsArray[i] won't exist so
        // just reapply initial config.
        newBreakpoint = breakpointsArray[i];
        breakpointChange = newBreakpoint !== currentBreakpoint;

        if (breakpointChange) {
            currentBreakpoint = newBreakpoint;

            // Trigger the breakpointChange event but skip the init one.
            if (e.type !== 'gridInit') grid.trigger('breakpointChange', [currentBreakpoint]);

            updateImgSrc = true;

            // Apply the settings of the detected breakpoint config before redrawing grid.
            params = self.options.breakpoints[currentBreakpoint];
            self.updateParams(params);
        }

        return breakpointChange;
    }


    /**
     * If breakpoints are defined, parse the different specific configs and optimize/cache all configs and
     * breakpoints values into a breakpointsArray for the best performances possible in resize event loop.
     *
     * @access Private.
     * @return void.
     */
    var initBreakpoints = function () {
        var breakpointsNum, initialConfigIndex = 0;
        hasBreakpoints = true;// Update private class var for convenient/meaningful use in different methods.
        breakpointsArray = [];

        // Convert to breakpoints object an array for sorting.
        for (var breakpoint in self.options.breakpoints) breakpointsArray.push(breakpoint * 1);
        breakpointsNum = breakpointsArray.length;

        // NATURAL numeric ascendant sorting (*1 is shorthand for parseInt()).
        breakpointsArray.sort(function (a, b) { return a * 1 - b * 1 });

        // Add the initial config (i.e. 'widest') into the breakpoints object and breakpoints array.
        // E.g. [479, 767, 1199, "1199++"].
        // It would be easier to have all integers but outputting breakpoint number to the end user through the breakpointChange
        // event has to be meaningful.
        initialConfigIndex = breakpointsArray[breakpointsNum - 1] + '++';
        self.options.breakpoints[initialConfigIndex] = JSON.parse(JSON.stringify(self.options));
        delete self.options.breakpoints[initialConfigIndex].breakpoints;// Don't need to keep this.
        breakpointsArray.push(initialConfigIndex);
    };


    /**
     * Init the grid, only once. This is the entry point and self-called.
     * Set the grid options from parameters,
     * bind the grid events,
     * fill the matrix with cells in DOM and render for the first time.
     *
     * @access Private.
     * @return void.
     */
    var init = function () {
        // Merge default settings and overriding options given as parameter.
        self.options = $.extend(defaults, options);

        // Init the core vars.
        grid = $(self.options.grid);
        delete self.options.grid;//!\\ Avoid circular error due to recursion.
        self.gridWidth = grid.width();
        cells = $(self.options.cells).not('.hidden');
        cellsNum = cells.length;
        cellWidth = 100 / self.options.cellsPerRow;
        cellHeight = self.options.cellHeight;

        // First set the grid approximate height before calculating the cells position.
        // After the cells placing the grid height will probably be different.
        grid.css('height', Math.ceil(cellsNum / self.options.cellsPerRow) * self.options.cellHeight);

        if (!$.isEmptyObject(self.options.sortingCriteria)) initSort();

        if (!$.isEmptyObject(self.options.breakpoints)) initBreakpoints();

        bindEvents();
        if(!isLayout2024){
            fillMatrix();
            render();
        }

        // Force check current breakpoint at init.
        if (!$.isEmptyObject(self.options.breakpoints)) $(window).trigger('gridInit');

        // Trigger ready custom event available for external use.
        grid.trigger('ready').addClass('ready' + (self.options.animationPlatform === 'js' ? '' : ' transitions'));
    }();

    return self;// Allow chaining. ;)
},



    /**
     * Helper function.
     * Creates a matrix of the given size and fill each cell with a default value.
     *
     * @param      {number}  cols          The columns number.
     * @param      {number}  rows          The rows number.
     * @param      {mixed}   defaultValue  The default value for each cell.
     * @return     {Array}   the generated matrix.
     */
    matrix = function (cols, rows, defaultValue) {
        for (var i = 0, arr = []; i < rows; i++) {
            arr.push([]);// Creates an empty line.
            arr[i].push(new Array(cols));// Adds cols to the empty line.
            for (var j = 0; j < cols; j++) arr[i][j] = defaultValue;// Initializes.
        }

        return arr;
    };



/**
 * Eventually make the jQuery plug-in wrapper.
 *
 * @param  {Object|String} firstArg: can hold either a method name or an object of parameters for init.
 * @return {Object} The current instance.
 */
$.fn.grid = function (firstArg) {
    // The DOM element on which we call the grid plugin.
    var gridElement = this[0];

    var gridElement = this[0], warn, error;

    // Handle errors and incorrect grid calls.
    switch (true) {
        case (!gridElement || gridElement === undefined):
            error = 'Can\'t instantiate The Grid on an empty jQuery collection.';
            break;
        case (['object', 'undefined', 'string'].indexOf(typeof firstArg) === -1):
            warn = 'Ignoring grid call with wrong params.';
            break;
        case (typeof firstArg === 'string' && typeof (gridElement).grid[firstArg] !== 'function'):
            warn = 'Ignoring unknown Grid method call "' + firstArg + '".';
            break;
    }
    if (warn || error) console[error ? 'error' : 'warn'](warn || error);

    // Instantiate The Grid.
    else if (typeof firstArg === 'object' || firstArg === undefined) {
        (firstArg || { grid: null }).grid = gridElement;
        gridElement.grid = new thegrid(firstArg);
    }

    // Call a grid method (with params) from its name as a string.
    // E.g. $('.thegrid').grid('filter', [cellsToToggle, hide, toggleAllOthers]);
    // First check method exists before calling it.
    else if (typeof firstArg === 'string' && gridElement !== undefined && typeof (gridElement).grid[firstArg] === 'function') {
        // Extract rest arguments from built-in 'arguments' pseudo-array (no array method avail on arguments).
        var args = [].slice.call(arguments, 1);

        // Call the object method with given args.
        gridElement.grid[firstArg].apply(this, args);
    }

    return this;
};
;
var CompareChart = {
    cookieName: 'comcart',
    categoryID: 0,
    languageID: 1,
    itemCount: 4,
    compareServer: 'http://asia.test.creative.com/compare',


    init: function (options) {
        if (typeof (options.compareServer) != 'undefined') {
            this.compareServer = options.compareServer;
        }
        if (typeof (options.categoryID) != 'undefined') {
            this.categoryID = options.categoryID;
        }
        if (typeof (options.languageID) != 'undefined') {
            this.languageID = options.languageID;
        }

        // Config underscore settings
        _.templateSettings = {
            evaluate: /\{%([\s\S]+?)%\}/g,   // excute code: {% code_to_execute %}
            interpolate: /\{\{(.+?)\}\}/g,      // print value: {{ value_name }}
            escape: /\{%-([\s\S]+?)%\}/g
        };

        this.bindEvents();
    },

    show: function () {
        var products = this.getProduct();
        if (products.length > 0) {
            this.loadHtml(products.length);
        }
        else {
            this.clear();
        }
    },

    bindEvents: function () {

        var self = this;

        $("#compare-button-2").on("click", function (e) {
            e.preventDefault();

            var products = self.getProduct();

            if (products.length > 1) self.compare();
            else notificationBar.show('<i class="i-cross-circle"></i> ' + res.txtCompareProductsErrorInfo, 4000);
        });

        $("#compare-results .close").click(function (e) {
            e.preventDefault();
            $("html").removeClass("no-scroll");
            $("#compare-results").toggleOverlay();
        });

        $("#compare-actions .close").on("click", function (e) {
            e.preventDefault();
            $("#compare-products").removeClass("open");
            $(".category-cell.selected").removeClass("selected");
            $("#compare-button-1").fadeTo(0, 1);// Not .show()/.hide() to keep height and prevent small jump of products.
            $(".category-cell").removeClass("compare cannot-compare").off("click");
        });

        $(document).on("click", ".compare-selected-product .compare-remove-product", function (e) {
            e.preventDefault();
            var products = self.getProduct();
            var productID = $(this).parent()
                .parent()
                .data("product-id");
            self.removeProduct(productID);
        })

        $("#compare-button-1").on("click", function (e) {
            e.preventDefault();
            var el = $(this);

            if (el.is(":visible")) {
                el.fadeTo(0, 0);
                self.loadCompareProducts();

                $("#compare-products").addClass("open");

                $(".category-cell").each(function (i, e) {
                    var el = $(e);
                    var productID = el.data("productid");
                    var compareProducts = self.getProduct();

                    if (el.hasClass("can-compare")) {
                        if ($.inArray(parseInt(productID), compareProducts) == -1) {
                            el.find(".compare-overlay > div").html(res.txtAddToCompare);
                        }
                        else {
                            el.find(".compare-overlay > div").html(res.remove_from_compare);
                        }
                    }
                    else {
                        el.addClass("cannot-compare");
                        el.find(".compare-overlay > div").html(res.na_to_compare);
                    }
                })
                    .addClass("compare")
                    .on("click", function (e) {
                        e.preventDefault();
                        var productID = $(this).data("productid");
                        self.addProduct(productID);
                    })
            }
            else {
                el.fadeTo(0, 0);
                $("#compare-products").removeClass("open");
            }
        });
    },

    loadCompareProducts: function () {
        if ($(".compare-selected-product").length === 0) {
            var compareProducts = this.getProduct();

            for (var i = 0; i < compareProducts.length; i++) {
                $(".category-cell[data-productid=" + compareProducts[i] + "]")
                    .addClass("selected")
                    .each(function (i, e) {
                        var el = $(e);
                        var productID = el.data("productid");
                        var link = el.find(".fulllink").attr("href");
                        var image = "https://img.creative.com/images/products/large/pdt_" + productID + ".png?width=120&height=120&mode=pad";
                        var name = el.find(".product-name").html().trim();
                        if (name.indexOf("new-tag") > -1) {
                            name = name.substring(0, name.indexOf("<span class=\"new-tag\""));
                        }

                        var tpl = _.template($("#compare-selected-product-template").html());
                        var product = {
                            "id": productID,
                            "name": name,
                            "link": link,
                            "image": image
                        };

                        $("#selected-products").append(tpl(product)).find('.compare-selected-product').removeClass('added');
                    })
                    .find(".compare-overlay > div")
                    .html(res.remove_from_compare);
            }
            $("#compare-products").addClass("open");
            $("#compare-button-2").removeClass("disabled");
        }

    },

    cutText: function (text, length) {
        if (text.length > length) {
            var desc = text;
            while (desc.length > length) {
                var newdesc = "";
                var descGroup = desc.split(' ');
                for (var i = 0; i < descGroup.length - 1; i++) {
                    newdesc += " " + descGroup[i];
                }
                desc = newdesc;
            }
            desc = desc.toString() + "...";
            return desc;
        }
        else { return text; }
    },

    timeoutHolder: null,

    showNotice: function (message) {
        notificationBar.show(message, 10000);
    },

    scroll: function () {
        $(window).scroll(function () {
            if ($(document).scrollTop() > $("#category-grid").offset().top + $("#category-grid").height() - $(window).height() + 150) {
                $("#cat-compare-holder").css("position", "absolute").css("bottom", "-128px").css("left", "2px");
            } else {
                $("#cat-compare-holder").css("position", "fixed").css("bottom", "0").css("left", ($("#category-grid").offset().left + 2) + "px");
            }
        });
    },

    clear: function () {
        $("#cat-compare-holder").remove();
        $("#category-grid").css("margin-bottom", "0");
    },

    getProduct: function () {
        var result = new Array();
        var cookies = $.cookie(this.cookieName + '-' + this.categoryID.toString());
        if (cookies != null) {
            var ids = cookies.toString().split(',');
            for (var i = 0; i < ids.length; i++) {
                if (ids[i] != null && ids[i] != '' && !isNaN(ids[i])) {
                    result.push(parseInt(ids[i]));
                }
            }
        }
        return result;
    },

    addProduct: function (productID) {
        var products = this.getProduct();
        var success = false;
        var el = $(".category-cell[data-productid=" + productID + "]");
        var link = el.find(".fulllink").attr("href");
        var image = "https://img.creative.com/images/products/large/pdt_" + productID + ".png?width=120&height=120&mode=pad";
        var name = el.find(".product-name").html().trim();
        if (name.indexOf("new-tag") > -1) {
            name = name.substring(0, name.indexOf("<span class=\"new-tag\""));
        }

        var product = {
            "id": productID,
            "name": name,
            "link": link,
            "image": image
        };
        var tpl = _.template($("#compare-selected-product-template").html());

        if (!el.hasClass("cannot-compare")) {
            if (products.length >= this.itemCount) {
                if ($.inArray(parseInt(productID), products) === -1) {
                    this.showNotice(res.txtCompareNotice);
                    success = false;
                }
            }
            else {
                if (!isNaN(productID)) {
                    if ($.inArray(parseInt(productID), products) === -1) {
                        products.push(productID);
                        this.updateCookie(products);
                        success = true;
                    }
                    else {
                        success = false;
                    }
                }
            }
        }
        else {
            success = false;
        }

        if (success) {
            if (!el.hasClass("selected")) {
                el.addClass("selected");
                el.find(".compare-overlay > div").html(res.remove_from_compare);
                var productHtml = tpl(product);
                $("#selected-products").append(productHtml);
                setTimeout(function () { $('#selected-products .compare-selected-product').removeClass('added'); }, 50);
                if (CompareChart.getProduct().length > 1) {
                    $("#compare-button-2").removeClass("disabled");
                }
            }
        }
        else {
            if (!el.hasClass("cannot-compare")) {
                this.removeProduct(productID);
                el.removeClass("selected");
                el.find(".compare-overlay > div").html(res.txtAddToCompare);
                $(".compare-selected-product[data-product-id=" + productID + "]").remove();

                if (CompareChart.getProduct().length < 2) {
                    $("#compare-button-2").addClass("disabled");
                }
            }
        }


        return success;
    },

    removeProduct: function (productID) {
        var result = new Array();
        var products = this.getProduct();
        for (var i = 0; i < products.length; i++) {
            if (products[i] != null && products[i] != '' && !isNaN(products[i])
            ) {
                if (parseInt(products[i]) != productID) {
                    result.push(products[i]);
                }

            }
        }
        this.updateCookie(result);

        $(".category-cell[data-productid=" + productID + "]:not('.cannot-compare')").removeClass("selected")
            .find(".compare-overlay > div")
            .html(res.txtAddToCompare);;

        $(".compare-selected-product[data-product-id=" + productID + "]:not('.cannot-compare')").remove();

        if (result.length < 2) {
            $("#compare-button-2").addClass("disabled");
        }
    },

    removeAll: function () {
        this.updateCookie(new Array());
    },

    updateCookie: function (products) {
        var value = "";

        for (var i = 0, max = products.length; i < max; i++) {
            value += products[i];
            if (i < products.length - 1) {
                value += ",";
            }
        }
        $.cookie(this.cookieName + '-' + this.categoryID.toString(), value);
    },

    compare: function () {
        var products = this.getProduct();
        var tpl = _.template($("#compare-results-template").html());
        let isLive = !(location.hostname.toLowerCase().includes('dev')
        || location.hostname.toLowerCase().includes('stage')
        || location.hostname.toLowerCase().includes('localhost'))
        var url = isLive ? "https://oxrz6c4lbi.execute-api.ap-southeast-1.amazonaws.com/prod" : "https://if1k4cyjr4.execute-api.ap-southeast-1.amazonaws.com/dev";
        
        url += "/compareresults?categoryid=" + this.categoryID +
            "&languageid=" + this.languageID +
            "&masterproductids=" + products.toString();

        $("#compare-button-2").addClass("disabled");
        $("#compare-results > .content").html('').parent().toggleOverlay().removeClass('ready');

        $.get(url, function (data) {
            $("#compare-results > .content").html(tpl(data)).parent().addClass('ready');

            $("#compare-results-products-header h2.product-name").each(function (i, j) {
                $(this).html($(".compare-selected-product:eq(" + i + ")").find(".product-name").html());
            })

            $("#compare-results-products-header .compare-product-link").each(function (i, j) {
                var productId = $(this).data("product-id");
                this.href = $("div.category-cell[data-productid=" + productId + "] a.fulllink").attr("href");
                console.log(this.href);
            })

            $("html").addClass("no-scroll");

            $("#compare-button-2").text(res.Page_compare)
                .removeClass("disabled");
        });
    },

};;
var grid1,
  fh,
  compareProducts = [],
  PROMOPRODUCTLIST = [],
  categoryPage = function () {
    var self = this;
    this.minprice = 999,
      this.maxprice = 0,
      this.qGroup = [],
      this.oriItems = [],
      this.FilterOption = {
        subcat: [],
        pricerange: [],
        feature: [],
        freeShipping: 0,
        inStock: 0,
        pupular: false,
        pricerank: "asc",
        newest: true,
        products: []
      },
      this.PriceData = [],
      this.FeatureData = [],
      this.fetchPrices = parseInt($('#category-wrapper').data('get-prices') || 0),
      this.currentSort = null,


      this.GetPriceData = function (data) {
        $.each(data, function (i, n) {
          var removFlag = true;
          $.each(self.oriItems, function (k, j) {
            try {
              if (n.ProductID == $(j).data("productid")) removFlag = false;
            }
            catch (e) { }
          });
          if (removFlag) n.Price = 0;
        });

        self.PriceData = data;

        $.each(data, function (i, n) {
          if (n.Price > self.maxprice) self.maxprice = n.Price;
          if (n.Price < self.minprice && n.Price > 0.01) self.minprice = n.Price;
        });

        var impressions = [];
        $.each(self.oriItems, function (i, n) {
          self.BindPrice(n);
          impressions.push(self.GTMBuildProductImpression(i, n));
        });

        self.GTMTrackProductImpression(impressions);

        return this;
      },

      this.BindPrice = function (element) {
        var productid = $(element).attr("data-productid");

        $.each(this.PriceData, function (i, n) {
          if (n.ProductID == productid) {
            var instock = "",
              freeShipping = "",
              newPrice = n.Price,
              oldPrice = n.ListPrice,
              priceHtml = n.ListPrice > n.Price ?
                n.PriceFormat.replace(".00", "") + " <span style='color:#f00;font-size:10px'>" + instock + " " + freeShipping + "</span><strike> " + n.ListPriceFormat.replace(".00", "") + "</strike>"
                : n.PriceFormat.replace(".00", "") + " <span style='color:#f00;font-size:10px'>" + instock + " " + freeShipping + "</span>";

            $(element)
              .attr("data-price", n.Price)
              .attr("data-free-shipping", n.FreeShipping === true ? "1" : "0")
              .attr("data-instock", n.InStock === true ? "1" : "0")
              .find("div.price").html(priceHtml);

            // console.log({ productid, instock:n.InStock, newPrice, oldPrice });

            if (!n.InStock) {
              $(element).find('.oos-marker').show();
            }

            if (n.Colors.length > 0) {
              const dedupedColors = Array.from(new Set(n.Colors));

              if (dedupedColors.length > 1) {
                const swatches = dedupedColors.map(c => {
                  return `<div title='${c}' data-color="${c.toLowerCase().replace(/\s/g, '-')}" class="swatch-${c.toLowerCase().replace(/\s/g, '-')}"></div>`
                }).join('');

                $(element).find('.bottom-bar > .color-swatches')
                  .attr('data-current-color', dedupedColors[0].toLowerCase().replace(/\s/g, '-'))
                  .append(swatches)
                  .addClass("ready");
              }
            }

            if (typeof (n.IconOverlay) == "string" && n.IconOverlay.length) {
              var icons = n.IconOverlay.split(',');
              for (var i = 0; i < icons.length; i++) {
                if (icons[i].length) {
                  var doImgTag = icons[i].toLowerCase().indexOf(".jpg") == -1
                    && icons[i].toLowerCase().indexOf(".gif") == -1
                    && icons[i].toLowerCase().indexOf(".png") == -1;

                  $(element).find("div > div.icon-overlayer")
                    .prepend(doImgTag ? ('<img src="' + res.IMAGESERVER + '/images/products' + icons[i] + '"/>') : ('<span>' + icons[i] + '</span>'));
                }
              }
            }
            if (n.ListPrice > n.Price && n.Price > 0.01 && global.store.id != 2) {
              var discount = Math.round((1 - newPrice / oldPrice) * 100);
              var domobj = $(element).find(".save-overlay");
              var cssTop = "bottom: 0;";

              if ($(element).hasClass('feature')) {
                var descObj = $("#productlist li.feature[data-productid='" + n.ProductID + "']").find("div > div.desc");
                if (descObj.length) cssTop = "bottom: " + (descObj.height() + 20) + "px;";
              }

              if (discount > 0) {
                domobj.html("<p><strong>" + discount + "</strong>%<br><span>off</span></p>");
                setTimeout(function () { domobj.addClass('show') }, 200);
              }
            }
            return;
          }
        });
      },

      //===========================================================================//
      // Google Tag Manager. tracking clicks.
      //===========================================================================//
      this.GTMTrackProductClicks = function () {
        $("ul#productlist li").on("click", "a.fulllink", function (e) {
          var productID = $(this).parent().parent().data("productid");
          var productName = $(this).parent().parent().find(".name").text();
          var productPrice = $(this).parent().parent().data("price");
          var productCategory = $(".section-category .title:eq(0)").text().trim();
          var pos = $(this).parent().parent().data("ranking");
          dataLayer.push({
            'event': 'productClick',
            'ecommerce': {
              'click': {
                'actionField': { 'list': 'Category Landing Page' },
                'products': [{
                  'name': productName,
                  'id': productID,
                  'price': productPrice,
                  'brand': 'Creative',
                  'category': productCategory,
                  'variant': '',
                  'position': pos
                }]
              }
            }
          });
        });
      }

    this.GTMBuildProductImpression = function (index, obj) {
      var productID = $(obj).data("productid") || "0";
      var productName = $(obj).find(".name").text();
      var productPrice = $(obj).data("price");

      return {
        'name': productName,
        'id': productID.toString(),
        'price': productPrice,
        'brand': 'Creative',
        'category': $(".section-category .title:eq(0)").text().trim(),
        'variant': '',
        'list': 'Category Landing Page',
        'position': (index + 1).toString()
      };
    },

      this.GTMTrackProductImpression = function (impressions) {
        dataLayer.push({
          'event': 'categoryPageDataLoaded',
          'ecommerce': {
            'currencyCode': currencyCode,
            'impressions': impressions
          }
        });
      };
    //===========================================================================//

    this.hasShippingFilter = function (products) {
      return !!products.find(function (p) {
        return p.FreeShipping
      })
    }

    // Allow chaining.
    return this;
  };

var fetchPrices = function () {
  $.ajax(
    {
      url: $('#category-wrapper').data('prices-api'),
      method: 'post',
      data: { h: global.store.customer.hashedEmail },
      dataType: 'json',
      success: function (data) {
        catPage.GetPriceData(data);

        if (!catPage.hasShippingFilter(data)) {
          $('.fs-filter-shipping').remove();
        }

        fh.initPriceRelatedFilters(data);

        if (urlParams.filters !== undefined) {
          filters = urlParams.filters.split(",").map(Number).filter(Boolean);
          $.each(filters, function (i, v) {
            $("#category-filter a[data-subcatid=" + v + "]").click();
          });
        }

        $('.color-swatches > div[class^=swatch-]').on('click', function (e) {
          e.preventDefault();
          const currentColor = $(e.target).parent().data('current-color');
          const selectedColor = $(e.target).data('color');
          const container = $(e.target).parent().parent().parent().parent();

          const slug = container.attr('href')
            .split('/')
            .pop();

          const picture = container.find('picture');
          const png = picture.find('img');
          const webp = picture.find('source');

          const pngPath = png.attr('src')
            .substr(0, png.attr('src').lastIndexOf('/') + 1);

          const pngQs = png.attr('src')
            .split('?')
            .pop();

          const webpPath = webp.attr('srcset')
            .substr(0, webp.attr('srcset').lastIndexOf('/') + 1);

          const webpQs = webp.attr('srcset')
            .split('?')
            .pop();

          const newPng = pngPath + 'cat-' + slug + '-' + selectedColor + '.png?' + pngQs;
          const newWebP = webpPath + 'cat-' + slug + '-' + selectedColor + '.webp?' + webpQs;

          png.attr('src', newPng);
          webp.attr('srcset', newWebP);

          $(e.target).parent().data('current-color', selectedColor);
        })

      }
    });
};

/**
 * This filter is made to optimize the filtering system.
 * As filters are provided by the old API in an overly complicated way, the code here is overly complicated in self.init()
 * To put things back in its right place.
 */
var filtersHandler = function () {
  // Private vars.
  var self = this,
    cellsWrapper = $('#category-grid'),
    cells = cellsWrapper.find('.category-cell'),
    productIdList = [],// Will be filled up with the list of all the products in the page.
    indexedProductIdList = {},// Will be filled up with the list of all the products in the page indexed by their product id.
    productPrices = {},// Will be filled up with the list of products prices indexed by their product id.
    productsByFilters = {},
    featuresParentsList = {},
    postponedFiltersLoad = [],// May contain a list of filter ids from url that need to wait for the API results to be loaded.
    priceRange = { min: null, max: 0, from: 0, to: 0 };// The current page products price range in min/max and selected range in from/to (updated after call to price API).

  // Public vars.
  // self.productsList = {};// Will be filled up with the list of all the products in the page indexed by their product id.
  self.filteredCells = cells;
  self.activeFilters = {};// Will be filled up with a list of filter ids going in pair with a boolean for active state. E.g. {302: true, 356: true, 427: false}.
  self.activeSorting = { sort: 'popular', order: 'asc' };


  /**
   * First call the filters API and execute the callback to fetch filters data.
   *
   * @access Private.
   * @return void.
   */
  var fetchFiltersFromAPI = function (callback) {
    if ($('#category-wrapper').data('compare-api') !== undefined) {
      $.get($('#category-wrapper').data('compare-api'), callback)
    }
  };

  /**
   * Init the category filters, The first layer of filters and most basic one.
   *
   * @access Private.
   * @return void.
   */
  var initCategoryFilters = function () {
    cells.each(function (i, curr) {
      var productId = parseInt($(curr).data('productid')),
        catsPerProduct = $(curr).data('subcat') ? $(curr).data('subcat').split(',') : [];

      productIdList.push(productId);
      indexedProductIdList[productId] = true;

      // Hot filters.
      var hot = $(curr).data('hot') || null;
      if (hot) {
        if (productsByFilters['hotf'] === undefined) productsByFilters['hotf'] = [];
        productsByFilters['hotf'].push(productId);
      }

      // Index all products by category.
      for (var i = 0, l = catsPerProduct.length; i < l; i++) {
        if (productsByFilters[catsPerProduct[i]] === undefined) productsByFilters[catsPerProduct[i]] = [];
        productsByFilters[catsPerProduct[i]].push(productId);
      }
    });
  };

  /**
   * Init the features filters, The secind layer of filters and maybe most complex one.
   * First do a call to the Filters API and display the feature filters in a callback.
   *
   * @access Private.
   * @return void.
   */
  var initFeatureFilters = function () {
    fetchFiltersFromAPI(function (data) {

      //enable compare button
      if (data && data.Products && data.Products.length > 0 && document.querySelector("#compare-button-1")) {
        document.querySelector("#compare-button-1").classList.add("ready");
      }
      // Index features by parent filter id for faster/simpler use.
      // This system is over-complicated at the source... So here parse everything and generate the right indexes to facilitate filtering.
      for (var i = 0, l = data.Filters.length; i < l; i++) {
        // A useless wrapper only for display purpose.
        var filterGroup = data.Filters[i];

        for (var j = 0, m = filterGroup.Filters.length; j < m; j++) {
          var filter = filterGroup.Filters[j],
            filterId = filter.ID;// The real filters.
          for (var k = 0, n = filter.Values.length; k < n; k++) {
            // The subfilters are called features. useless for filtering, so we'd better convert them to parent real filter.
            var feature = filter.Values[k];

            featuresParentsList[feature] = filterId;
          }
        }
      }

      var comparableProducts = []
      // Index products by id in productsList for faster use.
      for (var i = 0, l = data.Products.length; i < l; i++) {
        var product = data.Products[i],
          pid = parseInt(product.ProductID);
        // self.productsList[pid] = product.Values;

        if (indexedProductIdList.hasOwnProperty(pid)) comparableProducts.push(pid);

        // Index all products by Feature filter.
        for (var j = 0, m = product.Values.length; j < m; j++) {
          var feature = product.Values[j],
            index = featuresParentsList[feature] + 'f';
          if (productsByFilters[index] === undefined) productsByFilters[index] = [];
          // Don't add if already in.
          if (productsByFilters[index].indexOf(pid) === -1 && indexedProductIdList.hasOwnProperty(pid)) productsByFilters[index].push(pid);
        }
      }

      $('[data-productid="' + comparableProducts.join('"],[data-productid="') + '"]').addClass('can-compare');

      for (var i = 0, l = data.Filters.length; i < l; i++) {
        var html = renderFilter(data.Filters[i]);
        // if (html) $(".filters").append(html);
        if (html) $(".filters .other-filters").append(html);
      }

      if(isLayout2024){
        // show first filter from other filters group
        $(".filters .other-filters .filter-group:first-of-type").removeClass("closed").children("ul").removeAttr("style");
      }

      // If some filters from URL could not be loaded before this API result, then call the loadFiltersFromUrl()
      // again with only those filters in param.
      if (postponedFiltersLoad.length) loadFiltersFromUrl(postponedFiltersLoad);
    });
  };

  /**
   * Init the price related filters, The third layer of filters.
   *
   * @access Public.
   * @return void.
   */
  self.initPriceRelatedFilters = function (productsList) {
    // Index all products by category.
    for (var i = 0, l = productsList.length; i < l; i++) {
      var pid = parseInt(productsList[i].ProductID),
        price = parseFloat(productsList[i].Price) || 0;

      priceRange.min = Math.min(priceRange.min === null ? 10000 : priceRange.min, price);
      priceRange.max = Math.max(price, priceRange.max);

      // Index all the prices by product id.
      productPrices[pid] = price;

      // Index all products by free shipping filter. (fsf alias Free Shipping Feature)
      if (productsList[i].FreeShipping) {
        if (productsByFilters['fsf'] === undefined) productsByFilters['fsf'] = [];
        productsByFilters['fsf'].push(pid);
      }

      // Index all products by in stock filter. (isf alias In Stock Feature)
      if (productsList[i].InStock) {
        if (productsByFilters['isf'] === undefined) productsByFilters['isf'] = [];
        productsByFilters['isf'].push(pid);
      }
    }

    $('.is-filter,.fs-filter').slideDown(300);


    if (priceRange.max) {
      priceRange.from = priceRange.min;
      priceRange.to = priceRange.max;

      $('.price-filter').slideDown(300)
        .find("#priceslider").attr('value', priceRange.from + ';' + priceRange.to)
        .slider(
          {
            from: parseInt(priceRange.from),
            to: parseInt(priceRange.to),
            step: 1,
            format: {
              format: res.CURRENCYFORMAT.replace(/NT\$/g, "$").replace(/\{0\:|\}/g, "").replace("#0.00", "##").replace("#0.00", "##").replace(/\#0\;/g, "##;").replace("0.00", "0")
            },
            callback: function () {
              var slidervalue = $("#priceslider").slider("value").split(";");
              priceRange.from = parseInt(slidervalue[0]);
              priceRange.to = parseInt(slidervalue[1]);
              self.updateFilters();
              if(isLayout2024){
                //if price filter slide on default position, reset cell
                if(priceRange.from == priceRange.min && priceRange.to == priceRange.max){
                  updateCategoryCells("pricefilter_move", false);
                }
              }else{
                grid1.grid('redraw');
              }
            }
          });
      $('.jslider').on("touchstart touchmove touchend touchcancel", touchHandler);
    }
    else {
      $('#filter-instock').hide();
    }

    // If some filters from URL could not be loaded before this API result, then call the loadFiltersFromUrl()
    // again with only those filters in param.
    if (postponedFiltersLoad.length) loadFiltersFromUrl(postponedFiltersLoad);

    // console.log('initPriceRelatedFilters');
  };

  /**
   * A filter consist of either a group of features or product categories - considered as subfilters.
   * Here the wrapping filter is not used to filter anything. hmmm.. Sth wrong in the source of this system. :/
   *
   * @access Private.
   * @param  {[type]} filter [description]
   * @return {html}   the generated html for the features filter coming from the API.
   */
  var renderFilter = function (filter) {
    var hasMatch = false,
      featuresHtml = '',
      features = filter.Filters || [];

    // If any feature (subfilter).
    for (var i = 0, l = features.length; i < l; i++) {
      if (productsByFilters[features[i].ID + 'f'] && productsByFilters[features[i].ID + 'f'].length) {
        featuresHtml += '<li><a href="javascript:;" class="filter" data-filter="' + features[i].ID + '">' + features[i].Name + '</a></li>';
        hasMatch = true;
      }
    }


    // Return the filter HTML if any subfilter matches anything or false otherwise.
    if(isLayout2024){
      return hasMatch ?
      `<div class="filter-group feature-filters closed">
         <div class="title i-minus">${filter.Name} <span class="icon"><span class="i-arr-d-lt"></span><span class="i-arr-u-lt"></span></span><span class="filter-group-count"></span></div>
         <ul style="display:none;">${featuresHtml}</ul>
       </div>` : null;
    }else{
      return hasMatch ?
      `<div class="filter-group feature-filters"><div class="title i-minus">${filter.Name} <span class="filter-group-count"></span></div><ul>${featuresHtml}</ul></div>` : null;
    }    
  };

  /**
   * Apply a new filter.
   *
   * @access Public.
   * @param  int filterId: the filter id to activate.
   * @return void.
   */
  self.applyFilter = function (filterId) {
    self.updateFilters(filterId, true);
  };

  /**
   * Release a filter.
   *
   * @access Public.
   * @param  int filterId: the filter id to deactivate.
   * @return void.
   */
  self.releaseFilter = function (filterId) {
    self.updateFilters(filterId, false);
  };

  self.updateFilterCount = function (e) {
    $('.filter-group').each((i, j) => {
      const c = $(j).find('.selected').length;
      if (c > 0) {
        $(j).find('.title .filter-group-count').show().text(c);
      }
      else {
        $(j).find('.title .filter-group-count').hide();
      }
    })

    const priceFilterActive = (Math.floor(priceRange.from) > Math.floor(priceRange.min) || Math.floor(priceRange.to) < Math.floor(priceRange.max));

    const shopOnlineFilterCount = $('.price-avail-ship .selected').length + (priceFilterActive ? 1 : 0);
    if (shopOnlineFilterCount > 0) {
      $('.price-avail-ship .filter-group-count').text(shopOnlineFilterCount).show();
    }
    else {
      $('.price-avail-ship .filter-group-count').hide();
    }

    const totalFilters = $('.filter-group .selected').length + (priceFilterActive ? 1 : 0);

    if (totalFilters > 0) {
      $('.apply-filters .total-filter-count').show().text(totalFilters);
      $('.toggle-filters .total-filter-count').show().text(totalFilters);
    }
    else {
      $('.apply-filters .total-filter-count').hide();
      $('.toggle-filters .total-filter-count').hide();
    }


  }

  /**
   * Update the active filters list according to the last selected or released filter.
   * Then pass the resulting array of cells to show to The Grid.
   *
   * @access Public.
   * @param  int filterId: the filter id to toggle on or off.
   * @param  bool activate: if we are adding a filter a releasing a filter.
   * @return void.
   */
  self.updateFilters = function (filterId, activate) {
    var filteredCells = [],
      filteredProductIds = [],
      activeCategoryFiltersCount = 0,
      activeFeatureFiltersCount = 0,
      activePriceFilter = false;

    // Activate/Deactivate the current filter in the list of active filters.
    if (filterId) self.activeFilters[filterId] = activate;

    //----------------------- CATEGORIES -----------------------//
    // First loop through active filters to merge all the selected categories.
    for (var fid in self.activeFilters) {
      // Perform a OR filter on categories only. Then apply feature filters in a second loop.
      if (fid.indexOf('f') === -1 && self.activeFilters[fid]) {
        // Perform a "OR" filter (keep product if it matches this filter OR this one).
        filteredProductIds = filteredProductIds.concat(productsByFilters[fid]);
        activeCategoryFiltersCount++;
      }
      else if (fid.indexOf('f') > -1 && self.activeFilters[fid]) activeFeatureFiltersCount++;
    }

    // Remove dupes if at least 2 categories are selected.
    if (activeCategoryFiltersCount > 1) filteredProductIds = filteredProductIds.unique();
    // Fallback to all the products if no category is selected.
    else if (!activeCategoryFiltersCount) filteredProductIds = productIdList.clone();// Clone not to affect original array.

    // console.log('filtered ids after categories loop:', filteredProductIds);
    //----------------------------------------------------------//


    //------------------------ FEATURES ------------------------//
    // Second loop through active filters to remove products if they are not found in EVERY active feature filter.
    if (activeFeatureFiltersCount) {
      var tmpArr = filteredProductIds.clone();// Clone not to affect original array.
      for (var fid in self.activeFilters) {
        if (fid.indexOf('f') > -1 && self.activeFilters[fid]) {
          for (var i = 0, l = filteredProductIds.length; i < l; i++) {
            if ((productsByFilters[fid] || []).indexOf(parseInt(filteredProductIds[i])) === -1) delete tmpArr[i];
          }
        }
      }

      filteredProductIds = tmpArr.unique();
    }
    //----------------------------------------------------------//

    //------------------------- PRICES -------------------------//
    // Third loop through filtered products to remove products out of current price range if price range is defined.
    activePriceFilter = priceRange.max && (priceRange.to < priceRange.max || priceRange.from > priceRange.min);

    if (activePriceFilter) {
      tmpArr = [];
      for (var i = 0, l = filteredProductIds.length; i < l; i++) {
        var price = productPrices[filteredProductIds[i]] || 0;

        if (price < priceRange.from || price > priceRange.to) {
          // console.log("removing product "+filteredProductIds[i]+' as its price of '+price+' is not in the range: ['+priceRange.from+','+priceRange.to+'].');
        }
        else {
          tmpArr.push(filteredProductIds[i]);
        }
      }
      filteredProductIds = tmpArr;
    }
    //----------------------------------------------------------//

    $('.reset-filters')[activeCategoryFiltersCount || activeFeatureFiltersCount || activePriceFilter ? 'show' : 'hide']();

    // Now the filtered cells are including all the active filters.
    filteredCells = filteredProductIds.length ? cellsWrapper.find('[data-productid=' + filteredProductIds.join('],[data-productid=') + ']') : [];

    // Apply sorting on selection if any sorting filter is active.
    if (self.activeSorting) filteredCells = self.applySorting(filteredCells);

    // Show or hide the no match message according to the number of matches.
    $('.no-match')[filteredCells.length ? 'hide' : 'show']();

    // Show/hide the compare button depending on whether there are products that can be compared
    if ($('.can-compare').length > 0) {
      $('#compare-button-1')[filteredCells.length ? 'fadeIn' : 'fadeOut'](300);
    }

    // Show the price filter tag
    const priceFromLocalised = self.formatCurrency(global.store.currencyCode, priceRange.from, global.store.culture);
    const priceToLocalised = self.formatCurrency(global.store.currencyCode, priceRange.to, global.store.culture);
    const priceFilterTag = `<div class="filter-tag filter-tag-price" data-filter="price">${res.price_range}: ${global.store.id === 29 ? 'NT' : ''}${priceFromLocalised}–${global.store.id === 29 ? 'NT' : ''}${priceToLocalised}</div>`;
    const priceFilterTagExists = $('.filter-tags .filter-tag-price').length > 0;

    if (!priceFilterTagExists && activePriceFilter) {
      $('.filter-tags-wrapper').append(priceFilterTag);
    }
    else {
      $('.filter-tag-price').html(`${res.price_range}: $${priceRange.from}–$${priceRange.to}`);
    }

    if (priceRange.from === Math.floor(priceRange.min) && priceRange.to === Math.floor(priceRange.max)) {
      $('.filter-tags-wrapper')
        .find('.filter-tag-price')
        .remove();
    }

    const hasTags = $(`.filter-tags-wrapper .filter-tag`).length > 0;
    if (hasTags) {
      $('.filter-tags .filter-label').show();
    }
    else {
      $('.filter-tags .filter-label').hide();
    }

    self.updateFilterCount();

    // Finally give all the filtered and sorted cells to The Grid.
    // Grid redrawing is not performed here but ulteriorly from the method caller.
    self.filteredCells = filteredCells;

    //$('#category-grid-container').show();
    $('#category-grid-container').removeClass('filters-open');
    grid1.grid('filter', filteredCells, false, true);
  };

  /**
   * Apply sorting to the current filtered cells. ONLY APPLY ON A JQUERY COLLECTION.
   * If done on an array then passing array to jquery will loose the specific order.
   *
   * @access Public.
   * @param  {jQuery collection} filteredCells: the collection of cells to resort.
   * @return void.
   */
  self.applySorting = function (filteredCells) {
    filteredCells = filteredCells === undefined ? self.filteredCells : filteredCells;
    var multiplier = self.activeSorting.order === 'desc' ? -1 : 1;

    // Sort by popular.
    if (self.activeSorting.sort === 'popular') {
      var tmp = cellsWrapper.find('.category-cell').not(filteredCells);
      // console.log(cells, cellsWrapper.not(filteredCells))
      return cellsWrapper.find('.category-cell').not(tmp);
    }

    // Sort by price or date.
    return filteredCells.sort(function (a, b) {
      switch (self.activeSorting.sort) {
        case 'price':
          a = productPrices[$(a).data('productid')] || 0;
          b = productPrices[$(b).data('productid')] || 0;
          break;
        case 'newest':
          a = parseInt($(a).data('newflag')) || 0;
          b = parseInt($(b).data('newflag')) || 0;
          break;
        default:
          break;
      }

      return (a - b) * multiplier;
    });
  };

  /**
   * Reset ALL the filters and also tell The Grid.
   *
   * @access Public.
   * @return void.
   */
  self.resetFilters = function () {
    // Reset active filters.
    self.activeFilters = {};

    // reset price range selection.
    priceRange.from = Math.floor(priceRange.min);
    priceRange.to = Math.ceil(priceRange.max);

    // Reset active sorting.
    self.activeSorting = { sort: 'popular', order: 'asc' };

    // reset the previously cached filtered cells.
    self.filteredCells = cells;

    // Visually uncheck all filters and visually reset price range.
    $(".filters a.filter.selected").removeClass('selected');

    if ($("#priceslider").val() !== '') {
      $("#priceslider").slider("prc", 0, 100);
    }

    // Hide the no match message and reset filters button and show compare button.
    $('.no-match').hide();
    $('.pl-top .reset-filters').removeClass('show');

    if ($('.can-compare').length > 0) {
      $('#compare-button-1').fadeIn(300);
    }

    // Remove tags
    $('.filter-tags-wrapper .filter-tag').remove();
    $('.filter-tags .filter-label').hide();

    //$('#category-grid-container').show();
    $('#category-grid-container').removeClass('filters-open');

    // Tell The Grid to reset the collection of cells.
    grid1.grid('resetFilters');

    // Scroll to top of the category grid.
    if ($(window).width() <= 640) {
      $([document.documentElement, document.body]).animate({
        scrollTop: $(".pl-top").offset().top - 65
      }, 100);
    }
  };

  self.resetPriceFilter = function (e) {
    // reset price range selection.
    priceRange.from = priceRange.min;
    priceRange.to = priceRange.max;
    $("#priceslider").slider("prc", 0, 100);

    // Update filters and redraw grid
    self.updateFilters();
    if(isLayout2024){
      updateCategoryCells("pricefilter_reset",false)
    }else{
      grid1.grid('redraw');
    }

    // Remove tag
    $('.filter-tags-wrapper .filter-tag-price').remove();

    // Remove label if there are no more tags
    const hasTags = $(`.filter-tags .filter-tag`).length > 0;
    if (hasTags) {
      $('.filter-tags-wrapper .filter-label').show();
    }
    else {
      $('.filter-tags .filter-label').hide();
    }
  }

  /**
   * Load the filters from URL if any.
   * Look for query string "filters=int,int,..." or "filters=int|int|..." according to AND or OR type filter.
   * So far on the site all the feature filters behave as a refinement (AND)  e.g. Show cells that match this feature AND this one.
   * Now we want to allow a OR feature filter so we can do: Show cells that match this feature OR this one.
   * (So the matching products collection will be bigger).
   *
   * @access Private.
   * @param {array} filters: optional. If given will only loop among those filters.
   *                         They are given from the callback API to perform only the filters that could not be processed
   *                         before receiving API data.
   * @return void.
   */
  loadFiltersFromUrl = function (filters) {
    postponedFiltersLoad = [];// Empty the class var from any previously treated filters.
    var filters = filters || (location.search.match(/filters=([^&]+)/) || ['', ''])[1].split(',');

    for (var i = 0, l = filters.length; i < l; i++) {
      var hasOrFeatureFilters = filters[i].indexOf('|') > -1;

      if (hasOrFeatureFilters) {
        // Check if the filter matches are available yet (case for category filters that don't need API data),
        // If not available postpone this filter treatment until the API callback.
        if (!productsByFilters.hasOwnProperty(filters[i].split('|')[0] + 'f')) {
          postponedFiltersLoad.push(filters[i]);
          continue;
        }

        var orFilters = filters[i].split('|');

        // Create a virtual category including all the matches of each feature filter. ;)
        // For each feature filter that we want to combine, add the matches to a new virtual filter (merge arrays).
        for (var j = 0, m = orFilters.length; j < m; j++) {
          var fid = parseInt(orFilters[j]);

          if (fid && productsByFilters.hasOwnProperty(fid + 'f')) {
            productsByFilters[filters[i] + 'f'] = (productsByFilters[filters[i] + 'f'] || []).concat(productsByFilters[fid + 'f']);
          }
        }

        // After merging multiple matched product id lists, cleanup the duplicates.
        productsByFilters[filters[i] + 'f'] = productsByFilters[filters[i] + 'f'].unique();

        // Set the new virtual filter as active.
        self.activeFilters[filters[i] + 'f'] = true;

        // Append the new created virtual feature filter to the filters pane to let user enable/disable the page preset filter.
        if (!$('.pre-applied').length) {
          $('.filters').prepend('<div class="filter-group feature-filters pre-applied"><div class="title i-minus">' + res.pre_applied_filter + '</div><ul>'
            + '<li><a href="javascript:;" class="filter selected" data-filter="' + filters[i] + '">'
            + res['filters_' + filters[i]]
            + '</a></li>'
            + '</ul></div>');
        }
      }
      else {
        var fid = parseInt(filters[i]);

        // Check if the filter matches are available yet (case for category filters that don't need API data),
        // If not available postpone this filter treatment until the API callback.
        if (fid && (productsByFilters.hasOwnProperty(fid) || productsByFilters.hasOwnProperty(fid + 'f'))) {
          $('[data-filter="' + fid + '"]').addClass('selected');

          fid = productsByFilters.hasOwnProperty(fid) ? fid : (fid + 'f');
          self.activeFilters[fid] = true;
        }
        else postponedFiltersLoad.push(fid);
      }
    }

    if (!postponedFiltersLoad.length) {
      // Refresh the grid if no postponed filters.
      self.updateFilters();
      if(isLayout2024){
        updateCategoryCells("loadFiltersFromUrl",true)
      }else{
        grid1.grid('redraw');
      }
    }
    postponedFiltersLoad = filters.unique();// Wait the last loaded filters to refresh the grid (don't overload!).
  };

  self.toggleTag = function (el, override) {

    const elem = el.tagName === 'A' ? el : el.parentElement;
    const filterId = elem.getAttribute('data-filter');
    const tagText = elem.innerHTML.trim();
    const tag = `<div class="filter-tag" data-filter="${filterId}">${tagText}</div>`;
    const exists = $(`.filter-tags .filter-tag[data-filter=${filterId}]`).length > 0;

    if (($(elem).hasClass('selected') && !exists) || override) {
      $('.filter-tags-wrapper').append(tag);
    }
    else {
      $('.filter-tags').find(`div[data-filter=${filterId}]`).remove();
    }

    const hasTags = $(`.filter-tags .filter-tag`).length > 0;
    if (hasTags) {
      $('.filter-tags .filter-label').show();
    }
    else {
      $('.filter-tags .filter-label').hide();
    }
  }

  self.preloadTags = function (e) {
    var filters = filters || (location.search.match(/filters=([^&]+)/) || ['', ''])[1].split(',');
    filters.forEach(f => {
      const el = $(`.filters a[data-filter=\'${f}\']`);
      if (el.get(0) !== undefined) self.toggleTag(el.get(0), true);
    })
  }

  self.formatCurrency = function (currency, amount, culture) {

    const temp = new Intl.NumberFormat(
      global.store.culture, { style: 'currency', currency: global.store.currencyCode }).format(amount)
      .replace(/(\.|\,)00/, '')
      .replace('\u00A0', '')
      .replace('SGD', '$')
      .replace('HK', '')
      .replace('US', '');

    if (culture.toLowerCase() === 'it-it') {
      formatted = temp.substr(temp.length - 1, 1) + temp.substr(0, temp.length - 1);
    }
    else if (culture.toLowerCase() === 'ja-jp') {
      formatted = temp.substr(1, temp.length) + '円';
    }
    // else if (culture.toLowerCase() === 'el-gr') {
    //   formatted = temp.substr(1, temp.length) + temp.substr(0, 1);
    // }
    else {
      formatted = temp;
    }
    return formatted;
  }

  /**
   * First init the events related to the filters.
   *
   * @access private.
   * [bindEvents description]
   * @return {[type]} [description]
   */
  var bindEvents = function () {
    $('#category-wrapper').on('click', '.sliding-container.open .faded-overlay', function () {
      $('.toggle-filters').trigger('click');
    });

    // Left pane filters.
    $('.filters')
      .on('click', 'a.filter', function (e) {
        e.preventDefault();

        var filterActive = !$(this).hasClass('selected'),
          isFeatureFilter = $(this).parents('.feature-filters').length;
        filterId = $(this).data('filter');

        if ($(this).hasClass('hot-filter')) {
          // Enable/disable the hot filter.
          self.activeFilters[filterId + 'f'] = filterActive;

          // Update hot button in top bar.
          $('.pl-top .hot-filter')[filterActive ? 'addClass' : 'removeClass']('i-check-lt');
        }

        $(this).toggleClass('selected');

        self.toggleTag(e.target);
        self.updateFilterCount(e.target);

        self.updateFilters(filterId + (isFeatureFilter ? 'f' : ''), filterActive);
        if(!isLayout2024){
          grid1.grid('redraw');
        }
        
        //check all a.filter element, if all of them dun exist selected, run updateCategory() function
        if(isLayout2024 && isFilterEmpty() && document.querySelector('.sortby-dropdown').getAttribute("data-sort").includes("popular")){
          updateCategoryCells("filter_click_empty",false);
        }
      })
      .on('click', '.title', function (e) {
        e.preventDefault();

        var closed = !$(this).parent('.filter-group').hasClass('closed');

        $(this).parent('.filter-group')[closed ? 'addClass' : 'removeClass']('closed')
          .find('ul')[closed ? 'slideUp' : 'slideDown']();

        // For the Shop Online filter (Price/Availability/Free Shipping)
        // Hide all the items below it also
        if ($(this).parent('.filter-group').attr('id') === 'price-filter') {
          $('#price-filter').siblings()
            .each((i, j) => {
              $(j).find('ul')[closed ? 'slideUp' : 'slideDown']();
            })
        }
      })

    // Trigger hot filter on only if no current page filter is given by url.
    if (location.search.indexOf('filters=') === -1) $('.filter[data-filter="hot"]').trigger('click');

    // Top grid controls (sort reset filters and hot filter).
    $('.sortby-dropdown').on('click', 'li a', function () {
      //update value on parent container
      $('.sortby-dropdown').attr({
        "data-sort": $(this).data('sort'),
        "data-order": $(this).data('order')
      });

      $(this).parents('.sortby-dropdown').find('span span').html(this.innerHTML);
      $(this).parents('ul').hide();
      self.activeSorting = { sort: $(this).data('sort'), order: $(this).data('order') || 'asc' };
      self.filteredCells = self.applySorting();

      grid1.grid('filter', self.filteredCells, false, true);
      if(isLayout2024){
        sortCategoryCells($(this).data('sort'),$(this).data('order') || 'asc');
      }else{
        grid1.grid('redraw');
      }
    })

    // Top reset filters button.
    $(".reset-filters, .reset-filters-mobile,.filters-wrapper .filter-reset").on('click', function (e) {
      // There is also a reset filters button in the no-match message.
      $(".pl-top .reset-filters").hide();
      self.resetFilters();
      if(isLayout2024){
        updateCategoryCells("resetfilter_click",false)
      }else{
        grid1.grid('redraw');
      }

      if ($(e.target).hasClass('reset-filters-mobile')) {
        $('.sliding-container').toggleClass('open');
        $('.filter-buttons-wrapper').toggleClass('open');
        if(isLayout2024){
          $('#category-grid-container #category-grid').removeClass('open-filter-menu');
          $('#category-grid-container .btn-filter').toggleClass('selected');
        }
        $('#global-footer').toggleClass('filters-open');
        toggleChatWidget();
      }

      $('.filter-group-count').text(0).hide();
      $('.total-filter-count').text(0).hide();
      if ($(window).width() <= 640) {
        window.scrollTo(0, 0);
      }
    });

    // Top hot button filter.
    $(".pl-top .hot-filter").on('click', function (e) {
      e.preventDefault();
      $('[data-filter="hot"]').trigger('click');
    });

    // Filter tags
    $('.filter-tags').on('click', '.filter-tag', (e) => {
      const isPriceFilter = $(e.target).hasClass('filter-tag-price');

      if (isPriceFilter) {
        self.resetPriceFilter();
      }
      else {
        const elem = e.target.tagName === 'DIV' ? e.target : e.target.parentElement;
        const filterId = elem.getAttribute('data-filter');
        $(`.filters a[data-filter=\'${filterId}\']`).click();
      }
    })

    window.setTimeout(self.preloadTags, 500);

    if (isLayout2024) {
      let isSelected;
    
      const toggleElements = (condition) => {
        const elements = [
          [".sliding-container, .filter-buttons-wrapper", "open"],
          ["#category-grid-container, #global-footer", "filters-open"],
          ["#category-grid", "open-filter-menu"],
          ["body > #all", "blur"],
          ["body > #all", "open-filter-menu"],
          ["html", "no-scroll"]
        ];
        
        const screenWidth = window.innerWidth || document.documentElement.clientWidth;
        elements.forEach(([selectors, className]) => {
          if ((screenWidth < 768 || screenWidth > 1024) && (selectors === "body > #all" || selectors === "html")) {
            return; // Skip this iteration
          }
          [...document.querySelectorAll(selectors)].forEach(el => el.classList.toggle(className, condition));
        });
      };
    
      const resetFilters = () => {
        document.querySelector("#category-grid-container .btn-filter").classList.remove("selected");
        toggleElements(false); // Ensure all filters are closed
      };
    
      const handleToggle = () => {
        isSelected = document.querySelector("#category-grid-container .btn-filter").classList.toggle("selected");
        toggleElements(isSelected);
      };
    
      // Filter button click event
      document.querySelector("#category-grid-container .btn-filter").addEventListener("click", handleToggle);
    
      // Closing filter when clicking outside
      [...document.querySelectorAll("#category-grid-container #category-grid, body > #all")].forEach(el => {
        el.addEventListener("click", function (e) {
          if (e.target.classList.contains("open-filter-menu")) handleToggle();
        });
      });
    
      // Hamburger menu click event
      document.querySelector("#main-nav li > a.hamburger").addEventListener("click", resetFilters);

      // filter close button event
      document.querySelector("#category-filter-container .filter-close").addEventListener("click", handleToggle);
    }
    
  };

  /**
   * Init the filters handler display the missing filters, do calls to APIs and trigger events.
   *
   * @access private.
   * [init description]
   * @return {[type]} [description]
   */
  var init = function () {
    initCategoryFilters();

    if ($('#category-wrapper').data('compare-api') !== undefined) initFeatureFilters();

    bindEvents();

    if (location.search.indexOf('filters=') > -1) loadFiltersFromUrl();
  }();
};
Array.prototype.unique = function () {
  var u = {}, a = [];
  for (var i = 0, l = this.length; i < l; ++i) {
    if (u.hasOwnProperty(this[i]) || !this[i]) continue;
    a.push(this[i]);
    u[this[i]] = 1;
  }
  return a;
};

Array.prototype.clone = function () { return this.slice(0) };

toggleChatWidget = function () {
  if ($('.sliding-container').hasClass('open')) {
    if (typeof zE !== 'undefined') zE('webWidget', 'hide');
    $('#alert-box-toggle').hide();
  }
  else {
    if (typeof zE !== 'undefined') zE('webWidget', 'show');
    $('#alert-box-toggle').show();
  }
}

let initCategory = function () {
  //=============================== GRID LAYOUT ===============================//
  var baseOptions = {
    cells: '.category-cell',
    cellHeight: function (g) { 
      return ((g.gridWidth / g.options.cellsPerRow) * 262 / 400) + 100; 
    },
    updateGridHeight: true,
    animationDelay: 0,
    animationSpeed: 300,
    breakpoints: {
      1199: {
        cellsPerRow: 3,
      },
      1023: {
        cellsPerRow: 2,
      },
      370: {
        cellsPerRow: 1,
      }
    }
  };

  if (isLayout2024) {
    baseOptions.cellsPerRow = 3;
    baseOptions.updateGridHeight = false;
  } else {
    baseOptions.cellsPerRow = 5;
    baseOptions.breakpoints[1919] = { cellsPerRow: 5 };
    baseOptions.breakpoints[1599] = { cellsPerRow: 4 };
  }
  
  grid1 = $('#category-grid').grid(baseOptions);
  
  //===========================================================================//

  //=========================== FILTERS & SORTING =============================//
  // Instantiate categoryPage.
  catPage = new categoryPage();

  $('.toggle-filters').on('click', function () {
    $('.sliding-container').toggleClass('open');
    $('.filter-buttons-wrapper').toggleClass('open');
    $('#category-grid-container').addClass('filters-open');
    $('#global-footer').toggleClass('filters-open');
    self.toggleChatWidget();
  });

  $('.apply-filters').on('click', function () {
    $('.sliding-container').toggleClass('open');
    $('.filter-buttons-wrapper').toggleClass('open');
    $('#category-grid-container').removeClass('filters-open');
    $('#global-footer').toggleClass('filters-open');
    if(isLayout2024){
      $('#category-grid-container .btn-filter').removeClass('selected');
      $('#category-grid-container #category-grid').removeClass('open-filter-menu');
      updateCategoryCells("applyfilter_click", document.querySelectorAll('.filters a.filter.selected').length != 0)
    }else{
      grid1.grid('redraw');
    }
    self.toggleChatWidget();

    if ($(window).width() <= 640) {
      $([document.documentElement, document.body]).animate({
        scrollTop: $(".pl-top").offset().top - 65
      }, 0);
    }
  });

  // Add all the items in the core items array.
  $(".category-cell").each(function () { catPage.oriItems.push(this); });

  // Init filters.
  fh = new filtersHandler();

  if (catPage.fetchPrices) fetchPrices();

  // Top sort dropdown.
  $(".sortby-dropdown,.sortby-dropdown ul").width($(".sortby-dropdown ul").width() + 60);
  $(".sortby-dropdown")
    .on("mouseover", function () { $(".sortby-dropdown ul").show() })
    .on("mouseout", function () { $(".sortby-dropdown ul").hide() });

  if (isLayout2024) {
    $(".sortby-dropdown").on("click", function (e) {
        e.stopPropagation();
        $(".sortby-dropdown ul").toggle();
      });

    // click on other place close dropdown menu
    $(document).on("click", function () { $(".sortby-dropdown ul").hide(); });
  }
  //===========================================================================//

  //============================== COMPARE PRODUCTS ===========================//
  if ($('#category-wrapper').data('compare-api') !== undefined) {
    // Initialize compare chart with given in page "compareChartParams".
    CompareChart.init(compareChartParams);

    compareProducts = CompareChart.getProduct();
  }
  //===========================================================================//

  //=============================== Update Category Cell function =============//
  // Check the subcat group length
  // update the category cell (hide) and change the divider class if condition meet
  // different breakpoinmt will have different max Cells Per Group
  if(isLayout2024){
    updateCategoryCells("initCategory",false);
  }
  //===========================================================================//
};;
