/* Minification failed. Returning unminified contents.
(4028,9-10): run-time error JS1010: Expected identifier: .
(4028,9-10): run-time error JS1195: Expected expression: .
(4063,11-12): run-time error JS1010: Expected identifier: .
(4063,11-12): run-time error JS1195: Expected expression: .
(6241,76-77): run-time error JS1010: Expected identifier: .
(6241,76-77): run-time error JS1195: Expected expression: .
(6241,89-93): run-time error JS1034: Unmatched 'else'; no 'if' defined: else
 */
/*!
 * jQuery UI Touch Punch 0.2.3
 *
 * Copyright 2011–2014, Dave Furfero
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Depends:
 *  jquery.ui.widget.js
 *  jquery.ui.mouse.js
 */
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);;
/*! modernizr 3.0.0 (Custom Build) | MIT *
 * http://modernizr.com/download/?-touchevents !*/
!function(e,n,t){function o(e,n){return typeof e===n}function s(){var e,n,t,s,a,i,r;for(var l in c){if(e=[],n=c[l],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;t<n.options.aliases.length;t++)e.push(n.options.aliases[t].toLowerCase());for(s=o(n.fn,"function")?n.fn():n.fn,a=0;a<e.length;a++)i=e[a],r=i.split("."),1===r.length?Modernizr[r[0]]=s:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=s),f.push((s?"":"no-")+r.join("-"))}}function a(e){var n=u.className,t=Modernizr._config.classPrefix||"";if(h&&(n=n.baseVal),Modernizr._config.enableJSClass){var o=new RegExp("(^|\\s)"+t+"no-js(\\s|$)");n=n.replace(o,"$1"+t+"js$2")}Modernizr._config.enableClasses&&(n+=" "+t+e.join(" "+t),h?u.className.baseVal=n:u.className=n)}function i(){return"function"!=typeof n.createElement?n.createElement(arguments[0]):h?n.createElementNS.call(n,"http://www.w3.org/2000/svg",arguments[0]):n.createElement.apply(n,arguments)}function r(){var e=n.body;return e||(e=i(h?"svg":"body"),e.fake=!0),e}function l(e,t,o,s){var a,l,f,c,d="modernizr",p=i("div"),h=r();if(parseInt(o,10))for(;o--;)f=i("div"),f.id=s?s[o]:d+(o+1),p.appendChild(f);return a=i("style"),a.type="text/css",a.id="s"+d,(h.fake?h:p).appendChild(a),h.appendChild(p),a.styleSheet?a.styleSheet.cssText=e:a.appendChild(n.createTextNode(e)),p.id=d,h.fake&&(h.style.background="",h.style.overflow="hidden",c=u.style.overflow,u.style.overflow="hidden",u.appendChild(h)),l=t(p,e),h.fake?(h.parentNode.removeChild(h),u.style.overflow=c,u.offsetHeight):p.parentNode.removeChild(p),!!l}var f=[],c=[],d={_version:"3.0.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var t=this;setTimeout(function(){n(t[e])},0)},addTest:function(e,n,t){c.push({name:e,fn:n,options:t})},addAsyncTest:function(e){c.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=d,Modernizr=new Modernizr;var u=n.documentElement,p=d._config.usePrefixes?" -webkit- -moz- -o- -ms- ".split(" "):[];d._prefixes=p;var h="svg"===u.nodeName.toLowerCase(),m=d.testStyles=l;Modernizr.addTest("touchevents",function(){var t;if("ontouchstart"in e||e.DocumentTouch&&n instanceof DocumentTouch)t=!0;else{var o=["@media (",p.join("touch-enabled),("),"heartz",")","{#modernizr{top:9px;position:absolute}}"].join("");m(o,function(e){t=9===e.offsetTop})}return t}),s(),a(f),delete d.addTest,delete d.addAsyncTest;for(var v=0;v<Modernizr._q.length;v++)Modernizr._q[v]();e.Modernizr=Modernizr}(window,document);;

var Utils = (function () {
	return {
		setCookie: function (name, value, expires, path, domain, secure, sameSite) {
			var today = new Date();
			today.setTime(today.getTime());

			if (expires) {
				//calculate by day
				expires = expires * 86400000;
			}

			var expires_date = new Date(today.getTime() + (expires));

			document.cookie = name + "=" + escape(value) +
				((expires) ? ";expires=" + expires_date.toGMTString() : "") +
				((path) ? ";path=" + path : "") +
				((domain) ? ";domain=" + domain : "") +
				((secure) ? ";secure" : "") +
				((sameSite) ? ";SameSite=" + sameSite : ";SameSite=Lax");
		},
		insertCookie: function (name, value, expires, path, domain, secure) {
			var today = new Date();
			today.setTime(today.getTime());

			if (expires) {
				//calculate by day
				expires = expires * 86400000;
			}

			var expires_date = new Date(today.getTime() + (expires));

			value = Utils.getCookie(name) + "," + value;

			document.cookie = name + "=" + escape(value) +
				((expires) ? ";expires=" + expires_date.toGMTString() : "") +
				((path) ? ";path=" + path : "") +
				((domain) ? ";domain=" + domain : "") +
				((secure) ? ";secure" : "");
		},
		insertCookieHourly: function (name, value, expires, path, domain, secure) {
			var today = new Date();
			today.setTime(today.getTime());

			if (expires) {
				//calculate by hour
				expires = expires * 3600000;
			}

			var expires_date = new Date(today.getTime() + (expires));

			value = Utils.getCookie(name) + "," + value;

			document.cookie = name + "=" + escape(value) +
				((expires) ? ";expires=" + expires_date.toGMTString() : "") +
				((path) ? ";path=" + path : "") +
				((domain) ? ";domain=" + domain : "") +
				((secure) ? ";secure" : "");
		},
		getCookie: function (cookieName) {
			var theCookie = "" + document.cookie;
			var ind = theCookie.indexOf(cookieName);
			if (ind == -1 || cookieName == "") return "";
			var ind1 = theCookie.indexOf(';', ind);
			if (ind1 == -1) ind1 = theCookie.length;
			return unescape(theCookie.substring(ind + cookieName.length + 1, ind1));
		},
		deleteCookie: function(cookieName){
			if (Utils.getCookie(cookieName)) {
				document.cookie = cookieName + "=" +
					";path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT";
			}
		},
		loader: function (obj) {
			$(obj).html("<img src='/content/images/loading.gif' width='40px' height='40px' />");
		},
		hideLoader: function (obj){
			$(obj).html("");
		},
		preLoadCarouselImages: function (obj) {
			for (var i = 0; i < obj.length; i++) {
				//$("<img />").attr("src", obj[i].url + ".axd?preset=leftlargesquare");
				//$("<img />").attr("src", obj[i].url + ".axd?preset=rightlargesquare");

				var img = new Image(); //new img obj
				img.src = obj[i].url + ".axd?preset=leftlargesquare"; //set src either absolute or rel to css dir
			}
		},
		getQueryStringParam: function (key) {
			key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
			var match = location.search.match(new RegExp("[?&]" + key + "=([^&]+)(&|$)"));
			return match && decodeURIComponent(match[1].replace(/\+/g, " "));
		},
		escapeRegExp: function (str) {
			return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
		},
		replaceAll: function (str, find, replace) {
			return str.replace(new RegExp(Utils.escapeRegExp(find), 'g'), replace);
		}
	}
})();

var GeoLocation = (function () {
	var clientLatitude = Utils.getCookie("clientlat");
	var clientLongitude = Utils.getCookie("clientlng");

	return {
		init: function () {
			if (clientLatitude == "" && clientLongitude == "") {

				if (typeof (navigator.geolocation) !== 'undefined') {
					// One-shot position request.
					navigator.geolocation.getCurrentPosition(this.setLocation);
				} else {
					console.log("Unable to determine position");
				}
			}

			return this;
		},
		setLocation: function (position) {
			if (clientLatitude == "" && clientLongitude == "") {
				Utils.loader("#nearby-list");

				//save in cookie
				Utils.setCookie("clientlat", position.coords.latitude, 365, "/", null, false);
				Utils.setCookie("clientlng", position.coords.longitude, 365, "/", null, false);

				$("#nearby-list").nearbycampgrounds({ clientlat: position.coords.latitude, clientlng: position.coords.longitude });
			}
		}
	}
})();

var ChangeLocation = (function () {
	var onClick = function () {

		var form = $(this).data("changelocation").options.form

		$.ajax({
			url: "/nearby/NearbyForm/",
			context: $(form),
			success: function (html) {
				$(this).html(html);

				$("#nearby-form").findnearbycampgrounds({ container: form });
			}
		});

	};

	return {
		init: function (options, elem) {
			// Mix in the passed in options with the default options
			this.options = $.extend({}, this.options, options);

			// Save the element reference, both as a jQuery
			// reference and a normal reference
			this.elem = elem;
			this.$elem = $(elem);

			this.$elem.click(onClick);

			return this;
		},
		options: {
			form: null
		}
	}
})();

var FindNearbyCampgrounds = (function () {
	var onSubmit = function (event) {
		/* stop form from submitting normally */
		event.preventDefault();

		var container = $(this).data("findnearbycampgrounds").options.container;

		/* get some values from elements on the page: */
		var $form = $(this),
			formlocation = $form.find('input[name=location]').val(),
			formredirecturl = $form.find('input[name=redirecturl]').val(),
			action = $form.attr('action');

		$.ajax({
			url: action,
			type: "POST",
			data: { location: formlocation, redirecturl: formredirecturl },
			beforeSend: function () {
				//setup the loading image
				Utils.loader(container);
			},
			success: function (html) {
				$(container).html(html);

				$("#change-location").changelocation({ form: "#nearby-list" });
				$("#nearby-form").findnearbycampgrounds({ container: container });

				//Update the Find A Koa link in the nav
				FindAKOA.setLocationLink("#btn-nav-find-koa");

				//Update the hot deals for this new location
				$.ajax({
					url: "/hot-deals/hotdealsnearby",
					type: "POST",
					data: { location: formlocation, redirecturl: formredirecturl },
					success: function (html) {
						$("#hd-list").html(html);
					},
					error: function () {
						//console.log("error");
					}
				});
			},
			error: function () {
				//console.log("error");
			}
		});
	};

	return {
		init: function (options, elem) {
			// Mix in the passed in options with the default options
			this.options = $.extend({}, this.options, options);

			// Save the element reference, both as a jQuery
			// reference and a normal reference
			this.elem = elem;
			this.$elem = $(elem);

			this.$elem.submit(onSubmit);

			return this;
		},
		options: {
			container: null
		}
	}
})();

var NearbyCampgrounds = (function () {
	return {
		init: function (options, elem) {
			//console.log("nearbycampgrounds");
			// Mix in the passed in options with the default options
			this.options = $.extend({}, this.options, options);

			// Save the element reference, both as a jQuery
			// reference and a normal reference
			this.elem = elem;
			this.$elem = $(elem);

			if (this.options.clientlat != null && this.options.clientlng != null) {
				Utils.loader("#nearby-list");

				$.ajax({
					url: "/nearby/GetNearbyCampgrounds/",
					data: "dLatitude=" + this.options.clientlat + "dLongitude" + this.options.clientlng,
					context: this.$elem,
					success: function (html) {
						$(this).html(html);
						$("#change-location").changelocation({ form: "#nearby-list" });
					}
				});
			}

			return this;
		},
		options: {
			clientlat: null,
			clientlng: null
		}
	}
})();

var Vkr = (function () {
	var onSubmit = function (event) {
		/* stop form from submitting normally */
		event.preventDefault();

		var success = false;

		var formcontainer = $(this).data("vkr").options.container;
		var loadingcontainer = $(this).data("vkr").options.loading;

		/* get some values from elements on the page: */
		var $form = $(this),
			formlogin = $form.find("input[name=Login]").val(),
			formpassword = $form.find("input[name=Password]").val(),
			formreturnurl = $form.find("input[name=ReturnUrl]").val(),
			action = $form.attr('action');

		$.ajax({
			url: action,
			type: "POST",
			data: { Login: formlogin, Password: formpassword, ReturnUrl: formreturnurl },
			beforeSend: function () {
				$(loadingcontainer).css("text-align", "center");
				//setup the loading image
				Utils.loader(loadingcontainer);
			},
			success: function (html) {
				$(formcontainer).html(html);
				$("#" + $form.attr("id")).vkr({ container: formcontainer, loading: loadingcontainer });
			},
			error: function (data) {

			},
			complete: function (data) {
				LoadCof();
			}
		});
	}
	return {

		init: function (options, elem) {
			// Mix in the passed in options with the default options
			this.options = $.extend({}, this.options, options);

			// Save the element reference, both as a jQuery
			// reference and a normal reference
			this.elem = elem;
			this.$elem = $(elem);

			this.$elem.submit(onSubmit);

			return this;
		},
		options: {
			container: null,
			loading: null
		}
	}
})();

var FindAKOA = (function () {
	var onSubmit = function (event) {
		/* stop form from submitting normally */
		event.preventDefault();

		/* get some values from elements on the page: */
		var $form = $(this),
			formquery = $form.find("input[name=q]").val(),
			action = $form.attr('action');

		if (action == '/search/' && (formquery.indexOf('KOA') > 0 || formquery.indexOf('koa') > 0)) {
			window.location.href = action + '?txtLocation=' + formquery;
		} else {
			window.location.href = '/find-a-koa/?s=ext#' + encodeURIComponent('{"map":{"search":"' + formquery + '"}}');

			if ($('#results-map').length > 0) {
				// Reload the page if we are on the map page
				window.location.reload();
			}

			window.location.href = '/find-a-koa/?s=ext#' + encodeURIComponent('{"map":{"search":"' + formquery + '"}}');
		}
	}

	return {
		init: function (options, elem) {
			// Mix in the passed in options with the default options
			this.options = $.extend({}, this.options, options);

			// Save the element reference, both as a jQuery
			// reference and a normal reference
			this.elem = elem;
			this.$elem = $(elem);

			this.$elem.submit(onSubmit);

			return this;
		},
		setLocationLink: function (elem) {
			if (Utils.getCookie("clientlocation") != "") {
				$(elem).attr("href", '/find-a-koa/#' + encodeURIComponent('{"map":{"search":"' + Utils.getCookie("clientlocation") + '"}}'));
			}
		},
		options: null
	}
})();

var FindStatesProvinces = (function () {
	var onClick = function (event) {
		/* stop form from submitting normally */
		//event.preventDefault();

		var stateProvince = $(this).attr("class");

		if (stateProvince.indexOf("link-map-us") != -1) {
			//save in cookie
			Utils.setCookie("koamap", "us", 10, "/", null, false);
		}

		if (stateProvince.indexOf("link-map-ca") != -1) {
			//save in cookie
			Utils.setCookie("koamap", "ca", 10, "/", null, false);
		}
	}

	return {
		init: function (options, elem) {
			// Mix in the passed in options with the default options
			this.options = $.extend({}, this.options, options);

			// Save the element reference, both as a jQuery
			// reference and a normal reference
			this.elem = elem;
			this.$elem = $(elem);

			this.$elem.click(onClick);

			return this;
		},
		options: null
	}
})();

var Kompass = (function () {
	$(document).ready(function () {
		$('#KompassCountry').on("change", function () {
			var country = $(this).val();
			switch (country) {
				case 'United States':
				case 'Canada':
					$('#statesDiv').removeClass('d-none');
					break;
				default:
					$('#statesDiv').addClass('d-none');
					break;
			}
		});
		if ($('form#form-kompass').length > 0) {
			$('form#form-kompass').each(function (index, value) {
				KompassValidation(value, index);
			});
		}
		else if ($('form#form-kompass').length > 0) {
			KompassValidation($('form#form-kompass'));
		}
	});

	function KompassValidation(kompassForm, index) {
		if (!index) {
			index = 0;
		}
		$(kompassForm).validate({
			messages: {
				KompassEmailAddress: {
					required: "A valid Email Address is required",
					email: "A valid Email Address is required"
				},
				KompassFirstName: {
					required: "A valid First Name is required",
				},
				KompassLastName: {
					required: "A valid Last Name is required",
				},
				KompassCountry: {
					required: "A Country selection is required",
				},
				KompassStateProvince: {
					required: "A State/Province selection is required",
				}
			},
			errorLabelContainer: 'form#form-kompass:eq(' + index + ') .validation-summary-errors #error-list',
			errorClass: 'input-validation-error',
			wrapper: 'li',
			onkeyup: false,
			onfocusout: false,
			invalidHandler: function (event, validator) {
				$('form#form-kompass:eq(' + index + ') .validation-summary-errors').show();
			},
			submitHandler: function (form) {
				$('form#form-kompass:eq(' + index + ') .validation-summary-errors').hide();
				$('form#form-kompass:eq(' + index + ')').submit();
			}
		});
	}
})();

// Make sure Object.create is available in the browser (for our prototypal inheritance)
// Courtesy of Papa Crockford
// Note this is not entirely equal to native Object.create, but compatible with our use-case
if (typeof Object.create !== 'function') {
	Object.create = function (o) {
		function F() { } // optionally move this outside the declaration and into a closure if you need more speed.
		F.prototype = o;
		return new F();
	};
}
(function ($) {
	$.plugin = function (name, object) {
		$.fn[name] = function (options) {
			return this.each(function () {
				if (!$.data(this, name)) {
					$.data(this, name, Object.create(object).init(options, this));
				}
			});

		};
	};
})(jQuery);

$.fn.customCheckbox = function (settings) {
	// Add a click listener to the label
	$('label' + this.selector).click(function (e) {
		e.stopPropagation();
	});

	// Add change listener to the input
	$('input' + this.selector).change(function (e) {
		var $this = $(this);
		var $label = $('label[for="' + $this.attr('id') + '"] > span');

		if ($this.is(':checked')) {
			$label.addClass('checked');
		}
		else {
			$label.removeClass('checked');
		}
	});

	return this;
};

$(function () {
	//load the plugins
	$.plugin("changelocation", ChangeLocation);
	$.plugin("nearbycampgrounds", NearbyCampgrounds);
	$.plugin("findnearbycampgrounds", FindNearbyCampgrounds);
	$.plugin("findakoa", FindAKOA);
	$.plugin("findstatesprovinces", FindStatesProvinces);
	$.plugin("kompass", Kompass);

	$("#change-location").changelocation({ form: "#nearby-list" });
	$("#nearby-form").findnearbycampgrounds({ container: "#nearby-list" });
	$("#form-find-a-koa, #form-nav-find-a-koa").findakoa();
	$(".link-map-us, .link-map-ca").findstatesprovinces();
});

$(document).ready(function () {

	FindAKOA.setLocationLink("#btn-nav-find-koa");

	LoadReadMore();

	// Main Menu
	$('.mainMenu .dropdown-toggle').on('click mouseover mouseleave', function (e) {
		var position = $('.mainMenu .collapse').position();
		var width = $('.mainMenu .collapse').css("width");
		$(this).next('.main-menu-container').css("left", position.left + "px").css("width", width);

		var currentPosition = $(this).offset();
		var currentWidth = $(this).css("width").replace("px", "");
		$(this).next('.main-menu-container').find(".indicator").css("left", (currentPosition.left - position.left - 10) + parseInt(currentWidth) / 2 + "px");

		if (e.type == "click") {
			$('.mainMenu .dropdown-toggle .fa-caret-up').removeClass('fa-caret-up').addClass('fa-caret-down');
			if ($(this).attr("aria-expanded") != "true") {
				$(this).children('.main-menu-caret').removeClass('fa-caret-down').addClass('fa-caret-up');
				// TODO: Still need to adjust a little bit for using tab/enter on the home page of the site
				$(this).parent().on('hidden.bs.dropdown', function () {
					if ($(this).children('.dropdown-toggle').attr("aria-expanded") != "true") {
						if ($(this).children('.dropdown-toggle').children('.main-menu-caret').hasClass('fa-caret-up')) {
							$(this).children('.dropdown-toggle').children('.main-menu-caret').removeClass('fa-caret-up').addClass('fa-caret-down');
						}
						$(this).off('hidden.bs.dropdown');
					}
				});
			}
		}

		if (e.type == "mouseleave") {
			if ($(this).attr("aria-expanded") != "true") {
				if ($(this).children('.main-menu-caret').hasClass('fa-caret-up')) {
					$(this).children('.main-menu-caret').removeClass('fa-caret-up').addClass('fa-caret-down');
				}
			}
			else {
				// TODO: Still need to adjust a little bit for using tab/enter on the home page of the site
				$(this).parent().on('hidden.bs.dropdown', function () {
					if ($(this).children('.dropdown-toggle').attr("aria-expanded") != "true") {
						if ($(this).children('.dropdown-toggle').children('.main-menu-caret').hasClass('fa-caret-up')) {
							$(this).children('.dropdown-toggle').children('.main-menu-caret').removeClass('fa-caret-up').addClass('fa-caret-down');
						}
						$(this).off('hidden.bs.dropdown');
					}
				});
			}
		}
	});

	// Top Menu
	$(".sharedMenu .dropdown-toggle").on("click mouseover mouseleave", function (e) {
		var currentPosition = $(this).offset();
		var navbarPosition = $('.sharedMenu').offset();
		var left = navbarPosition.left - currentPosition.left;
		var width = $(".sharedMenu").css("width").replace("px", "");

		if ($(this).hasClass("last")) {
			$(this).next('.topMenuContainer').css("left", left + "px").css("width", parseInt(width) + 15);
		} else if ($(this).hasClass("app")) {
			$(this).next('.topMenuContainer').css("left", -190 + "px").css("width", parseInt(width) + 110);
		} else {
			$(this).next('.topMenuContainer').css("left", -150 + "px").css("width", parseInt(width) + 110);
		}

		$('.sharedMenu .dropdown-toggle .fa-caret-up').each(function (index, item) {
			if ($(item).parent('a').siblings('.dropdown-menu').css("display") == "none") {
				$(item).removeClass('fa-caret-up').addClass('fa-caret-down');
			}
		});
		if ($(this).siblings('.dropdown-menu').css("display") != "none") {
			$(this).children('.shared-menu-caret').removeClass('fa-caret-down').addClass('fa-caret-up');
		}

		if (e.type == "mouseleave") {
			if ($(this).siblings('.dropdown-menu').css("display") == "none") {
				if ($(this).children('.shared-menu-caret').hasClass('fa-caret-up')) {
					$(this).children('.shared-menu-caret').removeClass('fa-caret-up').addClass('fa-caret-down');
				}
			}
			else {
				$(this).parent().on('hide.bs.dropdown hidden.bs.dropdown', function () {
					if ($(this).children('.dropdown-menu').css("display") == "none") {
						if ($(this).children('.dropdown-toggle').children('.shared-menu-caret').hasClass('fa-caret-up')) {
							$(this).children('.dropdown-toggle').children('.shared-menu-caret').removeClass('fa-caret-up').addClass('fa-caret-down');
						}
						$(this).off('hide.bs.dropdown hidden.bs.dropdown');
					}
				});
			}
		}
	});

	$(document).keyup(function (e) {
		if (e.keyCode == 27) {
			var activeMenu = $(".sharedMenu .dropdown.open");
			if (activeMenu.length > 0) {
				$(activeMenu[0]).removeClass('open');
				$(activeMenu[0]).children('a').prop('aria-expanded', 'false');
				activeMenu = $(activeMenu[0]).children('.topMenuContainer');
			} else {
				activeMenu = $(".sharedMenu .dropdown:hover .topMenuContainer");
			}
			if (activeMenu.length > 0) {
				$(activeMenu[0]).css('display', 'none');
				$(activeMenu[0]).parent().on('mouseleave mouseenter', function () {
					$(this).children('.topMenuContainer').css('display', '');
					$(this).off('mouseleave mouseenter');
				});
			}
		}
	});

	$('#homeCarousel').on('slide.bs.carousel', function () {
		$('.caption').hide();
	});
	$('#homeCarousel').on('slid.bs.carousel', function () {
		$('.caption').show();
	});

	$('.prettySocial').prettySocial();

	$('.modal-iframe-fb').click(function (e) {
		e.preventDefault();

		var locationHref = $(this).attr('href'),

			//append popup=true if it's not already on the url, since this is a modal
			separator = (locationHref.indexOf("?") === -1) ? "?" : "&",
			newParam = separator + "popup=true";

		//deal with duplicate parameters
		newUrl = locationHref.replace(newParam, "");
		newUrl += newParam;
		locationHref = newUrl;

		$.fancybox.open({
			src: locationHref,
			type: 'iframe',

			toolbar: false,
			smallBtn: false,

			buttons: [],

			opts: {
				preload: false,
				afterShow: function (instance, current) {

					//add universal close button if it does not exist
					if ($(".fancybox-content [data-fancybox-close]").length === 0) {
						$(".fancybox-content").append("<div class='mr-1 mt-1'><button data-fancybox-close class='close' title='Close'><i class='fas fa-window-close'</i></button></div>");
					}

					if ($('.fancybox-content [data-fancybox-close]').attr("onClick") === undefined) {
						//wire up the custom close button
						$('.fancybox-content [data-fancybox-close]').on('click', function () {
							parent.jQuery.fancybox.getInstance().close();
						});
					}

					//hide the default button for now
					$(".fancybox-button--close").hide();
				}
			}
		});
	});

	var scrollTopPosition = 0;
	var lastKnownScrollTopPosition = 0;

	$('.modal-iframe-selectsite').click(function (e) {

		var isMobile = window.matchMedia("only screen and (max-width: 760px)");

		//if mobile - ignore - open full screen, otherwise open in a popup
		if (!isMobile.matches) {
			e.preventDefault();

			var title = $(this).attr('title');
			var locationHref = $(this).attr('href');

			//append popup=true
			locationHref += "&popup=true";

			$('#modal-window-iframe-extra-large').modal({ show: true });

			$('#modal-window-iframe-extra-large').on('shown.bs.modal', function () {
				var modal = $(this);
				modal.find('iframe').attr("src", locationHref);
			});
			$('#modal-window-iframe-extra-large').on('hidden.bs.modal', function (e) {
				//clear the iframe url, so it does not show on the next load
				var modal = $(this);
				modal.find('iframe').attr("src", '/content/images/1px_trans.png');
			});
		}
	});

	$('.modal-iframe-availability').click(function (e) {

		var isMobile = window.matchMedia("only screen and (max-width: 760px)");

		//if mobile - ignore - open full screen, otherwise open in a popup
		if (!isMobile.matches) {
			e.preventDefault();

			var title = $(this).attr('title');
			var locationHref = $(this).attr('href');

			//append popup=true
			locationHref += "&popup=true";

			if (typeof title !== typeof undefined && title !== false) {
				$('#modal-window-iframe-lg-title').text(title);
			}
			else {
				$('#modal-window-iframe-lg-title').text("");
			}

			$('#modal-window-iframe-lg').modal({ show: true });

			$('#modal-window-iframe-lg').on('shown.bs.modal', function () {
				var modal = $(this);
				modal.find('iframe').attr("src", locationHref);
			});
			$('#modal-window-iframe-lg').on('hidden.bs.modal', function (e) {
				//clear the iframe url, so it does not show on the next load
				var modal = $(this);
				modal.find('iframe').attr("src", '/content/images/1px_trans.png');
			});
		}
	});

	$('.modal-iframe').click(function (e) {
		e.preventDefault();

		var width = $(this).data('width');
		var scrolling = $(this).attr('data-scroll');
		var popup = $(this).attr('data-popup');
		var sitetypeHref = $(this).attr('href');
		var title = $(this).attr('title');

		if (typeof title !== typeof undefined && title !== false) {
			$('#modal-window-iframe-title').text(title);
		}
		else {
			$('#modal-window-iframe-title').text("");
		}
		// This locationHref is being used by trip planner to pass in the json string with the trip data
		var dataHref = $(this).attr('data-href');
		var locationHref = dataHref == "true" ? window.location.hash.replace(/^[^#]*#?(.*)$/, '$1') : "";
		var targetOffsetTop = $(this).offset().top;
		if (scrolling) {
			$('#modal-window-iframe-scroll').on('show.bs.modal', function () {
				var modal = $(this);
				if (popup == "true") {
					sitetypeHref = sitetypeHref + "?popup=true";
				}
				modal.find("iframe").attr("src", sitetypeHref);
				if (width) {
					modal.find(".modal-dialog").css("width", width + "px");
				}
				enquire.register("screen and (max-width:768px)", {
					match: function () {
						modal.find(".modal-dialog").css("margin-top", targetOffsetTop + "px");
					},
					unmatch: function () {
						modal.find(".modal-dialog").css("margin-top", "auto");
					}
				});
			});
			$("#modal-window-iframe-scroll").modal({ show: true });
		} else {

			$('#modal-window-iframe').on('show.bs.modal', function (event) {
				var modal = $(this);
				if (width) {
					modal.find(".modal-dialog").css("width", width + "px");
				}
			});
			$('#modal-window-iframe').on('shown.bs.modal', function () {
				var modal = $(this);
				if (locationHref != "") {
					sitetypeHref = sitetypeHref + "&sTripData=" + locationHref;
				}
				modal.find('iframe').attr("src", sitetypeHref);
			});
			$('#modal-window-iframe').on('hidden.bs.modal', function (e) {
				//clear the iframe url, so it does not show on the next load
				var modal = $(this);
				modal.find('iframe').attr("src", '/content/images/1px_trans.png');
			});

			$('#modal-window-iframe').modal({ show: true });
		}
		width = null;
	});

	$('.btn-sitetype-view-rates').on('click', function (e) {
		e.preventDefault();

		var title = $(this).attr('title');
		var locationHref = $(this).attr('href');

		//append popup=true
		locationHref += "&popup=true";

		$('#modal-window-iframe-extra-large').modal({ show: true });

		$('#modal-window-iframe-extra-large').on('shown.bs.modal', function () {
			var modal = $(this);
			modal.find('iframe').attr("src", locationHref);
		});
		$('#modal-window-iframe-extra-large').on('hidden.bs.modal', function (e) {
			//clear the iframe url, so it does not show on the next load
			var modal = $(this);
			modal.find('iframe').attr("src", '/content/images/1px_trans.png');
		});
	});

	$('.modal-iframe-lg').click(function (e) {
		e.preventDefault();
		var srcHref = $(this).attr('href');
		var title = $(this).attr('title');
		var popup = $(this).attr('data-popup');

		// This locationHref is being used by trip planner to pass in the json string with the trip data
		var dataHref = $(this).attr('data-href');
		var locationHref = dataHref == "true" ? window.location.hash.replace(/^[^#]*#?(.*)$/, '$1') : "";

		//display the title if it was included
		if (typeof title !== typeof undefined && title !== false) {
			$('#modal-window-iframe-lg-title').text(title);
		}
		else {
			$('#modal-window-iframe-lg-title').text("");
		}

		$('#modal-window-iframe-lg').on('shown.bs.modal', function () {
			var modal = $(this);
			if (popup == "true") {
				srcHref = srcHref + "?popup=true";
			}
			if (locationHref != "") {
				srcHref = srcHref + "&sTripData=" + locationHref;
			}
			modal.find('iframe').attr("src", srcHref);
		});

		$('#modal-window-iframe-lg').on('hidden.bs.modal', function (e) {
			//clear the iframe url, so it does not show on the next load
			var modal = $(this);
			modal.find('iframe').attr("src", '/content/images/1px_trans.png');
		});

		$('#modal-window-iframe-lg').modal({ show: true });

	});

	$('.modal-iframe-xl').click(function (e) {
		e.preventDefault();
		var srcHref = $(this).attr('href');
		var title = $(this).attr('title');
		var popup = $(this).attr('data-popup');

		// This locationHref is being used by trip planner to pass in the json string with the trip data
		var dataHref = $(this).attr('data-href');
		var locationHref = dataHref == "true" ? window.location.hash.replace(/^[^#]*#?(.*)$/, '$1') : "";

		//display the title if it was included
		if (typeof title !== typeof undefined && title !== false) {
			$('#modal-window-iframe-xl-title').text(title);
		}
		else {
			$('#modal-window-iframe-xl-title').text("");
		}

		$('#modal-window-iframe-xl').on('shown.bs.modal', function () {
			var modal = $(this);
			if (popup == "true") {
				srcHref = srcHref + "?popup=true";
			}
			if (locationHref != "") {
				srcHref = srcHref + "&sTripData=" + locationHref;
			}
			modal.find('iframe').attr("src", srcHref);
		});

		$('#modal-window-iframe-xl').on('hidden.bs.modal', function (e) {
			//clear the iframe url, so it does not show on the next load
			var modal = $(this);
			modal.find('iframe').attr("src", '/content/images/1px_trans.png');
		});

		$('#modal-window-iframe-xl').modal({ show: true });

	});

	$("#modal-window").on('hidden.bs.modal', function () {
		$("#modal-window .modal-content").empty();
	});
	$("#modal-window-iframe, #modal-window-iframe-scroll").on('hidden.bs.modal', function () {
		var iFrame = $(this).find("iframe");
		iFrame.attr("src", "");
	});

	$('.modal-iframe-youtube').click(function (e) {
		e.preventDefault();
		var sitetypeHref = $(this).attr('href');
		var sitetypeHrefAuto = sitetypeHref + "?autoplay=1&rel=0"
		$('#modal-window-iframe-youtube').on('shown.bs.modal', function () {
			var iFrame = $(this).find("iframe");
			iFrame.attr("src", sitetypeHrefAuto);
		});
		$("#modal-window-iframe-youtube").on('hidden.bs.modal', function () {
			var iFrame = $(this).find("iframe");
			iFrame.attr("src", sitetypeHref);
		});
		$('#modal-window-iframe-youtube').modal({ show: true });
	});

	// If this platform accepts touch events, don't fix the menu's at the top. Allow scrolling.
	if ((("ontouchstart" in document.documentElement) || $("html").hasClass("touchevents")) && $('#smartbanner').length) {
		$(".topMenu, .mainMenu, .background, .clpMainMenu").addClass("touch");
	}

	$.ajaxSetup({ cache: true });
	$.getScript('//connect.facebook.net/en_US/sdk.js', function () {
		FB.init({
			appId: '1685564315011866', //koa.com
			version: 'v3.3'
		});
	});

	$("a.fb-share").on("click", function (e) {
		e.preventDefault();
		$this = $(this);
		// gather link info
		var link = {
			url: $this.data('url') || '',
			title: $this.data('title') || '',
			description: $this.data('description') || '',
			media: $this.data('media') || ''
		};
		var ogObject = {};
		if (link.width != '' && link.height != '') {
			ogObject = {
				'og:url': link.url,
				'og:title': link.title,
				'og:description': link.description,
				'og:image': link.media
			};
		} else {
			ogObject = {
				'og:url': link.url,
				'og:title': link.title,
				'og:description': link.description,
				'og:image': link.media
			};
		}
		if (!$.isEmptyObject(FB)) {
			FB.ui({
				method: 'share_open_graph',
				action_type: 'og.shares',
				action_properties: JSON.stringify({
					object: ogObject
				})
			});
		}
	});

	$('#mainUserInfoButtonMobile').click(function () {
		if ($('#mainMobileLoginContainer').css('display') == 'none') {
			$('#mainMobileLoginContainer').html($('.login-box').parent().html());
			if ($('.search-wrapper').css('display') != 'none') {
				$('.search-wrapper').hide();
			}
			var navHeight = $('.mainMenu').css('height');
			$('#mainMobileLoginContainer').css('margin-top', navHeight).fadeIn(300);
			$(this).toggleClass('active').find('i').switchClass('fas fa-user-circle', 'fal fa-times', 0);
		} else {
			$('#mainMobileLoginContainer').fadeOut(300);
			$(this).toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-user-circle', 0);
			$('.search-wrapper').show();
		}
	});

	$('#mobileConnect').click(function () {
		if ($.trim($('#kompassMobileFormContainer').html()) == '') {
			$('#kompassMobileFormContainer').html($('#kompass').html());
		}
	});

	$('.mainMenu .navbar-toggler, .mainMenu .navbar-toggle').on('click', function (e) {
		if ($(this).find('i').hasClass('fa-times')) {
			$(this).find('i').switchClass('fal fa-times', 'fas fa-bars', 0);
			$(document).off('keyup', NavbarEscape);
		} else {
			$(this).find('i').switchClass('fas fa-bars', 'fal fa-times', 0);
			$(document).on('keyup', NavbarEscape);
			$('.mainCollapse a').on('focusout', function () {
				if ($(this)[0] == $('.mainCollapse a:visible').last()[0]) {
					var navbarDisplay = $('.mainCollapse').css("display");
					if (navbarDisplay && navbarDisplay != "none") {
						if ($('.navbar-toggle').length > 0) {
							$('.mainCollapse').removeClass('in');
							$('.navbar-toggle.active').removeClass('active').attr('aria-expanded', 'false');
							$('.navbar-toggle > .fa-times').removeClass('fa-times').addClass('fa-bars');
						}
						else {
							//Navbar-toggler logic
							$('.mainCollapse').removeClass('show');
							$('.navbar-toggler.active').removeClass('active').addClass('collapsed').attr('aria-expanded', 'false');
							$('.navbar-toggler.collapsed > .fa-times').removeClass('fa-times').addClass('fa-bars');
						}
					}
					$(document).off('keyup', NavbarEscape);
				}
			});
		}
		$(this).toggleClass('active');
	});

	$(document).on('click', '.upcoming-stays-slider .slider-navigation .slider-indicator', function () {
		var parent = $(this).parents('.login-box')[0];
		if (!$(this).hasClass('active')) {
			var sliderId = $(this).attr('id').replace('upcoming-stay', 'stay-info');
			removeActiveProfileSliders(parent);
			$(this).addClass('active');
			$(parent).find('#' + sliderId).addClass('active');
		}

	});
	$(document).on('click', '.upcoming-stays-slider .slider-container .upcoming-stays-slider-left', function () {
		var parent = $(this).parents('.login-box')[0];
		var currentId = $(parent).find('.upcoming-stays-slider .stay-info-container.active').attr('id').replace('stay-info-', '');
		var upcomingStays = $(parent).find('.upcoming-stays-slider .stay-info-container').length;
		if (upcomingStays > 1) {
			var nextId = currentId;
			if (currentId == 0) {
				nextId = upcomingStays - 1;
			}
			else {
				nextId--;
			}
			removeActiveProfileSliders(parent);
			$(parent).find('#upcoming-stay-' + nextId).addClass('active');
			$(parent).find('#stay-info-' + nextId).addClass('active');
		}
	});
	$(document).on('click', '.upcoming-stays-slider .slider-container .upcoming-stays-slider-right', function () {
		var parent = $(this).parents('.login-box')[0];
		var currentId = $(parent).find('.upcoming-stays-slider .stay-info-container.active').attr('id').replace('stay-info-', '');
		var upcomingStays = $(parent).find('.upcoming-stays-slider .stay-info-container').length;
		if (upcomingStays > 1) {
			var nextId = currentId;
			if (currentId == upcomingStays - 1) {
				nextId = 0;
			}
			else {
				nextId++;
			}
			removeActiveProfileSliders(parent);
			$(parent).find('#upcoming-stay-' + nextId).addClass('active');
			$(parent).find('#stay-info-' + nextId).addClass('active');
		}
	});
	function removeActiveProfileSliders(parent) {
		$(parent).find('.upcoming-stays-slider .stay-info-container.active').removeClass('active');
		$(parent).find('.upcoming-stays-slider .slider-navigation .slider-indicator.active').removeClass('active');
	}

	$('.faq-section').on('shown.bs.collapse', function (item) {
		$(item.target).siblings('.card-header').find('.fa-angle-down').removeClass('fa-angle-down').addClass('fa-angle-up');
	});
	$('.faq-section').on('hidden.bs.collapse', function (item) {
		$(item.target).siblings('.card-header').find('.fa-angle-up').removeClass('fa-angle-up').addClass('fa-angle-down');
	});



	//Sub-dropdown logic for wts pages
	$('.sub-dropdown .list-group-item').on('click', function () {
		$(".sub-dropdown .dropdown-menu").each(function () {
			$(this).hide();
		});
	});

	$('html').on('click', function () {
		$(".sub-dropdown .dropdown-menu").each(function () {
			$(this).hide();
		});
	});

	$('.sub-dropdown').on('click', function (e) {
		e.stopPropagation();
	});

	$('.sub-dropdown').on("click", function () {
		$(this).find('div.dropdown-menu').show();
	});

	$('#cpra_button').on('click', function (e) {
		if (window.airgap) {
			if (window.airgap.getRegimes().has('US_DNSS')) {
				transcend.showConsentManager({ viewState: 'CompleteOptions' });
			}
			else {
				transcend.showConsentManager();
			}
		}
	});

	//$("#sensible-weather-add").click(function () {
	//	$("#addon-d114726ba6079be95076ad4289962f8e").prop("checked", true);
	//});

	//$("#sensible-weather-remove").click(function () {
	//	$("#addon-d114726ba6079be95076ad4289962f8e").prop("checked", false);
	//});

	//$('.sensible-weather-add-checkout').on('click', function () {
	//	$(".addon-d114726ba6079be95076ad4289962f8e-" + $(this).data('id')).prop("checked", true);
	//});

	//$('.sensible-weather-remove-checkout').on('click', function () {
	//	$(".addon-d114726ba6079be95076ad4289962f8e-" + $(this).data('id')).prop("checked", false);
	//});
});

function NavbarEscape(e) {
	if (e.key == "Escape") {
		var navbarDisplay = $('.mainCollapse').css("display");
		if (navbarDisplay && navbarDisplay != "none") {
			if ($('.navbar-toggle').length > 0) {
				$('.mainCollapse').removeClass('in');
				$('.navbar-toggle.active').removeClass('active').attr('aria-expanded', 'false');
				$('.navbar-toggle > .fa-times').removeClass('fa-times').addClass('fa-bars');
			}
			else {
				//Navbar-toggler logic
				$('.mainCollapse').removeClass('show');
				$('.navbar-toggler.active').removeClass('active').addClass('collapsed').attr('aria-expanded', 'false');
				$('.navbar-toggler.collapsed > .fa-times').removeClass('fa-times').addClass('fa-bars');
			}
		}
		$(document).off('keyup', NavbarEscape);
	}
}

var LoadReadMore = function () {
	$('.description-div').each(function () {
		var descriptionDivHeightMax = 77;
		var descriptionDivHeightDefault = 65;
		var descriptionDivHeight = $(this).height();
		var diff = descriptionDivHeight - descriptionDivHeightDefault;
		var descriptionID = '#' + $(this).attr('id');
		var descriptionReadMoreID = '#description-read-more-' + $(this).attr('id');
		var descriptionDivExpanded = false;

		var readMoreLink = $(descriptionReadMoreID);

		if (descriptionDivHeight > descriptionDivHeightMax) {
			readMoreLink.show();
			$(this).height(descriptionDivHeightDefault);
			readMoreLink.click(function (e, obj) {
				e.preventDefault();
				if (this.descriptionDivExpanded) {
					$(descriptionID).animate({ height: descriptionDivHeightDefault }, 180);
					readMoreLink.removeClass().addClass('bold-blue-link-glyph').text('Read More');
				}
				else {
					$(descriptionID).height(descriptionDivHeight);
					readMoreLink.removeClass().addClass('link-close').text('Close').prepend('<span class="glyphicon glyphicon-remove koa-red-font" aria-hidden="true"></span> ');
				}
				this.descriptionDivExpanded = !this.descriptionDivExpanded;
			});
		}
		else {
			readMoreLink.hide();
		}
	});
}

// Used to animate objects. Using this currently to animate arrows on home page modules.
$.fn.animateRotate = function (angle, duration, easing, complete) {
	var args = $.speed(duration, easing, complete);
	var step = args.step;
	return this.each(function (i, e) {
		args.step = function (now) {
			$.style(e, 'transform', 'rotate(' + now + 'deg)');
			if (step) return step.apply(this, arguments);
		};

		$({ deg: 0 }).animate({ deg: angle }, args);
	});
};

function GetIEVer() {
	var iev = 0;
	var ieold = (/MSIE (\d+\.\d+);/.test(navigator.userAgent));
	var trident = !!navigator.userAgent.match(/Trident\/7.0/);
	var rv = navigator.userAgent.indexOf("rv:11.0");
	var edge = navigator.userAgent.indexOf("Edge");

	if (ieold) iev = new Number(RegExp.$1);
	if (navigator.appVersion.indexOf("MSIE 10") != -1) iev = 10;
	if (trident && rv != -1) iev = 11;
	if (edge != -1) iev = "Edge";

	return iev;
}
;
$(document).ready(function () {
	$('input.account-koa-btn-inverse[type="submit"]').click(function (e) {
		var thisButton = $(this);
		window.setTimeout(function () { thisButton.attr('disabled', 'disabled'); }, 0);
	});

	$('input.account-koa-btn-inverse[type="button"]').click(function (e) {
		var thisButton = $(this);
		window.setTimeout(function () { thisButton.attr('disabled', 'disabled'); }, 0);
	});
});
;
$(document).ready(function () {
	$(document).click(function (e) {
		if (!$("#saved-reservations").is(e.target) && !$("#saved-reservations").has(e.target).length) {
			$('#saved-reservations').collapse('hide');
		}
	});

	$('.saved-reservations-toggler').click(function () {
		var $navbar = $('#mainMenu .navbar-toggler');
		if ($navbar.attr('aria-expanded') === 'true') {
			$navbar.click();
		}
	});

	$('.save-res-save-link').click(function (e) {
		e.preventDefault();

		var link = $(this);
		var status = link.attr("data-status");

		if (status == 'Auto') {

			$.ajax({
				type: "POST",
				url: "/api/saved-reservations/save/",
				async: true,
				data: { id: $(this).attr("data-id") },
				success: function (response) {
					link.attr("data-status", "Saved");
					link.addClass("save-res-action-link-saved");

					var icon = link.find('.fa-star')
					icon.removeClass("far");
					icon.addClass("fas");
				}
			});

		}
		else {
			$.ajax({
				type: "POST",
				url: "/api/saved-reservations/remove/",
				async: true,
				data: { id: $(this).attr("data-id") },
				success: function (response) {
					link.attr("data-status", "Auto");
					link.removeClass("save-res-action-link-saved");
					var icon = link.find('.fa-star')
					icon.removeClass("fas");
					icon.addClass("far");
				}
			});
		}


		return false;
	});

	$('.save-res-delete-link').click(function (e) {
		e.preventDefault();

		var parentContainer = $("#save-res-panel-" + $(this).attr("data-id"));

		$.ajax({
			type: "POST",
			url: "/api/saved-reservations/delete/",
			async: true,
			data: { id: $(this).attr("data-id") },
			success: function (response) {
				parentContainer.fadeOut("normal", function () {
					$(this).remove();
				});
			}
		});
		return false;
	});

	$('.save-res-clear-history').click(function (e) {
		e.preventDefault();

		$.ajax({
			type: "POST",
			url: "/api/saved-reservations/delete-all/",
			async: true,
			success: function (response) {
				$($(".save-res-auto-save-panel").get().reverse()).each(function (index) {
					$(this).delay(400 * index).fadeOut("normal", function () {
						$(this).remove();
					});
				});
			}
		});

		return false;
	});

	$('.account-save-res-delete-link').click(function (e) {
		e.preventDefault();

		var parentContainer = $("#save-res-panel-" + $(this).attr("data-id"));

		$.ajax({
			type: "POST",
			url: "/api/saved-reservations/delete/",
			async: true,
			data: { id: $(this).attr("data-id") },
			success: function (response) {
				location.reload();
			}
		});
		return false;
	});

	$('.account-save-res-clear-history').click(function (e) {
		e.preventDefault();

		$.ajax({
			type: "POST",
			url: "/api/saved-reservations/delete-all/",
			async: true,
			success: function (response) {
				location.reload();
			}
		});

		return false;
	});
});
;
var mainShoppingCart;

$(document).ready(function () {	
	$('#shopping-cart-content').hide();
	$('#shopping-cart-wrapper').hide();

	mainShoppingCart = new shoppingCart({ sideBarVisible: false });

	$(".shopping-cart-toggler").click(function () {
		mainShoppingCart.toggleSideBar();
	});

	$("#shopping-cart-close-link-top").click(function () {
		mainShoppingCart.toggleSideBar();
	});

	$("#shopping-cart-close-link-bottom").click(function () {
		mainShoppingCart.toggleSideBar();
	});

	$(".btn-cart-view-cart").click(function (event) {
		event.preventDefault();

		$('#add-to-cart-modal').modal('hide');
		mainShoppingCart.showSideBar();
	});

	$("input[name='scReservation.Value.RewardID']").change(function () {
		var scId = $(this).attr('data-scid');
		var rewardId = $(this).val();

		$("#applyRewardsAmountButton").val(scId + "|" + rewardId);
		$("#applyRewardsAmountButton").click();
		return false;
	});

	$(document).on("click", ".btn-add-to-cart", function (event) {
		event.preventDefault();

		var addToCartButton = $(this);
		window.setTimeout(function () { addToCartButton.attr('disabled', 'disabled'); }, 0);

		var siteInfo = $(this).attr('data-href');

		var cartIcon = $('#shopping-cart-nav-link .shopping-cart-icon');

		if ($('#mainNavContent').css('display') == "none") {
			cartIcon = $('#shopping-cart-nav-link-mobile .shopping-cart-icon');
		}

		if (!cartIcon.length) {
			cartIcon = null;
		}

		if (!cartIcon) {
			mainShoppingCart.addToCart(null, siteInfo, addToCartButton);
			mainGa4DataLayer.addToCart(addToCartButton.data('siteid'),
			addToCartButton.data('nights'));
			return;
		}

		var imgToCart = $(this).closest('.reserve-sitetype-container').find("img.campsite-photo").eq(0);

		if (imgToCart) {
			var imgClone = imgToCart.clone()
				.offset({
					top: imgToCart.offset().top,
					left: imgToCart.offset().left
				})
				.css({
					'opacity': '0.5',
					'position': 'absolute',
					'z-index': '5000',
					'border': '0px',
					'width': imgToCart.width + "px",
					'height': imgToCart.height + "px"
				})
				.appendTo($('body'))
				.animate({
					'top': cartIcon.offset().top - 3,
					'left': cartIcon.offset().left + 1,
					'width': 25,
					'height': 25
				}, 1000, 'easeInOutExpo');

			imgClone.animate({
				height: 0,
				width: 0,
				top: '+=12px',
				left: '+=12px'
			}, function () {
				$(this).detach();

				mainShoppingCart.addToCart(cartIcon, siteInfo, addToCartButton);
				mainGa4DataLayer.addToCart(addToCartButton.data('siteid'),
				addToCartButton.data('nights'));
			});
		}
	});

	$('.btn-add-to-cart-step-3').on('click', function (event) {
		event.preventDefault();

		var addToCartButton = $(this);
		window.setTimeout(function () { addToCartButton.attr('disabled', 'disabled'); }, 0);

		var siteInfo = $(this).attr('data-href');
		var cartIcon = $('.shopping-cart-icon');

		mainShoppingCart.addToCart(cartIcon, siteInfo, addToCartButton);
	});

	$('.btn-add-to-cart-view-details').on('click', function (event) {
		event.preventDefault();

		var addToCartButton = $(this);
		window.setTimeout(function () { addToCartButton.attr('disabled', 'disabled'); }, 0);

		var siteInfo = $(this).attr('data-href');
		var cartIcon = $('.shopping-cart-icon');

		mainShoppingCart.addToCart(cartIcon, siteInfo, addToCartButton);
		mainGa4DataLayer.addToCart(addToCartButton.data('siteid'),
		addToCartButton.data('nights'));

		parent.$.fancybox.close();
	});

	$('.btn-select-site-add-to-cart').on('click', function (event) {
		event.preventDefault();		

		if (!$("#SiteID").val()) {
			$('#SiteID').addClass('is-invalid');
			$('#reserve-form-site-select-icon').show("fast");
			$('#SiteID').focus();
		}
		else {
			$('#SiteID').removeClass('is-invalid');
			$('#reserve-form-site-select-icon').hide("fast");

			var addToCartButton = $(this);
			window.setTimeout(function () { addToCartButton.attr('disabled', 'disabled'); }, 0);

			var siteInfo = $(this).attr('data-href');

			siteInfo += "&SiteID=" + $("#SiteID").val();
			siteInfo += "&SiteNumber=" + $("#SiteID option:selected").attr('name');
			var siteNumber = $("#SiteID option:selected").attr('name');

			if (self == top) {
				var cartIcon = $('.shopping-cart-icon');
				mainShoppingCart.addToCart(cartIcon, siteInfo, addToCartButton);				
				mainGa4DataLayer.addToCartSelectSite(siteNumber, addToCartButton.data('siteid'), addToCartButton.data('nights'));
			}
			else {
				var cartIcon = parent.$('.shopping-cart-icon');
				parent.mainShoppingCart.addToCart(cartIcon, siteInfo, addToCartButton);				
				mainGa4DataLayer.addToCartSelectSite(siteNumber, addToCartButton.data('siteid'), addToCartButton.data('nights'));
			}

			parent.$('#modal-window-iframe-extra-large').modal('hide');
		}
	});

	$(document).on("click", ".btn-cart-view-rates, .btn-checkout-view-rates", function (e) {
		e.preventDefault();

		var title = $(this).attr('title');
		var locationHref = $(this).attr('href');

		//append popup=true
		locationHref += "&popup=true";

		$('#modal-window-iframe-extra-large').modal({ show: true });

		$('#modal-window-iframe-extra-large').on('shown.bs.modal', function () {
			var modal = $(this);
			modal.find('iframe').attr("src", locationHref);
		});
		$('#modal-window-iframe-extra-large').on('hidden.bs.modal', function (e) {
			//clear the iframe url, so it does not show on the next load
			var modal = $(this);
			modal.find('iframe').attr("src", '/content/images/1px_trans.png');
		});
	});

	$('.btn-rates-add-to-cart').on('click', function (e) {
		e.preventDefault();

		parent.$('#modal-window-iframe-extra-large').modal('hide');
		parent.$('#' + $(this).attr('data-target')).click();

		return false;
	});

	$('.toggle-rewards-options').on('click', function () {
		let dataId = $(this).attr('data-id')
		$('.use-rewards-btn[data-id="' + dataId + '"]').hide();
		$('.rewards-points-options[data-id="' + dataId + '"]').show();
	});

	var elements = $(".using-points");

	elements.each(function () {
		$(this).find(".rewards-points-options").show();
	});
});

var shoppingCartUpdatedMonitor = function (options) {

	var vars = {
		shoppingCartMonitorCartUrl: "/api/shopping-cart/checkhascartupdated/",
		hashKey: ''
	};

	var root = this;
	var _allowMonitor = true;

	this.construct = function (options) {
		$.extend(vars, options);
	};

	this.beginMonitor = function () {
		beginMonitor();
	};

	this.stopMonitor = function () {
		stopMonitor();
	};

	var stopMonitor = function () {
		_allowMonitor = false;
	};

	var beginMonitor = function () {
		_allowMonitor = true;
		checkHash();
	};

	var checkHash = function () {

		var getData = vars.hashKey;

		$.get(vars.shoppingCartMonitorCartUrl + "?k=" + getData)
			.done(function (data) {
				if (data == "") {
					if (_allowMonitor) {
						setTimeout(checkHash, 10000);
					}
					return;
				}

				if (data.Valid) {
					if (_allowMonitor) {
						setTimeout(checkHash, 10000);
					}
				}
				else {
					if (_allowMonitor) {
						$('#add-to-cart-error-modal .modal-title').html("Cart Updated!");
						$('#add-to-cart-error-modal .error-message').html("Your cart has been updated and this checkout screen does not reflect those changes.  This page is being refreshed to reflect your updated cart.");
						$('#add-to-cart-error-modal').modal('show');

						setTimeout(() => window.location.reload(), 3000);
					}
				}
			})
			.fail(function () {
				if (_allowMonitor) {
					setTimeout(checkHash, 10000);
				}
			})
			.always(function () {

			});
	};

	this.construct(options);
};

var shoppingCart = function (options) {

	var vars = {
		shoppingCartAddToCartUrl: "/api/shopping-cart/add/",
		shoppingCartBookReservationUrl: "/api/shopping-cart/book/"		
	};

	var root = this;
	var _sideBarToggling = false
	var _sideBarVisible = false;
	var _currentBookingIndex = 0;
	var _reservationsToBook;

	this.construct = function (options) {
		$.extend(vars, options);
	};

	this.toggleSideBar = function () {
		toggleSideBar();
	};

	this.showSideBar = function () {
		showSideBar();
	};

	this.addToCart = function (cartIcon, siteInfo, addToCartButton) {
		addToCart(cartIcon, siteInfo, addToCartButton);
	};

	this.refreshCart = function () {
		refreshCart();
	};

	this.bookReservations = function (reservationsToBook) {		
		bookReservations(reservationsToBook);
	};

	this.continueBookingReservations = function (reservationsToBook) {
		bookReservations(reservationsToBook);
	};

	var toggleSideBar = function () {
		if (!_sideBarToggling) {
			_sideBarToggling = true;

			if (_sideBarVisible) {
				$('#shopping-cart-content').toggle("slide", { direction: 'right' }, function () {
					$("#shopping-cart-wrapper").hide();
					_sideBarVisible = false;
					_sideBarToggling = false;
				});
			}
			else {
				$("#shopping-cart-wrapper").show(0, function () {
					$('#shopping-cart-content').toggle("slide", { direction: 'right' });
					$("#shopping-cart-wrapper").show();
					_sideBarVisible = true;
					_sideBarToggling = false;
				});
			}
		}
	};

	var showSideBar = function () {
		if (!_sideBarVisible) {
			toggleSideBar();
		}
	};

	var refreshCart = function () {
		$.ajax({
			type: "POST",
			url: "/api/shopping-cart/get-all/",
			async: true,
			data: { id: $(this).attr("data-id") },
			success: function () {

			}
		});
	};

	var addToCart = function (cartIcon, siteInfo, addToCartButton) {

		if (cartIcon != null) {
			cartIcon.addClass('shopping-cart-loading-animation');
		}

		var postData = null;

		if (siteInfo !== 'undefined') {
			postData = siteInfo;
		}
		else {
			postData = addToCartButton.attr('data-href');
		}

		$.post(vars.shoppingCartAddToCartUrl, postData)
			.done(function (data) {
				$(document).trigger("OnShoppingCartAdd", data);
				if (data == "") {
					$('#add-to-cart-error-modal .error-message').html("Sorry, there was an error while adding your site to your cart.");
					$('#add-to-cart-error-modal').modal('show');

					return;
				}

				if (data.Success) {
					$('.shopping-cart-icon').attr('data-count', data.CartItemCount)
					$('#shopping-cart-cart-items-wrapper').html(data.CartItemsView);
					$('#add-to-cart-modal .warning-message').html('');

					if (data.CartItemCount == 0) {
						$('#shopping-cart-empty-cart-message').removeClass("d-none");
						$('#shopping-cart-checkout-button').addClass("d-none");
						$('#shopping-cart-clear-cart-wrapper').addClass("d-none");
					}
					else {
						$('#shopping-cart-empty-cart-message').addClass("d-none");
						$('#shopping-cart-checkout-button').removeClass("d-none");
						$('#shopping-cart-clear-cart-wrapper').removeClass("d-none");
					}

					if (data.WarningMessage) {
						$('#add-to-cart-modal .warning-message').html(data.WarningMessage);
					}

					$('#add-to-cart-modal').modal('show');
				}
				else {
					if (data.ErrorMessage) {
						$('#add-to-cart-error-modal .error-message').html(data.ErrorMessage);
						$('#add-to-cart-error-modal').modal('show');

						return false;
					}
				}
			})
			.fail(function () {
				$('#add-to-cart-error-modal .error-message').html("Sorry, there was an error while adding your site to your cart.");
				$('#add-to-cart-error-modal').modal('show');
			})
			.always(function () {
				if (cartIcon != null) {
					cartIcon.show();
					cartIcon.removeClass('shopping-cart-loading-animation');
				}
				window.setTimeout(function () { addToCartButton.removeAttr('disabled'); addToCartButton.blur(); }, 0);
			});
	};

	var bookReservations = function (reservationsToBook) {		
		bookReservation(_currentBookingIndex, reservationsToBook);
	};

	var bookReservation = function (currentIndex, reservationsToBook) {		
		if (currentIndex >= reservationsToBook.length) {

			_displayLeaveMessage = false;
			$("#shoppingCartConfirmationForm").submit();
			return false;
		}

		$("#shopping-cart-booking-modal-label").html("Booking Site " + (currentIndex + 1) + " of " + reservationsToBook.length);

		var shoppingCartID = reservationsToBook[currentIndex].id.replace('shopping-cart-modal-reservation-', '');

		reservationsToBook.each(function (index) {
			if (index == currentIndex) {
				$(this).removeClass('d-none');
			}
			else {
				$(this).addClass('d-none');
			}
		});

		var addonsArray = [];
		$('[name="Reservation.AddonsAccepted-' + shoppingCartID + '.Index"]').each(function () {
			var addonID = $(this).val();

			var addonValues = {
				"Index": addonID,
				"AddonID": $('[name="Reservation.AddonsAccepted-' + shoppingCartID + '['+addonID+'].AddonID"]').val(),
				"Quantity": $('[name="Reservation.AddonsAccepted-' + shoppingCartID + '[' + addonID + '].Quantity"]').val()
			};

			addonsArray.push(addonValues);
		});	
		var postData = {
			FirstName: $("#FirstName").val(),
			LastName: $("#LastName").val(),
			Address1: $("#Address1").val(),
			Address2: $("#Address2").val(),
			PhoneNumber: $("#PhoneNumber").val(),
			City: $("#City").val(),
			StateProvinceCode: $("#StateProvinceCode").val(),
			CountryCode: $("#CountryCode").val(),
			PostalCode: $("#PostalCode").val(),
			EmailAddress: $("#EmailAddress").val(),
			ConfirmEmailAddress: $("#ConfirmEmailAddress").val(),
			CreditCardNumber: $("#CreditCardNumber").val(),
			CreditCardType: $("#CreditCardType").val(),
			CreditCardExpMonth: $("#CreditCardExpMonth").val(),
			CreditCardExpYear: $("#CreditCardExpYear").val(),
			CreditCardSecurityCode: $("#CreditCardSecurityCode").val(),
			CreditCardToken: $("#CreditCardToken").val(),
			CreditCardConfirmationToken: $("#CreditCardConfirmationToken").val(),
			CartTransactionID: $("#CartTransactionID").val(),
			TermsAgree: $("#TermsAgree").val(),
			RewardsNumber: $("#RewardsNumber").val(),
			RewardsNumberPostalCode: $("#RewardsNumberPostalCode").val(),
			DonateCareCamps: currentIndex == 0 ? $("#DonateCareCamps").val() : "false",
			PersonID: $("#PersonID").val(),			

			//cart specific items
			SpecialRequests: $("#SpecialRequests-" + shoppingCartID).val(),
			EmailAddressCCs: $("#EmailAddressCCs-" + shoppingCartID).val(),
			CareCampsDonation: $("#CareCampsDonation-" + shoppingCartID).val(),
			BuyRewards: currentIndex == 0 ? $("#BuyRewards" + shoppingCartID).val() : "false",
			ShoppingCartReservationID: $("#ShoppingCartReservationID-" + shoppingCartID).val(),
			TransactionID: $("#ShoppingCartReservationID-" + shoppingCartID).val(),
			SecurityKey: $("#SecurityKey-" + shoppingCartID).val(),
			Order: currentIndex,
			TotalBookingCount: reservationsToBook.length,
			DateUpdated: $("#DateUpdated-" + shoppingCartID).val(),
			ExpectedDeposit: $("#ExpectedDeposit-" + shoppingCartID).val(),
			ExpectedTotal: $("#ExpectedTotal-" + shoppingCartID).val(),
			AddonsAccepted: addonsArray,
		};
		
		$.post(vars.shoppingCartBookReservationUrl, postData)		
			.done(function (data) {
				if (data == "") {
					$('#add-to-cart-error-modal .error-message').html("Sorry, there was an error while booking your site.");
					$('#add-to-cart-error-modal').modal('show');

					return;
				}

				if (data.Success) {

					if (data.VkrNumber && data.VkrExpiration) {
						$("#RewardsNumber").val(data.VkrNumber);
						$("#RewardsNumberPostalCode").val(data.VkrPostalCode);

						$("#Confirm_RewardsNumber").val(data.VkrNumber);
						$("#Confirm_RewardsNumberPostalCode").val(data.VkrPostalCode);
					}

					if (data.CartItem) {
						$("#Confirm-CartItems-" + currentIndex).val(JSON.stringify(data.CartItem));
						$("#Confirm-CartItems-Confirmations-" + currentIndex).val(data.ConfirmationNumber);
					}

					if (currentIndex == 0) {
						$("#Confirm_CareCampsDonationConfirmation").val(data.CareCampsDonationConfirmation);
						$("#Confirm_CareCampsDonationError").val(data.CareCampsDonationError);
					}

					if (currentIndex + 1 < reservationsToBook.length) {
						bookReservation(currentIndex + 1, reservationsToBook);
					}

					if (currentIndex + 1 == reservationsToBook.length) {
						reservationsToBook.each(function (index) {
							$(this).addClass('d-none');
						});

						$("#shopping-cart-booking-modal-label").html("Complete!");

						_displayLeaveMessage = false;

						//reservations complete. Submit privacy settings update.						
						updatePrivacySettings($("#PrivacyCustomerCare").val(), $("#PrivacyMarketing").is(":checked"), $("#PersonID").val());

						$("#shoppingCartConfirmationForm").submit();
					}
				}
				else {
					if (data.ErrorMessage) {
						$("#shopping-cart-booking-modal-label").html("Booking Site " + (currentIndex + 1) + " of " + reservationsToBook.length + " - Error!");
						$('#shopping-cart-booking-please-wait').hide();
						$('#shopping-cart-booking-response').removeClass('d-none');
						$('#shopping-cart-booking-error').removeClass('d-none');
						$('#shopping-cart-booking-error .error-message').html(data.ErrorMessage);
					}

					if (data.WarningMessage) {
						$("#shopping-cart-booking-modal-label").html("Booking Site " + (currentIndex + 1) + " of " + reservationsToBook.length + " - Warning!");
						$('#shopping-cart-booking-please-wait').hide();
						$('#shopping-cart-booking-response').removeClass('d-none');
						$('#shopping-cart-booking-warning').removeClass('d-none');

						$('#shopping-cart-booking-warning .error-message').html(data.WarningMessage);
					}

					if (data.ContinueOnConfirm) {
						$('#shopping-cart-booking-error-continue-button').removeClass('d-none');
					}
					else {
						$('#shopping-cart-booking-error-continue-button').addClass('d-none');
					}

					if (!data.BookOnContinue) {
						_currentBookingIndex = currentIndex + 1;
					}
				}
			})
			.fail(function () {
				$("#shopping-cart-booking-modal-label").html("Booking Site " + (currentIndex + 1) + " of " + reservationsToBook.length + " - Error!");
				$('#shopping-cart-booking-please-wait').hide();
				$('#shopping-cart-booking-response').removeClass('d-none');
				$('#shopping-cart-booking-error').removeClass('d-none');
				$('#shopping-cart-booking-error .error-message').html("Sorry, there was an error while trying to book your your site.");

				_currentBookingIndex = currentIndex;
			})
			.always(function () {

			});
	};

	var updatePrivacySettings = function (bPrivacyCustomerCare, bPrivacyMarketing, sPersonIdentifier) {		
		$.ajax({
			type: "POST",
			url: "/api/shopping-cart/update-person-privacy-settings/",
			async: true,
			data: {
				bPrivacyCustomerCare: bPrivacyCustomerCare,
				bPrivacyMarketing: bPrivacyMarketing,
				sPersonIdentifier: sPersonIdentifier
			},
			success: function (response) {
				//console.log("Privacy settings updated successfully:", response);
			},
			error: function (xhr, status, error) {
				//console.error("Error updating privacy settings:", error);
			}
		});
	};

	this.construct(options);
};






$(document).ready(function () {
	$(document).on("click", ".shopping-cart-delete-link", function (e) {
		e.preventDefault();

		var parentContainer = $("#shopping-cart-panel-" + $(this).attr("data-id"));

		var dataId = $(this).attr("data-id");

		$(document).trigger('onShoppingCartItemDeleting', [dataId]);

		$.ajax({
			type: "POST",
			url: "/api/shopping-cart/delete/",
			async: true,
			data: { id: $(this).attr("data-id") },
			success: function (data) {
				if (data.Success) {

					parentContainer.fadeOut("normal", function () {
						$(this).remove();

						$('.shopping-cart-icon').attr('data-count', data.CartItemCount)
						$('#shopping-cart-cart-items-wrapper').html(data.CartItemsView);

						if (data.CartItemCount == 0) {
							$('#shopping-cart-empty-cart-message').removeClass("d-none");
							$('#shopping-cart-checkout-button').addClass("d-none");
						}
						else {
							$('#shopping-cart-empty-cart-message').addClass("d-none");
							$('#shopping-cart-checkout-button').removeClass("d-none");
						}


					});

					if (data.CartItemDeleted) {
						mainGa4DataLayer.removeFromCartDataLayer(data.CartItemDeleted);
					}

					$(document).trigger('onShoppingCartItemDelete', [data, dataId]);
				}
				else {
					if (data.ErrorMessage) {
						$('#add-to-cart-error-modal .error-message').html(data.ErrorMessage);
						$('#add-to-cart-error-modal').modal('show');

						return false;
					}
				}
			}
		});

		return false;
	});


	$(document).on("click", ".shopping-cart-clear-cart", function (e) {
		e.preventDefault();

		if (!confirm('Are you sure you want to clear your entire shopping cart?')) {
			return;
		}

		$.ajax({
			type: "POST",
			url: "/api/shopping-cart/delete-all/",
			async: true,
			success: function (data) {
				var itemCount = $('.shopping-cart-icon').attr('data-count');

				$($(".shopping-cart-auto-save-panel").get().reverse()).each(function (index) {
					$(this).delay(400 * index).fadeOut("normal", function () {
						$(this).remove();

						if (index == itemCount - 1) {
							$('#shopping-cart-empty-cart-message').removeClass("d-none");
							$('#shopping-cart-checkout-button').addClass("d-none");
							$('#shopping-cart-panel-koa-rewards').addClass("d-none");
							$('.shopping-cart-icon').attr('data-count', 0);
							$('#shopping-cart-cart-items-wrapper').html('');
							$('#shopping-cart-clear-cart-wrapper').addClass("d-none");
						}
					});
				});

				if (data.Success) {
					if (data.CartItemDeleted) {
						mainGa4DataLayer.removeFromCartDataLayer(data.CartItemDeleted);
					}
				}


				//this calls the method on the home page
				$(document).trigger('onShoppingCartItemsDeleteAll', []);

			}
		});

		return false;
	});

	$(document).on("click", "#shopping-cart-booking-error-continue-button", function (e) {
		e.preventDefault();

		$('#shopping-cart-booking-please-wait').show();
		$('#shopping-cart-booking-response').addClass('d-none');
		$('#shopping-cart-booking-error').addClass('d-none');
		$('#shopping-cart-booking-warning').addClass('d-none');
		$('#shopping-cart-booking-warning .error-message').html('');
		$('#shopping-cart-booking-error .error-message').html('');

		mainShoppingCart.continueBookingReservations($('.shopping-cart-modal-reservation'));

		return false;
	});

	$(document).on("click", "#shopping-cart-booking-error-close-button", function (e) {
		e.preventDefault();

		_displayLeaveMessage = false;
		window.location.reload();

		return false;
	});
});

class Counter {
	constructor(counterContainerSelector, max = 99, min = 0) {
		this.counterContainers = $(counterContainerSelector);
		this.max = max;
		this.min = min;

		this.attachEventListeners();
	}

	attachEventListeners() {
		this.counterContainers.each((index, container) => {
			const $container = $(container);
			const $plusButton = $container.find('.counter-plus');
			const $minusButton = $container.find('.counter-minus');
			const $input = $container.find('.counter-input');

			$plusButton.off('click').on('click', () => this.increment($input));
			$minusButton.off('click').on('click', () => this.decrement($input));
		});
	}

	increment($input) {
		let currentValue = parseInt($input.val(), 10);
		if (currentValue < this.max) {
			$input.val(currentValue + 1);
		}
	}

	decrement($input) {
		let currentValue = parseInt($input.val(), 10);
		if (currentValue > this.min) {
			$input.val(currentValue - 1);
		}
	}
}
class shoppingCartLoadMoreHandler {
	constructor(containerSelector, itemSelector, loadMoreSelector, loadLessSelector, initialCount) {
		this.container = $(containerSelector);
		this.items = this.container.find(itemSelector);
		this.loadMoreButton = $(loadMoreSelector);
		this.loadLessButton = $(loadLessSelector);
		this.initialCount = initialCount;
		this.init();
	}

	updateCounts() {
		this.items.hide();
		this.items.slice(0, this.initialCount).show();

		this.loadMoreButton.show();
		this.loadLessButton.hide();
	}

	init() {
		this.loadMoreButton.on('click', (e) => this.showMore(e));
		this.loadLessButton.on('click', (e) => this.showLess(e));

		this.loadLessButton.hide();
	}

	showMore(e) {
		e.preventDefault();

		const hiddenItems = this.items.filter(':hidden');
		hiddenItems.slideDown();

		if (hiddenItems.length > 0) {
			this.loadMoreButton.hide();
			this.loadLessButton.fadeIn('slow');
		}
	}

	showLess(e) {
		e.preventDefault();

		this.items.not(`:lt(${this.initialCount})`).fadeOut();

		this.loadMoreButton.fadeIn('slow');
		this.loadLessButton.hide();
	}
}
;
/**
 * jQuery prettySocial: Use custom social share buttons
 * Author: Sonny T. <hi@sonnyt.com>, sonnyt.com
 */(function(a){a.fn.prettySocial=function(){var b={pinterest:{url:"http://pinterest.com/pin/create/button/?url={{url}}&media={{media}}&description={{description}}",popup:{width:685,height:500}},facebook:{url:"https://www.facebook.com/sharer/sharer.php?s=100&p[title]={{title}}&p[summary]={{description}}&p[url]={{url}}&p[images][0]={{media}}",popup:{width:626,height:436}},twitter:{url:"https://twitter.com/share?url={{url}}&via={{via}}&text={{description}}",popup:{width:685,height:500}},googleplus:{url:"https://plus.google.com/share?url={{url}}",popup:{width:600,height:600}},linkedin:{url:"https://www.linkedin.com/shareArticle?mini=true&url={{url}}&title={{title}}&summary={{description}}+&source={{via}}",popup:{width:600,height:600}}},d=function(f,e){var h=(window.innerWidth/2)-(f.popup.width/2),g=(window.innerHeight/2)-(f.popup.height/2);return window.open(e,"","toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width="+f.popup.width+", height="+f.popup.height+", top="+g+", left="+h)},c=function(f,g){var e=f.url.replace(/{{url}}/g,encodeURIComponent(g.url)).replace(/{{title}}/g,encodeURIComponent(g.title)).replace(/{{description}}/g,encodeURIComponent(g.description)).replace(/{{media}}/g,encodeURIComponent(g.media)).replace(/{{via}}/g,encodeURIComponent(g.via));return e};return this.each(function(){var i=a(this);var g=i.data("type"),f=b[g]||null;if(!f){a.error("Social site is not set.")}var h={url:i.data("url")||"",title:i.data("title")||"",description:i.data("description")||"",media:i.data("media")||"",via:i.data("via")||""};var e=c(f,h);if(navigator.userAgent.match(/Android|IEMobile|BlackBerry|iPhone|iPad|iPod|Opera Mini/i)){i.bind("touchstart",function(j){if(j.originalEvent.touches.length>1){return}i.data("touchWithoutScroll",true)}).bind("touchmove",function(){i.data("touchWithoutScroll",false);return}).bind("touchend",function(k){k.preventDefault();var j=i.data("touchWithoutScroll");if(k.originalEvent.touches.length>1||!j){return}d(f,e)})}else{i.bind("click",function(j){j.preventDefault();d(f,e)})}})}})(jQuery);
;
/*!
 * skrollr core
 *
 * Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr
 *
 * Free to use under terms of MIT license
 */
(function(window, document, undefined) {
	'use strict';

	/*
	 * Global api.
	 */
	var skrollr = {
		get: function() {
			return _instance;
		},
		//Main entry point.
		init: function(options) {
			return _instance || new Skrollr(options);
		},
		VERSION: '0.6.29'
	};

	//Minify optimization.
	var hasProp = Object.prototype.hasOwnProperty;
	var Math = window.Math;
	var getStyle = window.getComputedStyle;

	//They will be filled when skrollr gets initialized.
	var documentElement;
	var body;

	var EVENT_TOUCHSTART = 'touchstart';
	var EVENT_TOUCHMOVE = 'touchmove';
	var EVENT_TOUCHCANCEL = 'touchcancel';
	var EVENT_TOUCHEND = 'touchend';

	var SKROLLABLE_CLASS = 'skrollable';
	var SKROLLABLE_BEFORE_CLASS = SKROLLABLE_CLASS + '-before';
	var SKROLLABLE_BETWEEN_CLASS = SKROLLABLE_CLASS + '-between';
	var SKROLLABLE_AFTER_CLASS = SKROLLABLE_CLASS + '-after';

	var SKROLLR_CLASS = 'skrollr';
	var NO_SKROLLR_CLASS = 'no-' + SKROLLR_CLASS;
	var SKROLLR_DESKTOP_CLASS = SKROLLR_CLASS + '-desktop';
	var SKROLLR_MOBILE_CLASS = SKROLLR_CLASS + '-mobile';

	var DEFAULT_EASING = 'linear';
	var DEFAULT_DURATION = 1000;//ms
	var DEFAULT_MOBILE_DECELERATION = 0.004;//pixel/ms²

	var DEFAULT_SKROLLRBODY = 'skrollr-body';

	var DEFAULT_SMOOTH_SCROLLING_DURATION = 200;//ms

	var ANCHOR_START = 'start';
	var ANCHOR_END = 'end';
	var ANCHOR_CENTER = 'center';
	var ANCHOR_BOTTOM = 'bottom';

	//The property which will be added to the DOM element to hold the ID of the skrollable.
	var SKROLLABLE_ID_DOM_PROPERTY = '___skrollable_id';

	var rxTouchIgnoreTags = /^(?:input|textarea|button|select)$/i;

	var rxTrim = /^\s+|\s+$/g;

	//Find all data-attributes. data-[_constant]-[offset]-[anchor]-[anchor].
	var rxKeyframeAttribute = /^data(?:-(_\w+))?(?:-?(-?\d*\.?\d+p?))?(?:-?(start|end|top|center|bottom))?(?:-?(top|center|bottom))?$/;

	var rxPropValue = /\s*(@?[\w\-\[\]]+)\s*:\s*(.+?)\s*(?:;|$)/gi;

	//Easing function names follow the property in square brackets.
	var rxPropEasing = /^(@?[a-z\-]+)\[(\w+)\]$/;

	var rxCamelCase = /-([a-z0-9_])/g;
	var rxCamelCaseFn = function(str, letter) {
		return letter.toUpperCase();
	};

	//Numeric values with optional sign.
	var rxNumericValue = /[\-+]?[\d]*\.?[\d]+/g;

	//Used to replace occurences of {?} with a number.
	var rxInterpolateString = /\{\?\}/g;

	//Finds rgb(a) colors, which don't use the percentage notation.
	var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g;

	//Finds all gradients.
	var rxGradient = /[a-z\-]+-gradient/g;

	//Vendor prefix. Will be set once skrollr gets initialized.
	var theCSSPrefix = '';
	var theDashedCSSPrefix = '';

	//Will be called once (when skrollr gets initialized).
	var detectCSSPrefix = function() {
		//Only relevant prefixes. May be extended.
		//Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so.
		var rxPrefixes = /^(?:O|Moz|webkit|ms)|(?:-(?:o|moz|webkit|ms)-)/;

		//Detect prefix for current browser by finding the first property using a prefix.
		if(!getStyle) {
			return;
		}

		var style = getStyle(body, null);

		for(var k in style) {
			//We check the key and if the key is a number, we check the value as well, because safari's getComputedStyle returns some weird array-like thingy.
			theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes)));

			if(theCSSPrefix) {
				break;
			}
		}

		//Did we even detect a prefix?
		if(!theCSSPrefix) {
			theCSSPrefix = theDashedCSSPrefix = '';

			return;
		}

		theCSSPrefix = theCSSPrefix[0];

		//We could have detected either a dashed prefix or this camelCaseish-inconsistent stuff.
		if(theCSSPrefix.slice(0,1) === '-') {
			theDashedCSSPrefix = theCSSPrefix;

			//There's no logic behind these. Need a look up.
			theCSSPrefix = ({
				'-webkit-': 'webkit',
				'-moz-': 'Moz',
				'-ms-': 'ms',
				'-o-': 'O'
			})[theCSSPrefix];
		} else {
			theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-';
		}
	};

	var polyfillRAF = function() {
		var requestAnimFrame = window.requestAnimationFrame || window[theCSSPrefix.toLowerCase() + 'RequestAnimationFrame'];

		var lastTime = _now();

		if(_isMobile || !requestAnimFrame) {
			requestAnimFrame = function(callback) {
				//How long did it take to render?
				var deltaTime = _now() - lastTime;
				var delay = Math.max(0, 1000 / 60 - deltaTime);

				return window.setTimeout(function() {
					lastTime = _now();
					callback();
				}, delay);
			};
		}

		return requestAnimFrame;
	};

	var polyfillCAF = function() {
		var cancelAnimFrame = window.cancelAnimationFrame || window[theCSSPrefix.toLowerCase() + 'CancelAnimationFrame'];

		if(_isMobile || !cancelAnimFrame) {
			cancelAnimFrame = function(timeout) {
				return window.clearTimeout(timeout);
			};
		}

		return cancelAnimFrame;
	};

	//Built-in easing functions.
	var easings = {
		begin: function() {
			return 0;
		},
		end: function() {
			return 1;
		},
		linear: function(p) {
			return p;
		},
		quadratic: function(p) {
			return p * p;
		},
		cubic: function(p) {
			return p * p * p;
		},
		swing: function(p) {
			return (-Math.cos(p * Math.PI) / 2) + 0.5;
		},
		sqrt: function(p) {
			return Math.sqrt(p);
		},
		outCubic: function(p) {
			return (Math.pow((p - 1), 3) + 1);
		},
		//see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this
		bounce: function(p) {
			var a;

			if(p <= 0.5083) {
				a = 3;
			} else if(p <= 0.8489) {
				a = 9;
			} else if(p <= 0.96208) {
				a = 27;
			} else if(p <= 0.99981) {
				a = 91;
			} else {
				return 1;
			}

			return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a);
		}
	};

	/**
	 * Constructor.
	 */
	function Skrollr(options) {
		documentElement = document.documentElement;
		body = document.body;

		detectCSSPrefix();

		_instance = this;

		options = options || {};

		_constants = options.constants || {};

		//We allow defining custom easings or overwrite existing.
		if(options.easing) {
			for(var e in options.easing) {
				easings[e] = options.easing[e];
			}
		}

		_edgeStrategy = options.edgeStrategy || 'set';

		_listeners = {
			//Function to be called right before rendering.
			beforerender: options.beforerender,

			//Function to be called right after finishing rendering.
			render: options.render,

			//Function to be called whenever an element with the `data-emit-events` attribute passes a keyframe.
			keyframe: options.keyframe
		};

		//forceHeight is true by default
		_forceHeight = options.forceHeight !== false;

		if(_forceHeight) {
			_scale = options.scale || 1;
		}

		_mobileDeceleration = options.mobileDeceleration || DEFAULT_MOBILE_DECELERATION;

		_smoothScrollingEnabled = options.smoothScrolling !== false;
		_smoothScrollingDuration = options.smoothScrollingDuration || DEFAULT_SMOOTH_SCROLLING_DURATION;

		//Dummy object. Will be overwritten in the _render method when smooth scrolling is calculated.
		_smoothScrolling = {
			targetTop: _instance.getScrollTop()
		};

		//A custom check function may be passed.
		_isMobile = ((options.mobileCheck || function() {
			return (/Android|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent || navigator.vendor || window.opera);
		})());

		if(_isMobile) {
			_skrollrBody = document.getElementById(options.skrollrBody || DEFAULT_SKROLLRBODY);

			//Detect 3d transform if there's a skrollr-body (only needed for #skrollr-body).
			if(_skrollrBody) {
				_detect3DTransforms();
			}

			_initMobile();
			_updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_MOBILE_CLASS], [NO_SKROLLR_CLASS]);
		} else {
			_updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS], [NO_SKROLLR_CLASS]);
		}

		//Triggers parsing of elements and a first reflow.
		_instance.refresh();

		_addEvent(window, 'resize orientationchange', function() {
			var width = documentElement.clientWidth;
			var height = documentElement.clientHeight;

			//Only reflow if the size actually changed (#271).
			if(height !== _lastViewportHeight || width !== _lastViewportWidth) {
				_lastViewportHeight = height;
				_lastViewportWidth = width;

				_requestReflow = true;
			}
		});

		var requestAnimFrame = polyfillRAF();

		//Let's go.
		(function animloop(){
			_render();
			_animFrame = requestAnimFrame(animloop);
		}());

		return _instance;
	}

	/**
	 * (Re)parses some or all elements.
	 */
	Skrollr.prototype.refresh = function(elements) {
		var elementIndex;
		var elementsLength;
		var ignoreID = false;

		//Completely reparse anything without argument.
		if(elements === undefined) {
			//Ignore that some elements may already have a skrollable ID.
			ignoreID = true;

			_skrollables = [];
			_skrollableIdCounter = 0;

			elements = document.getElementsByTagName('*');
		} else if(elements.length === undefined) {
			//We also accept a single element as parameter.
			elements = [elements];
		}

		elementIndex = 0;
		elementsLength = elements.length;

		for(; elementIndex < elementsLength; elementIndex++) {
			var el = elements[elementIndex];
			var anchorTarget = el;
			var keyFrames = [];

			//If this particular element should be smooth scrolled.
			var smoothScrollThis = _smoothScrollingEnabled;

			//The edge strategy for this particular element.
			var edgeStrategy = _edgeStrategy;

			//If this particular element should emit keyframe events.
			var emitEvents = false;

			//If we're reseting the counter, remove any old element ids that may be hanging around.
			if(ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) {
				delete el[SKROLLABLE_ID_DOM_PROPERTY];
			}

			if(!el.attributes) {
				continue;
			}

			//Iterate over all attributes and search for key frame attributes.
			var attributeIndex = 0;
			var attributesLength = el.attributes.length;

			for (; attributeIndex < attributesLength; attributeIndex++) {
				var attr = el.attributes[attributeIndex];

				if(attr.name === 'data-anchor-target') {
					anchorTarget = document.querySelector(attr.value);

					if(anchorTarget === null) {
						throw 'Unable to find anchor target "' + attr.value + '"';
					}

					continue;
				}

				//Global smooth scrolling can be overridden by the element attribute.
				if(attr.name === 'data-smooth-scrolling') {
					smoothScrollThis = attr.value !== 'off';

					continue;
				}

				//Global edge strategy can be overridden by the element attribute.
				if(attr.name === 'data-edge-strategy') {
					edgeStrategy = attr.value;

					continue;
				}

				//Is this element tagged with the `data-emit-events` attribute?
				if(attr.name === 'data-emit-events') {
					emitEvents = true;

					continue;
				}

				var match = attr.name.match(rxKeyframeAttribute);

				if(match === null) {
					continue;
				}

				var kf = {
					props: attr.value,
					//Point back to the element as well.
					element: el,
					//The name of the event which this keyframe will fire, if emitEvents is
					eventType: attr.name.replace(rxCamelCase, rxCamelCaseFn)
				};

				keyFrames.push(kf);

				var constant = match[1];

				if(constant) {
					//Strip the underscore prefix.
					kf.constant = constant.substr(1);
				}

				//Get the key frame offset.
				var offset = match[2];

				//Is it a percentage offset?
				if(/p$/.test(offset)) {
					kf.isPercentage = true;
					kf.offset = (offset.slice(0, -1) | 0) / 100;
				} else {
					kf.offset = (offset | 0);
				}

				var anchor1 = match[3];

				//If second anchor is not set, the first will be taken for both.
				var anchor2 = match[4] || anchor1;

				//"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
				if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
					kf.mode = 'absolute';

					//data-end needs to be calculated after all key frames are known.
					if(anchor1 === ANCHOR_END) {
						kf.isEnd = true;
					} else if(!kf.isPercentage) {
						//For data-start we can already set the key frame w/o calculations.
						//#59: "scale" options should only affect absolute mode.
						kf.offset = kf.offset * _scale;
					}
				}
				//"relative" mode, where numbers are relative to anchors.
				else {
					kf.mode = 'relative';
					kf.anchors = [anchor1, anchor2];
				}
			}

			//Does this element have key frames?
			if(!keyFrames.length) {
				continue;
			}

			//Will hold the original style and class attributes before we controlled the element (see #80).
			var styleAttr, classAttr;

			var id;

			if(!ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) {
				//We already have this element under control. Grab the corresponding skrollable id.
				id = el[SKROLLABLE_ID_DOM_PROPERTY];
				styleAttr = _skrollables[id].styleAttr;
				classAttr = _skrollables[id].classAttr;
			} else {
				//It's an unknown element. Asign it a new skrollable id.
				id = (el[SKROLLABLE_ID_DOM_PROPERTY] = _skrollableIdCounter++);
				styleAttr = el.style.cssText;
				classAttr = _getClass(el);
			}

			_skrollables[id] = {
				element: el,
				styleAttr: styleAttr,
				classAttr: classAttr,
				anchorTarget: anchorTarget,
				keyFrames: keyFrames,
				smoothScrolling: smoothScrollThis,
				edgeStrategy: edgeStrategy,
				emitEvents: emitEvents,
				lastFrameIndex: -1
			};

			_updateClass(el, [SKROLLABLE_CLASS], []);
		}

		//Reflow for the first time.
		_reflow();

		//Now that we got all key frame numbers right, actually parse the properties.
		elementIndex = 0;
		elementsLength = elements.length;

		for(; elementIndex < elementsLength; elementIndex++) {
			var sk = _skrollables[elements[elementIndex][SKROLLABLE_ID_DOM_PROPERTY]];

			if(sk === undefined) {
				continue;
			}

			//Parse the property string to objects
			_parseProps(sk);

			//Fill key frames with missing properties from left and right
			_fillProps(sk);
		}

		return _instance;
	};

	/**
	 * Transform "relative" mode to "absolute" mode.
	 * That is, calculate anchor position and offset of element.
	 */
	Skrollr.prototype.relativeToAbsolute = function(element, viewportAnchor, elementAnchor) {
		var viewportHeight = documentElement.clientHeight;
		var box = element.getBoundingClientRect();
		var absolute = box.top;

		//#100: IE doesn't supply "height" with getBoundingClientRect.
		var boxHeight = box.bottom - box.top;

		if(viewportAnchor === ANCHOR_BOTTOM) {
			absolute -= viewportHeight;
		} else if(viewportAnchor === ANCHOR_CENTER) {
			absolute -= viewportHeight / 2;
		}

		if(elementAnchor === ANCHOR_BOTTOM) {
			absolute += boxHeight;
		} else if(elementAnchor === ANCHOR_CENTER) {
			absolute += boxHeight / 2;
		}

		//Compensate scrolling since getBoundingClientRect is relative to viewport.
		absolute += _instance.getScrollTop();

		return (absolute + 0.5) | 0;
	};

	/**
	 * Animates scroll top to new position.
	 */
	Skrollr.prototype.animateTo = function(top, options) {
		options = options || {};

		var now = _now();
		var scrollTop = _instance.getScrollTop();

		//Setting this to a new value will automatically cause the current animation to stop, if any.
		_scrollAnimation = {
			startTop: scrollTop,
			topDiff: top - scrollTop,
			targetTop: top,
			duration: options.duration || DEFAULT_DURATION,
			startTime: now,
			endTime: now + (options.duration || DEFAULT_DURATION),
			easing: easings[options.easing || DEFAULT_EASING],
			done: options.done
		};

		//Don't queue the animation if there's nothing to animate.
		if(!_scrollAnimation.topDiff) {
			if(_scrollAnimation.done) {
				_scrollAnimation.done.call(_instance, false);
			}

			_scrollAnimation = undefined;
		}

		return _instance;
	};

	/**
	 * Stops animateTo animation.
	 */
	Skrollr.prototype.stopAnimateTo = function() {
		if(_scrollAnimation && _scrollAnimation.done) {
			_scrollAnimation.done.call(_instance, true);
		}

		_scrollAnimation = undefined;
	};

	/**
	 * Returns if an animation caused by animateTo is currently running.
	 */
	Skrollr.prototype.isAnimatingTo = function() {
		return !!_scrollAnimation;
	};

	Skrollr.prototype.isMobile = function() {
		return _isMobile;
	};

	Skrollr.prototype.setScrollTop = function(top, force) {
		_forceRender = (force === true);

		if(_isMobile) {
			_mobileOffset = Math.min(Math.max(top, 0), _maxKeyFrame);
		} else {
			window.scrollTo(0, top);
		}

		return _instance;
	};

	Skrollr.prototype.getScrollTop = function() {
		if(_isMobile) {
			return _mobileOffset;
		} else {
			return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0;
		}
	};

	Skrollr.prototype.getMaxScrollTop = function() {
		return _maxKeyFrame;
	};

	Skrollr.prototype.on = function(name, fn) {
		_listeners[name] = fn;

		return _instance;
	};

	Skrollr.prototype.off = function(name) {
		delete _listeners[name];

		return _instance;
	};

	Skrollr.prototype.destroy = function() {
		var cancelAnimFrame = polyfillCAF();
		cancelAnimFrame(_animFrame);
		_removeAllEvents();

		_updateClass(documentElement, [NO_SKROLLR_CLASS], [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS, SKROLLR_MOBILE_CLASS]);

		var skrollableIndex = 0;
		var skrollablesLength = _skrollables.length;

		for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
			_reset(_skrollables[skrollableIndex].element);
		}

		documentElement.style.overflow = body.style.overflow = '';
		documentElement.style.height = body.style.height = '';

		if(_skrollrBody) {
			skrollr.setStyle(_skrollrBody, 'transform', 'none');
		}

		_instance = undefined;
		_skrollrBody = undefined;
		_listeners = undefined;
		_forceHeight = undefined;
		_maxKeyFrame = 0;
		_scale = 1;
		_constants = undefined;
		_mobileDeceleration = undefined;
		_direction = 'down';
		_lastTop = -1;
		_lastViewportWidth = 0;
		_lastViewportHeight = 0;
		_requestReflow = false;
		_scrollAnimation = undefined;
		_smoothScrollingEnabled = undefined;
		_smoothScrollingDuration = undefined;
		_smoothScrolling = undefined;
		_forceRender = undefined;
		_skrollableIdCounter = 0;
		_edgeStrategy = undefined;
		_isMobile = false;
		_mobileOffset = 0;
		_translateZ = undefined;
	};

	/*
		Private methods.
	*/

	var _initMobile = function() {
		var initialElement;
		var initialTouchY;
		var initialTouchX;
		var currentElement;
		var currentTouchY;
		var currentTouchX;
		var lastTouchY;
		var deltaY;

		var initialTouchTime;
		var currentTouchTime;
		var lastTouchTime;
		var deltaTime;

		_addEvent(documentElement, [EVENT_TOUCHSTART, EVENT_TOUCHMOVE, EVENT_TOUCHCANCEL, EVENT_TOUCHEND].join(' '), function(e) {
			var touch = e.changedTouches[0];

			currentElement = e.target;

			//We don't want text nodes.
			while(currentElement.nodeType === 3) {
				currentElement = currentElement.parentNode;
			}

			currentTouchY = touch.clientY;
			currentTouchX = touch.clientX;
			currentTouchTime = e.timeStamp;

			if(!rxTouchIgnoreTags.test(currentElement.tagName)) {
				e.preventDefault();
			}

			switch(e.type) {
				case EVENT_TOUCHSTART:
					//The last element we tapped on.
					if(initialElement) {
						initialElement.blur();
					}

					_instance.stopAnimateTo();

					initialElement = currentElement;

					initialTouchY = lastTouchY = currentTouchY;
					initialTouchX = currentTouchX;
					initialTouchTime = currentTouchTime;

					break;
				case EVENT_TOUCHMOVE:
					//Prevent default event on touchIgnore elements in case they don't have focus yet.
					if(rxTouchIgnoreTags.test(currentElement.tagName) && document.activeElement !== currentElement) {
						e.preventDefault();
					}

					deltaY = currentTouchY - lastTouchY;
					deltaTime = currentTouchTime - lastTouchTime;

					_instance.setScrollTop(_mobileOffset - deltaY, true);

					lastTouchY = currentTouchY;
					lastTouchTime = currentTouchTime;
					break;
				default:
				case EVENT_TOUCHCANCEL:
				case EVENT_TOUCHEND:
					var distanceY = initialTouchY - currentTouchY;
					var distanceX = initialTouchX - currentTouchX;
					var distance2 = distanceX * distanceX + distanceY * distanceY;

					//Check if it was more like a tap (moved less than 7px).
					if(distance2 < 49) {
						if(!rxTouchIgnoreTags.test(initialElement.tagName)) {
							initialElement.focus();

							//It was a tap, click the element.
							var clickEvent = document.createEvent('MouseEvents');
							clickEvent.initMouseEvent('click', true, true, e.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null);
							initialElement.dispatchEvent(clickEvent);
						}

						return;
					}

					initialElement = undefined;

					var speed = deltaY / deltaTime;

					//Cap speed at 3 pixel/ms.
					speed = Math.max(Math.min(speed, 3), -3);

					var duration = Math.abs(speed / _mobileDeceleration);
					var targetOffset = speed * duration + 0.5 * _mobileDeceleration * duration * duration;
					var targetTop = _instance.getScrollTop() - targetOffset;

					//Relative duration change for when scrolling above bounds.
					var targetRatio = 0;

					//Change duration proportionally when scrolling would leave bounds.
					if(targetTop > _maxKeyFrame) {
						targetRatio = (_maxKeyFrame - targetTop) / targetOffset;

						targetTop = _maxKeyFrame;
					} else if(targetTop < 0) {
						targetRatio = -targetTop / targetOffset;

						targetTop = 0;
					}

					duration = duration * (1 - targetRatio);

					_instance.animateTo((targetTop + 0.5) | 0, {easing: 'outCubic', duration: duration});
					break;
			}
		});

		//Just in case there has already been some native scrolling, reset it.
		window.scrollTo(0, 0);
		documentElement.style.overflow = body.style.overflow = 'hidden';
	};

	/**
	 * Updates key frames which depend on others / need to be updated on resize.
	 * That is "end" in "absolute" mode and all key frames in "relative" mode.
	 * Also handles constants, because they may change on resize.
	 */
	var _updateDependentKeyFrames = function() {
		var viewportHeight = documentElement.clientHeight;
		var processedConstants = _processConstants();
		var skrollable;
		var element;
		var anchorTarget;
		var keyFrames;
		var keyFrameIndex;
		var keyFramesLength;
		var kf;
		var skrollableIndex;
		var skrollablesLength;
		var offset;
		var constantValue;

		//First process all relative-mode elements and find the max key frame.
		skrollableIndex = 0;
		skrollablesLength = _skrollables.length;

		for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
			skrollable = _skrollables[skrollableIndex];
			element = skrollable.element;
			anchorTarget = skrollable.anchorTarget;
			keyFrames = skrollable.keyFrames;

			keyFrameIndex = 0;
			keyFramesLength = keyFrames.length;

			for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
				kf = keyFrames[keyFrameIndex];

				offset = kf.offset;
				constantValue = processedConstants[kf.constant] || 0;

				kf.frame = offset;

				if(kf.isPercentage) {
					//Convert the offset to percentage of the viewport height.
					offset = offset * viewportHeight;

					//Absolute + percentage mode.
					kf.frame = offset;
				}

				if(kf.mode === 'relative') {
					_reset(element);

					kf.frame = _instance.relativeToAbsolute(anchorTarget, kf.anchors[0], kf.anchors[1]) - offset;

					_reset(element, true);
				}

				kf.frame += constantValue;

				//Only search for max key frame when forceHeight is enabled.
				if(_forceHeight) {
					//Find the max key frame, but don't use one of the data-end ones for comparison.
					if(!kf.isEnd && kf.frame > _maxKeyFrame) {
						_maxKeyFrame = kf.frame;
					}
				}
			}
		}

		//#133: The document can be larger than the maxKeyFrame we found.
		_maxKeyFrame = Math.max(_maxKeyFrame, _getDocumentHeight());

		//Now process all data-end keyframes.
		skrollableIndex = 0;
		skrollablesLength = _skrollables.length;

		for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
			skrollable = _skrollables[skrollableIndex];
			keyFrames = skrollable.keyFrames;

			keyFrameIndex = 0;
			keyFramesLength = keyFrames.length;

			for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
				kf = keyFrames[keyFrameIndex];

				constantValue = processedConstants[kf.constant] || 0;

				if(kf.isEnd) {
					kf.frame = _maxKeyFrame - kf.offset + constantValue;
				}
			}

			skrollable.keyFrames.sort(_keyFrameComparator);
		}
	};

	/**
	 * Calculates and sets the style properties for the element at the given frame.
	 * @param fakeFrame The frame to render at when smooth scrolling is enabled.
	 * @param actualFrame The actual frame we are at.
	 */
	var _calcSteps = function(fakeFrame, actualFrame) {
		//Iterate over all skrollables.
		var skrollableIndex = 0;
		var skrollablesLength = _skrollables.length;

		for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
			var skrollable = _skrollables[skrollableIndex];
			var element = skrollable.element;
			var frame = skrollable.smoothScrolling ? fakeFrame : actualFrame;
			var frames = skrollable.keyFrames;
			var framesLength = frames.length;
			var firstFrame = frames[0];
			var lastFrame = frames[frames.length - 1];
			var beforeFirst = frame < firstFrame.frame;
			var afterLast = frame > lastFrame.frame;
			var firstOrLastFrame = beforeFirst ? firstFrame : lastFrame;
			var emitEvents = skrollable.emitEvents;
			var lastFrameIndex = skrollable.lastFrameIndex;
			var key;
			var value;

			//If we are before/after the first/last frame, set the styles according to the given edge strategy.
			if(beforeFirst || afterLast) {
				//Check if we already handled this edge case last time.
				//Note: using setScrollTop it's possible that we jumped from one edge to the other.
				if(beforeFirst && skrollable.edge === -1 || afterLast && skrollable.edge === 1) {
					continue;
				}

				//Add the skrollr-before or -after class.
				if(beforeFirst) {
					_updateClass(element, [SKROLLABLE_BEFORE_CLASS], [SKROLLABLE_AFTER_CLASS, SKROLLABLE_BETWEEN_CLASS]);

					//This handles the special case where we exit the first keyframe.
					if(emitEvents && lastFrameIndex > -1) {
						_emitEvent(element, firstFrame.eventType, _direction);
						skrollable.lastFrameIndex = -1;
					}
				} else {
					_updateClass(element, [SKROLLABLE_AFTER_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_BETWEEN_CLASS]);

					//This handles the special case where we exit the last keyframe.
					if(emitEvents && lastFrameIndex < framesLength) {
						_emitEvent(element, lastFrame.eventType, _direction);
						skrollable.lastFrameIndex = framesLength;
					}
				}

				//Remember that we handled the edge case (before/after the first/last keyframe).
				skrollable.edge = beforeFirst ? -1 : 1;

				switch(skrollable.edgeStrategy) {
					case 'reset':
						_reset(element);
						continue;
					case 'ease':
						//Handle this case like it would be exactly at first/last keyframe and just pass it on.
						frame = firstOrLastFrame.frame;
						break;
					default:
					case 'set':
						var props = firstOrLastFrame.props;

						for(key in props) {
							if(hasProp.call(props, key)) {
								value = _interpolateString(props[key].value);

								//Set style or attribute.
								if(key.indexOf('@') === 0) {
									element.setAttribute(key.substr(1), value);
								} else {
									skrollr.setStyle(element, key, value);
								}
							}
						}

						continue;
				}
			} else {
				//Did we handle an edge last time?
				if(skrollable.edge !== 0) {
					_updateClass(element, [SKROLLABLE_CLASS, SKROLLABLE_BETWEEN_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_AFTER_CLASS]);
					skrollable.edge = 0;
				}
			}

			//Find out between which two key frames we are right now.
			var keyFrameIndex = 0;

			for(; keyFrameIndex < framesLength - 1; keyFrameIndex++) {
				if(frame >= frames[keyFrameIndex].frame && frame <= frames[keyFrameIndex + 1].frame) {
					var left = frames[keyFrameIndex];
					var right = frames[keyFrameIndex + 1];

					for(key in left.props) {
						if(hasProp.call(left.props, key)) {
							var progress = (frame - left.frame) / (right.frame - left.frame);

							//Transform the current progress using the given easing function.
							progress = left.props[key].easing(progress);

							//Interpolate between the two values
							value = _calcInterpolation(left.props[key].value, right.props[key].value, progress);

							value = _interpolateString(value);

							//Set style or attribute.
							if(key.indexOf('@') === 0) {
								element.setAttribute(key.substr(1), value);
							} else {
								skrollr.setStyle(element, key, value);
							}
						}
					}

					//Are events enabled on this element?
					//This code handles the usual cases of scrolling through different keyframes.
					//The special cases of before first and after last keyframe are handled above.
					if(emitEvents) {
						//Did we pass a new keyframe?
						if(lastFrameIndex !== keyFrameIndex) {
							if(_direction === 'down') {
								_emitEvent(element, left.eventType, _direction);
							} else {
								_emitEvent(element, right.eventType, _direction);
							}

							skrollable.lastFrameIndex = keyFrameIndex;
						}
					}

					break;
				}
			}
		}
	};

	/**
	 * Renders all elements.
	 */
	var _render = function() {
		if(_requestReflow) {
			_requestReflow = false;
			_reflow();
		}

		//We may render something else than the actual scrollbar position.
		var renderTop = _instance.getScrollTop();

		//If there's an animation, which ends in current render call, call the callback after rendering.
		var afterAnimationCallback;
		var now = _now();
		var progress;

		//Before actually rendering handle the scroll animation, if any.
		if(_scrollAnimation) {
			//It's over
			if(now >= _scrollAnimation.endTime) {
				renderTop = _scrollAnimation.targetTop;
				afterAnimationCallback = _scrollAnimation.done;
				_scrollAnimation = undefined;
			} else {
				//Map the current progress to the new progress using given easing function.
				progress = _scrollAnimation.easing((now - _scrollAnimation.startTime) / _scrollAnimation.duration);

				renderTop = (_scrollAnimation.startTop + progress * _scrollAnimation.topDiff) | 0;
			}

			_instance.setScrollTop(renderTop, true);
		}
		//Smooth scrolling only if there's no animation running and if we're not forcing the rendering.
		else if(!_forceRender) {
			var smoothScrollingDiff = _smoothScrolling.targetTop - renderTop;

			//The user scrolled, start new smooth scrolling.
			if(smoothScrollingDiff) {
				_smoothScrolling = {
					startTop: _lastTop,
					topDiff: renderTop - _lastTop,
					targetTop: renderTop,
					startTime: _lastRenderCall,
					endTime: _lastRenderCall + _smoothScrollingDuration
				};
			}

			//Interpolate the internal scroll position (not the actual scrollbar).
			if(now <= _smoothScrolling.endTime) {
				//Map the current progress to the new progress using easing function.
				progress = easings.sqrt((now - _smoothScrolling.startTime) / _smoothScrollingDuration);

				renderTop = (_smoothScrolling.startTop + progress * _smoothScrolling.topDiff) | 0;
			}
		}

		//That's were we actually "scroll" on mobile.
		if(_isMobile && _skrollrBody) {
			//Set the transform ("scroll it").
			skrollr.setStyle(_skrollrBody, 'transform', 'translate(0, ' + -(_mobileOffset) + 'px) ' + _translateZ);
		}

		//Did the scroll position even change?
		if(_forceRender || _lastTop !== renderTop) {
			//Remember in which direction are we scrolling?
			_direction = (renderTop > _lastTop) ? 'down' : (renderTop < _lastTop ? 'up' : _direction);

			_forceRender = false;

			var listenerParams = {
				curTop: renderTop,
				lastTop: _lastTop,
				maxTop: _maxKeyFrame,
				direction: _direction
			};

			//Tell the listener we are about to render.
			var continueRendering = _listeners.beforerender && _listeners.beforerender.call(_instance, listenerParams);

			//The beforerender listener function is able the cancel rendering.
			if(continueRendering !== false) {
				//Now actually interpolate all the styles.
				_calcSteps(renderTop, _instance.getScrollTop());

				//Remember when we last rendered.
				_lastTop = renderTop;

				if(_listeners.render) {
					_listeners.render.call(_instance, listenerParams);
				}
			}

			if(afterAnimationCallback) {
				afterAnimationCallback.call(_instance, false);
			}
		}

		_lastRenderCall = now;
	};

	/**
	 * Parses the properties for each key frame of the given skrollable.
	 */
	var _parseProps = function(skrollable) {
		//Iterate over all key frames
		var keyFrameIndex = 0;
		var keyFramesLength = skrollable.keyFrames.length;

		for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
			var frame = skrollable.keyFrames[keyFrameIndex];
			var easing;
			var value;
			var prop;
			var props = {};

			var match;

			while((match = rxPropValue.exec(frame.props)) !== null) {
				prop = match[1];
				value = match[2];

				easing = prop.match(rxPropEasing);

				//Is there an easing specified for this prop?
				if(easing !== null) {
					prop = easing[1];
					easing = easing[2];
				} else {
					easing = DEFAULT_EASING;
				}

				//Exclamation point at first position forces the value to be taken literal.
				value = value.indexOf('!') ? _parseProp(value) : [value.slice(1)];

				//Save the prop for this key frame with his value and easing function
				props[prop] = {
					value: value,
					easing: easings[easing]
				};
			}

			frame.props = props;
		}
	};

	/**
	 * Parses a value extracting numeric values and generating a format string
	 * for later interpolation of the new values in old string.
	 *
	 * @param val The CSS value to be parsed.
	 * @return Something like ["rgba(?%,?%, ?%,?)", 100, 50, 0, .7]
	 * where the first element is the format string later used
	 * and all following elements are the numeric value.
	 */
	var _parseProp = function(val) {
		var numbers = [];

		//One special case, where floats don't work.
		//We replace all occurences of rgba colors
		//which don't use percentage notation with the percentage notation.
		rxRGBAIntegerColor.lastIndex = 0;
		val = val.replace(rxRGBAIntegerColor, function(rgba) {
			return rgba.replace(rxNumericValue, function(n) {
				return n / 255 * 100 + '%';
			});
		});

		//Handle prefixing of "gradient" values.
		//For now only the prefixed value will be set. Unprefixed isn't supported anyway.
		if(theDashedCSSPrefix) {
			rxGradient.lastIndex = 0;
			val = val.replace(rxGradient, function(s) {
				return theDashedCSSPrefix + s;
			});
		}

		//Now parse ANY number inside this string and create a format string.
		val = val.replace(rxNumericValue, function(n) {
			numbers.push(+n);
			return '{?}';
		});

		//Add the formatstring as first value.
		numbers.unshift(val);

		return numbers;
	};

	/**
	 * Fills the key frames with missing left and right hand properties.
	 * If key frame 1 has property X and key frame 2 is missing X,
	 * but key frame 3 has X again, then we need to assign X to key frame 2 too.
	 *
	 * @param sk A skrollable.
	 */
	var _fillProps = function(sk) {
		//Will collect the properties key frame by key frame
		var propList = {};
		var keyFrameIndex;
		var keyFramesLength;

		//Iterate over all key frames from left to right
		keyFrameIndex = 0;
		keyFramesLength = sk.keyFrames.length;

		for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
			_fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
		}

		//Now do the same from right to fill the last gaps

		propList = {};

		//Iterate over all key frames from right to left
		keyFrameIndex = sk.keyFrames.length - 1;

		for(; keyFrameIndex >= 0; keyFrameIndex--) {
			_fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
		}
	};

	var _fillPropForFrame = function(frame, propList) {
		var key;

		//For each key frame iterate over all right hand properties and assign them,
		//but only if the current key frame doesn't have the property by itself
		for(key in propList) {
			//The current frame misses this property, so assign it.
			if(!hasProp.call(frame.props, key)) {
				frame.props[key] = propList[key];
			}
		}

		//Iterate over all props of the current frame and collect them
		for(key in frame.props) {
			propList[key] = frame.props[key];
		}
	};

	/**
	 * Calculates the new values for two given values array.
	 */
	var _calcInterpolation = function(val1, val2, progress) {
		var valueIndex;
		var val1Length = val1.length;

		//They both need to have the same length
		if(val1Length !== val2.length) {
			throw 'Can\'t interpolate between "' + val1[0] + '" and "' + val2[0] + '"';
		}

		//Add the format string as first element.
		var interpolated = [val1[0]];

		valueIndex = 1;

		for(; valueIndex < val1Length; valueIndex++) {
			//That's the line where the two numbers are actually interpolated.
			interpolated[valueIndex] = val1[valueIndex] + ((val2[valueIndex] - val1[valueIndex]) * progress);
		}

		return interpolated;
	};

	/**
	 * Interpolates the numeric values into the format string.
	 */
	var _interpolateString = function(val) {
		var valueIndex = 1;

		rxInterpolateString.lastIndex = 0;

		return val[0].replace(rxInterpolateString, function() {
			return val[valueIndex++];
		});
	};

	/**
	 * Resets the class and style attribute to what it was before skrollr manipulated the element.
	 * Also remembers the values it had before reseting, in order to undo the reset.
	 */
	var _reset = function(elements, undo) {
		//We accept a single element or an array of elements.
		elements = [].concat(elements);

		var skrollable;
		var element;
		var elementsIndex = 0;
		var elementsLength = elements.length;

		for(; elementsIndex < elementsLength; elementsIndex++) {
			element = elements[elementsIndex];
			skrollable = _skrollables[element[SKROLLABLE_ID_DOM_PROPERTY]];

			//Couldn't find the skrollable for this DOM element.
			if(!skrollable) {
				continue;
			}

			if(undo) {
				//Reset class and style to the "dirty" (set by skrollr) values.
				element.style.cssText = skrollable.dirtyStyleAttr;
				_updateClass(element, skrollable.dirtyClassAttr);
			} else {
				//Remember the "dirty" (set by skrollr) class and style.
				skrollable.dirtyStyleAttr = element.style.cssText;
				skrollable.dirtyClassAttr = _getClass(element);

				//Reset class and style to what it originally was.
				element.style.cssText = skrollable.styleAttr;
				_updateClass(element, skrollable.classAttr);
			}
		}
	};

	/**
	 * Detects support for 3d transforms by applying it to the skrollr-body.
	 */
	var _detect3DTransforms = function() {
		_translateZ = 'translateZ(0)';
		skrollr.setStyle(_skrollrBody, 'transform', _translateZ);

		var computedStyle = getStyle(_skrollrBody);
		var computedTransform = computedStyle.getPropertyValue('transform');
		var computedTransformWithPrefix = computedStyle.getPropertyValue(theDashedCSSPrefix + 'transform');
		var has3D = (computedTransform && computedTransform !== 'none') || (computedTransformWithPrefix && computedTransformWithPrefix !== 'none');

		if(!has3D) {
			_translateZ = '';
		}
	};

	/**
	 * Set the CSS property on the given element. Sets prefixed properties as well.
	 */
	skrollr.setStyle = function(el, prop, val) {
		var style = el.style;

		//Camel case.
		prop = prop.replace(rxCamelCase, rxCamelCaseFn).replace('-', '');

		//Make sure z-index gets a <integer>.
		//This is the only <integer> case we need to handle.
		if(prop === 'zIndex') {
			if(isNaN(val)) {
				//If it's not a number, don't touch it.
				//It could for example be "auto" (#351).
				style[prop] = val;
			} else {
				//Floor the number.
				style[prop] = '' + (val | 0);
			}
		}
		//#64: "float" can't be set across browsers. Needs to use "cssFloat" for all except IE.
		else if(prop === 'float') {
			style.styleFloat = style.cssFloat = val;
		}
		else {
			//Need try-catch for old IE.
			try {
				//Set prefixed property if there's a prefix.
				if(theCSSPrefix) {
					style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val;
				}

				//Set unprefixed.
				style[prop] = val;
			} catch(ignore) {}
		}
	};

	/**
	 * Cross browser event handling.
	 */
	var _addEvent = skrollr.addEvent = function(element, names, callback) {
		var intermediate = function(e) {
			//Normalize IE event stuff.
			e = e || window.event;

			if(!e.target) {
				e.target = e.srcElement;
			}

			if(!e.preventDefault) {
				e.preventDefault = function() {
					e.returnValue = false;
					e.defaultPrevented = true;
				};
			}

			return callback.call(this, e);
		};

		names = names.split(' ');

		var name;
		var nameCounter = 0;
		var namesLength = names.length;

		for(; nameCounter < namesLength; nameCounter++) {
			name = names[nameCounter];

			if(element.addEventListener) {
				element.addEventListener(name, callback, false);
			} else {
				element.attachEvent('on' + name, intermediate);
			}

			//Remember the events to be able to flush them later.
			_registeredEvents.push({
				element: element,
				name: name,
				listener: callback
			});
		}
	};

	var _removeEvent = skrollr.removeEvent = function(element, names, callback) {
		names = names.split(' ');

		var nameCounter = 0;
		var namesLength = names.length;

		for(; nameCounter < namesLength; nameCounter++) {
			if(element.removeEventListener) {
				element.removeEventListener(names[nameCounter], callback, false);
			} else {
				element.detachEvent('on' + names[nameCounter], callback);
			}
		}
	};

	var _removeAllEvents = function() {
		var eventData;
		var eventCounter = 0;
		var eventsLength = _registeredEvents.length;

		for(; eventCounter < eventsLength; eventCounter++) {
			eventData = _registeredEvents[eventCounter];

			_removeEvent(eventData.element, eventData.name, eventData.listener);
		}

		_registeredEvents = [];
	};

	var _emitEvent = function(element, name, direction) {
		if(_listeners.keyframe) {
			_listeners.keyframe.call(_instance, element, name, direction);
		}
	};

	var _reflow = function() {
		var pos = _instance.getScrollTop();

		//Will be recalculated by _updateDependentKeyFrames.
		_maxKeyFrame = 0;

		if(_forceHeight && !_isMobile) {
			//un-"force" the height to not mess with the calculations in _updateDependentKeyFrames (#216).
			body.style.height = '';
		}

		_updateDependentKeyFrames();

		if(_forceHeight && !_isMobile) {
			//"force" the height.
			body.style.height = (_maxKeyFrame + documentElement.clientHeight) + 'px';
		}

		//The scroll offset may now be larger than needed (on desktop the browser/os prevents scrolling farther than the bottom).
		if(_isMobile) {
			_instance.setScrollTop(Math.min(_instance.getScrollTop(), _maxKeyFrame));
		} else {
			//Remember and reset the scroll pos (#217).
			_instance.setScrollTop(pos, true);
		}

		_forceRender = true;
	};

	/*
	 * Returns a copy of the constants object where all functions and strings have been evaluated.
	 */
	var _processConstants = function() {
		var viewportHeight = documentElement.clientHeight;
		var copy = {};
		var prop;
		var value;

		for(prop in _constants) {
			value = _constants[prop];

			if(typeof value === 'function') {
				value = value.call(_instance);
			}
			//Percentage offset.
			else if((/p$/).test(value)) {
				value = (value.slice(0, -1) / 100) * viewportHeight;
			}

			copy[prop] = value;
		}

		return copy;
	};

	/*
	 * Returns the height of the document.
	 */
	var _getDocumentHeight = function() {
		var skrollrBodyHeight = 0;
		var bodyHeight;

		if(_skrollrBody) {
			skrollrBodyHeight = Math.max(_skrollrBody.offsetHeight, _skrollrBody.scrollHeight);
		}

		bodyHeight = Math.max(skrollrBodyHeight, body.scrollHeight, body.offsetHeight, documentElement.scrollHeight, documentElement.offsetHeight, documentElement.clientHeight);

		return bodyHeight - documentElement.clientHeight;
	};

	/**
	 * Returns a string of space separated classnames for the current element.
	 * Works with SVG as well.
	 */
	var _getClass = function(element) {
		var prop = 'className';

		//SVG support by using className.baseVal instead of just className.
		if(window.SVGElement && element instanceof window.SVGElement) {
			element = element[prop];
			prop = 'baseVal';
		}

		return element[prop];
	};

	/**
	 * Adds and removes a CSS classes.
	 * Works with SVG as well.
	 * add and remove are arrays of strings,
	 * or if remove is ommited add is a string and overwrites all classes.
	 */
	var _updateClass = function(element, add, remove) {
		var prop = 'className';

		//SVG support by using className.baseVal instead of just className.
		if(window.SVGElement && element instanceof window.SVGElement) {
			element = element[prop];
			prop = 'baseVal';
		}

		//When remove is ommited, we want to overwrite/set the classes.
		if(remove === undefined) {
			element[prop] = add;
			return;
		}

		//Cache current classes. We will work on a string before passing back to DOM.
		var val = element[prop];

		//All classes to be removed.
		var classRemoveIndex = 0;
		var removeLength = remove.length;

		for(; classRemoveIndex < removeLength; classRemoveIndex++) {
			val = _untrim(val).replace(_untrim(remove[classRemoveIndex]), ' ');
		}

		val = _trim(val);

		//All classes to be added.
		var classAddIndex = 0;
		var addLength = add.length;

		for(; classAddIndex < addLength; classAddIndex++) {
			//Only add if el not already has class.
			if(_untrim(val).indexOf(_untrim(add[classAddIndex])) === -1) {
				val += ' ' + add[classAddIndex];
			}
		}

		element[prop] = _trim(val);
	};

	var _trim = function(a) {
		return a.replace(rxTrim, '');
	};

	/**
	 * Adds a space before and after the string.
	 */
	var _untrim = function(a) {
		return ' ' + a + ' ';
	};

	var _now = Date.now || function() {
		return +new Date();
	};

	var _keyFrameComparator = function(a, b) {
		return a.frame - b.frame;
	};

	/*
	 * Private variables.
	 */

	//Singleton
	var _instance;

	/*
		A list of all elements which should be animated associated with their the metadata.
		Exmaple skrollable with two key frames animating from 100px width to 20px:

		skrollable = {
			element: <the DOM element>,
			styleAttr: <style attribute of the element before skrollr>,
			classAttr: <class attribute of the element before skrollr>,
			keyFrames: [
				{
					frame: 100,
					props: {
						width: {
							value: ['{?}px', 100],
							easing: <reference to easing function>
						}
					},
					mode: "absolute"
				},
				{
					frame: 200,
					props: {
						width: {
							value: ['{?}px', 20],
							easing: <reference to easing function>
						}
					},
					mode: "absolute"
				}
			]
		};
	*/
	var _skrollables;

	var _skrollrBody;

	var _listeners;
	var _forceHeight;
	var _maxKeyFrame = 0;

	var _scale = 1;
	var _constants;

	var _mobileDeceleration;

	//Current direction (up/down).
	var _direction = 'down';

	//The last top offset value. Needed to determine direction.
	var _lastTop = -1;

	//The last time we called the render method (doesn't mean we rendered!).
	var _lastRenderCall = _now();

	//For detecting if it actually resized (#271).
	var _lastViewportWidth = 0;
	var _lastViewportHeight = 0;

	var _requestReflow = false;

	//Will contain data about a running scrollbar animation, if any.
	var _scrollAnimation;

	var _smoothScrollingEnabled;

	var _smoothScrollingDuration;

	//Will contain settins for smooth scrolling if enabled.
	var _smoothScrolling;

	//Can be set by any operation/event to force rendering even if the scrollbar didn't move.
	var _forceRender;

	//Each skrollable gets an unique ID incremented for each skrollable.
	//The ID is the index in the _skrollables array.
	var _skrollableIdCounter = 0;

	var _edgeStrategy;


	//Mobile specific vars. Will be stripped by UglifyJS when not in use.
	var _isMobile = false;

	//The virtual scroll offset when using mobile scrolling.
	var _mobileOffset = 0;

	//If the browser supports 3d transforms, this will be filled with 'translateZ(0)' (empty string otherwise).
	var _translateZ;

	//Will contain data about registered events by skrollr.
	var _registeredEvents = [];

	//Animation frame id returned by RequestAnimationFrame (or timeout when RAF is not supported).
	var _animFrame;

	//Expose skrollr as either a global variable or a require.js module.
	if(typeof define === 'function' && define.amd) {
		define([], function () {
			return skrollr;
		});
	} else if (typeof module !== 'undefined' && module.exports) {
		module.exports = skrollr;
	} else {
		window.skrollr = skrollr;
	}

}(window, document));
;
/* =============================================================
 * bootstrap3-typeahead.js v3.1.0
 * https://github.com/bassjobsen/Bootstrap-3-Typeahead
 * =============================================================
 * Original written by @mdo and @fat
 * =============================================================
 * Copyright 2014 Bass Jobsen @bassjobsen
 *
 * 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.
 * ============================================================ */


(function (root, factory) {

  'use strict';

  // CommonJS module is defined
  if (typeof module !== 'undefined' && module.exports) {
    module.exports = factory(require('jquery'));
  }
  // AMD module is defined
  else if (typeof define === 'function' && define.amd) {
    define(['jquery'], function ($) {
      return factory ($);
    });
  } else {
    factory(root.jQuery);
  }

}(this, function ($) {

  'use strict';
  // jshint laxcomma: true


 /* TYPEAHEAD PUBLIC CLASS DEFINITION
  * ================================= */

  var Typeahead = function (element, options) {
    this.$element = $(element);
    this.options = $.extend({}, $.fn.typeahead.defaults, options);
    this.matcher = this.options.matcher || this.matcher;
    this.sorter = this.options.sorter || this.sorter;
    this.select = this.options.select || this.select;
    this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true;
    this.highlighter = this.options.highlighter || this.highlighter;
    this.render = this.options.render || this.render;
    this.updater = this.options.updater || this.updater;
    this.displayText = this.options.displayText || this.displayText;
    this.source = this.options.source;
    this.delay = this.options.delay;
    this.$menu = $(this.options.menu);
    this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null;   
    this.shown = false;
    this.listen();
    this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' ? this.options.showHintOnFocus : false;
    this.afterSelect = this.options.afterSelect;
    this.addItem = false;
  };

  Typeahead.prototype = {

    constructor: Typeahead,

    select: function () {
      var val = this.$menu.find('.active').data('value');
      this.$element.data('active', val);
      if(this.autoSelect || val) {
        var newVal = this.updater(val);
        this.$element
          .val(this.displayText(newVal) || newVal)
          .change();
        this.afterSelect(newVal);
      }
      return this.hide();
    },

    updater: function (item) {
      return item;
    },

    setSource: function (source) {
      this.source = source;
    },

    show: function () {
      var pos = $.extend({}, this.$element.position(), {
        height: this.$element[0].offsetHeight
      }), scrollHeight;

      scrollHeight = typeof this.options.scrollHeight == 'function' ?
          this.options.scrollHeight.call() :
          this.options.scrollHeight;

      (this.$appendTo ? this.$menu.appendTo(this.$appendTo) : this.$menu.insertAfter(this.$element))
        .css({
          top: pos.top + pos.height + scrollHeight
        , left: pos.left
        })
        .show();

      this.shown = true;
      return this;
    },

    hide: function () {
      this.$menu.hide();
      this.shown = false;
      return this;
    },

    lookup: function (query) {
      var items;
      if (typeof(query) != 'undefined' && query !== null) {
        this.query = query;
      } else {
        this.query = this.$element.val() ||  '';
      }

      if (this.query.length < this.options.minLength) {
        return this.shown ? this.hide() : this;
      }

      var worker = $.proxy(function() {
        
        if($.isFunction(this.source)) this.source(this.query, $.proxy(this.process, this));
        else if (this.source) {
          this.process(this.source);
        }
      }, this);

      clearTimeout(this.lookupWorker);
      this.lookupWorker = setTimeout(worker, this.delay);
    },

    process: function (items) {
      var that = this;

      items = $.grep(items, function (item) {
        return that.matcher(item);
      });

      items = this.sorter(items);

      if (!items.length && !this.options.addItem) {
        return this.shown ? this.hide() : this;
      }
      
      if (items.length > 0) {
        this.$element.data('active', items[0]);
      } else {
        this.$element.data('active', null);
      }
      
      // Add item
      if (this.options.addItem){
        items.push(this.options.addItem);
      }

      if (this.options.items == 'all') {
        return this.render(items).show();
      } else {
        return this.render(items.slice(0, this.options.items)).show();
      }
    },

    matcher: function (item) {
    var it = this.displayText(item);
      return ~it.toLowerCase().indexOf(this.query.toLowerCase());
    },

    sorter: function (items) {
      var beginswith = []
        , caseSensitive = []
        , caseInsensitive = []
        , item;

      while ((item = items.shift())) {
        var it = this.displayText(item);
        if (!it.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item);
        else if (~it.indexOf(this.query)) caseSensitive.push(item);
        else caseInsensitive.push(item);
      }

      return beginswith.concat(caseSensitive, caseInsensitive);
    },

    highlighter: function (item) {
          var html = $('<div></div>');
          var query = this.query;
          var i = item.toLowerCase().indexOf(query.toLowerCase());
          var len, leftPart, middlePart, rightPart, strong;
          len = query.length;
          if(len === 0){
              return html.text(item).html();
          }
          while (i > -1) {
              leftPart = item.substr(0, i);
              middlePart = item.substr(i, len);
              rightPart = item.substr(i + len);
              strong = $('<strong></strong>').text(middlePart);
              html
                  .append(document.createTextNode(leftPart))
                  .append(strong);
              item = rightPart;
              i = item.toLowerCase().indexOf(query.toLowerCase());
          }
          return html.append(document.createTextNode(item)).html();
    },

    render: function (items) {
      var that = this;
      var self = this;
      var activeFound = false;
      items = $(items).map(function (i, item) {
        var text = self.displayText(item);
        i = $(that.options.item).data('value', item);
        i.find('a').html(that.highlighter(text));
        if (text == self.$element.val()) {
            i.addClass('active');
            self.$element.data('active', item);
            activeFound = true;
        }
        return i[0];
      });

      if (this.autoSelect && !activeFound) {        
        items.first().addClass('active');
        this.$element.data('active', items.first().data('value'));
      }
      this.$menu.html(items);
      return this;
    },

    displayText: function(item) {
      return item.name || item;
    },

    next: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , next = active.next();

      if (!next.length) {
        next = $(this.$menu.find('li')[0]);
      }

      next.addClass('active');
    },

    prev: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , prev = active.prev();

      if (!prev.length) {
        prev = this.$menu.find('li').last();
      }

      prev.addClass('active');
    },

    listen: function () {
      this.$element
        .on('focus',    $.proxy(this.focus, this))
        .on('blur',     $.proxy(this.blur, this))
        .on('keypress', $.proxy(this.keypress, this))
        .on('keyup',    $.proxy(this.keyup, this));

      if (this.eventSupported('keydown')) {
        this.$element.on('keydown', $.proxy(this.keydown, this));
      }

      this.$menu
        .on('click', $.proxy(this.click, this))
        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
        .on('mouseleave', 'li', $.proxy(this.mouseleave, this));
    },
    
    destroy : function () {
      this.$element.data('typeahead',null);
      this.$element.data('active',null);
      this.$element
        .off('focus')
        .off('blur')
        .off('keypress')
        .off('keyup');

      if (this.eventSupported('keydown')) {
        this.$element.off('keydown');
      }

      this.$menu.remove();
    },
    
    eventSupported: function(eventName) {
      var isSupported = eventName in this.$element;
      if (!isSupported) {
        this.$element.setAttribute(eventName, 'return;');
        isSupported = typeof this.$element[eventName] === 'function';
      }
      return isSupported;
    },

    move: function (e) {
      if (!this.shown) return;

      switch(e.keyCode) {
        case 9: // tab
        case 13: // enter
        case 27: // escape
          e.preventDefault();
          break;

        case 38: // up arrow
          // with the shiftKey (this is actually the left parenthesis)
          if (e.shiftKey) return;
          e.preventDefault();
          this.prev();
          break;

        case 40: // down arrow
          // with the shiftKey (this is actually the right parenthesis)
          if (e.shiftKey) return;
          e.preventDefault();
          this.next();
          break;
      }

      e.stopPropagation();
    },

    keydown: function (e) {
      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]);
      if (!this.shown && e.keyCode == 40) {
        this.lookup();
      } else {
        this.move(e);
      }
    },

    keypress: function (e) {
      if (this.suppressKeyPressRepeat) return;
      this.move(e);
    },

    keyup: function (e) {
      switch(e.keyCode) {
        case 40: // down arrow
        case 38: // up arrow
        case 16: // shift
        case 17: // ctrl
        case 18: // alt
          break;

        case 9: // tab
        case 13: // enter
          if (!this.shown) return;
          this.select();
          break;

        case 27: // escape
          if (!this.shown) return;
          this.hide();
          break;
        default:
          this.lookup();
      }

      e.stopPropagation();
      e.preventDefault();
   },

   focus: function (e) {
      if (!this.focused) {
        this.focused = true;
        if (this.options.showHintOnFocus) {
          this.lookup('');
        }
      }
    },

    blur: function (e) {
      this.focused = false;
      if (!this.mousedover && this.shown) this.hide();
    },

    click: function (e) {
      e.stopPropagation();
      e.preventDefault();
      this.select();
      this.$element.focus();
    },

    mouseenter: function (e) {
      this.mousedover = true;
      this.$menu.find('.active').removeClass('active');
      $(e.currentTarget).addClass('active');
    },

    mouseleave: function (e) {
      this.mousedover = false;
      if (!this.focused && this.shown) this.hide();
    }

  };


  /* TYPEAHEAD PLUGIN DEFINITION
   * =========================== */

  var old = $.fn.typeahead;

  $.fn.typeahead = function (option) {
	var arg = arguments;
     if (typeof option == 'string' && option == 'getActive') {
        return this.data('active');
     }
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('typeahead')
        , options = typeof option == 'object' && option;
      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)));
      if (typeof option == 'string') {
        if (arg.length > 1) {
          data[option].apply(data, Array.prototype.slice.call(arg ,1));
        } else {
          data[option]();
        }
      }
    });
  };

  $.fn.typeahead.defaults = {
    source: []
  , items: 8
  , menu: '<ul class="typeahead dropdown-menu" role="listbox"></ul>'
  , item: '<li><a href="#" role="option"></a></li>'
  , minLength: 1
  , scrollHeight: 0
  , autoSelect: true
  , afterSelect: $.noop
  , addItem: false
  , delay: 0
  };

  $.fn.typeahead.Constructor = Typeahead;


 /* TYPEAHEAD NO CONFLICT
  * =================== */

  $.fn.typeahead.noConflict = function () {
    $.fn.typeahead = old;
    return this;
  };


 /* TYPEAHEAD DATA-API
  * ================== */

  $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
    var $this = $(this);
    if ($this.data('typeahead')) return;
    $this.typeahead($this.data());
  });

}));
;
//     Underscore.js 1.8.3
//     http://underscorejs.org
//     (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Underscore may be freely distributed under the MIT license.

(function () {

	// Baseline setup
	// --------------

	// Establish the root object, `window` in the browser, or `exports` on the server.
	var root = this;

	// Save the previous value of the `_` variable.
	var previousUnderscore = root._;

	// Save bytes in the minified (but not gzipped) version:
	var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

	// Create quick reference variables for speed access to core prototypes.
	var
	  push = ArrayProto.push,
	  slice = ArrayProto.slice,
	  toString = ObjProto.toString,
	  hasOwnProperty = ObjProto.hasOwnProperty;

	// All **ECMAScript 5** native function implementations that we hope to use
	// are declared here.
	var
	  nativeIsArray = Array.isArray,
	  nativeKeys = Object.keys,
	  nativeBind = FuncProto.bind,
	  nativeCreate = Object.create;

	// Naked function reference for surrogate-prototype-swapping.
	var Ctor = function () { };

	// Create a safe reference to the Underscore object for use below.
	var _ = function (obj) {
		if (obj instanceof _) return obj;
		if (!(this instanceof _)) return new _(obj);
		this._wrapped = obj;
	};

	// Export the Underscore object for **Node.js**, with
	// backwards-compatibility for the old `require()` API. If we're in
	// the browser, add `_` as a global object.
	if (typeof exports !== 'undefined') {
		if (typeof module !== 'undefined' && module.exports) {
			exports = module.exports = _;
		}
		exports._ = _;
	} else {
		root._ = _;
	}

	// Current version.
	_.VERSION = '1.8.3';

	// Internal function that returns an efficient (for current engines) version
	// of the passed-in callback, to be repeatedly applied in other Underscore
	// functions.
	var optimizeCb = function (func, context, argCount) {
		if (context === void 0) return func;
		switch (argCount == null ? 3 : argCount) {
			case 1: return function (value) {
				return func.call(context, value);
			};
			case 2: return function (value, other) {
				return func.call(context, value, other);
			};
			case 3: return function (value, index, collection) {
				return func.call(context, value, index, collection);
			};
			case 4: return function (accumulator, value, index, collection) {
				return func.call(context, accumulator, value, index, collection);
			};
		}
		return function () {
			return func.apply(context, arguments);
		};
	};

	// A mostly-internal function to generate callbacks that can be applied
	// to each element in a collection, returning the desired result — either
	// identity, an arbitrary callback, a property matcher, or a property accessor.
	var cb = function (value, context, argCount) {
		if (value == null) return _.identity;
		if (_.isFunction(value)) return optimizeCb(value, context, argCount);
		if (_.isObject(value)) return _.matcher(value);
		return _.property(value);
	};
	_.iteratee = function (value, context) {
		return cb(value, context, Infinity);
	};

	// An internal function for creating assigner functions.
	var createAssigner = function (keysFunc, undefinedOnly) {
		return function (obj) {
			var length = arguments.length;
			if (length < 2 || obj == null) return obj;
			for (var index = 1; index < length; index++) {
				var source = arguments[index],
					keys = keysFunc(source),
					l = keys.length;
				for (var i = 0; i < l; i++) {
					var key = keys[i];
					if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
				}
			}
			return obj;
		};
	};

	// An internal function for creating a new object that inherits from another.
	var baseCreate = function (prototype) {
		if (!_.isObject(prototype)) return {};
		if (nativeCreate) return nativeCreate(prototype);
		Ctor.prototype = prototype;
		var result = new Ctor;
		Ctor.prototype = null;
		return result;
	};

	var property = function (key) {
		return function (obj) {
			return obj == null ? void 0 : obj[key];
		};
	};

	// Helper for collection methods to determine whether a collection
	// should be iterated as an array or as an object
	// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
	// Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
	var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
	var getLength = property('length');
	var isArrayLike = function (collection) {
		var length = getLength(collection);
		return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
	};

	// Collection Functions
	// --------------------

	// The cornerstone, an `each` implementation, aka `forEach`.
	// Handles raw objects in addition to array-likes. Treats all
	// sparse array-likes as if they were dense.
	_.each = _.forEach = function (obj, iteratee, context) {
		iteratee = optimizeCb(iteratee, context);
		var i, length;
		if (isArrayLike(obj)) {
			for (i = 0, length = obj.length; i < length; i++) {
				iteratee(obj[i], i, obj);
			}
		} else {
			var keys = _.keys(obj);
			for (i = 0, length = keys.length; i < length; i++) {
				iteratee(obj[keys[i]], keys[i], obj);
			}
		}
		return obj;
	};

	// Return the results of applying the iteratee to each element.
	_.map = _.collect = function (obj, iteratee, context) {
		iteratee = cb(iteratee, context);
		var keys = !isArrayLike(obj) && _.keys(obj),
			length = (keys || obj).length,
			results = Array(length);
		for (var index = 0; index < length; index++) {
			var currentKey = keys ? keys[index] : index;
			results[index] = iteratee(obj[currentKey], currentKey, obj);
		}
		return results;
	};

	// Create a reducing function iterating left or right.
	function createReduce(dir) {
		// Optimized iterator function as using arguments.length
		// in the main function will deoptimize the, see #1991.
		function iterator(obj, iteratee, memo, keys, index, length) {
			for (; index >= 0 && index < length; index += dir) {
				var currentKey = keys ? keys[index] : index;
				memo = iteratee(memo, obj[currentKey], currentKey, obj);
			}
			return memo;
		}

		return function (obj, iteratee, memo, context) {
			iteratee = optimizeCb(iteratee, context, 4);
			var keys = !isArrayLike(obj) && _.keys(obj),
				length = (keys || obj).length,
				index = dir > 0 ? 0 : length - 1;
			// Determine the initial value if none is provided.
			if (arguments.length < 3) {
				memo = obj[keys ? keys[index] : index];
				index += dir;
			}
			return iterator(obj, iteratee, memo, keys, index, length);
		};
	}

	// **Reduce** builds up a single result from a list of values, aka `inject`,
	// or `foldl`.
	_.reduce = _.foldl = _.inject = createReduce(1);

	// The right-associative version of reduce, also known as `foldr`.
	_.reduceRight = _.foldr = createReduce(-1);

	// Return the first value which passes a truth test. Aliased as `detect`.
	_.find = _.detect = function (obj, predicate, context) {
		var key;
		if (isArrayLike(obj)) {
			key = _.findIndex(obj, predicate, context);
		} else {
			key = _.findKey(obj, predicate, context);
		}
		if (key !== void 0 && key !== -1) return obj[key];
	};

	// Return all the elements that pass a truth test.
	// Aliased as `select`.
	_.filter = _.select = function (obj, predicate, context) {
		var results = [];
		predicate = cb(predicate, context);
		_.each(obj, function (value, index, list) {
			if (predicate(value, index, list)) results.push(value);
		});
		return results;
	};

	// Return all the elements for which a truth test fails.
	_.reject = function (obj, predicate, context) {
		return _.filter(obj, _.negate(cb(predicate)), context);
	};

	// Determine whether all of the elements match a truth test.
	// Aliased as `all`.
	_.every = _.all = function (obj, predicate, context) {
		predicate = cb(predicate, context);
		var keys = !isArrayLike(obj) && _.keys(obj),
			length = (keys || obj).length;
		for (var index = 0; index < length; index++) {
			var currentKey = keys ? keys[index] : index;
			if (!predicate(obj[currentKey], currentKey, obj)) return false;
		}
		return true;
	};

	// Determine if at least one element in the object matches a truth test.
	// Aliased as `any`.
	_.some = _.any = function (obj, predicate, context) {
		predicate = cb(predicate, context);
		var keys = !isArrayLike(obj) && _.keys(obj),
			length = (keys || obj).length;
		for (var index = 0; index < length; index++) {
			var currentKey = keys ? keys[index] : index;
			if (predicate(obj[currentKey], currentKey, obj)) return true;
		}
		return false;
	};

	// Determine if the array or object contains a given item (using `===`).
	// Aliased as `includes` and `include`.
	_.contains = _.includes = _.include = function (obj, item, fromIndex, guard) {
		if (!isArrayLike(obj)) obj = _.values(obj);
		if (typeof fromIndex != 'number' || guard) fromIndex = 0;
		return _.indexOf(obj, item, fromIndex) >= 0;
	};

	// Invoke a method (with arguments) on every item in a collection.
	_.invoke = function (obj, method) {
		var args = slice.call(arguments, 2);
		var isFunc = _.isFunction(method);
		return _.map(obj, function (value) {
			var func = isFunc ? method : value[method];
			return func == null ? func : func.apply(value, args);
		});
	};

	// Convenience version of a common use case of `map`: fetching a property.
	_.pluck = function (obj, key) {
		return _.map(obj, _.property(key));
	};

	// Convenience version of a common use case of `filter`: selecting only objects
	// containing specific `key:value` pairs.
	_.where = function (obj, attrs) {
		return _.filter(obj, _.matcher(attrs));
	};

	// Convenience version of a common use case of `find`: getting the first object
	// containing specific `key:value` pairs.
	_.findWhere = function (obj, attrs) {
		return _.find(obj, _.matcher(attrs));
	};

	// Return the maximum element (or element-based computation).
	_.max = function (obj, iteratee, context) {
		var result = -Infinity, lastComputed = -Infinity,
			value, computed;
		if (iteratee == null && obj != null) {
			obj = isArrayLike(obj) ? obj : _.values(obj);
			for (var i = 0, length = obj.length; i < length; i++) {
				value = obj[i];
				if (value > result) {
					result = value;
				}
			}
		} else {
			iteratee = cb(iteratee, context);
			_.each(obj, function (value, index, list) {
				computed = iteratee(value, index, list);
				if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
					result = value;
					lastComputed = computed;
				}
			});
		}
		return result;
	};

	// Return the minimum element (or element-based computation).
	_.min = function (obj, iteratee, context) {
		var result = Infinity, lastComputed = Infinity,
			value, computed;
		if (iteratee == null && obj != null) {
			obj = isArrayLike(obj) ? obj : _.values(obj);
			for (var i = 0, length = obj.length; i < length; i++) {
				value = obj[i];
				if (value < result) {
					result = value;
				}
			}
		} else {
			iteratee = cb(iteratee, context);
			_.each(obj, function (value, index, list) {
				computed = iteratee(value, index, list);
				if (computed < lastComputed || computed === Infinity && result === Infinity) {
					result = value;
					lastComputed = computed;
				}
			});
		}
		return result;
	};

	// Shuffle a collection, using the modern version of the
	// [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
	_.shuffle = function (obj) {
		var set = isArrayLike(obj) ? obj : _.values(obj);
		var length = set.length;
		var shuffled = Array(length);
		for (var index = 0, rand; index < length; index++) {
			rand = _.random(0, index);
			if (rand !== index) shuffled[index] = shuffled[rand];
			shuffled[rand] = set[index];
		}
		return shuffled;
	};

	// Sample **n** random values from a collection.
	// If **n** is not specified, returns a single random element.
	// The internal `guard` argument allows it to work with `map`.
	_.sample = function (obj, n, guard) {
		if (n == null || guard) {
			if (!isArrayLike(obj)) obj = _.values(obj);
			return obj[_.random(obj.length - 1)];
		}
		return _.shuffle(obj).slice(0, Math.max(0, n));
	};

	// Sort the object's values by a criterion produced by an iteratee.
	_.sortBy = function (obj, iteratee, context) {
		iteratee = cb(iteratee, context);
		return _.pluck(_.map(obj, function (value, index, list) {
			return {
				value: value,
				index: index,
				criteria: iteratee(value, index, list)
			};
		}).sort(function (left, right) {
			var a = left.criteria;
			var b = right.criteria;
			if (a !== b) {
				if (a > b || a === void 0) return 1;
				if (a < b || b === void 0) return -1;
			}
			return left.index - right.index;
		}), 'value');
	};

	// An internal function used for aggregate "group by" operations.
	var group = function (behavior) {
		return function (obj, iteratee, context) {
			var result = {};
			iteratee = cb(iteratee, context);
			_.each(obj, function (value, index) {
				var key = iteratee(value, index, obj);
				behavior(result, value, key);
			});
			return result;
		};
	};

	// Groups the object's values by a criterion. Pass either a string attribute
	// to group by, or a function that returns the criterion.
	_.groupBy = group(function (result, value, key) {
		if (_.has(result, key)) result[key].push(value); else result[key] = [value];
	});

	// Indexes the object's values by a criterion, similar to `groupBy`, but for
	// when you know that your index values will be unique.
	_.indexBy = group(function (result, value, key) {
		result[key] = value;
	});

	// Counts instances of an object that group by a certain criterion. Pass
	// either a string attribute to count by, or a function that returns the
	// criterion.
	_.countBy = group(function (result, value, key) {
		if (_.has(result, key)) result[key]++; else result[key] = 1;
	});

	// Safely create a real, live array from anything iterable.
	_.toArray = function (obj) {
		if (!obj) return [];
		if (_.isArray(obj)) return slice.call(obj);
		if (isArrayLike(obj)) return _.map(obj, _.identity);
		return _.values(obj);
	};

	// Return the number of elements in an object.
	_.size = function (obj) {
		if (obj == null) return 0;
		return isArrayLike(obj) ? obj.length : _.keys(obj).length;
	};

	// Split a collection into two arrays: one whose elements all satisfy the given
	// predicate, and one whose elements all do not satisfy the predicate.
	_.partition = function (obj, predicate, context) {
		predicate = cb(predicate, context);
		var pass = [], fail = [];
		_.each(obj, function (value, key, obj) {
			(predicate(value, key, obj) ? pass : fail).push(value);
		});
		return [pass, fail];
	};

	// Array Functions
	// ---------------

	// Get the first element of an array. Passing **n** will return the first N
	// values in the array. Aliased as `head` and `take`. The **guard** check
	// allows it to work with `_.map`.
	_.first = _.head = _.take = function (array, n, guard) {
		if (array == null) return void 0;
		if (n == null || guard) return array[0];
		return _.initial(array, array.length - n);
	};

	// Returns everything but the last entry of the array. Especially useful on
	// the arguments object. Passing **n** will return all the values in
	// the array, excluding the last N.
	_.initial = function (array, n, guard) {
		return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
	};

	// Get the last element of an array. Passing **n** will return the last N
	// values in the array.
	_.last = function (array, n, guard) {
		if (array == null) return void 0;
		if (n == null || guard) return array[array.length - 1];
		return _.rest(array, Math.max(0, array.length - n));
	};

	// Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
	// Especially useful on the arguments object. Passing an **n** will return
	// the rest N values in the array.
	_.rest = _.tail = _.drop = function (array, n, guard) {
		return slice.call(array, n == null || guard ? 1 : n);
	};

	// Trim out all falsy values from an array.
	_.compact = function (array) {
		return _.filter(array, _.identity);
	};

	// Internal implementation of a recursive `flatten` function.
	var flatten = function (input, shallow, strict, startIndex) {
		var output = [], idx = 0;
		for (var i = startIndex || 0, length = getLength(input) ; i < length; i++) {
			var value = input[i];
			if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
				//flatten current level of array or arguments object
				if (!shallow) value = flatten(value, shallow, strict);
				var j = 0, len = value.length;
				output.length += len;
				while (j < len) {
					output[idx++] = value[j++];
				}
			} else if (!strict) {
				output[idx++] = value;
			}
		}
		return output;
	};

	// Flatten out an array, either recursively (by default), or just one level.
	_.flatten = function (array, shallow) {
		return flatten(array, shallow, false);
	};

	// Return a version of the array that does not contain the specified value(s).
	_.without = function (array) {
		return _.difference(array, slice.call(arguments, 1));
	};

	// Produce a duplicate-free version of the array. If the array has already
	// been sorted, you have the option of using a faster algorithm.
	// Aliased as `unique`.
	_.uniq = _.unique = function (array, isSorted, iteratee, context) {
		if (!_.isBoolean(isSorted)) {
			context = iteratee;
			iteratee = isSorted;
			isSorted = false;
		}
		if (iteratee != null) iteratee = cb(iteratee, context);
		var result = [];
		var seen = [];
		for (var i = 0, length = getLength(array) ; i < length; i++) {
			var value = array[i],
				computed = iteratee ? iteratee(value, i, array) : value;
			if (isSorted) {
				if (!i || seen !== computed) result.push(value);
				seen = computed;
			} else if (iteratee) {
				if (!_.contains(seen, computed)) {
					seen.push(computed);
					result.push(value);
				}
			} else if (!_.contains(result, value)) {
				result.push(value);
			}
		}
		return result;
	};

	// Produce an array that contains the union: each distinct element from all of
	// the passed-in arrays.
	_.union = function () {
		return _.uniq(flatten(arguments, true, true));
	};

	// Produce an array that contains every item shared between all the
	// passed-in arrays.
	_.intersection = function (array) {
		var result = [];
		var argsLength = arguments.length;
		for (var i = 0, length = getLength(array) ; i < length; i++) {
			var item = array[i];
			if (_.contains(result, item)) continue;
			for (var j = 1; j < argsLength; j++) {
				if (!_.contains(arguments[j], item)) break;
			}
			if (j === argsLength) result.push(item);
		}
		return result;
	};

	// Take the difference between one array and a number of other arrays.
	// Only the elements present in just the first array will remain.
	_.difference = function (array) {
		var rest = flatten(arguments, true, true, 1);
		return _.filter(array, function (value) {
			return !_.contains(rest, value);
		});
	};

	// Zip together multiple lists into a single array -- elements that share
	// an index go together.
	_.zip = function () {
		return _.unzip(arguments);
	};

	// Complement of _.zip. Unzip accepts an array of arrays and groups
	// each array's elements on shared indices
	_.unzip = function (array) {
		var length = array && _.max(array, getLength).length || 0;
		var result = Array(length);

		for (var index = 0; index < length; index++) {
			result[index] = _.pluck(array, index);
		}
		return result;
	};

	// Converts lists into objects. Pass either a single array of `[key, value]`
	// pairs, or two parallel arrays of the same length -- one of keys, and one of
	// the corresponding values.
	_.object = function (list, values) {
		var result = {};
		for (var i = 0, length = getLength(list) ; i < length; i++) {
			if (values) {
				result[list[i]] = values[i];
			} else {
				result[list[i][0]] = list[i][1];
			}
		}
		return result;
	};

	// Generator function to create the findIndex and findLastIndex functions
	function createPredicateIndexFinder(dir) {
		return function (array, predicate, context) {
			predicate = cb(predicate, context);
			var length = getLength(array);
			var index = dir > 0 ? 0 : length - 1;
			for (; index >= 0 && index < length; index += dir) {
				if (predicate(array[index], index, array)) return index;
			}
			return -1;
		};
	}

	// Returns the first index on an array-like that passes a predicate test
	_.findIndex = createPredicateIndexFinder(1);
	_.findLastIndex = createPredicateIndexFinder(-1);

	// Use a comparator function to figure out the smallest index at which
	// an object should be inserted so as to maintain order. Uses binary search.
	_.sortedIndex = function (array, obj, iteratee, context) {
		iteratee = cb(iteratee, context, 1);
		var value = iteratee(obj);
		var low = 0, high = getLength(array);
		while (low < high) {
			var mid = Math.floor((low + high) / 2);
			if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
		}
		return low;
	};

	// Generator function to create the indexOf and lastIndexOf functions
	function createIndexFinder(dir, predicateFind, sortedIndex) {
		return function (array, item, idx) {
			var i = 0, length = getLength(array);
			if (typeof idx == 'number') {
				if (dir > 0) {
					i = idx >= 0 ? idx : Math.max(idx + length, i);
				} else {
					length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
				}
			} else if (sortedIndex && idx && length) {
				idx = sortedIndex(array, item);
				return array[idx] === item ? idx : -1;
			}
			if (item !== item) {
				idx = predicateFind(slice.call(array, i, length), _.isNaN);
				return idx >= 0 ? idx + i : -1;
			}
			for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
				if (array[idx] === item) return idx;
			}
			return -1;
		};
	}

	// Return the position of the first occurrence of an item in an array,
	// or -1 if the item is not included in the array.
	// If the array is large and already in sort order, pass `true`
	// for **isSorted** to use binary search.
	_.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
	_.lastIndexOf = createIndexFinder(-1, _.findLastIndex);

	// Generate an integer Array containing an arithmetic progression. A port of
	// the native Python `range()` function. See
	// [the Python documentation](http://docs.python.org/library/functions.html#range).
	_.range = function (start, stop, step) {
		if (stop == null) {
			stop = start || 0;
			start = 0;
		}
		step = step || 1;

		var length = Math.max(Math.ceil((stop - start) / step), 0);
		var range = Array(length);

		for (var idx = 0; idx < length; idx++, start += step) {
			range[idx] = start;
		}

		return range;
	};

	// Function (ahem) Functions
	// ------------------

	// Determines whether to execute a function as a constructor
	// or a normal function with the provided arguments
	var executeBound = function (sourceFunc, boundFunc, context, callingContext, args) {
		if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
		var self = baseCreate(sourceFunc.prototype);
		var result = sourceFunc.apply(self, args);
		if (_.isObject(result)) return result;
		return self;
	};

	// Create a function bound to a given object (assigning `this`, and arguments,
	// optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
	// available.
	_.bind = function (func, context) {
		if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
		if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
		var args = slice.call(arguments, 2);
		var bound = function () {
			return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
		};
		return bound;
	};

	// Partially apply a function by creating a version that has had some of its
	// arguments pre-filled, without changing its dynamic `this` context. _ acts
	// as a placeholder, allowing any combination of arguments to be pre-filled.
	_.partial = function (func) {
		var boundArgs = slice.call(arguments, 1);
		var bound = function () {
			var position = 0, length = boundArgs.length;
			var args = Array(length);
			for (var i = 0; i < length; i++) {
				args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
			}
			while (position < arguments.length) args.push(arguments[position++]);
			return executeBound(func, bound, this, this, args);
		};
		return bound;
	};

	// Bind a number of an object's methods to that object. Remaining arguments
	// are the method names to be bound. Useful for ensuring that all callbacks
	// defined on an object belong to it.
	_.bindAll = function (obj) {
		var i, length = arguments.length, key;
		if (length <= 1) throw new Error('bindAll must be passed function names');
		for (i = 1; i < length; i++) {
			key = arguments[i];
			obj[key] = _.bind(obj[key], obj);
		}
		return obj;
	};

	// Memoize an expensive function by storing its results.
	_.memoize = function (func, hasher) {
		var memoize = function (key) {
			var cache = memoize.cache;
			var address = '' + (hasher ? hasher.apply(this, arguments) : key);
			if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
			return cache[address];
		};
		memoize.cache = {};
		return memoize;
	};

	// Delays a function for the given number of milliseconds, and then calls
	// it with the arguments supplied.
	_.delay = function (func, wait) {
		var args = slice.call(arguments, 2);
		return setTimeout(function () {
			return func.apply(null, args);
		}, wait);
	};

	// Defers a function, scheduling it to run after the current call stack has
	// cleared.
	_.defer = _.partial(_.delay, _, 1);

	// Returns a function, that, when invoked, will only be triggered at most once
	// during a given window of time. Normally, the throttled function will run
	// as much as it can, without ever going more than once per `wait` duration;
	// but if you'd like to disable the execution on the leading edge, pass
	// `{leading: false}`. To disable execution on the trailing edge, ditto.
	_.throttle = function (func, wait, options) {
		var context, args, result;
		var timeout = null;
		var previous = 0;
		if (!options) options = {};
		var later = function () {
			previous = options.leading === false ? 0 : _.now();
			timeout = null;
			result = func.apply(context, args);
			if (!timeout) context = args = null;
		};
		return function () {
			var now = _.now();
			if (!previous && options.leading === false) previous = now;
			var remaining = wait - (now - previous);
			context = this;
			args = arguments;
			if (remaining <= 0 || remaining > wait) {
				if (timeout) {
					clearTimeout(timeout);
					timeout = null;
				}
				previous = now;
				result = func.apply(context, args);
				if (!timeout) context = args = null;
			} else if (!timeout && options.trailing !== false) {
				timeout = setTimeout(later, remaining);
			}
			return result;
		};
	};

	// Returns a function, that, as long as it continues to be invoked, will not
	// be triggered. The function will be called after it stops being called for
	// N milliseconds. If `immediate` is passed, trigger the function on the
	// leading edge, instead of the trailing.
	_.debounce = function (func, wait, immediate) {
		var timeout, args, context, timestamp, result;

		var later = function () {
			var last = _.now() - timestamp;

			if (last < wait && last >= 0) {
				timeout = setTimeout(later, wait - last);
			} else {
				timeout = null;
				if (!immediate) {
					result = func.apply(context, args);
					if (!timeout) context = args = null;
				}
			}
		};

		return function () {
			context = this;
			args = arguments;
			timestamp = _.now();
			var callNow = immediate && !timeout;
			if (!timeout) timeout = setTimeout(later, wait);
			if (callNow) {
				result = func.apply(context, args);
				context = args = null;
			}

			return result;
		};
	};

	// Returns the first function passed as an argument to the second,
	// allowing you to adjust arguments, run code before and after, and
	// conditionally execute the original function.
	_.wrap = function (func, wrapper) {
		return _.partial(wrapper, func);
	};

	// Returns a negated version of the passed-in predicate.
	_.negate = function (predicate) {
		return function () {
			return !predicate.apply(this, arguments);
		};
	};

	// Returns a function that is the composition of a list of functions, each
	// consuming the return value of the function that follows.
	_.compose = function () {
		var args = arguments;
		var start = args.length - 1;
		return function () {
			var i = start;
			var result = args[start].apply(this, arguments);
			while (i--) result = args[i].call(this, result);
			return result;
		};
	};

	// Returns a function that will only be executed on and after the Nth call.
	_.after = function (times, func) {
		return function () {
			if (--times < 1) {
				return func.apply(this, arguments);
			}
		};
	};

	// Returns a function that will only be executed up to (but not including) the Nth call.
	_.before = function (times, func) {
		var memo;
		return function () {
			if (--times > 0) {
				memo = func.apply(this, arguments);
			}
			if (times <= 1) func = null;
			return memo;
		};
	};

	// Returns a function that will be executed at most one time, no matter how
	// often you call it. Useful for lazy initialization.
	_.once = _.partial(_.before, 2);

	// Object Functions
	// ----------------

	// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
	var hasEnumBug = !{ toString: null }.propertyIsEnumerable('toString');
	var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
						'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

	function collectNonEnumProps(obj, keys) {
		var nonEnumIdx = nonEnumerableProps.length;
		var constructor = obj.constructor;
		var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;

		// Constructor is a special case.
		var prop = 'constructor';
		if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

		while (nonEnumIdx--) {
			prop = nonEnumerableProps[nonEnumIdx];
			if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
				keys.push(prop);
			}
		}
	}

	// Retrieve the names of an object's own properties.
	// Delegates to **ECMAScript 5**'s native `Object.keys`
	_.keys = function (obj) {
		if (!_.isObject(obj)) return [];
		if (nativeKeys) return nativeKeys(obj);
		var keys = [];
		for (var key in obj) if (_.has(obj, key)) keys.push(key);
		// Ahem, IE < 9.
		if (hasEnumBug) collectNonEnumProps(obj, keys);
		return keys;
	};

	// Retrieve all the property names of an object.
	_.allKeys = function (obj) {
		if (!_.isObject(obj)) return [];
		var keys = [];
		for (var key in obj) keys.push(key);
		// Ahem, IE < 9.
		if (hasEnumBug) collectNonEnumProps(obj, keys);
		return keys;
	};

	// Retrieve the values of an object's properties.
	_.values = function (obj) {
		var keys = _.keys(obj);
		var length = keys.length;
		var values = Array(length);
		for (var i = 0; i < length; i++) {
			values[i] = obj[keys[i]];
		}
		return values;
	};

	// Returns the results of applying the iteratee to each element of the object
	// In contrast to _.map it returns an object
	_.mapObject = function (obj, iteratee, context) {
		iteratee = cb(iteratee, context);
		var keys = _.keys(obj),
			  length = keys.length,
			  results = {},
			  currentKey;
		for (var index = 0; index < length; index++) {
			currentKey = keys[index];
			results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
		}
		return results;
	};

	// Convert an object into a list of `[key, value]` pairs.
	_.pairs = function (obj) {
		var keys = _.keys(obj);
		var length = keys.length;
		var pairs = Array(length);
		for (var i = 0; i < length; i++) {
			pairs[i] = [keys[i], obj[keys[i]]];
		}
		return pairs;
	};

	// Invert the keys and values of an object. The values must be serializable.
	_.invert = function (obj) {
		var result = {};
		var keys = _.keys(obj);
		for (var i = 0, length = keys.length; i < length; i++) {
			result[obj[keys[i]]] = keys[i];
		}
		return result;
	};

	// Return a sorted list of the function names available on the object.
	// Aliased as `methods`
	_.functions = _.methods = function (obj) {
		var names = [];
		for (var key in obj) {
			if (_.isFunction(obj[key])) names.push(key);
		}
		return names.sort();
	};

	// Extend a given object with all the properties in passed-in object(s).
	_.extend = createAssigner(_.allKeys);

	// Assigns a given object with all the own properties in the passed-in object(s)
	// (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
	_.extendOwn = _.assign = createAssigner(_.keys);

	// Returns the first key on an object that passes a predicate test
	_.findKey = function (obj, predicate, context) {
		predicate = cb(predicate, context);
		var keys = _.keys(obj), key;
		for (var i = 0, length = keys.length; i < length; i++) {
			key = keys[i];
			if (predicate(obj[key], key, obj)) return key;
		}
	};

	// Return a copy of the object only containing the whitelisted properties.
	_.pick = function (object, oiteratee, context) {
		var result = {}, obj = object, iteratee, keys;
		if (obj == null) return result;
		if (_.isFunction(oiteratee)) {
			keys = _.allKeys(obj);
			iteratee = optimizeCb(oiteratee, context);
		} else {
			keys = flatten(arguments, false, false, 1);
			iteratee = function (value, key, obj) { return key in obj; };
			obj = Object(obj);
		}
		for (var i = 0, length = keys.length; i < length; i++) {
			var key = keys[i];
			var value = obj[key];
			if (iteratee(value, key, obj)) result[key] = value;
		}
		return result;
	};

	// Return a copy of the object without the blacklisted properties.
	_.omit = function (obj, iteratee, context) {
		if (_.isFunction(iteratee)) {
			iteratee = _.negate(iteratee);
		} else {
			var keys = _.map(flatten(arguments, false, false, 1), String);
			iteratee = function (value, key) {
				return !_.contains(keys, key);
			};
		}
		return _.pick(obj, iteratee, context);
	};

	// Fill in a given object with default properties.
	_.defaults = createAssigner(_.allKeys, true);

	// Creates an object that inherits from the given prototype object.
	// If additional properties are provided then they will be added to the
	// created object.
	_.create = function (prototype, props) {
		var result = baseCreate(prototype);
		if (props) _.extendOwn(result, props);
		return result;
	};

	// Create a (shallow-cloned) duplicate of an object.
	_.clone = function (obj) {
		if (!_.isObject(obj)) return obj;
		return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
	};

	// Invokes interceptor with the obj, and then returns obj.
	// The primary purpose of this method is to "tap into" a method chain, in
	// order to perform operations on intermediate results within the chain.
	_.tap = function (obj, interceptor) {
		interceptor(obj);
		return obj;
	};

	// Returns whether an object has a given set of `key:value` pairs.
	_.isMatch = function (object, attrs) {
		var keys = _.keys(attrs), length = keys.length;
		if (object == null) return !length;
		var obj = Object(object);
		for (var i = 0; i < length; i++) {
			var key = keys[i];
			if (attrs[key] !== obj[key] || !(key in obj)) return false;
		}
		return true;
	};


	// Internal recursive comparison function for `isEqual`.
	var eq = function (a, b, aStack, bStack) {
		// Identical objects are equal. `0 === -0`, but they aren't identical.
		// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
		if (a === b) return a !== 0 || 1 / a === 1 / b;
		// A strict comparison is necessary because `null == undefined`.
		if (a == null || b == null) return a === b;
		// Unwrap any wrapped objects.
		if (a instanceof _) a = a._wrapped;
		if (b instanceof _) b = b._wrapped;
		// Compare `[[Class]]` names.
		var className = toString.call(a);
		if (className !== toString.call(b)) return false;
		switch (className) {
			// Strings, numbers, regular expressions, dates, and booleans are compared by value.
			case '[object RegExp]':
				// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
			case '[object String]':
				// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
				// equivalent to `new String("5")`.
				return '' + a === '' + b;
			case '[object Number]':
				// `NaN`s are equivalent, but non-reflexive.
				// Object(NaN) is equivalent to NaN
				if (+a !== +a) return +b !== +b;
				// An `egal` comparison is performed for other numeric values.
				return +a === 0 ? 1 / +a === 1 / b : +a === +b;
			case '[object Date]':
			case '[object Boolean]':
				// Coerce dates and booleans to numeric primitive values. Dates are compared by their
				// millisecond representations. Note that invalid dates with millisecond representations
				// of `NaN` are not equivalent.
				return +a === +b;
		}

		var areArrays = className === '[object Array]';
		if (!areArrays) {
			if (typeof a != 'object' || typeof b != 'object') return false;

			// Objects with different constructors are not equivalent, but `Object`s or `Array`s
			// from different frames are.
			var aCtor = a.constructor, bCtor = b.constructor;
			if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
									 _.isFunction(bCtor) && bCtor instanceof bCtor)
								&& ('constructor' in a && 'constructor' in b)) {
				return false;
			}
		}
		// Assume equality for cyclic structures. The algorithm for detecting cyclic
		// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.

		// Initializing stack of traversed objects.
		// It's done here since we only need them for objects and arrays comparison.
		aStack = aStack || [];
		bStack = bStack || [];
		var length = aStack.length;
		while (length--) {
			// Linear search. Performance is inversely proportional to the number of
			// unique nested structures.
			if (aStack[length] === a) return bStack[length] === b;
		}

		// Add the first object to the stack of traversed objects.
		aStack.push(a);
		bStack.push(b);

		// Recursively compare objects and arrays.
		if (areArrays) {
			// Compare array lengths to determine if a deep comparison is necessary.
			length = a.length;
			if (length !== b.length) return false;
			// Deep compare the contents, ignoring non-numeric properties.
			while (length--) {
				if (!eq(a[length], b[length], aStack, bStack)) return false;
			}
		} else {
			// Deep compare objects.
			var keys = _.keys(a), key;
			length = keys.length;
			// Ensure that both objects contain the same number of properties before comparing deep equality.
			if (_.keys(b).length !== length) return false;
			while (length--) {
				// Deep compare each member
				key = keys[length];
				if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
			}
		}
		// Remove the first object from the stack of traversed objects.
		aStack.pop();
		bStack.pop();
		return true;
	};

	// Perform a deep comparison to check if two objects are equal.
	_.isEqual = function (a, b) {
		return eq(a, b);
	};

	// Is a given array, string, or object empty?
	// An "empty" object has no enumerable own-properties.
	_.isEmpty = function (obj) {
		if (obj == null) return true;
		if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
		return _.keys(obj).length === 0;
	};

	// Is a given value a DOM element?
	_.isElement = function (obj) {
		return !!(obj && obj.nodeType === 1);
	};

	// Is a given value an array?
	// Delegates to ECMA5's native Array.isArray
	_.isArray = nativeIsArray || function (obj) {
		return toString.call(obj) === '[object Array]';
	};

	// Is a given variable an object?
	_.isObject = function (obj) {
		var type = typeof obj;
		return type === 'function' || type === 'object' && !!obj;
	};

	// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
	_.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function (name) {
		_['is' + name] = function (obj) {
			return toString.call(obj) === '[object ' + name + ']';
		};
	});

	// Define a fallback version of the method in browsers (ahem, IE < 9), where
	// there isn't any inspectable "Arguments" type.
	if (!_.isArguments(arguments)) {
		_.isArguments = function (obj) {
			return _.has(obj, 'callee');
		};
	}

	// Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
	// IE 11 (#1621), and in Safari 8 (#1929).
	if (typeof /./ != 'function' && typeof Int8Array != 'object') {
		_.isFunction = function (obj) {
			return typeof obj == 'function' || false;
		};
	}

	// Is a given object a finite number?
	_.isFinite = function (obj) {
		return isFinite(obj) && !isNaN(parseFloat(obj));
	};

	// Is the given value `NaN`? (NaN is the only number which does not equal itself).
	_.isNaN = function (obj) {
		return _.isNumber(obj) && obj !== +obj;
	};

	// Is a given value a boolean?
	_.isBoolean = function (obj) {
		return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
	};

	// Is a given value equal to null?
	_.isNull = function (obj) {
		return obj === null;
	};

	// Is a given variable undefined?
	_.isUndefined = function (obj) {
		return obj === void 0;
	};

	// Shortcut function for checking if an object has a given property directly
	// on itself (in other words, not on a prototype).
	_.has = function (obj, key) {
		return obj != null && hasOwnProperty.call(obj, key);
	};

	// Utility Functions
	// -----------------

	// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
	// previous owner. Returns a reference to the Underscore object.
	_.noConflict = function () {
		root._ = previousUnderscore;
		return this;
	};

	// Keep the identity function around for default iteratees.
	_.identity = function (value) {
		return value;
	};

	// Predicate-generating functions. Often useful outside of Underscore.
	_.constant = function (value) {
		return function () {
			return value;
		};
	};

	_.noop = function () { };

	_.property = property;

	// Generates a function for a given object that returns a given property.
	_.propertyOf = function (obj) {
		return obj == null ? function () { } : function (key) {
			return obj[key];
		};
	};

	// Returns a predicate for checking whether an object has a given set of
	// `key:value` pairs.
	_.matcher = _.matches = function (attrs) {
		attrs = _.extendOwn({}, attrs);
		return function (obj) {
			return _.isMatch(obj, attrs);
		};
	};

	// Run a function **n** times.
	_.times = function (n, iteratee, context) {
		var accum = Array(Math.max(0, n));
		iteratee = optimizeCb(iteratee, context, 1);
		for (var i = 0; i < n; i++) accum[i] = iteratee(i);
		return accum;
	};

	// Return a random integer between min and max (inclusive).
	_.random = function (min, max) {
		if (max == null) {
			max = min;
			min = 0;
		}
		return min + Math.floor(Math.random() * (max - min + 1));
	};

	// A (possibly faster) way to get the current timestamp as an integer.
	_.now = Date.now || function () {
		return new Date().getTime();
	};

	// List of HTML entities for escaping.
	var escapeMap = {
		'&': '&amp;',
		'<': '&lt;',
		'>': '&gt;',
		'"': '&quot;',
		"'": '&#x27;',
		'`': '&#x60;'
	};
	var unescapeMap = _.invert(escapeMap);

	// Functions for escaping and unescaping strings to/from HTML interpolation.
	var createEscaper = function (map) {
		var escaper = function (match) {
			return map[match];
		};
		// Regexes for identifying a key that needs to be escaped
		var source = '(?:' + _.keys(map).join('|') + ')';
		var testRegexp = RegExp(source);
		var replaceRegexp = RegExp(source, 'g');
		return function (string) {
			string = string == null ? '' : '' + string;
			return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
		};
	};
	_.escape = createEscaper(escapeMap);
	_.unescape = createEscaper(unescapeMap);

	// If the value of the named `property` is a function then invoke it with the
	// `object` as context; otherwise, return it.
	_.result = function (object, property, fallback) {
		var value = object == null ? void 0 : object[property];
		if (value === void 0) {
			value = fallback;
		}
		return _.isFunction(value) ? value.call(object) : value;
	};

	// Generate a unique integer id (unique within the entire client session).
	// Useful for temporary DOM ids.
	var idCounter = 0;
	_.uniqueId = function (prefix) {
		var id = ++idCounter + '';
		return prefix ? prefix + id : id;
	};

	// By default, Underscore uses ERB-style template delimiters, change the
	// following template settings to use alternative delimiters.
	_.templateSettings = {
		evaluate: /<%([\s\S]+?)%>/g,
		interpolate: /<%=([\s\S]+?)%>/g,
		escape: /<%-([\s\S]+?)%>/g
	};

	// When customizing `templateSettings`, if you don't want to define an
	// interpolation, evaluation or escaping regex, we need one that is
	// guaranteed not to match.
	var noMatch = /(.)^/;

	// Certain characters need to be escaped so that they can be put into a
	// string literal.
	var escapes = {
		"'": "'",
		'\\': '\\',
		'\r': 'r',
		'\n': 'n',
		'\u2028': 'u2028',
		'\u2029': 'u2029'
	};

	var escaper = /\\|'|\r|\n|\u2028|\u2029/g;

	var escapeChar = function (match) {
		return '\\' + escapes[match];
	};

	// JavaScript micro-templating, similar to John Resig's implementation.
	// Underscore templating handles arbitrary delimiters, preserves whitespace,
	// and correctly escapes quotes within interpolated code.
	// NB: `oldSettings` only exists for backwards compatibility.
	_.template = function (text, settings, oldSettings) {
		if (!settings && oldSettings) settings = oldSettings;
		settings = _.defaults({}, settings, _.templateSettings);

		// Combine delimiters into one regular expression via alternation.
		var matcher = RegExp([
		  (settings.escape || noMatch).source,
		  (settings.interpolate || noMatch).source,
		  (settings.evaluate || noMatch).source
		].join('|') + '|$', 'g');

		// Compile the template source, escaping string literals appropriately.
		var index = 0;
		var source = "__p+='";
		text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
			source += text.slice(index, offset).replace(escaper, escapeChar);
			index = offset + match.length;

			if (escape) {
				source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
			} else if (interpolate) {
				source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
			} else if (evaluate) {
				source += "';\n" + evaluate + "\n__p+='";
			}

			// Adobe VMs need the match returned to produce the correct offest.
			return match;
		});
		source += "';\n";

		// If a variable is not specified, place data values in local scope.
		if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

		source = "var __t,__p='',__j=Array.prototype.join," +
		  "print=function(){__p+=__j.call(arguments,'');};\n" +
		  source + 'return __p;\n';

		try {
			var render = new Function(settings.variable || 'obj', '_', source);
		} catch (e) {
			e.source = source;
			throw e;
		}

		var template = function (data) {
			return render.call(this, data, _);
		};

		// Provide the compiled source as a convenience for precompilation.
		var argument = settings.variable || 'obj';
		template.source = 'function(' + argument + '){\n' + source + '}';

		return template;
	};

	// Add a "chain" function. Start chaining a wrapped Underscore object.
	_.chain = function (obj) {
		var instance = _(obj);
		instance._chain = true;
		return instance;
	};

	// OOP
	// ---------------
	// If Underscore is called as a function, it returns a wrapped object that
	// can be used OO-style. This wrapper holds altered versions of all the
	// underscore functions. Wrapped objects may be chained.

	// Helper function to continue chaining intermediate results.
	var result = function (instance, obj) {
		return instance._chain ? _(obj).chain() : obj;
	};

	// Add your own custom functions to the Underscore object.
	_.mixin = function (obj) {
		_.each(_.functions(obj), function (name) {
			var func = _[name] = obj[name];
			_.prototype[name] = function () {
				var args = [this._wrapped];
				push.apply(args, arguments);
				return result(this, func.apply(_, args));
			};
		});
	};

	// Add all of the Underscore functions to the wrapper object.
	_.mixin(_);

	// Add all mutator Array functions to the wrapper.
	_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function (name) {
		var method = ArrayProto[name];
		_.prototype[name] = function () {
			var obj = this._wrapped;
			method.apply(obj, arguments);
			if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
			return result(this, obj);
		};
	});

	// Add all accessor Array functions to the wrapper.
	_.each(['concat', 'join', 'slice'], function (name) {
		var method = ArrayProto[name];
		_.prototype[name] = function () {
			return result(this, method.apply(this._wrapped, arguments));
		};
	});

	// Extracts the result from a wrapped and chained object.
	_.prototype.value = function () {
		return this._wrapped;
	};

	// Provide unwrapping proxy for some methods used in engine operations
	// such as arithmetic and JSON stringification.
	_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

	_.prototype.toString = function () {
		return '' + this._wrapped;
	};

	// AMD registration happens at the end for compatibility with AMD loaders
	// that may not enforce next-turn semantics on modules. Even though general
	// practice for AMD registration is to be anonymous, underscore registers
	// as a named module because, like jQuery, it is a base library that is
	// popular enough to be bundled in a third party lib, but not be part of
	// an AMD load request. Those cases could generate an error when an
	// anonymous define() is called outside of a loader request.
	if (typeof define === 'function' && define.amd) {
		define('underscore', [], function () {
			return _;
		});
	}
}.call(this));;
$.widget("custom.catcomplete", $.ui.autocomplete, {
	_create: function () {
		this._super();
		this.widget().menu("option", "items", "> :not(.ui-autocomplete-category)");
	},
	_renderItem: function (ul, item) {
		var searchMask = item.searchTerm;
		var regEx = new RegExp("(" + searchMask + ")", "ig");
		var replaceMask = "<b>$1</b>";

		return $("<li>")
			.append(item.label.replace(regEx, replaceMask))
			.appendTo(ul);
	},
	_renderMenu: function (ul, items) {
		var that = this,
		currentCategory = "";

		$.each(items, function (index, item) {
			// Set country indicator
			var countryIndicator = '';
			switch (item.state) {
				case "Alberta":
				case "British Columbia":
				case "Saskatchewan":
				case "Manitoba":
				case "Ontario":
				case "Quebec":
				case "New Brunswick":
				case "Nova Scotia":
				case "Newfoundland and Labrador":
				case "Prince Edward Island":
					countryIndicator = ' Canada';
					break;
				default:
					countryIndicator = '';
					break;
			}

			var li;
			if (item.isKoa == true)
			{
				li = that._renderItemData(ul, item);
				li.prepend("<img src='/content/images/icon_koa.png' alt='KOA Logo Icon' width='30' valign='middle' />");
				li.append(item.locationDetails);
			}
			else {
				li = that._renderItemData(ul, item);
			  li.prepend("<span aria-hidden=\"true\" class=\"fas fa-map-marker-alt fa-2x mr-1\"></span>");
				li.append(countryIndicator);
				if (item.category) {
					li.attr("aria-label", item.category + " : " + item.label);
				}
				li.append("<img src='/content/images/icon_koa.png' alt='KOA Logo Icon' width='30px' valign='middle' />");
				li.append(item.nearbyText);
				li.append(item.locationDetails);
			}
		});
	}
});
$(function () {
	var throttledRequest = _.debounce(function (request, response, channel) {
		$.ajax({
			url: '/handlers/autosearch.ashx?q=' + request.term + '&c=' + channel
			, cache: false
			, success: function (data) {

				var parsedData = [];
				var searchResult = JSON.parse(data);

				if (searchResult.campgroundsField != null) {
					$.each(searchResult.campgroundsField, function (index3, item3) {
						var campgroundResult = { searchTerm: request.term, label: item3.nameField, locationDetails: " <span class='city-name'>" + item3.cityField + ", " + item3.stateProvinceCodeField + "</span>", isKoa: true, state: item3.stateProvinceField };
						//add the label to the display array
						parsedData.push(campgroundResult);
					});
				}

				if (searchResult.searchLocationListField != null) {
					//add first 2 not in the list
					$.each(searchResult.searchLocationListField, function (index, item) {
						var additionalCount = 0;

						if (index == 0) { //only show the first result
							$.each(item.campgroundsField, function (index2, item2) {

								if (additionalCount < 2) {
									var result = $.grep(parsedData, function (e) { return e.label == item2.nameField; });
									if (result.length == 0) {
									  var campgroundResult = { searchTerm: request.term, label: item2.nameField, locationDetails: " <span class='city-name'>" + item2.cityField + ", " + item2.stateProvinceCodeField + "</span>", isKoa: true, isSubKoa: true, state: item2.stateProvinceField };
										parsedData.push(campgroundResult);
										additionalCount++;
									}
								}

							});
						}
					});

					$.each(searchResult.searchLocationListField, function (index, item) {

						var campgroundMatches = 0;
						var campgroundNotMatches = 0;
						$.each(item.campgroundsField, function (index2, item2) {
							if(item2.isMatchField)
							{
								campgroundMatches++;
							}
							else {
								campgroundNotMatches++;
							}
						});

						var nearbyText = "";

						if (campgroundMatches == campgroundMatches + campgroundNotMatches)
						{
							nearbyText = "<b>"+ campgroundMatches + " KOA" + ((item.campgroundsField.length > 1) ? "s" : "") + "</b> nearby";
						}
						else {
							nearbyText = "<b>" + campgroundMatches + " / " + campgroundNotMatches + "KOA " + ((item.campgroundsField.length > 1) ? "s" : "") + "</b> nearby";
						}

						var campgroundResult = { searchTerm: request.term, label: item.locationField, isKoa: false, nearbyText: nearbyText, state: "" };
						parsedData.push(campgroundResult);

					});
				}

				response(parsedData);
			}
		});
	}, 50);

	$("#txtLocation").catcomplete({
		delay: 0,
		minLength: 3,
		source: function (request, response) {
			throttledRequest(request, response, $("#txtLocation").attr("data-channel"));
		},
		appendTo: $("#txtLocationResults")
  });
  $("#txtLocationMobile").catcomplete({
		delay: 0,
		minLength: 3,
		source: function (request, response) {
			throttledRequest(request, response, $("#txtLocationMobile").attr("data-channel"));
		},
		appendTo: $("#txtLocationResultsMobile")
  });
	$("#input-find-nav").catcomplete({
		delay: 0,
		minLength: 3,
		source: function (request, response) {
			throttledRequest(request, response, $("#input-find-nav").attr("data-channel"));
		},
		appendTo: $("#qLocationResults")
	});
});

$(document).ready(function () {
	// Enable click event and hover on autocomplete categories so user can select destination or campground
	$("#txtLocationResults, #qLocationResults").on("click", ".ui-autocomplete-category", function () {
		var inputValue = $(this).html();
		var $input = $(this).closest(".form-group, .input-group").find(".ui-autocomplete-input");
		$input.val(inputValue);
		$input.catcomplete("close");
	});
	$(document).on("mouseover", ".ui-autocomplete-category", function () {
		$(this).addClass("koa-yellow-bg");
	});
	$(document).on("mouseleave", ".ui-autocomplete-category", function () {
		$(this).removeClass("koa-yellow-bg");
	});
});
;
/*!
 * enquire.js v2.1.6 - Awesome Media Queries in JavaScript
 * Copyright (c) 2017 Nick Williams - http://wicky.nillia.ms/enquire.js
 * License: MIT */

!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.enquire=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function d(a,b){this.query=a,this.isUnconditional=b,this.handlers=[],this.mql=window.matchMedia(a);var c=this;this.listener=function(a){c.mql=a.currentTarget||a,c.assess()},this.mql.addListener(this.listener)}var e=a(3),f=a(4).each;d.prototype={constuctor:d,addHandler:function(a){var b=new e(a);this.handlers.push(b),this.matches()&&b.on()},removeHandler:function(a){var b=this.handlers;f(b,function(c,d){if(c.equals(a))return c.destroy(),!b.splice(d,1)})},matches:function(){return this.mql.matches||this.isUnconditional},clear:function(){f(this.handlers,function(a){a.destroy()}),this.mql.removeListener(this.listener),this.handlers.length=0},assess:function(){var a=this.matches()?"on":"off";f(this.handlers,function(b){b[a]()})}},b.exports=d},{3:3,4:4}],2:[function(a,b,c){function d(){if(!window.matchMedia)throw new Error("matchMedia not present, legacy browsers require a polyfill");this.queries={},this.browserIsIncapable=!window.matchMedia("only all").matches}var e=a(1),f=a(4),g=f.each,h=f.isFunction,i=f.isArray;d.prototype={constructor:d,register:function(a,b,c){var d=this.queries,f=c&&this.browserIsIncapable;return d[a]||(d[a]=new e(a,f)),h(b)&&(b={match:b}),i(b)||(b=[b]),g(b,function(b){h(b)&&(b={match:b}),d[a].addHandler(b)}),this},unregister:function(a,b){var c=this.queries[a];return c&&(b?c.removeHandler(b):(c.clear(),delete this.queries[a])),this}},b.exports=d},{1:1,4:4}],3:[function(a,b,c){function d(a){this.options=a,!a.deferSetup&&this.setup()}d.prototype={constructor:d,setup:function(){this.options.setup&&this.options.setup(),this.initialised=!0},on:function(){!this.initialised&&this.setup(),this.options.match&&this.options.match()},off:function(){this.options.unmatch&&this.options.unmatch()},destroy:function(){this.options.destroy?this.options.destroy():this.off()},equals:function(a){return this.options===a||this.options.match===a}},b.exports=d},{}],4:[function(a,b,c){function d(a,b){var c=0,d=a.length;for(c;c<d&&b(a[c],c)!==!1;c++);}function e(a){return"[object Array]"===Object.prototype.toString.apply(a)}function f(a){return"function"==typeof a}b.exports={isFunction:f,isArray:e,each:d}},{}],5:[function(a,b,c){var d=a(2);b.exports=new d},{2:2}]},{},[5])(5)});;
/**
 * Lightbox v2.7.1
 * by Lokesh Dhakar - http://lokeshdhakar.com/projects/lightbox2/
 *
 * @license http://creativecommons.org/licenses/by/2.5/
 * - Free for use in both personal and commercial projects
 * - Attribution requires leaving author name, author link, and the license info intact
 */
(function () { var a = jQuery, b = function () { function a() { this.fadeDuration = 500, this.fitImagesInViewport = !0, this.resizeDuration = 700, this.positionFromTop = 50, this.showImageNumberLabel = !0, this.alwaysShowNavOnTouchDevices = !1, this.wrapAround = !1 } return a.prototype.albumLabel = function (a, b) { return "Image " + a + " of " + b }, a }(), c = function () { function b(a) { this.options = a, this.album = [], this.currentImageIndex = void 0, this.init() } return b.prototype.init = function () { this.enable(), this.build() }, b.prototype.enable = function () { var b = this; a("body").on("click", "a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]", function (c) { return b.start(a(c.currentTarget)), !1 }) }, b.prototype.build = function () { var b = this; a("<div id='lightboxOverlay' class='lightboxOverlay'></div><div id='lightbox' class='lightbox'><div class='lb-outerContainer'><div class='lb-container'><img class='lb-image' src='' alt='' /><div class='lb-nav'><a class='lb-prev' aria-label='Prev' href='' ></a><a class='lb-next' aria-label='Next' href='' ></a></div><div class='lb-loader'><a class='lb-cancel'></a></div></div></div><div class='lb-dataContainer'><div class='lb-data'><div class='lb-details'><span class='lb-caption'></span><span class='lb-number'></span></div><div class='lb-closeContainer'><a class='lb-close'></a></div></div></div></div>").appendTo(a("body")),this.$lightbox=a("#lightbox"),this.$overlay=a("#lightboxOverlay"),this.$outerContainer=this.$lightbox.find(".lb-outerContainer"),this.$container=this.$lightbox.find(".lb-container"),this.containerTopPadding=parseInt(this.$container.css("padding-top"),10),this.containerRightPadding=parseInt(this.$container.css("padding-right"),10),this.containerBottomPadding=parseInt(this.$container.css("padding-bottom"),10),this.containerLeftPadding=parseInt(this.$container.css("padding-left"),10),this.$overlay.hide().on("click",function(){return b.end(),!1}),this.$lightbox.hide().on("click",function(c){return"lightbox"===a(c.target).attr("id")&&b.end(),!1}),this.$outerContainer.on("click",function(c){return"lightbox"===a(c.target).attr("id")&&b.end(),!1}),this.$lightbox.find(".lb-prev").on("click",function(){return b.changeImage(0===b.currentImageIndex?b.album.length-1:b.currentImageIndex-1),!1}),this.$lightbox.find(".lb-next").on("click",function(){return b.changeImage(b.currentImageIndex===b.album.length-1?0:b.currentImageIndex+1),!1}),this.$lightbox.find(".lb-loader, .lb-close").on("click",function(){return b.end(),!1})},b.prototype.start=function(b){function c(a){d.album.push({link:a.attr("href"),title:a.attr("data-title")||a.attr("title")})}var d=this,e=a(window);e.on("resize",a.proxy(this.sizeOverlay,this)),a("select, object, embed").css({visibility:"hidden"}),this.sizeOverlay(),this.album=[];var f,g=0,h=b.attr("data-lightbox");if(h){f=a(b.prop("tagName")+'[data-lightbox="'+h+'"]');for(var i=0;i<f.length;i=++i)c(a(f[i])),f[i]===b[0]&&(g=i)}else if("lightbox"===b.attr("rel"))c(b);else{f=a(b.prop("tagName")+'[rel="'+b.attr("rel")+'"]');for(var j=0;j<f.length;j=++j)c(a(f[j])),f[j]===b[0]&&(g=j)}var k=e.scrollTop()+this.options.positionFromTop,l=e.scrollLeft();this.$lightbox.css({top:k+"px",left:l+"px"}).fadeIn(this.options.fadeDuration),this.changeImage(g)},b.prototype.changeImage=function(b){var c=this;this.disableKeyboardNav();var d=this.$lightbox.find(".lb-image");this.$overlay.fadeIn(this.options.fadeDuration),a(".lb-loader").fadeIn("slow"),this.$lightbox.find(".lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption").hide(),this.$outerContainer.addClass("animating");var e=new Image;e.onload=function(){var f,g,h,i,j,k,l;d.attr("src",c.album[b].link),f=a(e),d.width(e.width),d.height(e.height),c.options.fitImagesInViewport&&(l=a(window).width(),k=a(window).height(),j=l-c.containerLeftPadding-c.containerRightPadding-20,i=k-c.containerTopPadding-c.containerBottomPadding-120,(e.width>j||e.height>i)&&(e.width/j>e.height/i?(h=j,g=parseInt(e.height/(e.width/h),10),d.width(h),d.height(g)):(g=i,h=parseInt(e.width/(e.height/g),10),d.width(h),d.height(g)))),c.sizeContainer(d.width(),d.height())},e.src=this.album[b].link,this.currentImageIndex=b},b.prototype.sizeOverlay=function(){this.$overlay.width(a(window).width()).height(a(document).height())},b.prototype.sizeContainer=function(a,b){function c(){d.$lightbox.find(".lb-dataContainer").width(g),d.$lightbox.find(".lb-prevLink").height(h),d.$lightbox.find(".lb-nextLink").height(h),d.showImage()}var d=this,e=this.$outerContainer.outerWidth(),f=this.$outerContainer.outerHeight(),g=a+this.containerLeftPadding+this.containerRightPadding,h=b+this.containerTopPadding+this.containerBottomPadding;e!==g||f!==h?this.$outerContainer.animate({width:g,height:h},this.options.resizeDuration,"swing",function(){c()}):c()},b.prototype.showImage=function(){this.$lightbox.find(".lb-loader").hide(),this.$lightbox.find(".lb-image").fadeIn("slow"),this.updateNav(),this.updateDetails(),this.preloadNeighboringImages(),this.enableKeyboardNav()},b.prototype.updateNav=function(){var a=!1;try{document.createEvent("TouchEvent"),a=this.options.alwaysShowNavOnTouchDevices?!0:!1}catch(b){}this.$lightbox.find(".lb-nav").show(),this.album.length>1&&(this.options.wrapAround?(a&&this.$lightbox.find(".lb-prev, .lb-next").css("opacity","1"),this.$lightbox.find(".lb-prev, .lb-next").show()):(this.currentImageIndex>0&&(this.$lightbox.find(".lb-prev").show(),a&&this.$lightbox.find(".lb-prev").css("opacity","1")),this.currentImageIndex<this.album.length-1&&(this.$lightbox.find(".lb-next").show(),a&&this.$lightbox.find(".lb-next").css("opacity","1"))))},b.prototype.updateDetails=function(){var b=this;"undefined"!=typeof this.album[this.currentImageIndex].title&&""!==this.album[this.currentImageIndex].title&&this.$lightbox.find(".lb-caption").html(this.album[this.currentImageIndex].title).fadeIn("fast").find("a").on("click",function(){location.href=a(this).attr("href")}),this.album.length>1&&this.options.showImageNumberLabel?this.$lightbox.find(".lb-number").text(this.options.albumLabel(this.currentImageIndex+1,this.album.length)).fadeIn("fast"):this.$lightbox.find(".lb-number").hide(),this.$outerContainer.removeClass("animating"),this.$lightbox.find(".lb-dataContainer").fadeIn(this.options.resizeDuration,function(){return b.sizeOverlay()})},b.prototype.preloadNeighboringImages=function(){if(this.album.length>this.currentImageIndex+1){var a=new Image;a.src=this.album[this.currentImageIndex+1].link}if(this.currentImageIndex>0){var b=new Image;b.src=this.album[this.currentImageIndex-1].link}},b.prototype.enableKeyboardNav=function(){a(document).on("keyup.keyboard",a.proxy(this.keyboardAction,this))},b.prototype.disableKeyboardNav=function(){a(document).off(".keyboard")},b.prototype.keyboardAction=function(a){var b=27,c=37,d=39,e=a.keyCode,f=String.fromCharCode(e).toLowerCase();e===b||f.match(/x|o|c/)?this.end():"p"===f||e===c?0!==this.currentImageIndex?this.changeImage(this.currentImageIndex-1):this.options.wrapAround&&this.album.length>1&&this.changeImage(this.album.length-1):("n"===f||e===d)&&(this.currentImageIndex!==this.album.length-1?this.changeImage(this.currentImageIndex+1):this.options.wrapAround&&this.album.length>1&&this.changeImage(0))},b.prototype.end=function(){this.disableKeyboardNav(),a(window).off("resize",this.sizeOverlay),this.$lightbox.fadeOut(this.options.fadeDuration),this.$overlay.fadeOut(this.options.fadeDuration),a("select, object, embed").css({visibility:"visible"})},b}();a(function(){{var a=new b;new c(a)}})}).call(this);
;
!function(i){"use strict";"function"==typeof define&&define.amd?define(["jquery"],i):"undefined"!=typeof exports?module.exports=i(require("jquery")):i(jQuery)}(function(i){"use strict";var e=window.Slick||{};(e=function(){var e=0;return function(t,o){var s,n=this;n.defaults={accessibility:!0,adaptiveHeight:!1,appendArrows:i(t),appendDots:i(t),arrows:!0,asNavFor:null,prevArrow:'<button class="slick-prev" aria-label="Previous" type="button">Previous</button>',nextArrow:'<button class="slick-next" aria-label="Next" type="button">Next</button>',autoplay:!1,autoplaySpeed:3e3,centerMode:!1,centerPadding:"50px",cssEase:"ease",customPaging:function(e,t){return i('<button type="button" />').text(t+1)},dots:!1,dotsClass:"slick-dots",draggable:!0,easing:"linear",edgeFriction:.35,fade:!1,focusOnSelect:!1,focusOnChange:!1,infinite:!0,initialSlide:0,lazyLoad:"ondemand",mobileFirst:!1,pauseOnHover:!0,pauseOnFocus:!0,pauseOnDotsHover:!1,respondTo:"window",responsive:null,rows:1,rtl:!1,slide:"",slidesPerRow:1,slidesToShow:1,slidesToScroll:1,speed:500,swipe:!0,swipeToSlide:!1,touchMove:!0,touchThreshold:5,useCSS:!0,useTransform:!0,variableWidth:!1,vertical:!1,verticalSwiping:!1,waitForAnimate:!0,zIndex:1e3},n.initials={animating:!1,dragging:!1,autoPlayTimer:null,currentDirection:0,currentLeft:null,currentSlide:0,direction:1,$dots:null,listWidth:null,listHeight:null,loadIndex:0,$nextArrow:null,$prevArrow:null,scrolling:!1,slideCount:null,slideWidth:null,$slideTrack:null,$slides:null,sliding:!1,slideOffset:0,swipeLeft:null,swiping:!1,$list:null,touchObject:{},transformsEnabled:!1,unslicked:!1},i.extend(n,n.initials),n.activeBreakpoint=null,n.animType=null,n.animProp=null,n.breakpoints=[],n.breakpointSettings=[],n.cssTransitions=!1,n.focussed=!1,n.interrupted=!1,n.hidden="hidden",n.paused=!0,n.positionProp=null,n.respondTo=null,n.rowCount=1,n.shouldClick=!0,n.$slider=i(t),n.$slidesCache=null,n.transformType=null,n.transitionType=null,n.visibilityChange="visibilitychange",n.windowWidth=0,n.windowTimer=null,s=i(t).data("slick")||{},n.options=i.extend({},n.defaults,o,s),n.currentSlide=n.options.initialSlide,n.originalSettings=n.options,void 0!==document.mozHidden?(n.hidden="mozHidden",n.visibilityChange="mozvisibilitychange"):void 0!==document.webkitHidden&&(n.hidden="webkitHidden",n.visibilityChange="webkitvisibilitychange"),n.autoPlay=i.proxy(n.autoPlay,n),n.autoPlayClear=i.proxy(n.autoPlayClear,n),n.autoPlayIterator=i.proxy(n.autoPlayIterator,n),n.changeSlide=i.proxy(n.changeSlide,n),n.clickHandler=i.proxy(n.clickHandler,n),n.selectHandler=i.proxy(n.selectHandler,n),n.setPosition=i.proxy(n.setPosition,n),n.swipeHandler=i.proxy(n.swipeHandler,n),n.dragHandler=i.proxy(n.dragHandler,n),n.keyHandler=i.proxy(n.keyHandler,n),n.instanceUid=e++,n.htmlExpr=/^(?:\s*(<[\w\W]+>)[^>]*)$/,n.registerBreakpoints(),n.init(!0)}}()).prototype.activateADA=function(){this.$slideTrack.find(".slick-active").attr({"aria-hidden":"false"}).find("a, input, button, select").attr({tabindex:"0"})},e.prototype.addSlide=e.prototype.slickAdd=function(e,t,o){var s=this;if("boolean"==typeof t)o=t,t=null;else if(t<0||t>=s.slideCount)return!1;s.unload(),"number"==typeof t?0===t&&0===s.$slides.length?i(e).appendTo(s.$slideTrack):o?i(e).insertBefore(s.$slides.eq(t)):i(e).insertAfter(s.$slides.eq(t)):!0===o?i(e).prependTo(s.$slideTrack):i(e).appendTo(s.$slideTrack),s.$slides=s.$slideTrack.children(this.options.slide),s.$slideTrack.children(this.options.slide).detach(),s.$slideTrack.append(s.$slides),s.$slides.each(function(e,t){i(t).attr("data-slick-index",e)}),s.$slidesCache=s.$slides,s.reinit()},e.prototype.animateHeight=function(){var i=this;if(1===i.options.slidesToShow&&!0===i.options.adaptiveHeight&&!1===i.options.vertical){var e=i.$slides.eq(i.currentSlide).outerHeight(!0);i.$list.animate({height:e},i.options.speed)}},e.prototype.animateSlide=function(e,t){var o={},s=this;s.animateHeight(),!0===s.options.rtl&&!1===s.options.vertical&&(e=-e),!1===s.transformsEnabled?!1===s.options.vertical?s.$slideTrack.animate({left:e},s.options.speed,s.options.easing,t):s.$slideTrack.animate({top:e},s.options.speed,s.options.easing,t):!1===s.cssTransitions?(!0===s.options.rtl&&(s.currentLeft=-s.currentLeft),i({animStart:s.currentLeft}).animate({animStart:e},{duration:s.options.speed,easing:s.options.easing,step:function(i){i=Math.ceil(i),!1===s.options.vertical?(o[s.animType]="translate("+i+"px, 0px)",s.$slideTrack.css(o)):(o[s.animType]="translate(0px,"+i+"px)",s.$slideTrack.css(o))},complete:function(){t&&t.call()}})):(s.applyTransition(),e=Math.ceil(e),!1===s.options.vertical?o[s.animType]="translate3d("+e+"px, 0px, 0px)":o[s.animType]="translate3d(0px,"+e+"px, 0px)",s.$slideTrack.css(o),t&&setTimeout(function(){s.disableTransition(),t.call()},s.options.speed))},e.prototype.getNavTarget=function(){var e=this,t=e.options.asNavFor;return t&&null!==t&&(t=i(t).not(e.$slider)),t},e.prototype.asNavFor=function(e){var t=this.getNavTarget();null!==t&&"object"==typeof t&&t.each(function(){var t=i(this).slick("getSlick");t.unslicked||t.slideHandler(e,!0)})},e.prototype.applyTransition=function(i){var e=this,t={};!1===e.options.fade?t[e.transitionType]=e.transformType+" "+e.options.speed+"ms "+e.options.cssEase:t[e.transitionType]="opacity "+e.options.speed+"ms "+e.options.cssEase,!1===e.options.fade?e.$slideTrack.css(t):e.$slides.eq(i).css(t)},e.prototype.autoPlay=function(){var i=this;i.autoPlayClear(),i.slideCount>i.options.slidesToShow&&(i.autoPlayTimer=setInterval(i.autoPlayIterator,i.options.autoplaySpeed))},e.prototype.autoPlayClear=function(){var i=this;i.autoPlayTimer&&clearInterval(i.autoPlayTimer)},e.prototype.autoPlayIterator=function(){var i=this,e=i.currentSlide+i.options.slidesToScroll;i.paused||i.interrupted||i.focussed||(!1===i.options.infinite&&(1===i.direction&&i.currentSlide+1===i.slideCount-1?i.direction=0:0===i.direction&&(e=i.currentSlide-i.options.slidesToScroll,i.currentSlide-1==0&&(i.direction=1))),i.slideHandler(e))},e.prototype.buildArrows=function(){var e=this;!0===e.options.arrows&&(e.$prevArrow=i(e.options.prevArrow).addClass("slick-arrow"),e.$nextArrow=i(e.options.nextArrow).addClass("slick-arrow"),e.slideCount>e.options.slidesToShow?(e.$prevArrow.removeClass("slick-hidden").removeAttr("aria-hidden tabindex"),e.$nextArrow.removeClass("slick-hidden").removeAttr("aria-hidden tabindex"),e.htmlExpr.test(e.options.prevArrow)&&e.$prevArrow.prependTo(e.options.appendArrows),e.htmlExpr.test(e.options.nextArrow)&&e.$nextArrow.appendTo(e.options.appendArrows),!0!==e.options.infinite&&e.$prevArrow.addClass("slick-disabled").attr("aria-disabled","true")):e.$prevArrow.add(e.$nextArrow).addClass("slick-hidden").attr({"aria-disabled":"true",tabindex:"-1"}))},e.prototype.buildDots=function(){var e,t,o=this;if(!0===o.options.dots){for(o.$slider.addClass("slick-dotted"),t=i("<ul />").addClass(o.options.dotsClass),e=0;e<=o.getDotCount();e+=1)t.append(i("<li />").append(o.options.customPaging.call(this,o,e)));o.$dots=t.appendTo(o.options.appendDots),o.$dots.find("li").first().addClass("slick-active")}},e.prototype.buildOut=function(){var e=this;e.$slides=e.$slider.children(e.options.slide+":not(.slick-cloned)").addClass("slick-slide"),e.slideCount=e.$slides.length,e.$slides.each(function(e,t){i(t).attr("data-slick-index",e).data("originalStyling",i(t).attr("style")||"")}),e.$slider.addClass("slick-slider"),e.$slideTrack=0===e.slideCount?i('<div class="slick-track"/>').appendTo(e.$slider):e.$slides.wrapAll('<div class="slick-track"/>').parent(),e.$list=e.$slideTrack.wrap('<div class="slick-list"/>').parent(),e.$slideTrack.css("opacity",0),!0!==e.options.centerMode&&!0!==e.options.swipeToSlide||(e.options.slidesToScroll=1),i("img[data-lazy]",e.$slider).not("[src]").addClass("slick-loading"),e.setupInfinite(),e.buildArrows(),e.buildDots(),e.updateDots(),e.setSlideClasses("number"==typeof e.currentSlide?e.currentSlide:0),!0===e.options.draggable&&e.$list.addClass("draggable")},e.prototype.buildRows=function(){var i,e,t,o,s,n,r,l=this;if(o=document.createDocumentFragment(),n=l.$slider.children(),l.options.rows>1){for(r=l.options.slidesPerRow*l.options.rows,s=Math.ceil(n.length/r),i=0;i<s;i++){var d=document.createElement("div");for(e=0;e<l.options.rows;e++){var a=document.createElement("div");for(t=0;t<l.options.slidesPerRow;t++){var c=i*r+(e*l.options.slidesPerRow+t);n.get(c)&&a.appendChild(n.get(c))}d.appendChild(a)}o.appendChild(d)}l.$slider.empty().append(o),l.$slider.children().children().children().css({width:100/l.options.slidesPerRow+"%",display:"inline-block"})}},e.prototype.checkResponsive=function(e,t){var o,s,n,r=this,l=!1,d=r.$slider.width(),a=window.innerWidth||i(window).width();if("window"===r.respondTo?n=a:"slider"===r.respondTo?n=d:"min"===r.respondTo&&(n=Math.min(a,d)),r.options.responsive&&r.options.responsive.length&&null!==r.options.responsive){s=null;for(o in r.breakpoints)r.breakpoints.hasOwnProperty(o)&&(!1===r.originalSettings.mobileFirst?n<r.breakpoints[o]&&(s=r.breakpoints[o]):n>r.breakpoints[o]&&(s=r.breakpoints[o]));null!==s?null!==r.activeBreakpoint?(s!==r.activeBreakpoint||t)&&(r.activeBreakpoint=s,"unslick"===r.breakpointSettings[s]?r.unslick(s):(r.options=i.extend({},r.originalSettings,r.breakpointSettings[s]),!0===e&&(r.currentSlide=r.options.initialSlide),r.refresh(e)),l=s):(r.activeBreakpoint=s,"unslick"===r.breakpointSettings[s]?r.unslick(s):(r.options=i.extend({},r.originalSettings,r.breakpointSettings[s]),!0===e&&(r.currentSlide=r.options.initialSlide),r.refresh(e)),l=s):null!==r.activeBreakpoint&&(r.activeBreakpoint=null,r.options=r.originalSettings,!0===e&&(r.currentSlide=r.options.initialSlide),r.refresh(e),l=s),e||!1===l||r.$slider.trigger("breakpoint",[r,l])}},e.prototype.changeSlide=function(e,t){var o,s,n,r=this,l=i(e.currentTarget);switch(l.is("a")&&e.preventDefault(),l.is("li")||(l=l.closest("li")),n=r.slideCount%r.options.slidesToScroll!=0,o=n?0:(r.slideCount-r.currentSlide)%r.options.slidesToScroll,e.data.message){case"previous":s=0===o?r.options.slidesToScroll:r.options.slidesToShow-o,r.slideCount>r.options.slidesToShow&&r.slideHandler(r.currentSlide-s,!1,t);break;case"next":s=0===o?r.options.slidesToScroll:o,r.slideCount>r.options.slidesToShow&&r.slideHandler(r.currentSlide+s,!1,t);break;case"index":var d=0===e.data.index?0:e.data.index||l.index()*r.options.slidesToScroll;r.slideHandler(r.checkNavigable(d),!1,t),l.children().trigger("focus");break;default:return}},e.prototype.checkNavigable=function(i){var e,t;if(e=this.getNavigableIndexes(),t=0,i>e[e.length-1])i=e[e.length-1];else for(var o in e){if(i<e[o]){i=t;break}t=e[o]}return i},e.prototype.cleanUpEvents=function(){var e=this;e.options.dots&&null!==e.$dots&&(i("li",e.$dots).off("click.slick",e.changeSlide).off("mouseenter.slick",i.proxy(e.interrupt,e,!0)).off("mouseleave.slick",i.proxy(e.interrupt,e,!1)),!0===e.options.accessibility&&e.$dots.off("keydown.slick",e.keyHandler)),e.$slider.off("focus.slick blur.slick"),!0===e.options.arrows&&e.slideCount>e.options.slidesToShow&&(e.$prevArrow&&e.$prevArrow.off("click.slick",e.changeSlide),e.$nextArrow&&e.$nextArrow.off("click.slick",e.changeSlide),!0===e.options.accessibility&&(e.$prevArrow&&e.$prevArrow.off("keydown.slick",e.keyHandler),e.$nextArrow&&e.$nextArrow.off("keydown.slick",e.keyHandler))),e.$list.off("touchstart.slick mousedown.slick",e.swipeHandler),e.$list.off("touchmove.slick mousemove.slick",e.swipeHandler),e.$list.off("touchend.slick mouseup.slick",e.swipeHandler),e.$list.off("touchcancel.slick mouseleave.slick",e.swipeHandler),e.$list.off("click.slick",e.clickHandler),i(document).off(e.visibilityChange,e.visibility),e.cleanUpSlideEvents(),!0===e.options.accessibility&&e.$list.off("keydown.slick",e.keyHandler),!0===e.options.focusOnSelect&&i(e.$slideTrack).children().off("click.slick",e.selectHandler),i(window).off("orientationchange.slick.slick-"+e.instanceUid,e.orientationChange),i(window).off("resize.slick.slick-"+e.instanceUid,e.resize),i("[draggable!=true]",e.$slideTrack).off("dragstart",e.preventDefault),i(window).off("load.slick.slick-"+e.instanceUid,e.setPosition)},e.prototype.cleanUpSlideEvents=function(){var e=this;e.$list.off("mouseenter.slick",i.proxy(e.interrupt,e,!0)),e.$list.off("mouseleave.slick",i.proxy(e.interrupt,e,!1))},e.prototype.cleanUpRows=function(){var i,e=this;e.options.rows>1&&((i=e.$slides.children().children()).removeAttr("style"),e.$slider.empty().append(i))},e.prototype.clickHandler=function(i){!1===this.shouldClick&&(i.stopImmediatePropagation(),i.stopPropagation(),i.preventDefault())},e.prototype.destroy=function(e){var t=this;t.autoPlayClear(),t.touchObject={},t.cleanUpEvents(),i(".slick-cloned",t.$slider).detach(),t.$dots&&t.$dots.remove(),t.$prevArrow&&t.$prevArrow.length&&(t.$prevArrow.removeClass("slick-disabled slick-arrow slick-hidden").removeAttr("aria-hidden aria-disabled tabindex").css("display",""),t.htmlExpr.test(t.options.prevArrow)&&t.$prevArrow.remove()),t.$nextArrow&&t.$nextArrow.length&&(t.$nextArrow.removeClass("slick-disabled slick-arrow slick-hidden").removeAttr("aria-hidden aria-disabled tabindex").css("display",""),t.htmlExpr.test(t.options.nextArrow)&&t.$nextArrow.remove()),t.$slides&&(t.$slides.removeClass("slick-slide slick-active slick-center slick-visible slick-current").removeAttr("aria-hidden").removeAttr("data-slick-index").each(function(){i(this).attr("style",i(this).data("originalStyling"))}),t.$slideTrack.children(this.options.slide).detach(),t.$slideTrack.detach(),t.$list.detach(),t.$slider.append(t.$slides)),t.cleanUpRows(),t.$slider.removeClass("slick-slider"),t.$slider.removeClass("slick-initialized"),t.$slider.removeClass("slick-dotted"),t.unslicked=!0,e||t.$slider.trigger("destroy",[t])},e.prototype.disableTransition=function(i){var e=this,t={};t[e.transitionType]="",!1===e.options.fade?e.$slideTrack.css(t):e.$slides.eq(i).css(t)},e.prototype.fadeSlide=function(i,e){var t=this;!1===t.cssTransitions?(t.$slides.eq(i).css({zIndex:t.options.zIndex}),t.$slides.eq(i).animate({opacity:1},t.options.speed,t.options.easing,e)):(t.applyTransition(i),t.$slides.eq(i).css({opacity:1,zIndex:t.options.zIndex}),e&&setTimeout(function(){t.disableTransition(i),e.call()},t.options.speed))},e.prototype.fadeSlideOut=function(i){var e=this;!1===e.cssTransitions?e.$slides.eq(i).animate({opacity:0,zIndex:e.options.zIndex-2},e.options.speed,e.options.easing):(e.applyTransition(i),e.$slides.eq(i).css({opacity:0,zIndex:e.options.zIndex-2}))},e.prototype.filterSlides=e.prototype.slickFilter=function(i){var e=this;null!==i&&(e.$slidesCache=e.$slides,e.unload(),e.$slideTrack.children(this.options.slide).detach(),e.$slidesCache.filter(i).appendTo(e.$slideTrack),e.reinit())},e.prototype.focusHandler=function(){var e=this;e.$slider.off("focus.slick blur.slick").on("focus.slick blur.slick","*",function(t){t.stopImmediatePropagation();var o=i(this);setTimeout(function(){e.options.pauseOnFocus&&(e.focussed=o.is(":focus"),e.autoPlay())},0)})},e.prototype.getCurrent=e.prototype.slickCurrentSlide=function(){return this.currentSlide},e.prototype.getDotCount=function(){var i=this,e=0,t=0,o=0;if(!0===i.options.infinite)if(i.slideCount<=i.options.slidesToShow)++o;else for(;e<i.slideCount;)++o,e=t+i.options.slidesToScroll,t+=i.options.slidesToScroll<=i.options.slidesToShow?i.options.slidesToScroll:i.options.slidesToShow;else if(!0===i.options.centerMode)o=i.slideCount;else if(i.options.asNavFor)for(;e<i.slideCount;)++o,e=t+i.options.slidesToScroll,t+=i.options.slidesToScroll<=i.options.slidesToShow?i.options.slidesToScroll:i.options.slidesToShow;else o=1+Math.ceil((i.slideCount-i.options.slidesToShow)/i.options.slidesToScroll);return o-1},e.prototype.getLeft=function(i){var e,t,o,s,n=this,r=0;return n.slideOffset=0,t=n.$slides.first().outerHeight(!0),!0===n.options.infinite?(n.slideCount>n.options.slidesToShow&&(n.slideOffset=n.slideWidth*n.options.slidesToShow*-1,s=-1,!0===n.options.vertical&&!0===n.options.centerMode&&(2===n.options.slidesToShow?s=-1.5:1===n.options.slidesToShow&&(s=-2)),r=t*n.options.slidesToShow*s),n.slideCount%n.options.slidesToScroll!=0&&i+n.options.slidesToScroll>n.slideCount&&n.slideCount>n.options.slidesToShow&&(i>n.slideCount?(n.slideOffset=(n.options.slidesToShow-(i-n.slideCount))*n.slideWidth*-1,r=(n.options.slidesToShow-(i-n.slideCount))*t*-1):(n.slideOffset=n.slideCount%n.options.slidesToScroll*n.slideWidth*-1,r=n.slideCount%n.options.slidesToScroll*t*-1))):i+n.options.slidesToShow>n.slideCount&&(n.slideOffset=(i+n.options.slidesToShow-n.slideCount)*n.slideWidth,r=(i+n.options.slidesToShow-n.slideCount)*t),n.slideCount<=n.options.slidesToShow&&(n.slideOffset=0,r=0),!0===n.options.centerMode&&n.slideCount<=n.options.slidesToShow?n.slideOffset=n.slideWidth*Math.floor(n.options.slidesToShow)/2-n.slideWidth*n.slideCount/2:!0===n.options.centerMode&&!0===n.options.infinite?n.slideOffset+=n.slideWidth*Math.floor(n.options.slidesToShow/2)-n.slideWidth:!0===n.options.centerMode&&(n.slideOffset=0,n.slideOffset+=n.slideWidth*Math.floor(n.options.slidesToShow/2)),e=!1===n.options.vertical?i*n.slideWidth*-1+n.slideOffset:i*t*-1+r,!0===n.options.variableWidth&&(o=n.slideCount<=n.options.slidesToShow||!1===n.options.infinite?n.$slideTrack.children(".slick-slide").eq(i):n.$slideTrack.children(".slick-slide").eq(i+n.options.slidesToShow),e=!0===n.options.rtl?o[0]?-1*(n.$slideTrack.width()-o[0].offsetLeft-o.width()):0:o[0]?-1*o[0].offsetLeft:0,!0===n.options.centerMode&&(o=n.slideCount<=n.options.slidesToShow||!1===n.options.infinite?n.$slideTrack.children(".slick-slide").eq(i):n.$slideTrack.children(".slick-slide").eq(i+n.options.slidesToShow+1),e=!0===n.options.rtl?o[0]?-1*(n.$slideTrack.width()-o[0].offsetLeft-o.width()):0:o[0]?-1*o[0].offsetLeft:0,e+=(n.$list.width()-o.outerWidth())/2)),e},e.prototype.getOption=e.prototype.slickGetOption=function(i){return this.options[i]},e.prototype.getNavigableIndexes=function(){var i,e=this,t=0,o=0,s=[];for(!1===e.options.infinite?i=e.slideCount:(t=-1*e.options.slidesToScroll,o=-1*e.options.slidesToScroll,i=2*e.slideCount);t<i;)s.push(t),t=o+e.options.slidesToScroll,o+=e.options.slidesToScroll<=e.options.slidesToShow?e.options.slidesToScroll:e.options.slidesToShow;return s},e.prototype.getSlick=function(){return this},e.prototype.getSlideCount=function(){var e,t,o=this;return t=!0===o.options.centerMode?o.slideWidth*Math.floor(o.options.slidesToShow/2):0,!0===o.options.swipeToSlide?(o.$slideTrack.find(".slick-slide").each(function(s,n){if(n.offsetLeft-t+i(n).outerWidth()/2>-1*o.swipeLeft)return e=n,!1}),Math.abs(i(e).attr("data-slick-index")-o.currentSlide)||1):o.options.slidesToScroll},e.prototype.goTo=e.prototype.slickGoTo=function(i,e){this.changeSlide({data:{message:"index",index:parseInt(i)}},e)},e.prototype.init=function(e){var t=this;i(t.$slider).hasClass("slick-initialized")||(i(t.$slider).addClass("slick-initialized"),t.buildRows(),t.buildOut(),t.setProps(),t.startLoad(),t.loadSlider(),t.initializeEvents(),t.updateArrows(),t.updateDots(),t.checkResponsive(!0),t.focusHandler()),e&&t.$slider.trigger("init",[t]),!0===t.options.accessibility&&t.initADA(),t.options.autoplay&&(t.paused=!1,t.autoPlay())},e.prototype.initADA=function(){var e=this,t=Math.ceil(e.slideCount/e.options.slidesToShow),o=e.getNavigableIndexes().filter(function(i){return i>=0&&i<e.slideCount});e.$slides.add(e.$slideTrack.find(".slick-cloned")).attr({"aria-hidden":"true",tabindex:"-1"}).find("a, input, button, select").attr({tabindex:"-1"}),null!==e.$dots&&(e.$slides.not(e.$slideTrack.find(".slick-cloned")).each(function(t){var s=o.indexOf(t);i(this).attr({role:"tabpanel",id:"slick-slide"+e.instanceUid+t,tabindex:-1}),-1!==s&&i(this).attr({"aria-describedby":"slick-slide-control"+e.instanceUid+s})}),e.$dots.attr("role","tablist").find("li").each(function(s){var n=o[s];i(this).attr({role:"presentation"}),i(this).find("button").first().attr({role:"tab",id:"slick-slide-control"+e.instanceUid+s,"aria-controls":"slick-slide"+e.instanceUid+n,"aria-label":s+1+" of "+t,"aria-selected":null,tabindex:"-1"})}).eq(e.currentSlide).find("button").attr({"aria-selected":"true",tabindex:"0"}).end());for(var s=e.currentSlide,n=s+e.options.slidesToShow;s<n;s++)e.$slides.eq(s).attr("tabindex",0);e.activateADA()},e.prototype.initArrowEvents=function(){var i=this;!0===i.options.arrows&&i.slideCount>i.options.slidesToShow&&(i.$prevArrow.off("click.slick").on("click.slick",{message:"previous"},i.changeSlide),i.$nextArrow.off("click.slick").on("click.slick",{message:"next"},i.changeSlide),!0===i.options.accessibility&&(i.$prevArrow.on("keydown.slick",i.keyHandler),i.$nextArrow.on("keydown.slick",i.keyHandler)))},e.prototype.initDotEvents=function(){var e=this;!0===e.options.dots&&(i("li",e.$dots).on("click.slick",{message:"index"},e.changeSlide),!0===e.options.accessibility&&e.$dots.on("keydown.slick",e.keyHandler)),!0===e.options.dots&&!0===e.options.pauseOnDotsHover&&i("li",e.$dots).on("mouseenter.slick",i.proxy(e.interrupt,e,!0)).on("mouseleave.slick",i.proxy(e.interrupt,e,!1))},e.prototype.initSlideEvents=function(){var e=this;e.options.pauseOnHover&&(e.$list.on("mouseenter.slick",i.proxy(e.interrupt,e,!0)),e.$list.on("mouseleave.slick",i.proxy(e.interrupt,e,!1)))},e.prototype.initializeEvents=function(){var e=this;e.initArrowEvents(),e.initDotEvents(),e.initSlideEvents(),e.$list.on("touchstart.slick mousedown.slick",{action:"start"},e.swipeHandler),e.$list.on("touchmove.slick mousemove.slick",{action:"move"},e.swipeHandler),e.$list.on("touchend.slick mouseup.slick",{action:"end"},e.swipeHandler),e.$list.on("touchcancel.slick mouseleave.slick",{action:"end"},e.swipeHandler),e.$list.on("click.slick",e.clickHandler),i(document).on(e.visibilityChange,i.proxy(e.visibility,e)),!0===e.options.accessibility&&e.$list.on("keydown.slick",e.keyHandler),!0===e.options.focusOnSelect&&i(e.$slideTrack).children().on("click.slick",e.selectHandler),i(window).on("orientationchange.slick.slick-"+e.instanceUid,i.proxy(e.orientationChange,e)),i(window).on("resize.slick.slick-"+e.instanceUid,i.proxy(e.resize,e)),i("[draggable!=true]",e.$slideTrack).on("dragstart",e.preventDefault),i(window).on("load.slick.slick-"+e.instanceUid,e.setPosition),i(e.setPosition)},e.prototype.initUI=function(){var i=this;!0===i.options.arrows&&i.slideCount>i.options.slidesToShow&&(i.$prevArrow.show(),i.$nextArrow.show()),!0===i.options.dots&&i.slideCount>i.options.slidesToShow&&i.$dots.show()},e.prototype.keyHandler=function(i){var e=this;i.target.tagName.match("TEXTAREA|INPUT|SELECT")||(37===i.keyCode&&!0===e.options.accessibility?e.changeSlide({data:{message:!0===e.options.rtl?"next":"previous"}}):39===i.keyCode&&!0===e.options.accessibility&&e.changeSlide({data:{message:!0===e.options.rtl?"previous":"next"}}))},e.prototype.lazyLoad=function(){function e(e){i("img[data-lazy]",e).each(function(){var e=i(this),t=i(this).attr("data-lazy"),o=i(this).attr("data-srcset"),s=i(this).attr("data-sizes")||n.$slider.attr("data-sizes"),r=document.createElement("img");r.onload=function(){e.animate({opacity:0},100,function(){o&&(e.attr("srcset",o),s&&e.attr("sizes",s)),e.attr("src",t).animate({opacity:1},200,function(){e.removeAttr("data-lazy data-srcset data-sizes").removeClass("slick-loading")}),n.$slider.trigger("lazyLoaded",[n,e,t])})},r.onerror=function(){e.removeAttr("data-lazy").removeClass("slick-loading").addClass("slick-lazyload-error"),n.$slider.trigger("lazyLoadError",[n,e,t])},r.src=t})}var t,o,s,n=this;if(!0===n.options.centerMode?!0===n.options.infinite?s=(o=n.currentSlide+(n.options.slidesToShow/2+1))+n.options.slidesToShow+2:(o=Math.max(0,n.currentSlide-(n.options.slidesToShow/2+1)),s=n.options.slidesToShow/2+1+2+n.currentSlide):(o=n.options.infinite?n.options.slidesToShow+n.currentSlide:n.currentSlide,s=Math.ceil(o+n.options.slidesToShow),!0===n.options.fade&&(o>0&&o--,s<=n.slideCount&&s++)),t=n.$slider.find(".slick-slide").slice(o,s),"anticipated"===n.options.lazyLoad)for(var r=o-1,l=s,d=n.$slider.find(".slick-slide"),a=0;a<n.options.slidesToScroll;a++)r<0&&(r=n.slideCount-1),t=(t=t.add(d.eq(r))).add(d.eq(l)),r--,l++;e(t),n.slideCount<=n.options.slidesToShow?e(n.$slider.find(".slick-slide")):n.currentSlide>=n.slideCount-n.options.slidesToShow?e(n.$slider.find(".slick-cloned").slice(0,n.options.slidesToShow)):0===n.currentSlide&&e(n.$slider.find(".slick-cloned").slice(-1*n.options.slidesToShow))},e.prototype.loadSlider=function(){var i=this;i.setPosition(),i.$slideTrack.css({opacity:1}),i.$slider.removeClass("slick-loading"),i.initUI(),"progressive"===i.options.lazyLoad&&i.progressiveLazyLoad()},e.prototype.next=e.prototype.slickNext=function(){this.changeSlide({data:{message:"next"}})},e.prototype.orientationChange=function(){var i=this;i.checkResponsive(),i.setPosition()},e.prototype.pause=e.prototype.slickPause=function(){var i=this;i.autoPlayClear(),i.paused=!0},e.prototype.play=e.prototype.slickPlay=function(){var i=this;i.autoPlay(),i.options.autoplay=!0,i.paused=!1,i.focussed=!1,i.interrupted=!1},e.prototype.postSlide=function(e){var t=this;t.unslicked||(t.$slider.trigger("afterChange",[t,e]),t.animating=!1,t.slideCount>t.options.slidesToShow&&t.setPosition(),t.swipeLeft=null,t.options.autoplay&&t.autoPlay(),!0===t.options.accessibility&&(t.initADA(),t.options.focusOnChange&&i(t.$slides.get(t.currentSlide)).attr("tabindex",0).focus()))},e.prototype.prev=e.prototype.slickPrev=function(){this.changeSlide({data:{message:"previous"}})},e.prototype.preventDefault=function(i){i.preventDefault()},e.prototype.progressiveLazyLoad=function(e){e=e||1;var t,o,s,n,r,l=this,d=i("img[data-lazy]",l.$slider);d.length?(t=d.first(),o=t.attr("data-lazy"),s=t.attr("data-srcset"),n=t.attr("data-sizes")||l.$slider.attr("data-sizes"),(r=document.createElement("img")).onload=function(){s&&(t.attr("srcset",s),n&&t.attr("sizes",n)),t.attr("src",o).removeAttr("data-lazy data-srcset data-sizes").removeClass("slick-loading"),!0===l.options.adaptiveHeight&&l.setPosition(),l.$slider.trigger("lazyLoaded",[l,t,o]),l.progressiveLazyLoad()},r.onerror=function(){e<3?setTimeout(function(){l.progressiveLazyLoad(e+1)},500):(t.removeAttr("data-lazy").removeClass("slick-loading").addClass("slick-lazyload-error"),l.$slider.trigger("lazyLoadError",[l,t,o]),l.progressiveLazyLoad())},r.src=o):l.$slider.trigger("allImagesLoaded",[l])},e.prototype.refresh=function(e){var t,o,s=this;o=s.slideCount-s.options.slidesToShow,!s.options.infinite&&s.currentSlide>o&&(s.currentSlide=o),s.slideCount<=s.options.slidesToShow&&(s.currentSlide=0),t=s.currentSlide,s.destroy(!0),i.extend(s,s.initials,{currentSlide:t}),s.init(),e||s.changeSlide({data:{message:"index",index:t}},!1)},e.prototype.registerBreakpoints=function(){var e,t,o,s=this,n=s.options.responsive||null;if("array"===i.type(n)&&n.length){s.respondTo=s.options.respondTo||"window";for(e in n)if(o=s.breakpoints.length-1,n.hasOwnProperty(e)){for(t=n[e].breakpoint;o>=0;)s.breakpoints[o]&&s.breakpoints[o]===t&&s.breakpoints.splice(o,1),o--;s.breakpoints.push(t),s.breakpointSettings[t]=n[e].settings}s.breakpoints.sort(function(i,e){return s.options.mobileFirst?i-e:e-i})}},e.prototype.reinit=function(){var e=this;e.$slides=e.$slideTrack.children(e.options.slide).addClass("slick-slide"),e.slideCount=e.$slides.length,e.currentSlide>=e.slideCount&&0!==e.currentSlide&&(e.currentSlide=e.currentSlide-e.options.slidesToScroll),e.slideCount<=e.options.slidesToShow&&(e.currentSlide=0),e.registerBreakpoints(),e.setProps(),e.setupInfinite(),e.buildArrows(),e.updateArrows(),e.initArrowEvents(),e.buildDots(),e.updateDots(),e.initDotEvents(),e.cleanUpSlideEvents(),e.initSlideEvents(),e.checkResponsive(!1,!0),!0===e.options.focusOnSelect&&i(e.$slideTrack).children().on("click.slick",e.selectHandler),e.setSlideClasses("number"==typeof e.currentSlide?e.currentSlide:0),e.setPosition(),e.focusHandler(),e.paused=!e.options.autoplay,e.autoPlay(),e.$slider.trigger("reInit",[e])},e.prototype.resize=function(){var e=this;i(window).width()!==e.windowWidth&&(clearTimeout(e.windowDelay),e.windowDelay=window.setTimeout(function(){e.windowWidth=i(window).width(),e.checkResponsive(),e.unslicked||e.setPosition()},50))},e.prototype.removeSlide=e.prototype.slickRemove=function(i,e,t){var o=this;if(i="boolean"==typeof i?!0===(e=i)?0:o.slideCount-1:!0===e?--i:i,o.slideCount<1||i<0||i>o.slideCount-1)return!1;o.unload(),!0===t?o.$slideTrack.children().remove():o.$slideTrack.children(this.options.slide).eq(i).remove(),o.$slides=o.$slideTrack.children(this.options.slide),o.$slideTrack.children(this.options.slide).detach(),o.$slideTrack.append(o.$slides),o.$slidesCache=o.$slides,o.reinit()},e.prototype.setCSS=function(i){var e,t,o=this,s={};!0===o.options.rtl&&(i=-i),e="left"==o.positionProp?Math.ceil(i)+"px":"0px",t="top"==o.positionProp?Math.ceil(i)+"px":"0px",s[o.positionProp]=i,!1===o.transformsEnabled?o.$slideTrack.css(s):(s={},!1===o.cssTransitions?(s[o.animType]="translate("+e+", "+t+")",o.$slideTrack.css(s)):(s[o.animType]="translate3d("+e+", "+t+", 0px)",o.$slideTrack.css(s)))},e.prototype.setDimensions=function(){var i=this;!1===i.options.vertical?!0===i.options.centerMode&&i.$list.css({padding:"0px "+i.options.centerPadding}):(i.$list.height(i.$slides.first().outerHeight(!0)*i.options.slidesToShow),!0===i.options.centerMode&&i.$list.css({padding:i.options.centerPadding+" 0px"})),i.listWidth=i.$list.width(),i.listHeight=i.$list.height(),!1===i.options.vertical&&!1===i.options.variableWidth?(i.slideWidth=Math.ceil(i.listWidth/i.options.slidesToShow),i.$slideTrack.width(Math.ceil(i.slideWidth*i.$slideTrack.children(".slick-slide").length))):!0===i.options.variableWidth?i.$slideTrack.width(5e3*i.slideCount):(i.slideWidth=Math.ceil(i.listWidth),i.$slideTrack.height(Math.ceil(i.$slides.first().outerHeight(!0)*i.$slideTrack.children(".slick-slide").length)));var e=i.$slides.first().outerWidth(!0)-i.$slides.first().width();!1===i.options.variableWidth&&i.$slideTrack.children(".slick-slide").width(i.slideWidth-e)},e.prototype.setFade=function(){var e,t=this;t.$slides.each(function(o,s){e=t.slideWidth*o*-1,!0===t.options.rtl?i(s).css({position:"relative",right:e,top:0,zIndex:t.options.zIndex-2,opacity:0}):i(s).css({position:"relative",left:e,top:0,zIndex:t.options.zIndex-2,opacity:0})}),t.$slides.eq(t.currentSlide).css({zIndex:t.options.zIndex-1,opacity:1})},e.prototype.setHeight=function(){var i=this;if(1===i.options.slidesToShow&&!0===i.options.adaptiveHeight&&!1===i.options.vertical){var e=i.$slides.eq(i.currentSlide).outerHeight(!0);i.$list.css("height",e)}},e.prototype.setOption=e.prototype.slickSetOption=function(){var e,t,o,s,n,r=this,l=!1;if("object"===i.type(arguments[0])?(o=arguments[0],l=arguments[1],n="multiple"):"string"===i.type(arguments[0])&&(o=arguments[0],s=arguments[1],l=arguments[2],"responsive"===arguments[0]&&"array"===i.type(arguments[1])?n="responsive":void 0!==arguments[1]&&(n="single")),"single"===n)r.options[o]=s;else if("multiple"===n)i.each(o,function(i,e){r.options[i]=e});else if("responsive"===n)for(t in s)if("array"!==i.type(r.options.responsive))r.options.responsive=[s[t]];else{for(e=r.options.responsive.length-1;e>=0;)r.options.responsive[e].breakpoint===s[t].breakpoint&&r.options.responsive.splice(e,1),e--;r.options.responsive.push(s[t])}l&&(r.unload(),r.reinit())},e.prototype.setPosition=function(){var i=this;i.setDimensions(),i.setHeight(),!1===i.options.fade?i.setCSS(i.getLeft(i.currentSlide)):i.setFade(),i.$slider.trigger("setPosition",[i])},e.prototype.setProps=function(){var i=this,e=document.body.style;i.positionProp=!0===i.options.vertical?"top":"left","top"===i.positionProp?i.$slider.addClass("slick-vertical"):i.$slider.removeClass("slick-vertical"),void 0===e.WebkitTransition&&void 0===e.MozTransition&&void 0===e.msTransition||!0===i.options.useCSS&&(i.cssTransitions=!0),i.options.fade&&("number"==typeof i.options.zIndex?i.options.zIndex<3&&(i.options.zIndex=3):i.options.zIndex=i.defaults.zIndex),void 0!==e.OTransform&&(i.animType="OTransform",i.transformType="-o-transform",i.transitionType="OTransition",void 0===e.perspectiveProperty&&void 0===e.webkitPerspective&&(i.animType=!1)),void 0!==e.MozTransform&&(i.animType="MozTransform",i.transformType="-moz-transform",i.transitionType="MozTransition",void 0===e.perspectiveProperty&&void 0===e.MozPerspective&&(i.animType=!1)),void 0!==e.webkitTransform&&(i.animType="webkitTransform",i.transformType="-webkit-transform",i.transitionType="webkitTransition",void 0===e.perspectiveProperty&&void 0===e.webkitPerspective&&(i.animType=!1)),void 0!==e.msTransform&&(i.animType="msTransform",i.transformType="-ms-transform",i.transitionType="msTransition",void 0===e.msTransform&&(i.animType=!1)),void 0!==e.transform&&!1!==i.animType&&(i.animType="transform",i.transformType="transform",i.transitionType="transition"),i.transformsEnabled=i.options.useTransform&&null!==i.animType&&!1!==i.animType},e.prototype.setSlideClasses=function(i){var e,t,o,s,n=this;if(t=n.$slider.find(".slick-slide").removeClass("slick-active slick-center slick-current").attr("aria-hidden","true"),n.$slides.eq(i).addClass("slick-current"),!0===n.options.centerMode){var r=n.options.slidesToShow%2==0?1:0;e=Math.floor(n.options.slidesToShow/2),!0===n.options.infinite&&(i>=e&&i<=n.slideCount-1-e?n.$slides.slice(i-e+r,i+e+1).addClass("slick-active").attr("aria-hidden","false"):(o=n.options.slidesToShow+i,t.slice(o-e+1+r,o+e+2).addClass("slick-active").attr("aria-hidden","false")),0===i?t.eq(t.length-1-n.options.slidesToShow).addClass("slick-center"):i===n.slideCount-1&&t.eq(n.options.slidesToShow).addClass("slick-center")),n.$slides.eq(i).addClass("slick-center")}else i>=0&&i<=n.slideCount-n.options.slidesToShow?n.$slides.slice(i,i+n.options.slidesToShow).addClass("slick-active").attr("aria-hidden","false"):t.length<=n.options.slidesToShow?t.addClass("slick-active").attr("aria-hidden","false"):(s=n.slideCount%n.options.slidesToShow,o=!0===n.options.infinite?n.options.slidesToShow+i:i,n.options.slidesToShow==n.options.slidesToScroll&&n.slideCount-i<n.options.slidesToShow?t.slice(o-(n.options.slidesToShow-s),o+s).addClass("slick-active").attr("aria-hidden","false"):t.slice(o,o+n.options.slidesToShow).addClass("slick-active").attr("aria-hidden","false"));"ondemand"!==n.options.lazyLoad&&"anticipated"!==n.options.lazyLoad||n.lazyLoad()},e.prototype.setupInfinite=function(){var e,t,o,s=this;if(!0===s.options.fade&&(s.options.centerMode=!1),!0===s.options.infinite&&!1===s.options.fade&&(t=null,s.slideCount>s.options.slidesToShow)){for(o=!0===s.options.centerMode?s.options.slidesToShow+1:s.options.slidesToShow,e=s.slideCount;e>s.slideCount-o;e-=1)t=e-1,i(s.$slides[t]).clone(!0).attr("id","").attr("data-slick-index",t-s.slideCount).prependTo(s.$slideTrack).addClass("slick-cloned");for(e=0;e<o+s.slideCount;e+=1)t=e,i(s.$slides[t]).clone(!0).attr("id","").attr("data-slick-index",t+s.slideCount).appendTo(s.$slideTrack).addClass("slick-cloned");s.$slideTrack.find(".slick-cloned").find("[id]").each(function(){i(this).attr("id","")})}},e.prototype.interrupt=function(i){var e=this;i||e.autoPlay(),e.interrupted=i},e.prototype.selectHandler=function(e){var t=this,o=i(e.target).is(".slick-slide")?i(e.target):i(e.target).parents(".slick-slide"),s=parseInt(o.attr("data-slick-index"));s||(s=0),t.slideCount<=t.options.slidesToShow?t.slideHandler(s,!1,!0):t.slideHandler(s)},e.prototype.slideHandler=function(i,e,t){var o,s,n,r,l,d=null,a=this;if(e=e||!1,!(!0===a.animating&&!0===a.options.waitForAnimate||!0===a.options.fade&&a.currentSlide===i))if(!1===e&&a.asNavFor(i),o=i,d=a.getLeft(o),r=a.getLeft(a.currentSlide),a.currentLeft=null===a.swipeLeft?r:a.swipeLeft,!1===a.options.infinite&&!1===a.options.centerMode&&(i<0||i>a.getDotCount()*a.options.slidesToScroll))!1===a.options.fade&&(o=a.currentSlide,!0!==t?a.animateSlide(r,function(){a.postSlide(o)}):a.postSlide(o));else if(!1===a.options.infinite&&!0===a.options.centerMode&&(i<0||i>a.slideCount-a.options.slidesToScroll))!1===a.options.fade&&(o=a.currentSlide,!0!==t?a.animateSlide(r,function(){a.postSlide(o)}):a.postSlide(o));else{if(a.options.autoplay&&clearInterval(a.autoPlayTimer),s=o<0?a.slideCount%a.options.slidesToScroll!=0?a.slideCount-a.slideCount%a.options.slidesToScroll:a.slideCount+o:o>=a.slideCount?a.slideCount%a.options.slidesToScroll!=0?0:o-a.slideCount:o,a.animating=!0,a.$slider.trigger("beforeChange",[a,a.currentSlide,s]),n=a.currentSlide,a.currentSlide=s,a.setSlideClasses(a.currentSlide),a.options.asNavFor&&(l=(l=a.getNavTarget()).slick("getSlick")).slideCount<=l.options.slidesToShow&&l.setSlideClasses(a.currentSlide),a.updateDots(),a.updateArrows(),!0===a.options.fade)return!0!==t?(a.fadeSlideOut(n),a.fadeSlide(s,function(){a.postSlide(s)})):a.postSlide(s),void a.animateHeight();!0!==t?a.animateSlide(d,function(){a.postSlide(s)}):a.postSlide(s)}},e.prototype.startLoad=function(){var i=this;!0===i.options.arrows&&i.slideCount>i.options.slidesToShow&&(i.$prevArrow.hide(),i.$nextArrow.hide()),!0===i.options.dots&&i.slideCount>i.options.slidesToShow&&i.$dots.hide(),i.$slider.addClass("slick-loading")},e.prototype.swipeDirection=function(){var i,e,t,o,s=this;return i=s.touchObject.startX-s.touchObject.curX,e=s.touchObject.startY-s.touchObject.curY,t=Math.atan2(e,i),(o=Math.round(180*t/Math.PI))<0&&(o=360-Math.abs(o)),o<=45&&o>=0?!1===s.options.rtl?"left":"right":o<=360&&o>=315?!1===s.options.rtl?"left":"right":o>=135&&o<=225?!1===s.options.rtl?"right":"left":!0===s.options.verticalSwiping?o>=35&&o<=135?"down":"up":"vertical"},e.prototype.swipeEnd=function(i){var e,t,o=this;if(o.dragging=!1,o.swiping=!1,o.scrolling)return o.scrolling=!1,!1;if(o.interrupted=!1,o.shouldClick=!(o.touchObject.swipeLength>10),void 0===o.touchObject.curX)return!1;if(!0===o.touchObject.edgeHit&&o.$slider.trigger("edge",[o,o.swipeDirection()]),o.touchObject.swipeLength>=o.touchObject.minSwipe){switch(t=o.swipeDirection()){case"left":case"down":e=o.options.swipeToSlide?o.checkNavigable(o.currentSlide+o.getSlideCount()):o.currentSlide+o.getSlideCount(),o.currentDirection=0;break;case"right":case"up":e=o.options.swipeToSlide?o.checkNavigable(o.currentSlide-o.getSlideCount()):o.currentSlide-o.getSlideCount(),o.currentDirection=1}"vertical"!=t&&(o.slideHandler(e),o.touchObject={},o.$slider.trigger("swipe",[o,t]))}else o.touchObject.startX!==o.touchObject.curX&&(o.slideHandler(o.currentSlide),o.touchObject={})},e.prototype.swipeHandler=function(i){var e=this;if(!(!1===e.options.swipe||"ontouchend"in document&&!1===e.options.swipe||!1===e.options.draggable&&-1!==i.type.indexOf("mouse")))switch(e.touchObject.fingerCount=i.originalEvent&&void 0!==i.originalEvent.touches?i.originalEvent.touches.length:1,e.touchObject.minSwipe=e.listWidth/e.options.touchThreshold,!0===e.options.verticalSwiping&&(e.touchObject.minSwipe=e.listHeight/e.options.touchThreshold),i.data.action){case"start":e.swipeStart(i);break;case"move":e.swipeMove(i);break;case"end":e.swipeEnd(i)}},e.prototype.swipeMove=function(i){var e,t,o,s,n,r,l=this;return n=void 0!==i.originalEvent?i.originalEvent.touches:null,!(!l.dragging||l.scrolling||n&&1!==n.length)&&(e=l.getLeft(l.currentSlide),l.touchObject.curX=void 0!==n?n[0].pageX:i.clientX,l.touchObject.curY=void 0!==n?n[0].pageY:i.clientY,l.touchObject.swipeLength=Math.round(Math.sqrt(Math.pow(l.touchObject.curX-l.touchObject.startX,2))),r=Math.round(Math.sqrt(Math.pow(l.touchObject.curY-l.touchObject.startY,2))),!l.options.verticalSwiping&&!l.swiping&&r>4?(l.scrolling=!0,!1):(!0===l.options.verticalSwiping&&(l.touchObject.swipeLength=r),t=l.swipeDirection(),void 0!==i.originalEvent&&l.touchObject.swipeLength>4&&(l.swiping=!0,i.preventDefault()),s=(!1===l.options.rtl?1:-1)*(l.touchObject.curX>l.touchObject.startX?1:-1),!0===l.options.verticalSwiping&&(s=l.touchObject.curY>l.touchObject.startY?1:-1),o=l.touchObject.swipeLength,l.touchObject.edgeHit=!1,!1===l.options.infinite&&(0===l.currentSlide&&"right"===t||l.currentSlide>=l.getDotCount()&&"left"===t)&&(o=l.touchObject.swipeLength*l.options.edgeFriction,l.touchObject.edgeHit=!0),!1===l.options.vertical?l.swipeLeft=e+o*s:l.swipeLeft=e+o*(l.$list.height()/l.listWidth)*s,!0===l.options.verticalSwiping&&(l.swipeLeft=e+o*s),!0!==l.options.fade&&!1!==l.options.touchMove&&(!0===l.animating?(l.swipeLeft=null,!1):void l.setCSS(l.swipeLeft))))},e.prototype.swipeStart=function(i){var e,t=this;if(t.interrupted=!0,1!==t.touchObject.fingerCount||t.slideCount<=t.options.slidesToShow)return t.touchObject={},!1;void 0!==i.originalEvent&&void 0!==i.originalEvent.touches&&(e=i.originalEvent.touches[0]),t.touchObject.startX=t.touchObject.curX=void 0!==e?e.pageX:i.clientX,t.touchObject.startY=t.touchObject.curY=void 0!==e?e.pageY:i.clientY,t.dragging=!0},e.prototype.unfilterSlides=e.prototype.slickUnfilter=function(){var i=this;null!==i.$slidesCache&&(i.unload(),i.$slideTrack.children(this.options.slide).detach(),i.$slidesCache.appendTo(i.$slideTrack),i.reinit())},e.prototype.unload=function(){var e=this;i(".slick-cloned",e.$slider).remove(),e.$dots&&e.$dots.remove(),e.$prevArrow&&e.htmlExpr.test(e.options.prevArrow)&&e.$prevArrow.remove(),e.$nextArrow&&e.htmlExpr.test(e.options.nextArrow)&&e.$nextArrow.remove(),e.$slides.removeClass("slick-slide slick-active slick-visible slick-current").attr("aria-hidden","true").css("width","")},e.prototype.unslick=function(i){var e=this;e.$slider.trigger("unslick",[e,i]),e.destroy()},e.prototype.updateArrows=function(){var i=this;Math.floor(i.options.slidesToShow/2),!0===i.options.arrows&&i.slideCount>i.options.slidesToShow&&!i.options.infinite&&(i.$prevArrow.removeClass("slick-disabled").attr("aria-disabled","false"),i.$nextArrow.removeClass("slick-disabled").attr("aria-disabled","false"),0===i.currentSlide?(i.$prevArrow.addClass("slick-disabled").attr("aria-disabled","true"),i.$nextArrow.removeClass("slick-disabled").attr("aria-disabled","false")):i.currentSlide>=i.slideCount-i.options.slidesToShow&&!1===i.options.centerMode?(i.$nextArrow.addClass("slick-disabled").attr("aria-disabled","true"),i.$prevArrow.removeClass("slick-disabled").attr("aria-disabled","false")):i.currentSlide>=i.slideCount-1&&!0===i.options.centerMode&&(i.$nextArrow.addClass("slick-disabled").attr("aria-disabled","true"),i.$prevArrow.removeClass("slick-disabled").attr("aria-disabled","false")))},e.prototype.updateDots=function(){var i=this;null!==i.$dots&&(i.$dots.find("li").removeClass("slick-active").end(),i.$dots.find("li").eq(Math.floor(i.currentSlide/i.options.slidesToScroll)).addClass("slick-active"))},e.prototype.visibility=function(){var i=this;i.options.autoplay&&(document[i.hidden]?i.interrupted=!0:i.interrupted=!1)},i.fn.slick=function(){var i,t,o=this,s=arguments[0],n=Array.prototype.slice.call(arguments,1),r=o.length;for(i=0;i<r;i++)if("object"==typeof s||void 0===s?o[i].slick=new e(o[i],s):t=o[i].slick[s].apply(o[i].slick,n),void 0!==t)return t;return o}});
;
var Slider = (function () {
    var index = 0;

    var Previous = function () {
        index--;

        //fire load attached to stage
        $(this).parent().load();
    };

    var Next = function () {
        index++;

        //fire load attached to stage
        $(this).parent().load();
    };

    var LoadCarousel = function () {
        if (index == $(this).data("slider").options.data.length)
            index = 0;

        if (index == -1)
            index = $(this).data("slider").options.data.length - 1;

        $(this).find("img.carousel-image").attr({
            "src": $(this).data("slider").options.data[index].url + $(this).data("slider").options.preset,
            "alt": $(this).data("slider").options.data[index].title 
        });
    };

    return {
        init: function (options, elem) {
            var $elem = null;
            var $prev = null;
            var $next = null;

            // Mix in the passed in options with the default options
            this.options = $.extend({}, this.options, options);

            //save private variable options
            $options = this.options;

            // Save the element reference, both as a jQuery
            // reference and a normal reference
            this.elem = elem;
            $elem = $(elem);

            $prev = $('.carousel-prev', $elem);
            $next = $('.carousel-next', $elem);

            //load events
            $prev.click(Previous);
            $next.click(Next);

            //bind load event to stage
            $elem.load(LoadCarousel);

            // return this so we can chain/use the bridge with less code.
            return this;
        },
        options: {
            data: null,
            preset: ""
        }
    }
})();;
$(document).ready(function () {
  $('#userInfoButton').click(function () {
	if ($('#userInfoContainer').css('display') == 'none') {
	  var navHeight = $('.nav-main').css('height');
	  $('#userInfoContainer').css('margin-top', navHeight).css("display", "flex").hide().fadeIn(300);
	  $(this).toggleClass('active').find('i').switchClass('fas fa-user-circle', 'fal fa-times', 0);
	  if ($('#shareContainer').css('display') != 'none') {
		$('#shareContainer').fadeOut(300);
		$('#shareButton').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-share', 0);
	  }
	} else {
	  $('#userInfoContainer').fadeOut(300);
	  $(this).toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-user-circle', 0);
	}
  });

  $('#userInfoButtonMobile').click(function () {
	if ($('#mobileLoginContainer').css('display') == 'none') {
	  $('#mobileLoginContainer').html($('#userInfoContainer').html());
	  var navHeight = $('.nav-main').css('height');
	  $('#mobileLoginContainer').css('margin-top', navHeight).css("display", "flex").hide().fadeIn(300);
	  $(this).toggleClass('active').find('i').switchClass('fas fa-user-circle', 'fal fa-times', 0);

	  if ($('#mobileFindCampgroundContainer').css('display') != 'none') {
		$('#mobileFindCampgroundContainer').fadeOut(300);
		$('#findCampgroundButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'far fa-search', 0);
	  }

	  if ($('#shareContainer').css('display') != 'none') {
		$('#shareContainer').fadeOut(300);
		$('#shareButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-share', 0);
	  }

	} else {
	  $('#mobileLoginContainer').fadeOut(300);
	  $(this).toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-user-circle', 0);
	}
  });

  $('#shareButton, #shareButtonMobile').click(function () {
	if ($('#shareContainer').css('display') == 'none') {
	  var navHeight = $('.nav-main').css('height');
	  $('#shareContainer').css('margin-top', navHeight).css("display", "flex").hide().fadeIn(300);
	  $(this).toggleClass('active').find('i').switchClass('fas fa-share', 'fal fa-times', 0);

	  if ($('#userInfoContainer').css('display') != 'none') {
		$('#userInfoContainer').fadeOut(300);
		$('#userInfoButton').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-user-circle', 0);
	  }

	  if ($('#mobileFindCampgroundContainer').css('display') != 'none') {
		$('#mobileFindCampgroundContainer').fadeOut(300);
		$('#findCampgroundButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'far fa-search', 0);
	  }

	  if ($('#mobileLoginContainer').css('display') != 'none') {
		$('#mobileLoginContainer').fadeOut(300);
		$('#userInfoButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-user-circle', 0);
	  }

	} else {
	  $('#shareContainer').fadeOut(300);
	  $(this).toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-share', 0);
	}
  });

  $('#findCampgroundButtonMobile').click(function () {
	if ($('#mobileFindCampgroundContainer').css('display') == 'none') {
	  var navHeight = $('.nav-main').css('height');
	  $('#mobileFindCampgroundContainer').css('margin-top', navHeight).css("display", "flex").hide().fadeIn(300);
	  $(this).toggleClass('active').find('i').switchClass('far fa-search', 'fal fa-times', 0);

	  if ($('#mobileLoginContainer').css('display') != 'none') {
		$('#mobileLoginContainer').fadeOut(300);
		$('#userInfoButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-user-circle', 0);
	  }

	  if ($('#shareContainer').css('display') != 'none') {
		$('#shareContainer').fadeOut(300);
		$('#shareButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-share', 0);
	  }
	  $('#txtLocationMobile').focus();

	} else {
	  $('#mobileFindCampgroundContainer').fadeOut(300);
	  $(this).toggleClass('active').find('i').switchClass('fal fa-times', 'far fa-search', 0);
	}
  });

  $('.clpMainMenu-collapse').on('show.bs.collapse', function (e) {
	$('#clpMainMenuButton').toggleClass('active').find('i').switchClass('fas fa-bars', 'fal fa-times', 0);
	$(document).on('keyup', NavbarEscape);
	$('.clpMainMenu-collapse a').on('focusout', function () {
	  if ($(this)[0] == $('.clpMainMenu-collapse a:visible').last()[0]) {
		var navbarDisplay = $('.clpMainMenu-collapse').css("display");
		  if (navbarDisplay && navbarDisplay != "none") {
			$('.clpMainMenu-collapse').removeClass('show');
			$('.navbar-toggler.active').removeClass('active').addClass('collapsed').attr('aria-expanded', 'false');
			$('.navbar-toggler.collapsed > .fa-times').removeClass('fa-times').addClass('fa-bars');
		  }
		  $(document).off('keyup', NavbarEscape);
		}
  });
  });

  $('.clpMainMenu-collapse').on('hide.bs.collapse', function (e) {
	$('#clpMainMenuButton').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-bars', 0);
	$(document).off('keyup', NavbarEscape);
  });

  $('.clpMainMenu-collapse .dropdown').on('show.bs.dropdown', function (e) {
	$(e.relatedTarget).addClass('active');
  });

  $('.clpMainMenu-collapse .dropdown').on('hide.bs.dropdown', function (e) {
	$(e.relatedTarget).removeClass('active');
  });

	// Set up some touch events for the menu
  window.addEventListener('touchstart', function onFirstTouch() {
	window.USER_IS_TOUCHING = true;
	window.removeEventListener('touchstart', onFirstTouch, false);
  }, false);

  $(document).on(window.USER_IS_TOUCHING ? 'touchstart' : 'click', function (e) {
	if (!$('#userInfoButton, #userInfoButtonMobile, #userInfoIcon, #findCampgroundIcon, #findCampgroundButtonMobile, #shareButton, #shareIcon, #txtLocationMobile').is(e.target) &&
	  $(e.target).closest('#txtLocationResultsMobile').length == 0) {
	  if ($('#userInfoContainer').css('display') != 'none') {
		if (!$(e.target).parents('#userInfoContainer').length ) {
		  $('#userInfoContainer').fadeOut(300);
		  $('#userInfoButton').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-user-circle', 0);
		}
	  }
	  if ($('#mobileLoginContainer').css('display') != 'none') {
		if (!$(e.target).parents('#mobileLoginContainer').length) {
		  $('#mobileLoginContainer').fadeOut(300);
		  $('#userInfoButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-user-circle', 0);
		}
	  }
	  if ($('#mobileFindCampgroundContainer').css('display') != 'none') {
		$('#mobileFindCampgroundContainer').fadeOut(300);
		$('#findCampgroundButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'far fa-search', 0);
	  }
	  if ($('#shareContainer').css('display') != 'none') {
		$('#shareContainer').fadeOut(300);
		$('#shareButton').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-share', 0);
		$('#shareButtonMobile').toggleClass('active').find('i').switchClass('fal fa-times', 'fas fa-share', 0);
	  }
	}
  });

 });

function NavbarEscape(e) {
  if (e.key == "Escape") {
	var navbarDisplay = $('.clpMainMenu-collapse').css("display");
	if (navbarDisplay && navbarDisplay != "none") {
	  $('.clpMainMenu-collapse').removeClass('show');
	  $('.navbar-toggler.active').removeClass('active').addClass('collapsed').attr('aria-expanded', 'false');
	  $('.navbar-toggler.collapsed > .fa-times').removeClass('fa-times').addClass('fa-bars');
	}
	$(document).off('keyup', NavbarEscape);
  }
}
;
$(document).ready(function () {
  $('.dropdown-menu.translation-item').click(function (e) {
		e.stopPropagation();
  });

  $('#mobileKOAMainMenuTrigger').on('click', function (e) {
		e.preventDefault();
		$(this).toggleClass('active koa-yellow-font');
		$('#mobileKOAMainMenuContainer').toggle();
  });

	$('.btn-mobile-reserve, #getDirections').click(function (e) {
		var href = $(this).data("href");
		var offsetTop;
		offsetTop = href === "#" ? 0 : $(href).offset().top - ($(".main-nav").outerHeight());
		scrollToItem(offsetTop);
		e.preventDefault();
	});

	function scrollToItem(offsetTop) {
		$("html, body").stop().animate({
			scrollTop: offsetTop
		},
	  500,
	  "swing");
	}

	$('.clp-nav .navbar-toggler').on('click', function (e) {
		if ($(this).find('i').hasClass('fa-plus')) {
			$(this).find('i').removeClass('fa-plus').addClass('fa-times');
		} else {
			$(this).find('i').removeClass('fa-times').addClass('fa-plus');
		}
	});

	// Starting with a default height of 800px for a section, if content of any section is greater than
	// the default height, set the read more link.
	$('.clp-section .btn-read-more').click(function () {
		var $contentDiv = $(this).parent().find('.clp-section-content');
		var divHeight = $contentDiv.height();
		var scrollHeight = $contentDiv[0].scrollHeight;
		if (scrollHeight > divHeight) {
			$contentDiv.animate({ height: scrollHeight }, 300);
			var wrapperHeight = $(this).closest('.clp-section-content-wrapper').height();
			var fullHeight = wrapperHeight + 100 + (scrollHeight - divHeight);
			$(this).closest('.clp-section-content-wrapper').animate({ minHeight: fullHeight }, 300);
			$(this).closest('.clp-section').animate({ minHeight: fullHeight }, 300);
		} else {
			var defaultHeight = 898;
			var defaultContentHeight = 500;
			if ($(this).closest('.clp-section-content-wrapper').outerHeight() > defaultHeight) {
				$(this).closest('.clp-section-content-wrapper').animate({ minHeight: defaultHeight }, 300);
				$(this).closest('.clp-section').animate({ minHeight: defaultHeight }, 300);
			}
			if ($(this).parent().find('.clp-section-content').height() > defaultContentHeight) {
				$(this).parent().find('.clp-section-content').animate({ height: defaultContentHeight }, 300);
			}
		}

		var btnText = 'Read More';
		var indicatorHtml = '<i class="fas fa-plus-circle ml-1"></i>';

		if ($(this).find('i').hasClass('fa-plus-circle')) {
			btnText = 'Read Less';
			indicatorHtml = '<i class="fas fa-minus-circle ml-1"></i>';
		}

		TranslationUtility.isLoaded()
			? Translate.translateReadMoreReadLessButton($(this), btnText, indicatorHtml)
			: $(this).html(btnText + ' ' + indicatorHtml);
	});

	$('.card-body .btn-read-more').click(function () {
		var $contentDiv = $(this).parent().find('.card-read-more');
		var divHeight = $contentDiv.height();
		var scrollHeight = $contentDiv[0].scrollHeight;
		if (scrollHeight > divHeight) {
			$contentDiv.animate({ height: scrollHeight }, 300);
			$contentDiv.find('.gradient').animate({ opacity: 0 }, 300).addClass('d-none');
		} else {
			var defaultHeight = 200;
			if ($contentDiv.height() > defaultHeight) {
				$contentDiv.animate({ height: defaultHeight }, 300);
				$contentDiv.find('.gradient').animate({ opacity: 1 }, 300).removeClass('d-none');
			}
		}

		var btnText = 'Read More';
		var indicatorHtml = '<i class="fas fa-plus-circle ml-1"></i>';

		if ($(this).find('i').hasClass('fa-plus-circle')) {
			btnText = 'Read Less';
			indicatorHtml = '<i class="fas fa-minus-circle ml-1"></i>';
		}

		TranslationUtility.isLoaded()
			? Translate.translateReadMoreReadLessButton($(this), btnText, indicatorHtml)
			: $(this).html(btnText + ' ' + indicatorHtml);

	});

	$('.activity-link').on('click', function () {
		var photoUrl = $(this).data('photo');
		var counter = $(this).data('counter');
		var title = $(this).data('title');
		var description = $(this).data('description');
		if (photoUrl !== '') {
			$('#activityHeroImg' + counter).css('opacity', 1);
			$('#activityHeroImg' + counter).siblings('.clp-section-hero-img').css('opacity', 0);
		}
		if (description !== '') {
			if (TranslationUtility.isLoaded()) {
				Translate.translateDescription(title, $('#recreationTitle'));
				Translate.translateDescription(description, $('#recreationDescription'));
			} else {
				$('#recreationTitle').html(title);
				$('#recreationDescription').html(description);
			}
			$('#recreationSummary').hide();
			$('#recreationDescriptionContainer').show(300);
		}
		$(this).addClass('active');
		$(this).parent().siblings().find('.activity-link').removeClass('active');
	});

	$('.btn-email').click(function (e) {
	    e.preventDefault();

	    var locationHref = $(this).attr('href');

	    $.fancybox.open({
	        src: locationHref,
	        type: 'iframe',

	        toolbar: false,
	        smallBtn: false,

	        buttons: [],

	        opts: {
	            preload: false,
	            afterShow: function (instance, current) {
	                //add universal close button
	                $(".fancybox-content").append("<div class='mr-1 mt-1'><button data-fancybox-close class='close' title='Close'><i class='fas fa-window-close'</i></button></div>");

	                //wire up the custom close button
	                $('.fancybox-content [data-fancybox-close]').on('click', function () {
	                    parent.jQuery.fancybox.getInstance().close();
	                });
	            }
	        }
	    });
	});

	enquire.register('(min-width: 320px) and (max-width: 575px)', {
		match: function () {
			ReadMore.Setup();
			HorizontalReserve.Setup();
		},
		unmatch: function () {
			ReadMore.Setup();
		}
	});

	enquire.register('(min-width: 576px) and (max-width: 767px)', {
		match: function () {
			ReadMore.Setup();
			HorizontalReserve.Setup();
		},
		unmatch: function () {
			ReadMore.Setup();
		}
	});

	// As user scrolls up or down page, fix the reservation bar to top of window under main nav
	// when bottom of .clp-wrapper reaches the same position.
	enquire.register('(min-width: 768px)', {
		match: function () {
			$(window).on('scroll', function () {
				var distanceFromTop = $(document).scrollTop();
				if (distanceFromTop >= $('.clp-wrapper').outerHeight(true) - $('.main-nav').outerHeight(true)) {
					$('#reserveForm').addClass('fix-to-top');
					var checkInText = typeof weglotPlaceholderCheckInText !== 'undefined' ? weglotPlaceholderCheckInText : 'Check in';
					var checkOutText = typeof weglotPlaceholderCheckOutText !== 'undefined' ? weglotPlaceholderCheckOutText : 'Check out';
					$('#Reservation_CheckInDate').prop('placeholder', checkInText);
					$('#Reservation_CheckOutDate').prop('placeholder', checkOutText);
				}
				else {
					$('#reserveForm').removeClass('fix-to-top');
					$('#Reservation_CheckInDate').prop('placeholder', '');
					$('#Reservation_CheckOutDate').prop('placeholder', '');
				}
			});

			ReadMore.Setup();
			HorizontalReserve.Breakdown();
		},
		unmatch: function () {
			$(window).off('scroll');
			ReadMore.Setup();
		}
	});

	enquire.register('(min-width: 769px)', {
		match: function () {
			CardReadMore.Setup();
		},
		unmatch: function () {
			CardReadMore.Setup();
		}
	});

	enquire.register('(min-width: 992px) and (max-width: 1199px)', {
		match: function () {
			ReadMore.Setup();
			CardReadMore.Setup();
		},
		unmatch: function () {
			ReadMore.Setup();
			CardReadMore.Setup();
		}
	});

	enquire.register('(min-width: 1200px) and (max-width: 1599px)', {
		match: function () {
			ReadMore.Setup();
			CardReadMore.Setup();
		},
		unmatch: function () {
			ReadMore.Setup();
			CardReadMore.Setup();
		}
	});

	enquire.register('(min-width: 1600px)', {
		match: function () {
			ReadMore.Setup();
			CardReadMore.Setup();
		},
		unmatch: function () {
			ReadMore.Setup();
			CardReadMore.Setup();
		}
	});

	$('#ratingsSlider').slick({
		dots: true,
		infinite: true,
		speed: 300,
		slidesToShow: 3,
		slidesToScroll: 3,
		responsive: [
		  {
		  	breakpoint: 1600,
		  	settings: {
		  		slidesToShow: 2,
		  		slidesToScroll: 2,
		  		infinite: true,
		  		dots: true
		  	}
		  },
		  {
		  	breakpoint: 992,
		  	settings: {
		  		slidesToShow: 1,
		  		slidesToScroll: 1,
		  		dots: true
		  	}
		  },
		  {
		  	breakpoint: 768,
		  	settings: {
		  		slidesToShow: 1,
		  		slidesToScroll: 1,
		  		dots: false
		  	}
		  }
		]
	});

	$('#eventsSlider, #blogSlider').slick({
		dots: true,
		infinite: true,
		speed: 300,
		slidesToShow: 2,
		slidesToScroll: 2,
		responsive: [
			{
				breakpoint: 992,
				settings: {
					slidesToShow: 1,
					slidesToScroll: 1,
					infinite: true,
					dots: true
				}
			}
		]
	});

	$('#eventsSliderNoSlide').slick({
		dots: false,
		infinite: true,
		speed: 300,
		slidesToShow: 2,
		slidesToScroll: 2,
		responsive: [
			{
				breakpoint: 992,
				settings: {
					slidesToShow: 1,
					slidesToScroll: 1,
					infinite: true,
					dots: false
				}
			}
		]
	});

	$('.local-area-overlay-btn, .close-overlay, .local-area-overlay').click(function () {
		var $parent = $(this).parent();
		$parent.find('.local-area-overlay').toggleClass('overlay-visible');
		$parent.find('.close-overlay').toggle();
	});

	$('.img-cover-box > picture').click(function() {
		$(this).siblings('.local-area-overlay').toggleClass('overlay-visible');
		$(this).siblings('.close-overlay').toggle();
	});

	$('[data-toggle="popover"]').popover();

	//START: Functions for Popover Logic
	$('[data-toggle="popover"]').on('click', function () {
		$(this).popover('show');
	});

	$('[data-toggle="popover"]').on('keyup', function (e) {
		if (e.which == 13) {
			$(this).popover('show');
		}
	});

	$('[data-toggle="popover"]').on('shown.bs.popover', function () {
		$(this).attr('aria-expanded', 'true');
		$(this).parent().on('keydown', function (event) {
			if (event.key == "Escape") {
				$(this).children('[data-toggle="popover"]').popover('hide');
			}
		});

		$('.popover').on('focusout', function () {
			$('.popover').off('focusout');
			$(this).popover('hide');
		});

		$('[data-toggle="popover"]').on('focusout', function (e) {
			$('[data-toggle="popover"]').off('focusout');
			setTimeout(popoverFocusLeave, 100, this);
		});

		$('body').on('click', function (e) {
			$('[data-toggle=popover]').each(function () {
				if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
					$(this).popover('hide');
				}
			});
		});
	});

	$('[data-toggle="popover"]').on('hidden.bs.popover', function () {
		$(this).attr('aria-expanded', 'false');
	});

	function  popoverFocusLeave(current) {
		var focused = $('.popover :focus');
		if (!focused || focused.length == 0) {
			$(current).popover('hide');
		}
	}
	//END: Functions for Popover Logic

	$(".photos-social a").click(function (e) {

		e.preventDefault();
		var social = $(this).data("social");
		var $tile = $(this).parents(".img-cover-box");
		var image = $tile.data("src");

		var text = $.trim($tile.data("title"));

		if (social == "facebook") {
			var url = "https://www.facebook.com/dialog/feed?app_id=1685564315011866&" +
				"link=" + encodeURIComponent(location.href) + "&" +
				"display=popup&" +
				"name=" + encodeURIComponent(document.title) + "&" +
				"caption=&" +
				"description=" + encodeURIComponent(text) + "&" +
				"picture=" + encodeURIComponent(qualifyURL(image)) + "&" +
				"ref=share&" +
				"actions={%22name%22:%22View%20the%20gallery%22,%20%22link%22:%22" + encodeURIComponent(location.href) + "%22}&" +
				"redirect_uri=http://koa.com";

			var w = window.open(url, "ftgw", "location=1,status=1,scrollbars=1,width=600,height=400");
			w.moveTo((screen.width / 2) - (300), (screen.height / 2) - (200));
		}

		if (social == "twitter") {
			var w = window.open("https://twitter.com/intent/tweet?url=" + encodeURI(location.href.split('#')[0]) + "&text=" + encodeURI(text), "ftgw", "location=1,status=1,scrollbars=1,width=600,height=400");
			w.moveTo((screen.width / 2) - (300), (screen.height / 2) - (200));
		}

		if (social == "pinterest") {
			var url = "http://pinterest.com/pin/create/button/?url=" + encodeURIComponent(location.href) + "&description=" + encodeURI(text);

			url += ("&media=" + encodeURIComponent(qualifyURL(image)));

			var w = window.open(url, "ftgw", "location=1,status=1,scrollbars=1,width=600,height=400");
			w.moveTo((screen.width / 2) - (300), (screen.height / 2) - (200));
		}

		if (social == "google-plus") {
			var url = "https://plus.google.com/share?url=" + encodeURI(location.href);

			var w = window.open(url, "ftgw", "location=1,status=1,scrollbars=1,width=600,height=400");
			w.moveTo((screen.width / 2) - (300), (screen.height / 2) - (200));
		}
	});

	$('#newsletterSignup').on('click', function (e) {
		e.preventDefault();
		var email = $('#email').val();
		var alias = $(this).data('alias');
		var postUrl = $('#form-newsletter-signup').attr('action');

		$.ajax({
			type: 'POST',
			url: postUrl,
			data: { 'email': email },
			dataType: 'json'
		})
		.done(function (response) {
			response = JSON.parse(response);
			if (response.result == 'success') {
				$('#newsletterSignupResponse').html('Thanks for signing up!').addClass('bg-success').show(500);
				setTimeout(function () {
					$('#newsletterSignupResponse').hide(500);
					$('#newsletterSignupResponse').html('').removeClass('bg-success');
					$('#email').val('');
				}, 3000);
			} else {
			$('#newsletterSignupResponse').html('There was an error adding your email.').addClass('bg-danger').show(500);
				setTimeout(function () {
					$('#newsletterSignupResponse').hide(500);
					$('#newsletterSignupResponse').html('').removeClass('bg-danger');
				}, 3000);
			}
		})
		.fail(function (jqXHR, textStatus, error) { console.log('fail'); });
	});

	$('#virtualTour').on('click', function () {
		$(this).find('i').toggleClass('fa-spin');
		var href = $('#virtualTourContainer').find('iframe').attr('src');
		href += '?autoplay=1&mute=1';
		$('#virtualTourContainer').find('iframe').attr('src', href);
		setTimeout(function () {
			$('#virtualTour').hide();
			$('#virtualTourContainer').show();
		}, 1000);
	});

	$('.js-goto-content-anchor').on('click', function(e) {
			var href = $(this).attr('href');
			if (href.length) {
				var viewportWidth = $(window).width();
				var viewportHeight = $(window).height();

				if (viewportWidth < 768 || viewportHeight < 850) {
					e.preventDefault();
					window.location = href + '#content';
				}
			}
		}
	);

	PositionScrollDownLink();

	$('#link-scroll-down').on('click', function() {
		ScrollDown();
		return false;
	});

	$(window).resize(function () {
		waitForFinalEvent(function() {
			PositionScrollDownLink();
		}, 200, 'positionScrollDownLink');
	});

	var clpWrapper = document.querySelector('.clp-wrapper');
	var observer;
	var scrollOffset = $(clpWrapper).height() - $(window).innerHeight();

	if ('IntersectionObserver' in window &&
	'IntersectionObserverEntry' in window &&
	'intersectionRatio' in window.IntersectionObserverEntry.prototype) {
		var intersectionThreshold = ($(window).innerHeight() / ($(window).innerHeight() + 100)) - .1;

	observer = new IntersectionObserver(function (entry, observer) {
			// Can't use isIntersecting since that doesn't work in Firefox for some reason
			if ($(window).scrollTop() > scrollOffset && entry[0].intersectionRatio < intersectionThreshold) {
				$('#link-scroll-down').addClass('d-none');
				observer.unobserve(entry[0].target);
			}
		}, { threshold: intersectionThreshold });

		observer.observe(clpWrapper);
	}
});

var waitForFinalEvent = (function () {
	var timers = {};
	return function (callback, ms, uniqueId) {
		if (!uniqueId) {
			uniqueId = "Don't call this twice without a uniqueId";
		}
		if (timers[uniqueId]) {
			clearTimeout (timers[uniqueId]);
		}
		timers[uniqueId] = setTimeout(callback, ms);
	};
})();

function PositionScrollDownLink() {
	var scrollLink = $('#link-scroll-down');

	if ($(window).height() > 850 && $(window).width() > 767) {
		if (!scrollLink.hasClass('d-none')) {
			scrollLink.addClass('d-none');
		}

		return true;
	}

	if ($('.clp-content-anchor').length) {
		if ($('.clp-content-anchor').offset().top < $(window).height()) {
			if (!scrollLink.hasClass('d-none')) {
				scrollLink.addClass('d-none');
			}

			return true;
		}
		if ($(window).scrollTop() < $(window).height() / 4) {
			var heightAdjustment = 60;
			var widthAdjustment = 50;
			var topOffset = ($('.clp-wrapper-secondary').outerHeight() || 0) - ($('#campgroundContactOverlay').outerHeight() || 0) - heightAdjustment;

			if (topOffset > $(window).height() - scrollLink.outerHeight()) {
				topOffset = $(window).height() - heightAdjustment;
			}

			scrollLink.offset({ top: topOffset, left: $(window).width() - widthAdjustment });
			scrollLink.removeClass('d-none');
		}
	}
}

function ScrollDown() {
	if ($('#campgroundContactOverlay').length && $('.main-nav').length) {
		$('html, body').animate({ scrollTop: $('#campgroundContactOverlay').offset().top - $('.main-nav').height() - 10 }, 1200);
		$('#link-scroll-down').addClass('d-none');
	}
	else if ($('.clp-content-anchor') && $('.main-nav').length) {
		var scrollTop = $('.clp-content-anchor').offset().top - $('.main-nav').height() - 10;
		if ($('.clp-nav').length) {
			scrollTop -= $('.clp-nav').outerHeight();
		}
		$('html, body').animate({ scrollTop: scrollTop }, 1200);
		$('#link-scroll-down').addClass('d-none');
	}
}

function ScrollToContent() {
	if ($('.clp-content-anchor').length && $(window).scrollTop() === 0) {
		var viewportWidth = $(window).width();
		var viewportHeight = $(window).height();
		var topModifier = 0;

		if (viewportWidth < 768) {
			topModifier = viewportHeight >= 768 ? 367 : 325;
		} else {
			if (viewportHeight < 850) {
				if (viewportWidth > 1323) {
					topModifier = 330;
				} else if (viewportWidth > 1033) {
					topModifier = 420;
				} else {
					topModifier = 440;
				}
			}
		}

		if (topModifier > 0) {
			$('html, body').delay(800).animate({ scrollTop: $('.clp-content-anchor').offset().top - topModifier }, 1500);
		}
	}
}

var ReadMore = (function () {
	return {
		Setup: function () {
			$('.clp-section-content').each(function () {
				var divHeight = $(this).height();
				var scrollHeight = $(this)[0].scrollHeight;
				if (scrollHeight > divHeight) {
					$(this).parent().find('.btn-read-more').show();
				} else {
					$(this).parent().find('.btn-read-more').hide();
				}
			});
		}
	}
})();

var CardReadMore = (function () {
	return {
		Setup: function () {
			$('.card-read-more').each(function () {
				var divHeight = $(this).height();
				var scrollHeight = $(this)[0].scrollHeight;
				if (scrollHeight > (divHeight)) {
					$(this).parent().find('.btn-read-more').show();
					$(this).find('.gradient').show().animate({ opacity: 1 }, 0);
					$(this).css('margin-bottom', '0px');
				} else {
					$(this).parent().find('.btn-read-more').hide();
					$(this).find('.gradient').animate({ opacity: 0 }, 0).hide();
					$(this).css('margin-bottom', '53px');
				}
			});
		}
	}
})();

var HorizontalReserve = (function () {
	return {
		Setup: function () {
			$('#campgroundHorizontalReserve').addClass('mobile-reserve');
			var checkInText = typeof weglotPlaceholderCheckInText !== 'undefined' ? weglotPlaceholderCheckInText : 'Check in';
			var checkOutText = typeof weglotPlaceholderCheckOutText !== 'undefined' ? weglotPlaceholderCheckOutText : 'Check out';
			$('#Reservation_CheckInDate').prop('placeholder', checkInText);
			$('#Reservation_CheckOutDate').prop('placeholder', checkOutText);
		},
		Breakdown: function () {
			$('#campgroundHorizontalReserve').removeClass('mobile-reserve');
			$('#Reservation_CheckInDate').prop('placeholder', '');
			$('#Reservation_CheckOutDate').prop('placeholder', '');
		}
	}
})();

var TranslationUtility = (function () {
	return {
		isLoaded: function () {
			return typeof Translate !== 'undefined';
		}
	}
})();

function HeroLoad(img) {
	var src = img.currentSrc ? img.currentSrc : img.src;
	img.style.opacity = 0;
	$('.hero-img, #introMessageImg').css('background', 'url("' + src + '")');
}

function ScrollAndFocusCheckinDate() {
	var elementSelector = '.reservationFormH';
	if ($('.validation-summary-errors').length) {
		elementSelector = '.validation-summary-errors';
	}

	var elementTop = $(elementSelector).offset().top;
	var elementBottom = elementTop + $(elementSelector).outerHeight();
	var viewportTop = $(window).scrollTop();
	var viewportBottom = viewportTop + $(window).height();

	if (elementBottom > viewportTop && elementTop < viewportBottom) {
		$('#Reservation_CheckInDate2').focus();
		return false;
	}

	$('html, body').animate({ scrollTop: $(elementSelector).offset().top - 70 }, 1500, function () {
		$('#Reservation_CheckInDate2').focus();
	});

	return false;
}

$(document).keyup(function (e) {
	if (e.keyCode == 27) {
		var elementHovered = $('.img-cover-box:hover');
		var elementFocused = $('.img-cover-box :focus').parents('.img-cover-box');
		if (elementHovered.length == 1) {
			$(elementHovered[0]).addClass('img-disabled');
			$(elementHovered[0]).on('mouseleave mouseenter', function () {
				$(this).removeClass('img-disabled');
				$(this).off('mouseleave mouseenter');
			});
		}
		if (elementFocused.length == 1) {
			$(elementFocused[0]).addClass('img-disabled');
			$(elementFocused[0]).on('focusout mouseleave mouseenter', function () {
				$(this).removeClass('img-disabled');
				$(this).off('focusout mouseleave mouseenter');
			});
		}
	}
});

$.extend($.datepicker, {
	_doKeyDown: function (event) {
		var onSelect, dateStr, sel,
			inst = $.datepicker._getInst(event.target),
			handled = true,
			isRTL = inst.dpDiv.is(".ui-datepicker-rtl");

		inst._keyEvent = true;
		if ($.datepicker._datepickerShowing) {
			switch (event.keyCode) {
				case 9: $.datepicker._hideDatepicker();
					handled = false;
					break; // hide on tab out
				case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." +
					$.datepicker._currentClass + ")", inst.dpDiv);
					if (sel[0]) {
						$.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
					}

					onSelect = $.datepicker._get(inst, "onSelect");
					if (onSelect) {
						dateStr = $.datepicker._formatDate(inst);

						// trigger custom callback
						onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
					} else {
						$.datepicker._hideDatepicker();
					}

					return false; // don't submit the form
				case 27: $.datepicker._hideDatepicker();
					break; // hide on escape
				case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
					-$.datepicker._get(inst, "stepBigMonths") :
					-$.datepicker._get(inst, "stepMonths")), "M");
					break; // previous month/year on page up/+ ctrl
				case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
					+$.datepicker._get(inst, "stepBigMonths") :
					+$.datepicker._get(inst, "stepMonths")), "M");
					break; // next month/year on page down/+ ctrl
				case 35: if (event.ctrlKey || event.metaKey) {
					$.datepicker._clearDate(event.target);
				}
					handled = event.ctrlKey || event.metaKey;
					break; // clear on ctrl or command +end
				case 36: if (event.ctrlKey || event.metaKey) {
					$.datepicker._gotoToday(event.target);
				}
					handled = event.ctrlKey || event.metaKey;
					break; // current on ctrl or command +home
				case 37: if (true) {
					$.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D");
				}
					handled = true;
					// -1 day on ctrl or command +left
					if (event.originalEvent.altKey) {
						$.datepicker._adjustDate(event.target, (event.ctrlKey ?
							-$.datepicker._get(inst, "stepBigMonths") :
							-$.datepicker._get(inst, "stepMonths")), "M");
					}
					// next month/year on alt +left on Mac
					break;
				case 38: if (true) {
					$.datepicker._adjustDate(event.target, -7, "D");
				}
					handled = true;
					break; // -1 week on ctrl or command +up
				case 39: if (true) {
					$.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D");
				}
					handled = true;
					// +1 day on ctrl or command +right
					if (event.originalEvent.altKey) {
						$.datepicker._adjustDate(event.target, (event.ctrlKey ?
							+$.datepicker._get(inst, "stepBigMonths") :
							+$.datepicker._get(inst, "stepMonths")), "M");
					}
					// next month/year on alt +right
					break;
				case 40: if (true) {
					$.datepicker._adjustDate(event.target, +7, "D");
				}
					handled = true;
					break; // +1 week on ctrl or command +down
				default: handled = false;
			}
		} else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
			$.datepicker._showDatepicker(this);
		} else {
			handled = false;
		}

		if (handled) {
			event.preventDefault();
			event.stopPropagation();
		}
	}
})
;
var selectedFilterId;
var slideoutHeight;
var sitesLoaded = false;
var featuresLoaded = false;
var availabilitySet = false;
var currentCategory;
var siteTypesArray;
var errorMessages = {
	checkInDate: "",
	checkOutDate: "",
	adultCount: "",
	kidsCount: "",
	freeCount: "",
	pets: "",
	equipmentType: "",
	length: "",
	slideouts: ""
};
var isMobile;
var smsChecked = true;

$(document).ready(function () {
	isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
	$(document).on("OnMapLoading", function () {
		console.log("Map Loading");
	});
	$(document).on("OnMapLoaded", function () {
		console.log("Map Loaded");
		if (isMobile) {
			_campMap.SetGestureHandling(true);
		}
		addMapOverlay();
	});
	$(document).on("OnSiteTypeLoaded", function (event, obj) {
		console.log("Sites Loaded");
		populateSiteList(obj.siteArray);
		sitesLoaded = true;
		if (featuresLoaded) {
			setFilterCounts();
		}
	});
	$(document).on("OnFeatureLoaded", function (event, obj) {
		console.log("Features Loaded");
		populateFeatureList(obj.featureArray);
		featuresLoaded = true;
		if (sitesLoaded) {
			setFilterCounts();
		}
	});
	$(document).on("OnSiteTypeClicked", function (event, siteDetails) {
		if (siteDetails.site) {
			if (siteDetails.showTooltip) {
				closeOpenSlideout(false);
			}
			else {
				populateSiteSlideout(siteDetails.site);
			}
		}
	});
	$(document).on("OnPoiClicked", function (event, featureDetails) {
		if (featureDetails) {
			populateFeatureSlideout(featureDetails);
		}
	});
	$(document).on("OnMapClicked", function () {
		console.log("Map Clicked");
		closeOpenSlideout();
	});
	$(document).on("OnFiltering", function (event) {
		console.log("Map Filtering");
	});
	$(document).on("OnFiltered", function (event, filterArray) {
		console.log("Map Filtered");
		//TODO: If availability set, populate availability list instead of site/feature list
		var matchingCount = filterArray.matchingSites.length + filterArray.matchingFeatures.filter(x => (x.categoryNameNormalized == "recreation") || (x.categoryNameNormalized == "food_and_drink") || (x.categoryNameNormalized == "info")).length;
		if (filterArray.populateList) {
			hideListElements();
			if (matchingCount > 0) {
				if (availabilitySet) {
					if (_campMap.smsEnabled) {
						populateAvailabilityList(filterArray.matchingSites);
					}
					else {
						var siteTypes = groupBy(filterArray.matchingSites, 'siteTypeID');
						matchingCount = Object.keys(siteTypes).length;
						populateAvailabilityList(siteTypes);
					}

				}
				else {
					populateSiteList(filterArray.matchingSites);
					populateFeatureList(filterArray.matchingFeatures);
				}
			}
			else {
				showEmptyList();
			}
		}
		if (selectedFilterId) {
			$('#camp-map-campsitelist-slideout .list-row li[data-id="' + selectedFilterId + '"]').addClass('selected');
			selectedFilterId = null;
			$('#camp-map-campsitelist-slideout .camp-map-filters-clear').removeClass('d-none');
			if (isMobile) {
				closeOpenSlideout(false);
			}
		}
		else {
			if (matchingCount > 0) {
				$('#camp-map-campsitelist-slideout .camp-map-filters-clear').addClass('d-none');
			}
			updateListButton(matchingCount);
		}
	});
	$(document).on("OnAvailabilityUpdated", function (event, filterArray) {
		console.log("Availability Updated");
		if (availabilitySet) {
			if (_campMap.smsEnabled) {
				var availableCount = filterArray.matchingSites.length;
				if (filterArray.populateList) {
					hideListElements();
					if (availableCount > 0) {
						populateAvailabilityList(filterArray.matchingSites);
					}
					else {
						showEmptyList();
					}
				}
				if (availableCount > 0) {
					$('#camp-map-campsitelist-slideout .camp-map-availability-clear').addClass('d-none');
				}
				updateListButton(availableCount);
			}
			else {
				var siteCount = filterArray.matchingSites.length;
				if (filterArray.populateList) {
					hideListElements();
					if (siteCount > 0) {
						var siteTypes = groupBy(filterArray.matchingSites, 'siteTypeID');
						var siteTypeCount = Object.keys(siteTypes).length;
						populateAvailabilityList(siteTypes);

						if (siteTypeCount > 0) {
							$('#camp-map-campsitelist-slideout .camp-map-availability-clear').addClass('d-none');
						}
						updateListButton(siteTypeCount);
					}
					else {
						showEmptyList();
					}
				}
			}
		}
		else {
			hideListElements();
			var matchingCount = filterArray.matchingSites.length + filterArray.matchingFeatures.filter(x => (x.categoryNameNormalized == "recreation") || (x.categoryNameNormalized == "food_and_drink") || (x.categoryNameNormalized == "info")).length;

			if (matchingCount > 0) {
				populateSiteList(filterArray.matchingSites);
				populateFeatureList(filterArray.matchingFeatures);
			}
			else {
				showEmptyList();
			}

			if ($("#camp-map-filters-btn .filters-count").text() == '') {
				updateListButton();
			}
			else {
				updateListButton(matchingCount);
			}
		}
	});

	$(document).on('OnShoppingCartAdd', function (event, shoppingCartData) {
		showShoppingCartPopup(shoppingCartData);
	});

	$(document).on('onShoppingCartItemDelete', function (event, data) {
		if (data.Success && data.CartItemDeleted.SiteID) {
			_campMap.RemoveSiteFromShoppingCart(data.CartItemDeleted.SiteID);
			if ($('#camp-map-campsite-slideout .site-add-cart').is(':visible') && $('#camp-map-campsite-slideout .site-add-cart').hasClass('btn-disabled') && $('.camp-map-campsite-slideout-btn-availability').data('k2SiteId') == data.CartItemDeleted.SiteID) {
				$('#camp-map-campsite-slideout .site-add-cart').removeClass('btn-disabled');
				$('#camp-map-campsite-slideout .site-add-cart').html('<i class="fa fa-shopping-cart my-auto pr-2"></i><p class="mb-0">Add To Cart</p>');
			}
		}
	});

	$(document).on('onShoppingCartItemsDeleteAll', function (event) {
		_campMap.RemoveAllSitesFromShoppingCart();
		if ($('#camp-map-campsite-slideout .site-add-cart').is(':visible') && $('#camp-map-campsite-slideout .site-add-cart').hasClass('btn-disabled')) {
			$('#camp-map-campsite-slideout .site-add-cart').removeClass('btn-disabled');
			$('#camp-map-campsite-slideout .site-add-cart').html('<i class="fa fa-shopping-cart my-auto pr-2"></i><p class="mb-0">Add To Cart</p>');
		}
	});

	$(document).on('click', '.camp-map-filter-container', function (e) {
		$(this).toggleClass('selected');
		if (!$(e.target).hasClass('filter-checkbox')) {
			var input = $(this).find('.filter-checkbox');
			if (input) {
				$(input).prop('checked', !$(input).is(':checked'));
			}
		}
	});

	$(document).on('click', '.camp-map-filters-apply', function () {
		var campMapFilter = {};
		var filterCount = 0
		$('#camp-map-filters .accommodations-filters input:checked').each(function (index, item) {
			if (index == 0) {
				campMapFilter.accomodations = Array();
			}
			var accomodation = $(item).val();
			if (accomodation) {
				campMapFilter.accomodations.push(_campMap.categories[accomodation]);
				filterCount++;
			}
		});
		$('#camp-map-filters .amenities-filters input:checked').each(function (index, item) {
			if (index == 0) {
				campMapFilter.amenities = Array();
			}
			var amenity = $(item).val();
			if (amenity) {
				campMapFilter.amenities.push(_campMap.categories[amenity]);
				filterCount++;
			}
		});
		$('#camp-map-filters .features-filters input:checked').each(function (index, item) {
			if (index == 0) {
				campMapFilter.features = Array();
			}
			var feature = $(item).val();
			if (feature) {
				var feature_list = _campMap.categories[feature];
				if (Array.isArray(feature_list)) {
					feature_list.forEach(function (feature_item) {
						campMapFilter.features.push(feature_item);
					});
				}
				else {
					campMapFilter.features.push(feature_list);
				}
				filterCount++;
			}
		});
		_campMap.applyFilters(campMapFilter, true, false, availabilitySet);
		updateFilterButton(filterCount);
		closeOpenSlideout();
	});

	$(document).on('click', '.camp-map-filters-clear', function () {
		clearFilterMenu(this);
	});

	$(document).on('click', '.site-get-directions', function () {
		var id = $('#camp-map-campsite-slideout').data('id');
		if (id) {
			_campMap.calculateAndDisplayRoute(id);
		}
	});

	$(document).on('click', '.feature-get-directions', function () {
		var id = $('#camp-map-poi-slideout').data('id');
		if (id) {
			_campMap.calculateAndDisplayRoute(id);
		}
	});

	$(document).on('click', '.site-view-map', function () {
		var id = $('#camp-map-campsite-slideout').data('id');
		if (id) {
			_campMap.selectSite(id, "site");
		}
	});

	$(document).on('click', '.availability-list-item', function () {
		var id = $(this).data('id');
		if (id) {
			_campMap.selectSite(id, "site", true);
		}
	});

	$(document).on('click', '.availability-list-choose-site', function () {
		var id = $(this).data('id');
		if (id) {
			_campMap.selectSite(id, "site");
		}
	});

	$(document).on('click', '.availability-list-view-list', function () {
		$('#camp-map-list-btn').trigger('click');
	});

	$(document).on('click', '.feature-view-map', function () {
		var id = $('#camp-map-poi-slideout').data('id');
		if (id) {
			_campMap.selectSite(id, "feature");
		}
	});

	$(document).on('click', '.site-reset-search, .camp-map-availability-clear', function () {
		$('.camp-map-availability-clear').addClass('d-none');
		closeOpenSlideout();
		clearAvailabilitySlideout(true);
	});

	$(document).on('click', '#camp-map-campsitelist-slideout .list-row li', function () {
		var id = $(this).data('id');
		var selected = $(this).hasClass('selected');
		if (id && !$(this).hasClass('availability-list-item')) {
			$('#camp-map-campsitelist-slideout .list-row li.selected').removeClass('selected');
			if (selected) {
				selectedFilterId = null;
				clearFilterMenu(this);
			}
			else {
				selectedFilterId = id;
				if ($(this).parent('.site-list').length > 0) {
					_campMap.applyFilters({ tiers: [id] }, false);
				}
				else {
					_campMap.applyFilters({ features: [id] }, false);
				}
			}
		}
	});

	$(document).on('click', '.exit-route', function () {
		_campMap.exitRoute();
	});

	$(document).on('show.bs.collapse', '#smsCollapse', function () {
		$('.sms-more-info .fa').removeClass('fa-chevron-down').addClass('fa-chevron-up');
	});

	$(document).on('hide.bs.collapse', '#smsCollapse', function () {
		$('.sms-more-info .fa').removeClass('fa-chevron-up').addClass('fa-chevron-down');
	});

	$(document).on('change', '.route-mode-toggle input', function () {
		var routeOverlay = $(this).parents('.route-overlay');
		var id = $(routeOverlay).attr('id');
		if (id) {
			_campMap.switchDirectionMode(id, this.value);
		}
	});

	$(document).on('click', '.btn-map-toggle', function () {
		if (!$(this).hasClass('selected')) {
			$('.btn-map-toggle.selected').removeClass('selected');
			$(this).addClass('selected');
			$('.site-map-image').toggle();
			$('#camp-map').toggle();
		}
	});

	$(document).on('change', '#ReservationsViewData_Reservation_SiteCategory', function () {
		var categoryValue = $(this).val();
		if (categoryValue == "R") {
			$('#camp-map-slideouts-options').show();
		}
		else {
			$('#camp-map-slideouts-options').hide();
			$('#yesSlideouts').prop('checked', 'false');
			$('#noSlideouts').prop('checked', 'true');
		}
		var equipment = _campMap.getSiteTypeEquipment(categoryValue);
		populateEquipmentList(equipment);
	});

	$(document).on('change', '#ReservationsViewData_Reservation_EquipmentType', function () {
		var equipmentValue = $(this).val();
		var category = $('#ReservationsViewData_Reservation_SiteCategory').val();
		var equipment = _campMap.getEquipment(equipmentValue);
		if (equipment) {
			if (equipment.IsLengthRequired) {
				$('#camp-map-length-options').show();
				if (category == 'R') {
					$('#camp-map-slideouts-options').removeClass('pl-0').removeClass('pr-3').addClass('pl-3 pr-0');
				}
			}
			else {
				$('#camp-map-length-options').hide();
				$(".availability-equipmentLength").val('');
				if (category == 'R') {
					$('#camp-map-slideouts-options').removeClass('pl-3').removeClass('pr-0').addClass('pl-0 pr-3');
				}
			}
		}
	});

	$('.camp-map-reservation-form').submit(function (e) {
		e.preventDefault();
	});

	$(document).on('change', 'input[name="yes-sms"]', function () {
		if (this.value == "no") {
			smsChecked = false;
			$('.camp-map-campsite-slideout-btn-availability').data('action', 'reserve-site-type');
			$('#smsDisclaimerCollapse').collapse('show');
		}
		else {
			smsChecked = true;
			$('.camp-map-campsite-slideout-btn-availability').data('action', 'reserve-site');
			$('#smsDisclaimerCollapse').collapse('hide');

		}
	});

	$(document).on('click', '.camp-map-campsite-slideout-btn-availability', function (e) {
		if (_campMap.availabilityEnabled) {
			e.preventDefault();
			var siteType = $(this).data('siteTypeValue');
			var siteTypeId = $(this).data('siteTypeId');
			var siteId = $(this).data('siteId');
			var action = $(this).data('action');
			switch (action) {
				case "reserve-site":
					UpdateAndSubmitSelectSiteForm(siteId);
					break;
				case "reserve-site-type":
					if (_campMap.smsEnabled) {
						if (_campMap.getAvailability(siteId)) {
							UpdateAndSubmitSelectGroupForm(siteId);
						}
						else {
							openAndFilterSiteType(siteTypeId);
						}
					}
					else {
						//TODO: Probably need to update this to just pass in siteTypeId for non sms campgrounds
						UpdateAndSubmitSelectGroupForm(siteId);
					}
					break;
				case "check-availability":
				default:
					if (_campMap.smsEnabled) {
						openAvailabilitySlideout(siteType, siteId);
					}
					else {
						openAvailabilitySlideout(siteType);
					}
			}
		}
	});

	$(document).on('click', '.reservation-edit-btn', function (e) {
		e.preventDefault();
		var siteType = $('.camp-map-campsite-slideout-btn-availability').data('siteTypeValue');
		var siteId = $('.camp-map-campsite-slideout-btn-availability').data('siteId');
		if (_campMap.smsEnabled) {
			openAvailabilitySlideout(siteType, siteId);
		}
		else {
			openAvailabilitySlideout(siteType);
		}
	});

	$(document).on('click', '.camp-map-availability-search-submit, .camp-map-availability-update', function () {
		checkAvailability(_campMap.campgroundName);
	});

	$(document).on('click', '.site-view-calendar', function () {
		//Collect all the data and send the request
		var siteTypeId = $('.camp-map-campsite-slideout-btn-availability').data('siteTypeId');
		getAvailabilityCalendarUrl(_campMap.campgroundName, siteTypeId);

	});

	$(document).on('click', '.site-add-cart', function () {
		if (!$(this).hasClass('btn-disabled')) {
			var siteTypeId = $('.camp-map-campsite-slideout-btn-availability').data('siteTypeId');
			var siteId;
			if ($('.camp-map-campsite-slideout-btn-availability').data('action') == 'reserve-site') {
				siteId = $('.camp-map-campsite-slideout-btn-availability').data('siteId');
			}
			getShoppingCartUrl(_campMap.campgroundName, siteTypeId, siteId);
		}
	});

	$('#numKids').on('change', function () {
		// Additional JavaScript
		let numKids = parseInt($(this).val())

		$('#camp-map-num-kids-filter').empty()

		var colSize = (numKids >= 3) ? "col-4" : "col-6";

		for (let numKidsSpaces = 0; numKidsSpaces < numKids; numKidsSpaces++) {
			var paddingClass = numKids > 2 ? (numKidsSpaces % 3 == 0 ? "pl-0 pr-3" : numKidsSpaces % 3 == 2 ? "pl-3 pr-0" : "pl-2 pr-2") : numKidsSpaces % 2 == 0 ? "pl-0 pr-3" : "pl-3 pr-0";
			$('#camp-map-num-kids-filter').append(`\
				<div class="${colSize} ${paddingClass}">\
					<p class="font-weight-bold mb-0 pt-2"> Kid #${numKidsSpaces + 1} Age</p >\
					<select name="ages" id="kidNum${numKidsSpaces + 1}Age" class="w-100 availability-children-ages form-control ada-form-outline-resbar">\
						<option value="1">1</option>\
						<option value="2">2</option>\
						<option value="3">3</option>\
						<option value="4">4</option>\
						<option value="5">5</option>\
						<option value="6">6</option>\
						<option value="7">7</option>\
						<option value="8">8</option>\
						<option value="9">9</option>\
						<option value="10">10</option>\
						<option value="11">11</option>\
						<option value="12">12</option>\
						<option value="13">13</option>\
						<option value="14">14</option>\
						<option value="15">15</option>\
						<option value="16">16</option>\
						<option value="17">17</option>\
					</select>\
				</div >\
				`);

		}
	});

	$(document).on('click', '#camp-map-list-btn', function () {
		var isOpen = $('#camp-map-campsitelist-slideout').hasClass('camp-map-slideout-show-menu');
		closeOpenSlideout();
		if (!isOpen) {
			$('#camp-map-campsitelist-slideout').css('top', slideoutHeight);
			$('#camp-map-campsitelist-slideout').toggleClass('camp-map-slideout-show-menu');
		}
	});
	$(document).on('click', '#camp-map-availability-btn', function () {
		var isOpen = $('#camp-map-availability-search').hasClass('camp-map-slideout-show-menu');
		closeOpenSlideout();
		if (!isOpen) {
			openAvailabilitySlideout();
		}
	});
	$(document).on('click', '#camp-map-filters-btn', function () {
		var isOpen = $('#camp-map-filters').hasClass('camp-map-slideout-show-menu');
		closeOpenSlideout();
		if (!isOpen) {
			$('#camp-map-filters').css('top', slideoutHeight);
			$('#camp-map-filters').addClass('camp-map-slideout-show-menu');
		}
	});
	$('.camp-map-campsite-slideout-close, .camp-map-poi-slideout-close').on('click touchstart', function () {
		closeOpenSlideout();
	});
	$('.camp-map-filters-close, .camp-map-availability-search-close, .camp-map-campsitelist-slideout-close').on('click touchstart', function () {
		closeOpenSlideout(false);
	});
	$(document).on('click', '#camp-map-overlay .error-container .btn-error-overlay-close, #camp-map-overlay .message-container .btn-message-overlay-close', function () {
		_campMap.SetDisplayMode('view');
	});
	$(document).on('click', '.availability-site-tooltip.control .close-control', function () {
		_campMap.deselectFeature();
	});
	$(document).on('click', '.availability-date-group .input-group-append', function () {
		var input = $(this).siblings('input');
		if (input.length > 0) {
			$(input).focus();
		}
	});
});

function addMapOverlay() {
	$('#camp-map-wrapper').appendTo('#camp-map > div');
	$('#camp-map-overlay').appendTo('#camp-map > div');
	$('#camp-map').prepend('<div class="inner-shadow-bottom"></div>');
	//$('#add-to-cart-modal').appendTo('#camp-map > div');
	//$('#add-to-cart-error-modal').appendTo('#camp-map > div');
	//$('#shopping-cart-wrapper').appendTo('#camp-map > div');
	setDraggables();
	setDatePickers();
};

function setDraggables() {
	//TODO: Maybe set up event listener for if screen width changes?
	if ($(window).width() < 768) {
		$('#camp-map-wrapper').children().each(function (index, item) {
			let mapHeight = $('#camp-map > div:not(.inner-shadow-bottom)').height();
			$(item).draggable({
				handle: '.draggable-handle, .slideout-top',
				axis: 'y',
				start: function (event, ui) {
					mapHeight = $('#camp-map > div:not(.inner-shadow-bottom)').height();
				},
				drag: function (event, ui) {
					ui.position.top = Math.max(60, ui.position.top);
					ui.position.top = Math.min(mapHeight - 82, ui.position.top);
					$(ui.helper).css('height', 'calc(100% - ' + ui.position.top + 'px)');
					$(ui.helper).css('transition', 'transform 0.3s linear');
				},
				stop: function (event, ui) {
					let mapHeight = $('#camp-map > div:not(.inner-shadow-bottom)').height();
					let slideoutPercentage = (mapHeight - ui.position.top) / mapHeight;
					let snapHeight = 0;
					let scrollable = false;
					let closeSlideout = false;
					if (slideoutPercentage > .6) {
						snapHeight = 60;
						scrollable = true;
					}
					//else if (slideoutPercentage > .25) {
					//	snapHeight = (mapHeight + 100) / 2;
					//	scrollable = true;
					//}
					else if (slideoutPercentage < (100 / mapHeight)) {
						closeOpenSlideout();
						let closingElement = $(this);
						setTimeout(function () {
							$(closingElement).css('top', '60px');
							slideoutHeight = null;
							$(closingElement).css('transition', '0.5s');
							$(closingElement).css('top', 60);
							$(closingElement).css('height', 'calc(100% - 60px)');
							$(closingElement).css('overflow-y', 'auto');
							$('.draggable-handle').css('height', '5.5rem');
							$(closingElement).children('.draggable-handle').css('top', '0');
						}, 300);
					}
					else {
						snapHeight = ui.position.top;
					}
					$(ui.helper).css('transition', '0.5s');
					$(ui.helper).css('top', snapHeight);
					$(ui.helper).css('height', 'calc(100% - ' + snapHeight + 'px)');
					if (scrollable) {
						$(ui.helper).css('overflow-y', 'auto');
						$('.draggable-handle').css('height', '5.5rem');
						$(ui.helper).children('.draggable-handle').css('top', '0');
					}
					else {
						$(ui.helper).css('overflow-y', 'hidden');
						$('.draggable-handle').css('height', '100%');
						$(ui.helper).children('.draggable-handle').css('top', $(ui.helper).scrollTop());
					}
					//if (ui.position.top > mapHeight - 150) {
					//	closeOpenSlideout();
					//	let closingElement = $(this);
					//	setTimeout(function () {
					//		$(closingElement).css('top', '100px');
					//		closingElement = null;
					//	}, 300);
					//	return false;
					//}
				}
			});
		});
	}
};

function hideListElements() {
	$('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row h4, #camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row ul').each(function (index, item) {
		$(item).addClass('d-none');
	})
	$('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row p.no-availability-message').addClass('d-none');
	$('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row p.empty-list-message').addClass('d-none');
	$('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row p.empty-list-availability-message').addClass('d-none');
};

function showEmptyList(availabilityMessage = false) {
	$('.empty-list-title').removeClass('d-none');
	if (availabilityMessage) {
		$('.empty-list-availability-message').removeClass('d-none');
		$('.camp-map-campsitelist-slideout-bottom .camp-map-availability-clear').removeClass('d-none');
	}
	else {
		$('.empty-list-message').removeClass('d-none');
		$('#camp-map-campsitelist-slideout .camp-map-filters-clear').removeClass('d-none');
	}
};

function populateSiteList(siteArray) {
	if (siteArray.length > 0) {
		var categories = groupBy(siteArray, 'category', 'name');
		var categoriesOrdered = Object.keys(categories).sort(function (a, b) {
			return categories[b].length - categories[a].length;
		});
		var list = $('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row ul.site-list');
		$(list).empty();
		categoriesOrdered.forEach(function (category) {
			var siteTypes = groupBy(categories[category], 'tier', 'code');
			var siteTypesOrdered = Object.keys(siteTypes).sort(function (a, b) {
				return siteTypes[b].length - siteTypes[a].length;
			});
			siteTypesOrdered.forEach(function (site) {
				if (site) {
					var siteHtml = '<li data-id="';
					siteHtml += siteTypes[site][0].tier.code;
					siteHtml += '"><div class="campsite-list-item container-fluid py-2"><div class="row"><div class="col-3 d-flex px-2"><img class="item-image" src="'
					//TODO: Replace this with site image when we get that in the data. Something like: siteHtml += siteTypes[site][0].images[0];
					siteHtml += siteTypes[site][0].images[0];
					siteHtml += '" /></div><div class="col-9 pl-0"><p class="item-title">';
					siteHtml += siteTypes[site][0].tier.label;
					siteHtml += '</p><p class="item-category"><span class="fa fa-circle';
					if (siteTypes[site][0].tier.solidOutline) {
						siteHtml += ' solid-outline';
					}
					siteHtml += '" style="color:';
					siteHtml += siteTypes[site][0].tier.color;
					siteHtml += ';"></span> ';
					siteHtml += siteTypes[site].length;
					siteHtml += ' Similar Sites</p></div></div></div></li>';
					$(list).append(siteHtml);
				}
			});
		});
		$(list).removeClass('d-none');
		$('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row h4.site-title').removeClass('d-none');
	}
};

function populateSiteSlideout(site) {
	openSiteSlideout(false); //Probably don't need this here
	//TODO: Not sure if we want to build the html here or just set text for elements, but can do all the data populating here.
	$('#camp-map-campsite-slideout').data('id', site.id);
	$('#camp-map-campsite-slideout h3').text(site.tier.label);
	if (_campMap.smsEnabled) {
		$('.camp-map-campsite-slideout-details').text('Site ' + site.name + ' Details');
		$('.camp-map-campsite-slideout-details').removeClass('d-none');
		$('.camp-map-campsite-slideout-general-information .general-information-disclaimer').text("Descriptions, photos, and details represent a group of similar campsites.");
	}
	else {
		$('.camp-map-campsite-slideout-details').addClass('d-none');
		$('.camp-map-campsite-slideout-general-information .general-information-disclaimer').text("Please note that while you've clicked on a specific site, we cannot guarantee that exact site will be reserved for you, as the descriptions, photos, and details may represent a group of similar campsites.");
	}
	$('.camp-map-campsite-slideout-general-information .general-information-category').text(site.category.label + " - " + site.similarSites + " sites like this.");

	var siteTypeString = '';
	var siteTypeValue = '';
	switch (site.category.label.toLowerCase()) {
		case 'rv':
			siteTypeString = 'rv-camping-sites/';
			siteTypeValue = 'R';
			break;
		case 'cabin':
			siteTypeString = 'lodging/';
			siteTypeValue = 'A';
			break;
		case 'tent':
			siteTypeString = 'tent-camping-sites/';
			siteTypeValue = 'T';
			break;
		default:
			siteTypeString = 'O';
	}


	var siteTypeLink = "/campgrounds/" + _campMap.campgroundName + "/site-type/" + siteTypeString;

	//sometimes it doesn't come back with a value. only add it if we have one.
	if (site.siteTypeID) {
		siteTypeLink = siteTypeLink + "#site-" + site.siteTypeID;
		$('.camp-map-campsite-slideout-btn-availability').data('siteTypeId', site.siteTypeID);
	}

	if (site.k2SiteId) {
		$('.camp-map-campsite-slideout-btn-availability').data('k2SiteId', site.k2SiteId);
	}

	if (!_campMap.availabilityEnabled) {
		$('.camp-map-campsite-slideout-btn-availability').attr('href', siteTypeLink);
	}

	$('.camp-map-campsite-slideout-btn-availability').data('siteTypeValue', siteTypeValue);
	$('.camp-map-campsite-slideout-btn-availability').data('siteId', site.id);

	//TODO: Set button text based on reservation information

	//TODO: Probably a new function to populate reservation details on slideout.

	if (site.description) {
		$('.camp-map-campsite-slideout-description').text(site.description);
		$('.camp-map-campsite-slideout-description').show();
		$('.camp-map-campsite-slideout-description-title').show();
	}
	else {
		$('.camp-map-campsite-slideout-description').text("");
		$('.camp-map-campsite-slideout-description').hide();
		$('.camp-map-campsite-slideout-description-title').hide();
	}

	if (site.shade) {
		$('.camp-map-campsite-slideout-shade').text(site.shade);
		$('.camp-map-campsite-slideout-shade-container').show();
	}
	else {
		$('.camp-map-campsite-slideout-shade').text("");
		$('.camp-map-campsite-slideout-shade-container').hide();
	}

	if (site.surface) {
		$('.camp-map-campsite-slideout-surface').text(site.surface);
		$('.camp-map-campsite-slideout-surface-container').show();
	}
	else {
		$('.camp-map-campsite-slideout-surface').text("");
		$('.camp-map-campsite-slideout-surface-container').hide();
	}

	//TODO: Clear out contents of row that doesn't have an identifier for now
	$('.camp-map-amenity-row').html('');
	site.amenities.forEach(function (group) {
		if (group.name) {
			var html = '<div class="col-6">';
			html += '<p class="mb-1 font-weight-bold">' + group.label + '</p>';
			//TODO: Replace this with the line below if the featuretag name is general info or something similar
			//html += '<p class="mb-1 text-red">Up to ' + feature.maxpeople + ' people</p>';
			if (group.amenities) {
				html += '<ul class="camp-map-slideout-' + group.name + '">';
				group.amenities.forEach(function (amenity) {
					html += '<li class="amenity-item"><img class="amenity-item-icon" src="https://cmwa.azureedge.net/static/map/img/new/' + (amenity.icon ? amenity.icon : amenity.name + "AmenityIcon") + '.svg"><p class="amenity-item-label">' + amenity.label + '</p></li>';
				});
				html += '</ul>';
			}
			html += '</div>';
			//TODO: Append this to the row at the bottom of the slideout. Doesn't have an identifier for now
			$('.camp-map-amenity-row').append(html);
		}
	});
	$('#camp-map-slideout-carousel-campsite').carousel('dispose');
	$('#camp-map-slideout-carousel-campsite .carousel-inner').html('');
	site.images.forEach(function (image, index) {
		if (image) {
			$('#camp-map-slideout-carousel-campsite .carousel-inner').append('<div class="carousel-item h-100 ' + (index == 0 ? 'active' : '') + '"><img class="camp-map-slideout-carousel-image w-100" src="' + image + '"></div>');
		}
	});
	if (site.images.length > 1) {
		$('#camp-map-slideout-carousel-campsite .carousel-controls').show();
	}
	else {
		$('#camp-map-slideout-carousel-campsite .carousel-controls').hide();
	}
	$('#camp-map-slideout-carousel-campsite').carousel();

	updateSiteReservation(site.id, siteTypeValue, site.isAvailable);

	openSiteSlideout(true);
};

function openSiteSlideout(open) {
	closeOpenSlideout(false);
	$('#camp-map-campsite-slideout').css('top', slideoutHeight);
	$('#camp-map-campsite-slideout').toggleClass('camp-map-slideout-show-menu', open);
};

function updateSiteReservation(siteId, siteTypeValue, isSiteAvailable = false) {
	var site = _campMap.getSite(siteId);
	if (_campMap.smsEnabled) {
		if (!isSiteAvailable) {
			isSiteAvailable = _campMap.getAvailability(siteId)
		}
	}
	var isSiteTypeAvailable = _campMap.getSiteTypeAvailability(siteId);

	if (availabilitySet) {
		var reservationDetails = getReservationDetails();
		if (reservationDetails.maidInput) {
			if (reservationDetails.maidInput.CheckInDate && reservationDetails.maidInput.CheckOutDate) {
				var nights = Math.floor(Math.abs(new Date(reservationDetails.maidInput.CheckOutDate) - new Date(reservationDetails.maidInput.CheckInDate)) / (1000 * 60 * 60 * 24));
				$('.campsite-reservation-container .reservation-dates-container .reservation-nights').text(nights + ' Nights');
				$('.campsite-reservation-container .reservation-dates-container .reservation-checkin').text(reservationDetails.maidInput.CheckInDate);
				$('.campsite-reservation-container .reservation-dates-container .reservation-checkout').text(reservationDetails.maidInput.CheckOutDate);
				$('.campsite-reservation-container .reservation-dates-container').show();
			}
			else {
				$('.campsite-reservation-container .reservation-dates-container').hide();
			}

			if (reservationDetails.maidInput.Adults) {
				$('.campsite-reservation-container .reservation-guest-container .reservation-adults').text(reservationDetails.maidInput.Adults);
				$('.campsite-reservation-container .reservation-guest-container .reservation-adults-container').show();
			}
			else {
				$('.campsite-reservation-container .reservation-guest-container .reservation-adults-container').hide();
			}

			if (reservationDetails.maidInput.Kids > 0) {
				$('.campsite-reservation-container .reservation-guest-container .reservation-kids').text(reservationDetails.maidInput.Kids);
				$('.campsite-reservation-container .reservation-guest-container .reservation-kids-container').show();
			}
			else {
				$('.campsite-reservation-container .reservation-guest-container .reservation-kids-container').hide();
			}

			if (reservationDetails.maidInput.Free > 0) {
				$('.campsite-reservation-container .reservation-guest-container .reservation-kids-free').text(reservationDetails.maidInput.Free);
				$('.campsite-reservation-container .reservation-guest-container .reservation-kids-free-container').show();
			}
			else {
				$('.campsite-reservation-container .reservation-guest-container .reservation-kids-free-container').hide();
			}


			if (reservationDetails.maidInput.Pets == "Yes") {
				$('.campsite-reservation-container .reservation-guest-container .reservation-pets').text('Yes');
				$('.campsite-reservation-container .reservation-guest-container .reservation-pets-container').show();
			}
			else if (reservationDetails.maidInput.Pets == "No") {
				$('.campsite-reservation-container .reservation-guest-container .reservation-pets').text('No');
				$('.campsite-reservation-container .reservation-guest-container .reservation-pets-container').show();
			}
			else {
				$('.campsite-reservation-container .reservation-guest-container .reservation-adults-container').hide();
			}



			if (reservationDetails.maidInput.EquipmentType) {
				var equipment = _campMap.getEquipment(reservationDetails.maidInput.EquipmentType);
				$('.campsite-reservation-container .reservation-equipment-container .reservation-equipment-type').text(equipment.Type);
				$('.campsite-reservation-container .reservation-equipment-container .reservation-equipment-type-container').show();
			}
			else {
				$('.campsite-reservation-container .reservation-equipment-container .reservation-equipment-type-container').hide();
			}

			if (reservationDetails.maidInput.EquipmentLength > 0) {
				$('.campsite-reservation-container .reservation-equipment-container .reservation-equipment-length').text(reservationDetails.maidInput.EquipmentLength);
				$('.campsite-reservation-container .reservation-equipment-container .reservation-equipment-length-container').show();
			}
			else {
				$('.campsite-reservation-container .reservation-equipment-container .reservation-equipment-length-container').hide();
			}

			if (reservationDetails.maidInput.SlideOuts == "Yes") {
				$('.campsite-reservation-container .reservation-equipment-container .reservation-slideouts').text('Has Slideouts');
				$('.campsite-reservation-container .reservation-equipment-container .reservation-slideouts-container').show();
			}
			else if (reservationDetails.maidInput.SiteCategory == "R" && reservationDetails.maidInput.SlideOuts == "No") {
				$('.campsite-reservation-container .reservation-equipment-container .reservation-slideouts').text('No Slideouts');
				$('.campsite-reservation-container .reservation-equipment-container .reservation-slideouts-container').show();
			}
			else {
				$('.campsite-reservation-container .reservation-equipment-container .reservation-slideouts-container').hide();
			}

			$('.campsite-reservation-container .availability-message').removeClass('no-match-message').removeClass('match-message');
			$('.campsite-reservation-container .availability-message .far').removeClass('fa-check').removeClass('fa-info-circle');
			$('.campsite-reservation-secondary-button-container .btn-visible').removeClass('btn-visible');
			$('.campsite-reservation-secondary-button-container .btn-stacked').removeClass('btn-stacked');
			$('.campsite-reservation-secondary-button-container .btn-disabled').removeClass('btn-disabled');

			if (site.inShoppingCart) {
				$('.campsite-reservation-secondary-button-container .site-add-cart').addClass('btn-disabled');
				$('.campsite-reservation-secondary-button-container .site-add-cart').html('<i class="fa fa-check my-auto pr-2"></i><p class="mb-0">Added To Cart</p>');
			}
			else {
				$('.campsite-reservation-secondary-button-container .site-add-cart').html('<i class="fa fa-shopping-cart my-auto pr-2"></i><p class="mb-0">Add To Cart</p>');
			}
			if (_campMap.smsEnabled) {
				$('.campsite-reservation-container .camp-map-campsite-slideout-sms-wrapper').css('display', 'none');
				$('.campsite-reservation-container .camp-map-campsite-slideout-sms-wrapper .campsite-reservation-sitenumber').text(site.name);
				if (isSiteAvailable) {
					$('.campsite-reservation-container .availability-message .availability-message-details').text('Great news, Site ' + site.name + ' is available!');
					$('.campsite-reservation-container .availability-message').addClass('match-message');
					$('.campsite-reservation-container .availability-message .far').addClass('fa-check');
					$('.campsite-reservation-container .camp-map-campsite-slideout-sms-wrapper').css('display', 'flex');
					$('.camp-map-campsite-slideout-btn-availability span').text('Reserve Now');
					if (smsChecked) {
						$('.camp-map-campsite-slideout-btn-availability').data('action', 'reserve-site');
					}
					else {
						$('.camp-map-campsite-slideout-btn-availability').data('action', 'reserve-site-type');
					}
					$('.campsite-reservation-secondary-button-container .site-add-cart').addClass('btn-visible');
				}
				else if (isSiteTypeAvailable) {
					$('.campsite-reservation-container .availability-message .availability-message-details').text('Sorry, Site ' + site.name + ' is unavailable based on your search criteria, but sites in the same group are available for booking.');
					$('.campsite-reservation-container .availability-message').addClass('no-match-message');
					$('.campsite-reservation-container .availability-message .far').addClass('fa-info-circle');
					$('.camp-map-campsite-slideout-btn-availability span').text('Reserve Similar Site');
					$('.camp-map-campsite-slideout-btn-availability').data('action', 'reserve-site-type');
					//$('.campsite-reservation-secondary-button-container .site-view-calendar').addClass('btn-visible');//.addClass('btn-stacked');
					//$('.campsite-reservation-secondary-button-container .site-get-directions').addClass('btn-visible').addClass('btn-stacked');
					$('.campsite-reservation-secondary-button-container .site-view-map').addClass('btn-visible');//.addClass('btn-stacked');
				}
				else {
					$('.campsite-reservation-container .availability-message .availability-message-details').text('Sorry, there are 0 site groups available. Maybe try different search details or start over?');
					$('.campsite-reservation-container .availability-message').addClass('no-match-message');
					$('.campsite-reservation-container .availability-message .far').addClass('fa-info-circle');
					$('.camp-map-campsite-slideout-btn-availability span').text('Edit Reservation Details');
					$('.camp-map-campsite-slideout-btn-availability').data('action', 'check-availability');
					if (siteTypeValue != currentCategory) {
						$('.campsite-reservation-container .availability-message .availability-message-details').text('Sorry, there are 0 site groups available. Maybe try different search details or start over?');
					}
					else {
						//$('.campsite-reservation-secondary-button-container .site-view-calendar').addClass('btn-visible');//.addClass('btn-stacked');
						//$('.campsite-reservation-secondary-button-container .site-get-directions').addClass('btn-stacked');
						//$('.campsite-reservation-secondary-button-container .site-reset-search').addClass('btn-stacked');
					}
					//$('.campsite-reservation-secondary-button-container .site-get-directions').addClass('btn-visible');
					$('.campsite-reservation-secondary-button-container .site-reset-search').addClass('btn-visible');
				}
			}
			else {
				if (isSiteTypeAvailable) {
					$('.campsite-reservation-container .availability-message .availability-message-details').text('Great news, this site group is available!');
					$('.campsite-reservation-container .availability-message').addClass('match-message');
					$('.campsite-reservation-container .availability-message .far').addClass('fa-check');
					$('.camp-map-campsite-slideout-btn-availability span').text('Reserve Now');
					$('.camp-map-campsite-slideout-btn-availability').data('action', 'reserve-site-type');
					$('.campsite-reservation-secondary-button-container .site-add-cart').addClass('btn-visible');//.addClass('btn-stacked');
					//$('.campsite-reservation-secondary-button-container .site-get-directions').addClass('btn-visible').addClass('btn-stacked');
					$('.campsite-reservation-secondary-button-container .site-view-map').addClass('btn-visible');//.addClass('btn-stacked');
				}
				else {
					$('.campsite-reservation-container .availability-message .availability-message-details').text('Sorry, there are 0 site groups available. Maybe try different search details or start over?');
					$('.campsite-reservation-container .availability-message').addClass('no-match-message');
					$('.campsite-reservation-container .availability-message .far').addClass('fa-info-circle');
					$('.camp-map-campsite-slideout-btn-availability span').text('Edit Reservation Details');
					$('.camp-map-campsite-slideout-btn-availability').data('action', 'check-availability');
					//$('.campsite-reservation-secondary-button-container .site-view-calendar').addClass('btn-visible');//.addClass('btn-stacked');
					//$('.campsite-reservation-secondary-button-container .site-get-directions').addClass('btn-visible').addClass('btn-stacked');
					$('.campsite-reservation-secondary-button-container .site-view-map').addClass('btn-visible');//.addClass('btn-stacked');
				}
			}
		}
		$('.campsite-reservation-container .availability-message').css('display', 'flex');
		$('.campsite-reservation-details').css('display', 'flex');
	}
	else {
		$('.campsite-reservation-container .availability-message').css('display', 'none');
		$('.campsite-reservation-container .camp-map-campsite-slideout-sms-wrapper').css('display', 'none');
		$('.campsite-reservation-details').css('display', 'none');
		$('.camp-map-campsite-slideout-btn-availability span').text('Check Availability');
		$('.camp-map-campsite-slideout-btn-availability').data('action', 'check-availability');

		$('.campsite-reservation-secondary-button-container .btn-visible').removeClass('btn-visible');
		$('.campsite-reservation-secondary-button-container .btn-stacked').removeClass('btn-stacked');
		//$('.campsite-reservation-secondary-button-container .site-get-directions').addClass('btn-visible');
		$('.campsite-reservation-secondary-button-container .site-view-map').addClass('btn-visible');
	}
};


function populateFeatureList(featureArray) {
	//TODO: We may need to wait for both the features and poi to load
	var categories = groupBy(featureArray, 'categoryNameNormalized');
	Object.keys(categories).forEach(function (category) {
		var list = $('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row ul.' + category + '-list');
		var featureTypes = groupBy(categories[category], 'name');
		var featureTypesOrdered = Object.keys(featureTypes).sort(function (a, b) {
			return featureTypes[b].length - featureTypes[a].length;
		});
		if (list && list.length > 0) {
			$(list).empty();
			featureTypesOrdered.forEach(function (feature) {
				if (feature && featureTypes[feature][0].name) {
					var siteHtml = '<li data-id="';
					siteHtml += featureTypes[feature][0].name.replace(/\s/g, '');
					siteHtml += '"><div class="campsite-list-item container-fluid py-2"><div class="row"><div class="col-3 d-flex px-2"><img class="item-image" src="';
					siteHtml += featureTypes[feature][0].images[0];
					siteHtml += '" /></div><div class="col-9 pl-0"><p class="item-title">';
					siteHtml += featureTypes[feature][0].name;
					if (featureTypes[feature].length > 1) {
						siteHtml += '</p><p class="item-category">';
						siteHtml += featureTypes[feature].length;
						siteHtml += ' Similar Features';
					}
					siteHtml += '</p></div></div></div></li>';
					$(list).append(siteHtml);
				}
			});
			$(list).removeClass('d-none');
			$('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row h4.' + category + '-title').removeClass('d-none');
		}
	});
};

function populateFeatureSlideout(feature) {
	$('#camp-map-poi-slideout').data('id', feature.id);
	if (feature.categoryName) {
		$('.camp-map-poi-title').text(feature.categoryName);
	}
	else if (feature.title) {
		$('.camp-map-poi-title').text(feature.title);
	}
	else {
		$('.camp-map-poi-title').text(feature.name);
	}
	if (feature.description) {
		$('.camp-map-poi-description').text(feature.description);
		$('.camp-map-poi-description').show();
	}
	else {
		$('.camp-map-poi-description').hide();
	}
	$('#camp-map-slideout-carousel-poi').carousel('dispose');
	$('#camp-map-slideout-carousel-poi .carousel-inner').html('');
	feature.images.forEach(function (image, index) {
		if (image) {
			$('#camp-map-slideout-carousel-poi .carousel-inner').append('<div class="carousel-item h-100 ' + (index == 0 ? 'active' : '') + '"><img class="camp-map-slideout-carousel-image w-100" src="' + image + '"></div>');
		}
	});
	if (feature.images.length > 1) {
		$('#camp-map-slideout-carousel-poi .carousel-controls').show();
	}
	else {
		$('#camp-map-slideout-carousel-poi .carousel-controls').hide();
	}
	$('#camp-map-slideout-carousel-poi').carousel();

	openFeatureSlideout(true);
};

function openFeatureSlideout(open) {
	closeOpenSlideout(false);
	//TODO: Populate with classes/etc.
	$('#camp-map-poi-slideout').css('top', slideoutHeight);
	$('#camp-map-poi-slideout').toggleClass('camp-map-slideout-show-menu', open);
};

function openAvailabilitySlideout(siteType, siteId = null) {
	populateAvailabilitySlideout(siteType);
	if (siteType) {
		$('#camp-map-availability-search').css('top', slideoutHeight);// + 50
		$('#camp-map-availability-search').addClass('camp-map-slideout-show-menu');
	}
	else {
		$('#camp-map-availability-search').css('top', slideoutHeight);
		$('#camp-map-availability-search').addClass('camp-map-slideout-show-menu');
	}
	if (siteId) {
		$('#camp-map-availability-search').data('siteId', siteId);
	}
	else {
		$('#camp-map-availability-search').data('siteId', '');
	}
};

function populateAvailabilityList(siteArray, noAvailableSites = false) {
	if (siteArray.length > 0 || Object.keys(siteArray).length > 0) {
		var list = $('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row ul.site-list');
		$(list).empty();
		if (noAvailableSites) {
			siteArray.sort(function (a, b) {
				return ((a.siteTitle < b.siteTitle) ? -1 : ((a.siteTitle > b.siteTitle) ? 1 : 0));
			})
			siteArray.forEach(function (site) {
				if (site) {
					var siteHtml = '<li class="no-availability-list-item"><p class="item-title">';
					siteHtml += site.siteTitle;
					siteHtml += '</p><p class="item-body">';
					siteHtml += site.reasonNotAvailable;
					siteHtml += '</p></li>';
					$(list).append(siteHtml);
				}
			});
			$('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .no-availability-message').removeClass('d-none');
		}
		else {
			if (_campMap.smsEnabled) {
				var siteTypes = groupBy(siteArray, 'tier', 'code');
				var siteTypesOrdered = Object.keys(siteTypes).sort(function (a, b) {
					return siteTypes[b].length - siteTypes[a].length;
				});
				siteTypesOrdered.forEach(function (siteType) {
					if (siteType) {
						var siteHtml = '<li class="mt-4" data-id="';
						siteHtml += siteTypes[siteType][0].tier.code;
						siteHtml += '" data-sitetypeid="';
						siteHtml += siteTypes[siteType][0].siteTypeID;
						siteHtml += '"><div class="campsite-list-item container-fluid py-2"><div class="row"><div class="col-3 d-flex px-2"><img class="item-image" src="'
						//TODO: Replace this with site image when we get that in the data. Something like: siteHtml += siteTypes[site][0].images[0];
						siteHtml += siteTypes[siteType][0].images[0];
						siteHtml += '" /></div><div class="col-9 pl-0"><p class="item-title">';
						siteHtml += siteTypes[siteType][0].tier.label;
						siteHtml += '</p><p class="item-category"><span class="fa fa-circle';
						if (siteTypes[siteType][0].tier.solidOutline) {
							siteHtml += ' solid-outline';
						}
						siteHtml += '" style="color:';
						siteHtml += siteTypes[siteType][0].tier.color;
						siteHtml += ';"></span> ';
						siteHtml += siteTypes[siteType].length;
						siteHtml += ' Similar Sites</p></div></div></div></li>';
						$(list).append(siteHtml);
						var siteList = siteTypes[siteType];
						siteList.sort(function (a, b) {
							var intA = parseInt((a.name.replace(/^[0|\D]*/g, "")));
							var intB = parseInt((b.name.replace(/^[0|\D]*/g, "")));
							if (intA && intB) {
								return (intA - intB);
							}
							else if (intA) {
								return -1;
							}
							else if (intB) {
								return 1;
							}
							else {
								return ((a.name < b.name) ? -1 : ((a.name > b.name) ? 1 : 0));
							}
						});
						siteList.forEach(function (site) {
							if (site) {
								var siteHtml = '<li class="availability-list-item" data-id="';
								siteHtml += site.id;
								siteHtml += '" data-sitetypeid="';
								siteHtml += site.siteTypeID;
								siteHtml += '"><p class="item-title"><span class="fa fa-circle';
								if (site.tier.solidOutline) {
									siteHtml += ' solid-outline';
								}
								siteHtml += '" style="color:';
								siteHtml += site.tier.color;
								siteHtml += ';"></span> ';
								siteHtml += site.name;
								siteHtml += '</p><button class="availability-list-view-map btn-yellow-border-font-black"><span class="fa fa-map-marker-alt"></span><span class="px-2">View On Map</span></button></li>';
								$(list).append(siteHtml);
							}
						});
					}
				});
			}
			else {
				var siteTypesOrdered = Object.keys(siteArray).sort(function (a, b) {
					return siteArray[b].length - siteArray[a].length;
				});
				siteTypesOrdered.forEach(function (site) {
					if (site) {
						var siteHtml = '<li class="availability-list-item site-type-item" data-id="';
						siteHtml += siteArray[site][0].id;
						siteHtml += '" data-sitetypeid="';
						siteHtml += siteArray[site][0].tier.code
						siteHtml += '"><p class="item - title">';
						siteHtml += siteArray[site][0].tier.label;
						siteHtml += '</p><div class="item-btn-wrapper"><button class="availability-list-view-map btn-yellow-border-font-black""><span class="fa fa-map-marker-alt"></span><span class="px-2">View On Map</span></button></div></li>';
						$(list).append(siteHtml);
					}
				});
			}
		}
		$(list).removeClass('d-none');
	}
};

function openAndFilterSiteType(siteTypeId) {
	var list = $('#camp-map-campsitelist-slideout .camp-map-campsitelist-slideout-inner .list-row ul.site-list');
	$(list).children('li').each(function (index, item) {
		if ($(item).data('sitetypeid') != siteTypeId) {
			$(item).addClass('d-none');
		}
	});

	if (!isMobile) {
		$('#camp-map-list-btn').trigger('click');
	}

	var siteTypeGroup = $(list).children('li.mt-4:not(.d-none)');
	$(siteTypeGroup).removeClass('selected');
	$(siteTypeGroup).trigger('click');

};

function populateAvailabilitySlideout(category) {
	if (category) {
		$('#ReservationsViewData_Reservation_SiteCategory').val(category);
		$('#ReservationsViewData_Reservation_SiteCategory').trigger('change');
		$('#camp-map-category-options').hide();
	}
	else {
		category = $('#ReservationsViewData_Reservation_SiteCategory').val();
		$('#camp-map-category-options').show();
	}
};

function clearAvailabilitySlideout(openSlideout = false) {
	$(".availability-checkInDate").val('');
	$(".availability-checkOutDate").val('');
	$(".availability-adults").val('1');
	$(".availability-children").val('0');
	$(".availability-children").trigger('change');

	$('#noPets').prop('checked', false);
	$('#yesPets').prop('checked', false);
	$(".availability-equipmentType").val('');
	$(".availability-equipmentType").trigger('change');
	$(".availability-equipmentLength").val('');
	$('#noSlideouts').prop('checked', false);
	$('#yesSlideouts').prop('checked', false);
	$(".availability-category").val('R');
	$(".availability-category").trigger('change');

	availabilitySet = false;
	_campMap.clearAvailability();
	updateAvailabilityButton();
	if (openSlideout) {
		openAvailabilitySlideout();
	}
};

function closeOpenSlideout(deselectMapItem = true) {
	if (deselectMapItem) {
		_campMap.deselectFeature();
	}
	if ($('.camp-map-slideout-show-menu').length == 1) {
		slideoutHeight = $('.camp-map-slideout-show-menu').position().top;
	}
	if ($('.camp-map-slideout-show-menu').length > 1 && $('#camp-map-availability-search').hasClass('camp-map-slideout-show-menu')) {
		$('#camp-map-availability-search').removeClass('camp-map-slideout-show-menu');
	}
	else {
		$('.camp-map-slideout-show-menu').removeClass('camp-map-slideout-show-menu');
		$('.camp-map-slideout-show-menu-btn-wrapper').removeClass('camp-map-slideout-show-menu-btn-wrapper');
	}
};

function updateFilterButton(filterCount) {
	if (filterCount >= 0) {
		$('#camp-map-filters-btn').addClass('active');
		$("#camp-map-filters-btn .filters-count").text('(' + filterCount + ')');
	}
	else {
		$('#camp-map-filters-btn').removeClass('active');
		$("#camp-map-filters-btn .filters-count").text('');
	}
};

function updateListButton(filterCount) {
	if (filterCount >= 0) {
		$('#camp-map-list-btn').addClass('active');
		$("#camp-map-list-btn .filters-count").text('(' + filterCount + ')');
	}
	else {
		$('#camp-map-list-btn').removeClass('active');
		$("#camp-map-list-btn .filters-count").text('');
	}
};

function updateAvailabilityButton() {
	if (availabilitySet) {
		$('#camp-map-availability-btn').html('<span class="far fa-pencil-alt pr-2"></span> Edit Search');
		$('#camp-map-availability-btn').addClass('active');
		$('.camp-map-availability-search-bottom .camp-map-availability-clear, .camp-map-availability-search-bottom .camp-map-availability-update').removeClass('d-none');
		$('.camp-map-availability-search-bottom .camp-map-availability-search-submit').addClass('d-none');
	}
	else {
		$('#camp-map-availability-btn').html('<span class="fa fa-calendar-alt pr-2"></span> Check Availability');
		$('#camp-map-availability-btn').removeClass('active');
		$('.camp-map-availability-search-bottom .camp-map-availability-clear, .camp-map-availability-search-bottom .camp-map-availability-update').addClass('d-none');
		$('.camp-map-availability-search-bottom .camp-map-availability-search-submit').removeClass('d-none');
	}
};

function clearFilterMenu(button) {
	$('#camp-map-filters .camp-map-filters-section-container .camp-map-filter-container').each(function (index, item) {
		$(item).removeClass('selected');
		$(item).children('input:checked').prop('checked', false);
	});
	_campMap.applyFilters(null, true, false, availabilitySet);
	updateFilterButton();
	if (!availabilitySet) {
		updateListButton();
		if ($(button).parents('#camp-map-campsitelist-slideout').length == 0) {
			closeOpenSlideout();
		}
	}
};

function setFilterCounts() {
	$('#camp-map-filters .accommodations-filters input').each(function (index, item) {
		var accomodation = $(item).val();
		if (accomodation) {
			var count = _campMap.applyFilters({ accomodations: [_campMap.categories[accomodation]] }, false, true, availabilitySet);
			if (count >= 0) {
				$(this).siblings('.filter-label').append(' (' + count + ')');
			}
		}
	});
	$('#camp-map-filters .amenities-filters input').each(function (index, item) {
		var amenity = $(item).val();
		if (amenity) {
			var count = _campMap.applyFilters({ amenities: [_campMap.categories[amenity]] }, false, true, availabilitySet);
			if (count >= 0) {
				$(this).siblings('.filter-label').append(' (' + count + ')');
			}
		}
	});
	$('#camp-map-filters .features-filters input').each(function (index, item) {
		var feature = $(item).val();
		if (feature) {
			var feature_list = _campMap.categories[feature];
			var filter_array = { features: Array() }
			if (Array.isArray(feature_list)) {
				feature_list.forEach(function (feature_item) {
					filter_array.features.push(feature_item);
				});
			}
			else {
				filter_array.features.push(feature_list);
			}
			var count = _campMap.applyFilters(filter_array, false, true, availabilitySet);
			if (count >= 0) {
				$(this).siblings('.filter-label').append(' (' + count + ')');
			}
		}
	});
};

function setDatePickers() {
	var closures = _campMap.GetCampgroundClosures();
	var closureArray = Array();
	if (closures) {
		closureArray = closures.replace(/['"]+/g, '').split(",");
	}
	var lastResevation = _campMap.GetLastReservationDays();
	var culture = _campMap.GetCulture();

	var defaultDateString = new Date("1/1/0001").toString();
	var selectedCheckInString = new Date($("#ReservationsViewData_Reservation_CheckInDate").val()).toString();
	var selectedCheckOutString = new Date($("#ReservationsViewData_Reservation_CheckOutDate").val()).toString();
	if (selectedCheckInString == defaultDateString) {
		$("#ReservationsViewData_Reservation_CheckInDate").val('');
	}
	if (selectedCheckOutString == defaultDateString) {
		$("#ReservationsViewData_Reservation_CheckOutDate").val('');
	}

	$("#ReservationsViewData_Reservation_CheckInDate").datepicker("destroy");
	$("#ReservationsViewData_Reservation_CheckOutDate").datepicker("destroy");

	var dates = $("#ReservationsViewData_Reservation_CheckInDate, #ReservationsViewData_Reservation_CheckOutDate").datepicker({
		changeMonth: true,
		changeYear: true,
		numberOfMonths: 1,
		disableTouchKeyboard: true,
		ignoreReadonly: true,
		minDate: 0,
		maxDate: lastResevation,
		defaultDate: null,
		beforeShowDay: function (checkDate) {
			var checkDateString = (checkDate.getMonth() + 1) + "/" + checkDate.getDate() + "/" + checkDate.getFullYear();

			var selectedCheckInString = $.datepicker.formatDate("m/d/yy", $("#ReservationsViewData_Reservation_CheckInDate").datepicker('getDate'));
			var selectedCheckOutString = $.datepicker.formatDate("m/d/yy", $("#ReservationsViewData_Reservation_CheckOutDate").datepicker('getDate'));

			var selectedCheckIn = $("#ReservationsViewData_Reservation_CheckInDate").datepicker('getDate');
			var selectedCheckOut = $("#ReservationsViewData_Reservation_CheckOutDate").datepicker('getDate');


			if (checkDateString == selectedCheckInString) {
				return [true, "date-highlight-end-point", ""]
			}
			if (checkDateString == selectedCheckOutString) {
				return [true, "date-highlight-end-point", ""]
			}

			if (checkDate > selectedCheckIn && checkDate < selectedCheckOut) {
				return [true, "date-highlight-mid", ""]
			}

			if (closureArray.length > 0) {
				if ($.inArray(checkDateString, closureArray) == -1) {
					return [true, ""];
				}
				else {
					return [false, "", "Unavailable"];
				}
			}

			//default to normal/selectable.
			return [true, "", ""];
		},
		onSelect: function (selectedDate) {
			var option = this.id == "ReservationsViewData_Reservation_CheckInDate" ? "minDate" : "maxDate",
				instance = $(this).data("datepicker"),
				date = $.datepicker.parseDate(
					instance.settings.dateFormat ||
					$.datepicker._defaults.dateFormat,
					selectedDate, instance.settings);
			if (this.id == "ReservationsViewData_Reservation_CheckInDate") {
				dates.not(this).datepicker("option", option, date);
				//TODO: ValidateCheckIn();
			}
			else {
				//TODO: ValidateCheckOut();
			}
			//TODO: Error messaging for form -- UpdateErrorMessages();
		},
		beforeShow: function (element, inst) {
			var minDate = $("#ReservationsViewData_Reservation_CheckInDate").val();
			if (minDate != "") {
				$("#ReservationsViewData_Reservation_CheckOutDate").datepicker("option", "minDate", new Date(minDate));
			}
			var offset = $(element).offset().top - $('#camp-map-availability-search').offset().top + $(element).parent().height();
			setTimeout(function () {
				$('#ui-datepicker-div').css('top', offset + 'px');
			}, 0);
		}
	});

	if (culture == "fr-CA") {
		var options = $.extend(
			$.datepicker.regional['"' + culture + '"'],
			{ dateFormat: "D, M d, yy" }
		);
		$.datepicker.setDefaults(options);
	}
	else if (culture.startsWith("es")) {
		var options = $.extend(
			$.datepicker.regional['"' + culture + '"'],
			{ dateFormat: "D, M d, yy" }
		);
		$.datepicker.setDefaults(options);
	}
	else {
		var options = $.extend(
			$.datepicker.regional[""],
			{ dateFormat: "D, M d, yy" }
		);
		$.datepicker.setDefaults(options);
	}


	if ($(window).width() < 768) {
		$("#ReservationsViewData_Reservation_CheckInDate").attr("readonly", "true");
		$("#ReservationsViewData_Reservation_CheckOutDate").attr("readonly", "true");
	}
	else {
		$("#ReservationsViewData_Reservation_CheckInDate").removeAttr("readonly");
		$("#ReservationsViewData_Reservation_CheckOutDate").removeAttr("readonly");
	}

	//TODO: Need to get datepickers working in full screen.
	$('#ui-datepicker-div').appendTo('#camp-map-availability-search');
}

function populateEquipmentList(equipment) {
	if (equipment.length > 0) {
		var equipmentSelect = $('#ReservationsViewData_Reservation_EquipmentType');
		var currentValue = $(equipmentSelect).val();

		$(equipmentSelect).find('option').not(':first').remove();
		equipment.forEach(function (item) {
			$(equipmentSelect).append('<option value="' + item.Code + '">' + item.Type + '</option>')
		});

		if (currentValue) {
			var exists = false;
			$(equipmentSelect).children('option').each(function () {
				if (this.value == currentValue) {
					exists = true;
					return false;
				}
			});
			if (exists) {
				$(equipmentSelect).val(currentValue).change();
			}
		}
	}
};

function showCampMapError(errorMessagesArray) {
	$('#camp-map-overlay .error-container .error-list-wrapper').empty();
	if (!errorMessagesArray || errorMessagesArray.length == 0) {
		errorMessagesArray = [_campMap.reservationErrorMessages.DefaultExceptionStr];
	}
	errorMessagesArray.forEach(function (item) {
		$('#camp-map-overlay .error-container .error-list-wrapper').append('<p>' + item + '</p>');
	});
	_campMap.SetDisplayMode("error");
};

function updateReservationDetails() {
	var reservationDetails = {};
	reservationDetails.SiteCategory = $(".availability-category").val();
	reservationDetails.CheckInDate = $.datepicker.formatDate("m/d/yy", $(".availability-checkInDate").datepicker('getDate'));
	reservationDetails.CheckOutDate = $.datepicker.formatDate("m/d/yy", $(".availability-checkOutDate").datepicker('getDate'));
	reservationDetails.Adults = $(".availability-adults").val();
	reservationDetails.Kids = 0;
	reservationDetails.Free = 0;
	$('select.availability-children-ages').each(function (index) {
		var selectedChildAge = $(this).val();
		if (selectedChildAge != null) {
			if (_campMap.kidChargeAge != 0) {
				if (_campMap.kidChargeAge != 99) {
					if (selectedChildAge >= _campMap.kidChargeAge) {
						reservationDetails.Kids++;
					}
					else {
						reservationDetails.Free++;
					}
				}
				else {
					reservationDetails.Kids++;
				}
			}
			else {
				reservationDetails.Free++;
			}
		}
	});
	reservationDetails.Pets = $('input.availability-pets:checked').val()
	reservationDetails.EquipmentType = $(".availability-equipmentType").val();
	reservationDetails.EquipmentLength = $(".availability-equipmentLength").val();
	reservationDetails.SlideOuts = $('input.availability-slideouts:checked').val();

	currentCategory = reservationDetails.SiteCategory;
	_campMap.SetCurrentReservationDetails(reservationDetails);
	return reservationDetails;
};

function getReservationDetails(updateDetails = false, siteType = null) {
	var reservationDetails = _campMap.GetCurrentReservationDetails();
	if (updateDetails) {
		reservationDetails = updateReservationDetails();
	}
	if (reservationDetails == null) {
		return;
	}
	var nights = Math.floor(Math.abs(new Date(reservationDetails.CheckOutDate) - new Date(reservationDetails.CheckInDate)) / (1000 * 60 * 60 * 24));

	var returnData = {
		maidInput: {
			SiteCategory: reservationDetails.SiteCategory,
			CheckInDate: reservationDetails.CheckInDate,
			CheckOutDate: reservationDetails.CheckOutDate,
			Adults: reservationDetails.Adults,
			Kids: reservationDetails.Kids,
			Free: reservationDetails.Free,
			Pets: reservationDetails.Pets,
			EquipmentType: reservationDetails.EquipmentType,
			EquipmentLength: reservationDetails.EquipmentLength,
			SlideOuts: reservationDetails.SlideOuts,
			Nights: nights,
			CampgroundAlias: _campMap.campgroundName
		}
	}

	if (siteType) {
		returnData.maidInput.AvailableCount = siteType.AvailableCount;
		returnData.maidInput.Display = siteType.Display;
		returnData.maidInput.KOASiteTypeID = siteType.KOASiteTypeID;
		returnData.maidInput.SiteTypeID = siteType.SiteTypeID;
		returnData.maidInput.QuotedAveragePerNight = siteType.QuotedAveragePerNight;
		returnData.maidInput.QuotedAmount = siteType.QuotedAmount;
		returnData.maidInput.ReasonNotAvailable = siteType.ReasonNotAvailable;
		returnData.maidInput.Title = siteType.Title;
		returnData.maidInput.VkrDiscount = siteType.VkrDiscount;
	}

	return returnData;
};

function checkAvailability(alias) {
	if (ValidateCampMapForm() > 0) {
		return;
	}
	_campMap.SetDisplayMode("loading");
	var searchAvailabilityUrl = "/api/map-availability/check-availability/";

	var sendData = getReservationDetails(true);
	$.ajax({
		url: searchAvailabilityUrl,
		data: JSON.stringify(sendData),
		type: 'POST',
		contentType: "application/json; charset=utf-8",
		dataType: "json",
		success: function (data) {
			_campMap.SetDisplayMode("view");
			var campsiteSlideoutOpen = $('#camp-map-campsite-slideout').hasClass('camp-map-slideout-show-menu');

			if (data == "" || !data.Success) {
				//Error trying to retrieve availability
				//return data.ErrorMessage? Or just show generic error
				availabilitySet = false;
				updateAvailabilityButton();
				currentCategory = null;
				$('.campmap-no-reservations-wrapper').empty();
				if (data.ErrorMessages.length > 0) {
					data.ErrorMessages.forEach(function (item) {
						$('.campmap-no-reservations-wrapper').append('<p>' + item + '</p>');
					});
				}
				$('.campmap-no-reservations-wrapper').css('display', 'flex');
				$('#camp-map-availability-search .camp-map-availability-search-inner').animate({ scrollTop: 0 }, 500);
			}
			else {
				availabilitySet = true;
				updateAvailabilityButton();
				$('.campmap-no-reservations-wrapper').css('display', 'none');
			}

			if (data.AvailableSites.length > 0) {
				$('.camp-map-campsitelist-slideout-bottom .camp-map-availability-clear').addClass('d-none');
				var availableSites = Array();
				var availableSiteTypes = Array();
				var unavailableSiteTypes = Array();
				siteTypesArray = data.AvailableSites;
				data.AvailableSites.forEach(function (site) {
					if (site) {
						var siteTypeSerialized = site.Serialized;
						if (_campMap.smsEnabled) {
							if (site.AvailableCount > 0) {
								if (site.SelectableSites.length > 0) {
									site.SelectableSites.forEach(function (selectableSite) {
										availableSites.push({ siteNumber: selectableSite.SiteNumber, siteId: selectableSite.SiteID, serialized: siteTypeSerialized });
									});
								}
							}
						}
						if (site.Display) {
							availableSiteTypes.push({ siteTypeId: site.SiteTypeID, serialized: siteTypeSerialized });
						}
						else if (site.ReasonNotAvailable) {
							unavailableSiteTypes.push({ siteTypeId: site.SiteTypeID, siteTitle: site.Title, reasonNotAvailable: site.ReasonNotAvailable })
						}
					}
				});
				if (availableSites.length > 0) {
					_campMap.updateAvailability(availableSites, campsiteSlideoutOpen);
					if (availableSiteTypes.length > 0) {
						_campMap.updateSiteArrayAvailability(availableSiteTypes, campsiteSlideoutOpen);
					}
				}
				else if (availableSiteTypes.length > 0) {
					_campMap.updateSiteArrayAvailability(availableSiteTypes, campsiteSlideoutOpen, true);
				}
				else if (unavailableSiteTypes.length > 0) {
					$('.camp-map-campsitelist-slideout-bottom .camp-map-availability-clear').removeClass('d-none');
					hideListElements();
					populateAvailabilityList(unavailableSiteTypes, true);
					$('#camp-map-list-btn').trigger('click');
				}
				else {
					hideListElements();
					showEmptyList(true);
					$('#camp-map-list-btn').trigger('click');
				}
				//If site slideout open, update details
				if (campsiteSlideoutOpen) {
					var siteId = $('#camp-map-availability-search').data('siteId');
					updateSiteReservation(siteId, currentCategory);
				}
				//If list slideout is open, we just opened it and don't want to close it.
				if (!$('#camp-map-campsitelist-slideout').hasClass('camp-map-slideout-show-menu')) {
					closeOpenSlideout(false);
				}
			}
			else {
				hideListElements();
				showEmptyList(true);
				$('#camp-map-list-btn').trigger('click');
			}
		},
		error: function (data) {
			_campMap.SetDisplayMode("view");
		}
	});
};

function getAvailabilityCalendarUrl(alias, siteTypeId) {
	var siteType = siteTypesArray.find(x => x.SiteTypeID == siteTypeId);
	var getAvailabilityUrl = "/api/map-availability/get-availability-calendar-url/";
	var sendData = getReservationDetails(false, siteType);

	$.ajax({
		url: getAvailabilityUrl,
		data: JSON.stringify(sendData),
		type: 'POST',
		contentType: "application/json; charset=utf-8",
		dataType: "json",
		success: function (data) {
			if (data == "" || !data.Success) {
				if (data) {
					showCampMapError(data.ErrorMessages);
				}
				return;
			}
			else {
				//if mobile - ignore - open full screen, otherwise open in a popup
				if (!isMobile.matches) {
					var title = _campMap.campgroundName + " " + sendData.maidInput.Title;
					var locationHref = data.AvailabilityCalendarUrl;

					//append popup=true
					locationHref += "&popup=true";

					if (typeof title !== typeof undefined && title !== false) {
						$('#modal-window-iframe-lg-title').text(title);
					}
					else {
						$('#modal-window-iframe-lg-title').text("");
					}

					$('#modal-window-iframe-lg').modal({ show: true });

					$('#modal-window-iframe-lg').on('shown.bs.modal', function () {
						var modal = $(this);
						modal.find('iframe').attr("src", locationHref);
					});
					$('#modal-window-iframe-lg').on('hidden.bs.modal', function (e) {
						//clear the iframe url, so it does not show on the next load
						var modal = $(this);
						modal.find('iframe').attr("src", '/content/images/1px_trans.png');
					});
				}
				else {
					window.location = data.AvailabilityCalendarUrl;
				}
			}
		}
	});
};

function getShoppingCartUrl(alias, siteTypeId, siteId) {

	var siteType = siteTypesArray.find(x => x.SiteTypeID == siteTypeId);
	var getShoppingCartUrl = "/api/map-availability/get-shopping-cart-url/";
	var sendData = getReservationDetails(false, siteType);
	if (siteId) {
		sendData.maidInput.SiteID = _campMap.getK2Id(siteId);
		sendData.maidInput.SiteNumber = _campMap.getK2SiteName(siteId);
	}
	$.ajax({
		url: getShoppingCartUrl,
		data: JSON.stringify(sendData),
		type: 'POST',
		contentType: "application/json; charset=utf-8",
		dataType: "json",
		success: function (data) {
			if (data == "" || !data.Success) {
				if (data) {
					showCampMapError(data.ErrorMessages);
				}
				return;
			}
			else {
				var siteInfo = data.ShoppingCartUrl;
				var addToCartButton = $('.site-add-cart');
				window.setTimeout(function () { addToCartButton.attr('disabled', 'disabled'); }, 0);

				if (self == top) {
					var cartIcon = $('.shopping-cart-icon');
					mainShoppingCart.addToCart(cartIcon, siteInfo, addToCartButton);
				}
			}
		}
	});
};

function showShoppingCartPopup(shoppingCartData) {
	if (_campMap.IsFullScreen()) {
		if (shoppingCartData == "") {
			$('#add-to-cart-error-modal').one('shown.bs.modal', function () {
				$('#add-to-cart-error-modal').modal('hide');
			});
			showCampMapError(["Sorry, there was an error while adding your site to your cart."]);
		}
		else if (shoppingCartData.Success) {
			$('#add-to-cart-modal').one('shown.bs.modal', function () {
				$('#add-to-cart-modal').modal('hide');
			});
			$('#camp-map-overlay .message-container .message-list-wrapper').empty();
			$('#camp-map-overlay .message-container .message-button-wrapper').empty();

			$('#camp-map-overlay .message-container .message-list-wrapper').append(`<div class="success-message p-2"><span class="far pr-2 fa-check"></span><p class="availability-message-details mb-0">Site ${shoppingCartData.CartItem.SiteNumber ? shoppingCartData.CartItem.SiteNumber : ""} added to cart!</p></div>`);
			$('#camp-map-overlay .message-container .message-button-wrapper').append('<a href="/shopping-cart/checkout/" class="button-small btn-cart-checkout chevron-right koa-red-bg text-center text-uppercase my-2 my-lg-0 mr-lg-3">Checkout Now</a>');
			$('#camp-map-overlay .message-container .message-button-wrapper').append('<button type="button" class="btn btn-message-overlay-close btn-yellow-border-font-black px-4">Continue Shopping</button>');

			if (shoppingCartData.CartItem.SiteNumber && $('#camp-map-campsite-slideout .site-add-cart').is(':visible')) {
				$('#camp-map-campsite-slideout .site-add-cart').addClass('btn-disabled');
				$('#camp-map-campsite-slideout .site-add-cart').html('<i class="fa fa-check my-auto pr-2"></i><p class="mb-0">Added To Cart</p>');
			}

			_campMap.SetDisplayMode("overlaymessage");
		}
		else if (shoppingCartData.ErrorMessage) {
			$('#add-to-cart-error-modal').one('shown.bs.modal', function () {
				$('#add-to-cart-error-modal').modal('hide');
			});
			showCampMapError([shoppingCartData.ErrorMessage]);
		}
	}
	else {
		if (shoppingCartData.Success) {
			if (shoppingCartData.CartItem.SiteNumber && $('#camp-map-campsite-slideout .site-add-cart').is(':visible')) {
				$('#camp-map-campsite-slideout .site-add-cart').addClass('btn-disabled');
				$('#camp-map-campsite-slideout .site-add-cart').html('<i class="fa fa-check my-auto pr-2"></i><p class="mb-0">Added To Cart</p>');
			}
		}
	}

	if (shoppingCartData.Success) {
		_campMap.AddSiteToShoppingCart(shoppingCartData.CartItem.SiteID);
	}
};

function UpdateAndSubmitSelectSiteForm(siteId) {
	var form = $('#siteSelectForm');
	var reservationDetails = getReservationDetails();
	if (reservationDetails == null) {
		return;
	}
	var siteID = _campMap.getK2Id(siteId);
	var siteTypeID = _campMap.getSiteTypeId(siteId);

	$(form).children('#SiteID').val(siteID);
	$(form).children('#SiteTypeID').val(siteTypeID);
	$(form).children('#SiteCategory').val(reservationDetails.maidInput.SiteCategory);
	$(form).children('#Adults').val(reservationDetails.maidInput.Adults);
	$(form).children('#Kids').val(reservationDetails.maidInput.Kids);
	$(form).children('#KidsFree').val(reservationDetails.maidInput.Free);
	$(form).children('#Pets').val(reservationDetails.maidInput.Pets);
	$(form).children('#SlideOuts').val(reservationDetails.maidInput.SlideOuts);
	$(form).children('#EquipmentType').val(reservationDetails.maidInput.EquipmentType);
	$(form).children('#EquipmentLength').val(reservationDetails.maidInput.EquipmentLength);
	$(form).children('#CheckInDate').val(reservationDetails.maidInput.CheckInDate);
	$(form).children('#CheckOutDate').val(reservationDetails.maidInput.CheckOutDate);
	$(form).children('#SiteCategoryID').val(reservationDetails.maidInput.SiteCategory);
	$(form).children('#CampgroundAlias').val(reservationDetails.maidInput.CampgroundAlias);
	form.submit();
};

function UpdateAndSubmitSelectGroupForm(siteId) {
	var form = $('#siteGroupSelectForm');
	var reservationDetails = getReservationDetails();
	if (reservationDetails == null) {
		return;
	}
	var siteTypeID = _campMap.getSiteTypeId(siteId);

	$(form).children('#SiteTypeID').val(siteTypeID);
	$(form).children('#SiteCategory').val(reservationDetails.maidInput.SiteCategory);
	$(form).children('#Adults').val(reservationDetails.maidInput.Adults);
	$(form).children('#Kids').val(reservationDetails.maidInput.Kids);
	$(form).children('#Free').val(reservationDetails.maidInput.Free);
	$(form).children('#Pets').val(reservationDetails.maidInput.Pets);
	$(form).children('#SlideOuts').val(reservationDetails.maidInput.SlideOuts);
	$(form).children('#EquipmentType').val(reservationDetails.maidInput.EquipmentType);
	$(form).children('#EquipmentLength').val(reservationDetails.maidInput.EquipmentLength);
	$(form).children('#CheckInDate').val(reservationDetails.maidInput.CheckInDate);
	$(form).children('#CheckOutDate').val(reservationDetails.maidInput.CheckOutDate);
	$(form).children('#Nights').val(reservationDetails.maidInput.Nights);
	$(form).children('#SiteCategory').val(reservationDetails.maidInput.SiteCategory);
	form.submit();
};

function groupBy(xs, key, secondkey) {
	return xs.reduce(function (rv, x) {
		if (secondkey) {
			(rv[x[key][secondkey]] = rv[x[key][secondkey]] || []).push(x);
		}
		else {
			(rv[x[key]] = rv[x[key]] || []).push(x);
		}
		return rv;
	}, {});
};


//Form Validation Functions
function ValidateCampMapForm() {
	//reset error messages
	errors = [];
	//date error count
	var checkInErrorCount = 0;
	var checkOutErrorCount = 0;
	var dateErrorCount = 0;
	//error counts for dropdowns
	var guestErrorCount = 0;
	var equipmentErrorCount = 0;

	checkInErrorCount = ValidateCampMapCheckIn();
	checkOutErrorCount = ValidateCampMapCheckOut();
	dateErrorCount = checkInErrorCount + checkOutErrorCount;

	guestErrorCount = ValidateCampMapGuests();
	equipmentErrorCount = ValidateCampMapEquipment();

	UpdateCampMapErrorMessages();

	//any errors and don't submit
	//if (equipmentErrorCount + guestErrorCount + dateErrorCount > 0) {
	//	//set our focus on the first field that needs attention
	//	if (checkInErrorCount > 0) {
	//		$('#Reservation_CheckInDate').focus();
	//	}
	//	else if (checkOutErrorCount > 0) {
	//		var i = 1;
	//	}
	//	else if (dateErrorCount == 0 && guestErrorCount > 0) {
	//		//open guest dropdown.
	//		$('#adultsKidsPetsBtn').click();
	//	}
	//	else if (dateErrorCount == 0 && equipmentErrorCount > 0) {
	//		//open equipment dropdown.
	//		$('#typeLengthSlideoutsBtn').click();
	//	}

	//	event.preventDefault();
	//	event.stopPropagation();
	//	totalClicks += 1;
	//}

	return equipmentErrorCount + guestErrorCount + dateErrorCount;
}

function ValidateCampMapCheckIn() {
	var errorCount = 0;
	var todaysDate = Date(new Date().toDateString()); //get only the date, drop the time

	var checkInString = $('.availability-checkInDate').val();
	var checkInDate = new Date(checkInString);

	var today = new Date();
	var campgroundLastReservationDate = new Date(today).setDate(today.getDate() + parseInt(_campMap.GetLastReservationDays()));
	var reservationCutoff = _campMap.sameDayReservationCutoff;

	//if we have a cutoff and we're trying to reserve for the same day.
	var pastSameDayCutoff = false;
	if (new Date().toDateString() == checkInDate.toDateString()) {
		if (reservationCutoff > 0) {
			pastSameDayCutoff = IsPastSameDayReservationCutoff(reservationCutoff);
		}
	}

	if (checkInString == "" || new Date(checkInString) < todaysDate || !IsValidDate(checkInString) || new Date(checkInString) > campgroundLastReservationDate) {// || pastSameDayCutoff) {
		$('.availability-checkInDate, .checkin-date-append').addClass('is-invalid');
		errorMessages.checkInDate = _campMap.reservationErrorMessages.CheckInDateMissingStr;
		errorCount++;
	} else {
		errorMessages.checkInDate = "";
		$('.availability-checkInDate, .checkin-date-append').removeClass('is-invalid')
	}
	return errorCount;
}

function ValidateCampMapCheckOut() {
	var errorCount = 0;

	var checkOutString = $('.availability-checkOutDate').val();
	var checkInString = $('.availability-checkInDate').val();
	var today = new Date();
	var campgroundLastReservationDate = new Date(today).setDate(today.getDate() + parseInt(_campMap.GetLastReservationDays()));

	if (checkOutString == "" || new Date(checkInString) >= new Date(checkOutString) || !IsValidDate(checkOutString) || new Date(checkOutString) > campgroundLastReservationDate) {
		$('.availability-checkOutDate, .checkout-date-append').addClass('is-invalid');
		$('.availability-checkOutDate').val("");
		errorMessages.checkOutDate = _campMap.reservationErrorMessages.CheckOutDateMissingStr;
		errorCount++;
	} else {
		errorMessages.checkOutDate = "";
		$('.availability-checkOutDate, .checkout-date-append').removeClass('is-invalid')
	}
	return errorCount;
}

function ValidateCampMapGuestsAdults() {
	var errorCount = 0;
	//adults
	var adultCount = $('.availability-adults').val()
	if (adultCount <= 0 || adultCount > 8 || isNaN(adultCount)) {
		errorCount++;
		$('.availability-adults').addClass('is-invalid');
		errorMessages.adultCount = _campMap.reservationErrorMessages.AdultsCountStr;
	} else {
		errorMessages.adultCount = "";
		$('.availability-adults').removeClass('is-invalid')
	}
	return errorCount;
}

function ValidateCampMapGuestsKids() {
	var errorCount = 0;
	if ($('.availability-children').length) {
		var kidsCount = $('.availability-children').val()
		if (kidsCount < 0 || kidsCount > 8 || isNaN(kidsCount)) {
			errorCount++;
			$('.availability-children').addClass('is-invalid');
			errorMessages.kidsCount = _campMap.reservationErrorMessages.KidsCountStr;
		} else {
			errorMessages.kidsCount = "";
			$('.availability-children').removeClass('is-invalid')
		}
	}
	return errorCount;
}

function ValidateCampMapGuestsFree() {
	var errorCount = 0;
	var errorMessageArray = Array();
	if ($('.availability-children-ages').length) {
		$('.availability-children-ages').each(function (index, item) {
			var age = $(item).val()
			if (age < 0 || age > 17 || isNaN(age)) {
				errorCount++;
				$(item).addClass('is-invalid');
				errorMessageArray.push({ id: $(item).attr('id'), message: _campMap.reservationErrorMessages.KidsAgesStr });
			} else {
				$(item).removeClass('is-invalid')
			}
		});
	}
	if (errorMessageArray.length > 0) {
		errorMessages.kidsAges = errorMessageArray;
	}
	else {
		errorMessages.kidsAges = "";
	}
	return errorCount;
}

function ValidateCampMapGuestsPets() {
	var errorCount = 0;
	if (!$(".availability-pets:checked").val()) {
		errorCount++;
		$('.availability-pets-group').addClass('is-invalid');
		errorMessages.pets = _campMap.reservationErrorMessages.PetsNotSelectedStr;
	} else {
		errorMessages.pets = "";
		$('.availability-pets-group').removeClass('is-invalid')
	}
	return errorCount;
}

function ValidateCampMapGuests() {
	var guestErrorCount = 0;

	//adults
	guestErrorCount += ValidateCampMapGuestsAdults();
	//kids -- if we have em
	guestErrorCount += ValidateCampMapGuestsKids();
	//free (kids) -- if we have em
	guestErrorCount += ValidateCampMapGuestsFree();
	//pets
	guestErrorCount += ValidateCampMapGuestsPets();

	return guestErrorCount;
}

function ValidateCampMapEquipmentType(updateDisplay = true) {
	var errorCount = 0;
	if ($('.availability-equipmentType').val() == '') {
		errorCount++;
		if (updateDisplay) {
			$('.availability-equipmentType').addClass('is-invalid');
			errorMessages.equipmentType = _campMap.reservationErrorMessages.EquipmentTypeMissingStr;
		}
	} else {
		errorMessages.equipmentType = "";
		$('.availability-equipmentType').removeClass('is-invalid')
	}

	return errorCount;
}

function ValidateCampMapEquipmentLength(updateDisplay = true) {
	var errorCount = 0;
	if ($('#camp-map-length-options').css('display') != 'none') {
		var length = parseInt($('.availability-equipmentLength').val(), 10);
		if (length <= 0 || length > 99 || isNaN(length)) {
			errorCount++;
			if (updateDisplay) {
				$('.availability-equipmentLength').addClass('is-invalid');
				errorMessages.length = _campMap.reservationErrorMessages.EquipmentLengthStr;
			}
		} else {
			errorMessages.length = "";
			$('.availability-equipmentLength').removeClass('is-invalid')
		}
	}
	else {
		//empty string can cause odd validation on next (reserve) page. If empty, make it 0.
		if ($('.availability-equipmentLength').val() == "") {
			($('.availability-equipmentLength').val("0"));
		}
	}
	return errorCount;
}

function ValidateCampMapEquipmentSlideouts(updateDisplay = true) {
	var errorCount = 0;
	if ($('#camp-map-slideouts-options').css('display') != 'none' && !$(".availability-slideouts:checked").val()) {
		errorCount++;
		if (updateDisplay) {
			$('.availability-slideouts-group').addClass('is-invalid');
			errorMessages.slideouts = _campMap.reservationErrorMessages.SlideOutsNotSelectedStr;
		}
	} else {
		errorMessages.slideouts = "";
		$('.availability-slideouts-group').removeClass('is-invalid');

		//no selection can cause odd validation on sms (reserve) page. If empty, make it 0.
		if (!$(".availability-slideouts:checked").val()) {
			$('#noSlideouts').prop('checked', 'true');
		}
	}
	return errorCount;
}

function ValidateCampMapEquipment(updateDisplay = true) {
	var equipmentErrorCount = 0;

	//equipment type
	equipmentErrorCount += ValidateCampMapEquipmentType(updateDisplay);
	//length
	equipmentErrorCount += ValidateCampMapEquipmentLength(updateDisplay);
	//slide outs
	equipmentErrorCount += ValidateCampMapEquipmentSlideouts(updateDisplay);

	return equipmentErrorCount;
}

//kind of a special case. On category change, some fields get hidden, we need to account for those here.
function ValidateCampMapOnCategoryChange() {
	//if we're not showing length, but are showing an error, hide it.
	if (!$('#camp-map-length-options').css('display') != 'none' && errorMessages.length != "") {
		errorMessages.length = "";
		$('.availability-equipmentLength').removeClass('is-invalid')
	}

	//if we're not showing slideouts, but are showing an error, hide it
	if ($('#camp-map-slideouts-options').css('display') == 'none' && errorMessages.slideouts != "") {
		errorMessages.slideouts = "";
		$('.availability-slideouts-group').removeClass('is-invalid')
	}

}

function PetsClick(updateValidation = false) {
	if (updateValidation) {
		ValidateCampMapGuestsPets();
		UpdateCampMapErrorMessages();
	}
}

function SlideoutsClick(updateValidation = false) {
	//TODO: Put into function? UpdateEquipment();
	if (updateValidation) {
		ValidateEquipmentSlideouts();
		UpdateCampMapErrorMessages();
	}
}

function IsValidDate(inputDate) {
	validFormats = ["MM/DD/YYYY", "MM/D/YYYY", "M/DD/YYYY", "M/D/YYYY", "YYYY-MM-D", "YYYY-MM-DD", "ddd, MMM D, YYYY"]
	var m = moment(inputDate, validFormats, true)
	return m.isValid();
}

function IsPastSameDayReservationCutoff(cutOffMinutes) {
	var d = new Date();
	//current minutes past UTC midnight
	var utcMinutes = d.getUTCHours() * 60 + d.getUTCMinutes();

	console.log(utcMinutes + ", " + cutOffMinutes)

	return utcMinutes > cutOffMinutes;
}

function UpdateCampMapErrorMessages() {
	var errorSummary = $("#campmap-validation-summary-errors");


	var showErrors = false;
	var errorList = $("#campmap-error-list");
	errorList.empty();
	if (errorMessages.checkInDate.length > 0) {
		showErrors = true;
		errorList.append("<li><a href='#ReservationsViewData_Reservation_CheckInDate'>" + errorMessages.checkInDate + "</a></li>");
	}
	if (errorMessages.checkOutDate.length > 0) {
		showErrors = true;
		errorList.append("<li><a href='#ReservationsViewData_Reservation_CheckOutDate'>" + errorMessages.checkOutDate + "</a></li>");
	}
	if (errorMessages.adultCount.length > 0) {
		showErrors = true;
		errorList.append("<li><a class='error-link-guests' href='#numAdults'>" + errorMessages.adultCount + "</a></li>");
	}
	if (errorMessages.kidsCount.length > 0) {
		showErrors = true;
		errorList.append("<li><a class='error-link-guests' href='#numKids'>" + errorMessages.kidsCount + "</a></li>");
	}
	//TODO: Fix this one to go to correct fields
	if (errorMessages.kidsAges.length > 0) {
		showErrors = true;
		errorMessages.kidsAges.forEach(function (item) {
			errorList.append("<li><a class='error-link-guests' href='#" + item.id + "'>" + item.message + "</a></li>");
		});
	}
	if (errorMessages.pets.length > 0) {
		showErrors = true;
		errorList.append("<li><a class='error-link-guests' href='#petsFieldset'>" + errorMessages.pets + "</a></li>");
	}
	if (errorMessages.equipmentType.length > 0) {
		showErrors = true;
		errorList.append("<li><a class='error-link-equipment' href='#ReservationsViewData_Reservation_EquipmentType'>" + errorMessages.equipmentType + "</a></li>");
	}
	if (errorMessages.length.length > 0) {
		showErrors = true;
		errorList.append("<li><a class='error-link-equipment' href='#length'>" + errorMessages.length + "</a></li>");
	}
	if (errorMessages.slideouts.length > 0) {
		showErrors = true;
		errorList.append("<li><a class='error-link-equipment' href='#slideOutsFieldset'>" + errorMessages.slideouts + "</a></li>");
	}

	if (showErrors) {
		//show errors
		errorSummary.show();
		$('#camp-map-availability-search .camp-map-availability-search-inner').animate({ scrollTop: 0 }, 500);
	}
	else {
		//hide our error div
		errorSummary.hide();
	}
}
;
var _campMap;
var campMap = function (options) {
	var vars = {
		latitude: 39.828175,
		longitude: -98.5795,
		zoomStart: 18.5,
		geoJsonDirectory: "",
		campMapAlias: "",
		zoomMin: 1,
		zoomMax: 20,
		bounds: null,
		tileBounds: null,
		campgroundId: null,
		campgroundName: "",
		campgroundEquipment: null,
		categoryEquipment: null,
		campgroundClosures: "",
		lastReservationDateDays: "",
		sameDayReservationCutoff: "",
		culture: "",
		selectMySite: false,
		kidChargeAge: "",
		reservationText: null,
		reservationErrorMessages: null,
		availabilityEnabled: false,
		categoryFilters: null
	};

	this.campgroundName = options.campgroundName;
	this.sameDayReservationCutoff = options.sameDayReservationCutoff;
	this.smsEnabled = options.selectMySite;
	this.kidChargeAge = options.kidChargeAge;
	this.reservationText = options.reservationText;
	this.reservationErrorMessages = options.reservationErrorMessages;
	this.availabilityEnabled = options.availabilityEnabled;

	this.categories = {
		// Accomodations
		RV: "1000.1005",
		Cabin: "3000.1601",
		Tent: "1000.1001",
		UniqueLodging: "TODO",
		// Amenities
		Electric: "electricity",
		Water: "water",
		Barbecue: "barbecue",
		Patio: "terrace",
		// Recreation/Features
		Pool: 243,
		Basketball: 340,
		MiniGolf: 386,
		Playground: 327,
		FoodDrink: 'food_and_drink',
		Bathroom: [ 58, 53 ],
		Kampstore: 163,
		FrontDesk: 78 //TODO: Not sure if this should be changed to a string value instead
	};
	var localCategories = this.categories;

	var _map;
	var _map_controls;
	var _geo_json_sites;
	var _geo_json_site_directory;
	var _geo_json_poi;
	var _geo_json_poi_directory;
	var _geo_json_features;
	var _geo_json_feature_directory;
	var _geo_json_metadata;
	var _directions_renderer;
	var _directions_service;
	var _directions_origin;
	var _directions_destination;
	var _directions_mode = 'V';
	var _directions_line;
	var _directions_destination_line;
	var _directions_destination_circle;
	var _map_extent;
	var _map_tile_extent;
	var _map_loaded_listener;
	var _data_layer_sites;
	var _data_layer_features;
	var _data_layer_poi;
	var _selected_feature = Array();
	var _selected_tooltip = Array();
	var _bottom_control_visible = false;
	var _sites_array = Array();
	var _features_array = Array();
	var _current_reservation_details;
	var _is_full_screen = false;
	var _bounds_listener;

	//Map Styles
	var _site_style = {
		fillColor: 'green',
		strokeWeight: 1,
		strokeColor: '#dbe4d0',
		strokeOpacity: 0.5
	};
	var _poi_style = {
		fillColor: 'red',
		strokeWeight: 1,
		strokeColor: ''
	}
	var _selected_feature_style = {
		fillColor: 'transparent',
		strokeWeight: 1,
		strokeColor: 'blue',
		strokeOpacity: 1
	};
	var _transparent_style = {
		fillColor: 'transparent',
		strokeColor: 'transparent',
		icon: ''
	};
	var _unavailable_style = {
		fillColor: '#cecece',
		strokeColor: '#999',
		strokeWeight: 2,
		fillOpacity: 0.5,
		cursor: 'not-allowed'
	};
	var _visible_false = {
		visible: false
	};
	var _example_filter = {
		accomodations: [
			this.categories.RV //RV -"3000.1601" //Cabin
		],
		amenities: [
			"electricity",
			"water",
			"wifi"
		],
		features: [
			this.categories.FoodDrink
		]
	};

	var _DISPLAY_MODE = {
		NONSMS: "nonsms",
		VIEW: "view",
		DIRECTIONS: "directions",
		LOADING: "loading",
		ERROR: "error",
		OVERLAYMESSAGE: "overlaymessage"
	};

	var _displayMode = _DISPLAY_MODE.VIEW;

	this.construct = function (options) {
		$.extend(vars, options);
		setFiles();
		$.getJSON(_geo_json_metadata, setData);
	};

	//$('#camp-map').append('\
	//	<div id="camp-map-custom-overlay" class="position-relative">\
	//		<div id="camp-map-btn-wrapper" class="position-absolute p-md-3 d-flex">\
	//		<button id="camp-map-list-btn" class="camp-map-menu-btn mr-md-3 pt-2 pb-2 pl-md-3 pr-md-3 pt-md-2 pb-md-2">\
	//			<span class="fa fa-list-ul pr-2"></span>\
	//			List\
	//		</button>\
	//		<button id="camp-map-availability-btn" class="camp-map-menu-btn mr-md-3 pt-2 pb-2 pl-md-3 pr-md-3 pt-md-2 pb-md-2">\
	//			<span class="fa fa-calendar-alt pr-2"></span>\
	//			Check Availability\
	//		</button>\
	//		<button id="camp-map-filters-btn" class="camp-map-menu-btn pt-2 pb-2 pl-md-3 pr-md-3 pt-md-2 pb-md-2">\
	//			<span class="fal fa-filter pr-2"></span>\
	//			Filters\
	//		</button>\
	//		</div>\
	//	</div>');
	var createControls = function () {
		const controlContainer = document.createElement("div");
		$(controlContainer).attr('id', 'camp-map-custom-overlay');
		$(controlContainer).addClass('position-relative');
		const controlWrapper = document.createElement("div");
		$(controlWrapper).attr('id', 'camp-map-btn-wrapper');
		$(controlWrapper).addClass('position-absolute p-md-3 d-flex');

		const listButton = document.createElement("button");
		$(listButton).addClass('camp-map-menu-btn mr-md-3 pt-2 pb-2 pl-md-3 pr-md-3 pt-md-2 pb-md-2');
		$(listButton).attr('id', 'camp-map-list-btn');
		listButton.innerHTML = '<span class="far fa-compass pr-2"></span> Legend <span class="filters-count"></span>';
		listButton.type = "button";
		controlWrapper.appendChild(listButton);

		if (vars.availabilityEnabled) {
			const availabilityButton = document.createElement("button");
			$(availabilityButton).addClass('camp-map-menu-btn mr-md-3 pt-2 pb-2 pl-3 pr-3 pt-md-2 pb-md-2');
			$(availabilityButton).attr('id', 'camp-map-availability-btn');
			availabilityButton.innerHTML = '<span class="fa fa-calendar-alt pr-2"></span> Check Availability';
			availabilityButton.type = "button";
			controlWrapper.appendChild(availabilityButton);
		}

		const filterButton = document.createElement("button");
		$(filterButton).addClass('camp-map-menu-btn pt-2 pb-2 pl-md-3 pr-md-3 pt-md-2 pb-md-2');
		$(filterButton).attr('id', 'camp-map-filters-btn');
		filterButton.innerHTML = '<span class="fal fa-filter pr-2"></span> Filters <span class="filters-count"></span>';
		filterButton.type = "button";
		controlWrapper.appendChild(filterButton);

		controlContainer.appendChild(controlWrapper);
		_map.controls[google.maps.ControlPosition.TOP_LEFT].push(controlContainer);
	}

	var setData = function (data) {
		if (data) {
			$.extend(vars, data);
			if (data.center) {
				vars.longitude = x2lon(data.center[0]);
				vars.latitude = y2lat(data.center[1]);
			}
			if (data.extent && data.extent.length == 4) {
				vars.bounds = {
					west: x2lon(data.extent[0]),
					south: y2lat(data.extent[1]),
					east: x2lon(data.extent[2]),
					north: y2lat(data.extent[3])
				};
			}
			if (data.tileExtent && data.tileExtent.length == 4) {
				vars.tileBounds = {
					west: x2lon(data.tileExtent[0]),
					south: y2lat(data.tileExtent[1]),
					east: x2lon(data.tileExtent[2]),
					north: y2lat(data.tileExtent[3])
				};
			}
			if (vars.zoomMax > 21.5) {
				vars.zoomMax = 21.5;
			}
			if (!vars.selectMySite) {
				_displayMode = _DISPLAY_MODE.NONSMS;
			}
		}
		_map_extent = new google.maps.LatLngBounds(vars.bounds);
		if (vars.tileBounds) {
			_map_tile_extent = new google.maps.LatLngBounds(vars.tileBounds);
		}
		initializeMap();
	};

	var setFiles = function () {
		_geo_json_sites = vars.geoJsonDirectory + "campsites.json";
		_geo_json_site_directory = vars.geoJsonDirectory + "/site_details/";
		_geo_json_poi = vars.geoJsonDirectory + "poi.json";
		_geo_json_poi_directory = vars.geoJsonDirectory + "/poi_details/";
		_geo_json_features = vars.geoJsonDirectory + "features.json";
		_geo_json_feature_directory = vars.geoJsonDirectory + "/feature_details/";
		_geo_json_metadata = vars.geoJsonDirectory + "metadata.json";
	};

	var initializeMap = function () {
		$(document).trigger("OnMapLoading");
		var mapProp = {
			center: new google.maps.LatLng(vars.latitude, vars.longitude),
			zoom: vars.zoomStart,
			isFractionalZoomEnabled: true,
			styles: [
				{ elementType: 'all', stylers: [{ visibility: 'off' }] },
			],
			minZoom: vars.zoomMin,
			maxZoom: vars.zoomMax,
			campgroundName: vars.campgroundName,
			restriction: {
				latLngBounds: vars.bounds,
				strictBounds: false
			},
			gestureHandling: 'greedy'
		};
		_directions_renderer = new google.maps.DirectionsRenderer({
			suppressMarkers: true,
			polylineOptions: new google.maps.Polyline({
				strokeColor: '#E4002B',
				strokeOpacity: 1.0,
				strokeWeight: 4
			})
		});
		_directions_service = new google.maps.DirectionsService();
		_map = new google.maps.Map($('#camp-map')[0], mapProp);
		_directions_renderer.setMap(_map);
		_directions_renderer.setPanel(document.getElementById("sidebar"));
		_map_loaded_listener = _map.addListener("tilesloaded", onMapLoaded);
		_map.addListener("click", onMapClicked);

		$(document).trigger("OnSiteTypeLoading");
		_data_layer_sites = new google.maps.Data({ map: _map });
		_data_layer_sites.loadGeoJson(_geo_json_sites, {}, sitesLoaded);
		_data_layer_sites.setStyle(setSiteStyle);
		_data_layer_sites.addListener("click", function (event) {
			onMapFeatureClicked(event, "site");
		});

		$(document).trigger("OnFeaturesLoading");
		_data_layer_features = new google.maps.Data({ map: _map });
		_data_layer_features.loadGeoJson(_geo_json_features, {}, featuresLoaded);
		_data_layer_features.setStyle(setFeatureStyle);
		_data_layer_features.addListener("click", function (event) {
			onMapFeatureClicked(event, "feature");
		});

		$(document).trigger("OnPoiLoading");
		_data_layer_poi = new google.maps.Data({ map: _map });
		_data_layer_poi.loadGeoJson(_geo_json_poi, {}, poiLoaded);
		_data_layer_poi.setStyle(setPoiStyle);
		_data_layer_poi.addListener("click", function (event) {
			onMapFeatureClicked(event, "poi");
		});

		loadCampMapBaseTiles();
		loadCampMapCustomTiles();
		showHideItems(); //for hiding items at high zoom levels.

		google.maps.event.addListener(_map, 'bounds_changed', onBoundsChanged);
	}

	this.SetGestureHandling = function (auto) {
		var gestureType = auto ? 'auto' : 'greedy';
		_map.set('gestureHandling', gestureType);
	}

	function showHideItems() {
		google.maps.event.addListener(_map, 'zoom_changed', function (event) {
			_data_layer_poi.setStyle(setPoiStyle);
			_data_layer_features.setStyle(setFeatureStyle);
		});
	}

	this.SetDisplayMode = function (displayMode) {
		switch (displayMode) {
			case _DISPLAY_MODE.DIRECTIONS:
				$("#camp-map-overlay").hide();
				_map_controls = _map.controls[google.maps.ControlPosition.TOP_LEFT].pop();
				$('#camp-map-wrapper').hide();
				_selected_feature.forEach(function (feature) {
					if (feature.label) {
						feature.label.set('minZoom', '100');
					}
				});
				_displayMode = _DISPLAY_MODE.DIRECTIONS;
				break;
			case _DISPLAY_MODE.ERROR:
				$('#camp-map-wrapper').hide();
				$('#camp-map-overlay .animation-container').css('display', 'none');
				$('#camp-map-overlay .message-container').css('display', 'none');
				$('#camp-map-overlay .error-container').css('display', 'flex');
				$("#camp-map-overlay").show();
				break;
			case _DISPLAY_MODE.OVERLAYMESSAGE:
				$('#camp-map-wrapper').hide();
				$('#camp-map-overlay .animation-container').css('display', 'none');
				$('#camp-map-overlay .error-container').css('display', 'none');
				$('#camp-map-overlay .message-container').css('display', 'flex');
				$("#camp-map-overlay").show();
				break;
			case _DISPLAY_MODE.LOADING:
				$('#camp-map-wrapper').hide();
				$('#camp-map-overlay .error-container').css('display', 'none');
				$('#camp-map-overlay .message-container').css('display', 'none');
				$('#camp-map-overlay .animation-container').css('display', 'flex');
				$("#camp-map-overlay").show();
				break;
			case _DISPLAY_MODE.VIEW:
			default:
				$("#camp-map-overlay").hide();
				if (_map_controls) {
					_map.controls[google.maps.ControlPosition.TOP_LEFT].push(_map_controls);
					_map_controls = null;
				}
				//TODO: Need to check if this exists first
				if (_map.controls[google.maps.ControlPosition.BOTTOM_CENTER].length > 0) {
					_map.controls[google.maps.ControlPosition.BOTTOM_CENTER].pop();
				}
				$('#camp-map-wrapper').show();
				_selected_feature.forEach(function (feature) {
					if (feature.label) {
						feature.label.set('minZoom', '19');
					}
				});
				if (this.smsEnabled) {
					_displayMode = _DISPLAY_MODE.VIEW;
				}
				else {
					_displayMode = _DISPLAY_MODE.NONSMS;
				}
				break;
		}
	};

	var getUserLocation = function () {
		if ("geolocation" in navigator) {
			navigator.geolocation.getCurrentPosition(function (position) {
				_directions_origin = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
			});
		}
		if (!_directions_origin || !_map_extent.contains(_directions_origin)) {
			var camp_entrance = _features_array.find(x => x.category == 99);
			if (camp_entrance) {
				_directions_origin = getObjectCenter(_data_layer_poi.getFeatureById(camp_entrance.id));
			}
			else {
				camp_entrance = _features_array.find(x => x.category == 100);
				if (camp_entrance) {
					_directions_origin = getObjectCenter(_data_layer_features.getFeatureById(camp_entrance.id));
				}
				else {
					//Set to map center?
				}
			}
		}
	};

	this.exitRoute = function () {
		//_directions_renderer.setDirections({ routes: [] });
		_directions_line.setMap(null);
		_directions_destination_line.setMap(null);
		_directions_destination_circle.setMap(null);
		this.SetDisplayMode(_DISPLAY_MODE.VIEW);
	};

	this.calculateAndDisplayRoute = function (id) {
		getUserLocation();
		this.SetDisplayMode(_DISPLAY_MODE.LOADING);
		var destination = _data_layer_sites.getFeatureById(id);
		var is_site = true;
		if (!destination) {
			destination = _data_layer_features.getFeatureById(id);
			is_site = false;
		}
		if (!destination) {
			destination = _data_layer_poi.getFeatureById(id);
		}
		if (destination) {
			_directions_destination = getObjectCenter(destination);
			$.ajax({
				type: "POST",
				url: "/camp-map/directions/",
				async: true,
				data: {
					originLat: _directions_origin.lat(),
					originLng: _directions_origin.lng(),
					destinationLat: _directions_destination.lat(),
					destinationLng: _directions_destination.lng(),
					alias: vars.campMapAlias,
					mode: _directions_mode
				},
				success: function (data) {
					if (data && data.Path) {
						var path = JSON.parse(data.Path);
						var pathCoordinates = Array();
						var pathBounds = new google.maps.LatLngBounds();
						path.features[0].geometry.coordinates.forEach(function (item) {
							var pathPoint = new google.maps.LatLng(y2lat(item[1]), x2lon(item[0]));
							pathCoordinates.push(pathPoint);
							pathBounds.extend(pathPoint);
						});

						_directions_line = new google.maps.Polyline({
							path: pathCoordinates,
							strokeOpacity: 1,
							strokeColor: "#E4002B",
							strokeWeight: 4,
							map: _map
						});

						_directions_destination_line = new google.maps.Polyline({
							path: [
								pathCoordinates[pathCoordinates.length - 1],
								_directions_destination,
							],
							strokeOpacity: 0,
							strokeColor: "#E4002B",
							icons: [
								{
									icon: {
										path: "M 0,-1 0,1",
										strokeOpacity: 1,
										scale: 2,
									},
									offset: "0",
									repeat: "10px",
								},
							],
							map: _map,
						});

						_directions_destination_circle = new google.maps.Circle({
							center: _directions_destination,
							radius: 2,
							strokeOpacity: 1,
							strokeColor: "#E4002B",
							strokeWeight: 2,
							fillColor: "#fff",
							fillOpacity: 1,
							map: _map
						});

						_map.fitBounds(pathBounds);

						const route_overlay = document.createElement("div");
						$(route_overlay).addClass('route-overlay d-flex flex-column');
						$(route_overlay).attr('id', id);
						//const route_flex = document.createElement('div');
						//$(route_flex).addClass('d-flex')
						const route_data = document.createElement("div");
						$(route_data).addClass('px-3 pt-3 route-data container');
						const route_label = document.createElement("h4");
						$(route_label).addClass('mb-1');
						$(route_label).text('Directions to:')
						route_data.appendChild(route_label);
						const route_site = document.createElement("p");
						$(route_site).addClass('mb-2');
						route_site.innerHTML = (is_site ? 'Site ' : '') + (destination.getProperty('name') ? destination.getProperty('name') : destination.getProperty('title'));
						route_data.appendChild(route_site);
						const route_info = document.createElement("div");
						$(route_info).addClass('row')
						const route_distance_column = document.createElement("div");
						$(route_distance_column).addClass('col-6');
						const route_distance = document.createElement("p");
						route_distance.innerHTML = '<b>Distance:</b> ' + Math.round(path.features[0].properties.distance * 3.28084) + ' ft';
						route_distance_column.appendChild(route_distance);
						route_info.appendChild(route_distance_column);
						const route_time_column = document.createElement("div");
						$(route_time_column).addClass('col-6');
						const route_time = document.createElement("p");
						route_time.innerHTML = '<b>Est. Arrival:</b> ' + Math.round(path.features[0].properties.time / 60) + ' min';
						route_time_column.appendChild(route_time);
						route_info.appendChild(route_time_column);
						//route_flex.appendChild(route_data);
						route_data.appendChild(route_info);
						route_overlay.appendChild(route_data);

						const route_button = document.createElement("button");
						$(route_button).addClass('btn exit-route border-0 p-0 text-white fa fa-times');
						route_overlay.appendChild(route_button);

						const route_select_label = document.createElement("div");
						$(route_select_label).addClass('mx-3 mb-1');
						$(route_select_label).text('Travel by:');
						route_overlay.appendChild(route_select_label);

						const route_select = document.createElement("div");
						$(route_select).addClass('mx-3 mb-2 p-1 route-mode-toggle btn-group btn-group-toggle');
						$(route_select).data('toggle', 'buttons');

						const driving_label = document.createElement("label");
						$(driving_label).addClass("btn");
						const driving_option = document.createElement("input");
						$(driving_option).prop('type', 'radio');
						$(driving_option).prop('name', 'options');
						$(driving_option).attr('id', 'driving-option');
						$(driving_option).val('V');
						driving_label.appendChild(driving_option);
						$(driving_label).append(' Driving');

						const walking_label = document.createElement("label");
						$(walking_label).addClass("btn");
						const walking_option = document.createElement("input");
						$(walking_option).prop('type', 'radio');
						$(walking_option).prop('name', 'options');
						$(walking_option).attr('id', 'walking-option');
						$(walking_option).val('P');
						walking_label.appendChild(walking_option);
						$(walking_label).append(' Walking');
						switch (_directions_mode) {
							case 'P':
								$(walking_label).addClass('active');
								break;
							default:
								$(driving_label).addClass('active');
								break;
						}
						route_select.appendChild(driving_label);
						route_select.appendChild(walking_label);
						route_overlay.appendChild(route_select)


						_map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(route_overlay);
						_campMap.SetDisplayMode(_DISPLAY_MODE.DIRECTIONS);
					}
				}
			});
		}
	};

	this.switchDirectionMode = function (id, mode) {
		if (_directions_mode != mode) {
			_directions_mode = mode;
			this.exitRoute();
			this.calculateAndDisplayRoute(id);
		}
	};

	var getObjectCenter = function (data_object) {
		var object_bounds = new google.maps.LatLngBounds();
		data_object.getGeometry().forEachLatLng(function (position) {
			object_bounds.extend(position);
		});
		return object_bounds.getCenter();
	};

	var loadCampMapBaseTiles = function () {
		var tile_url = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}';
		var layerID = 'camp_map_tile_layer';

		var layer = new google.maps.ImageMapType({
			name: layerID,
			getTileUrl: function (coord, zoom) {
				if (zoom < 20) {
					var url = tile_url
						.replace('{x}', coord.x)
						.replace('{y}', coord.y)
						.replace('{z}', zoom);
					return url;
				}
			},
			tileSize: new google.maps.Size(256, 256),
			minZoom: 1,
			maxZoom: 20
		});

		_map.mapTypes.set(layerID, layer);
		_map.overlayMapTypes.insertAt(1, layer);
	};

	var loadCampMapCustomTiles = function () {
		var tile_url = 'https://cmwa.azureedge.net/tiles/production/' + vars.campMapAlias + '/{z}/{x}/{y}.png';
		var layerID = 'camp_map_custom_layer';

		var layer = new google.maps.ImageMapType({
			name: layerID,
			getTileUrl: function (coord, zoom) {
				if (tileExtentContainsPoint(coord, zoom)) {
					var url = tile_url
						.replace('{x}', coord.x)
						.replace('{y}', coord.y)
						.replace('{z}', zoom);
					return url;
				}
			},
			tileSize: new google.maps.Size(256, 256),
			minZoom: 19,
			maxZoom: 19
		});

		_map.mapTypes.set(layerID, layer);
		_map.overlayMapTypes.insertAt(2, layer);
	};

	var tileExtentContainsPoint = function (coord, zoom) {
		var containsPoint = false;
		var bounds = getTileBounds(coord, zoom);
		bounds.forEach(function (position) {
			if (_map_tile_extent == null || _map_tile_extent.contains(position)) {
				containsPoint = true;
			}
		});
		return containsPoint;
	};

	var getTileBounds = function (coord, zoom){
		var tile = normalizeTile(coord, zoom);
		var t = Math.pow(2, tile.z),
			s = 256 / t,
			sw = {
				x: tile.x * s,
				y: (tile.y * s) + s
			},
			se = {
				x: tile.x * s + s,
				y: (tile.y * s) + s
			},
			ne = {
				x: tile.x * s + s,
				y: (tile.y * s)
			},
			nw = {
				x: tile.x * s,
				y: (tile.y * s)
			};
		return [fromPointToLatLng(sw), fromPointToLatLng(se), fromPointToLatLng(ne), fromPointToLatLng(nw) ]
	}
	var normalizeTile = function(coord, zoom) {
		var t = Math.pow(2, zoom);
		coord.x = ((coord.x % t) + t) % t;
		coord.y = ((coord.y % t) + t) % t;
		return {
			x: coord.x,
			y: coord.y,
			z: zoom
		};
	}
	var fromPointToLatLng = function (point) {
		return new google.maps.LatLng(
			(2 * Math.atan(Math.exp((point.y - 128) / -(256 / (2 * Math.PI)))) -
				Math.PI / 2) / (Math.PI / 180),
			(point.x - 128) / (256 / 360)
		);
	}

	var sitesLoaded = function (sites) {
		sites.forEach(function (site, index) {
			if (site) {
				setSiteData(site);
			}
		});
		$(document).trigger("OnSiteTypeLoaded", [{ siteArray: _sites_array }]);
	};

	var setSiteData = function (siteObject) {
		var filterInfo = siteObject.getProperty("campsiteFilterInfo");
		var category;
		switch (filterInfo.categoryCode) {
			case localCategories.Cabin:
				category = {
					code: filterInfo.categoryCode,
					label: "Cabin",
					name: "cabin"
				};
				break;
			case localCategories.Tent:
				category = {
					code: filterInfo.categoryCode,
					label: "Tent",
					name: "tent"
				};
				break;
			case localCategories.UniqueLodging:
				category = {
					code: filterInfo.categoryCode,
					label: "Unique Lodging",
					name: "unique-lodging"
				};
				break;
			default:
				category = {
					code: filterInfo.categoryCode,
					label: "RV",
					name: "rv"
				};
				break;
		}
		var images = Array();
		if (filterInfo.primaryImage && filterInfo.primaryImage.length > 4) {
			var imageBaseUrl = "/content/campgrounds/" + vars.campgroundName + "/campmap/";
			images.push(imageBaseUrl + filterInfo.primaryImage);
		}
		if (images.length == 0) {
			images.push("/content/images/campmap/default.jpg");
		}
		var site_amenities = Array();
		if (filterInfo.featureAmenities) {
			var amenityGroup = {
				name: "feature",
				label: "Feature",
				amenities: Array()
			};
			filterInfo.featureAmenities.forEach(function (amenity) {
				amenityGroup.amenities.push({
					name: amenity,
					label: amenity
				});
			});
			site_amenities.push(amenityGroup);
		}
		var style = siteObject.getProperty('styleRules');
		var color = shouldTextBeBlack(style.fillColor) ? '#000' : '#fff';
		var bounds = new google.maps.LatLngBounds();
		_sites_array.push({
			id: siteObject.getId(),
			name: siteObject.getProperty("name"),
			description: filterInfo.koaSiteTypeDescription,
			category: category,
			tier: {
				code: siteObject.getProperty('tier_id'),
				label: filterInfo.koaSiteTypeName,
				color: style.fillColor,
				solidOutline: isTextLighter(style.fillColor, 0.6)
			},
			//TODO: Maybe build into object like other ones below
			amenities: site_amenities,
			images: images,
			siteTypeID: filterInfo.koaSiteTypeId,
			siteID: filterInfo.koaSiteId,
			fullData: false
		});

		processPoints(siteObject.getGeometry(), bounds.extend, bounds);
		siteObject.label = new MapLabel({
			text: siteObject.getProperty("name"),
			position: bounds.getCenter(),
			minZoom: 19,
			map: _map,
			fontSize: 12,
			align: 'center',
			fontColor: color,
			strokeColor: 'transparent'
		});
	};

	var loadSiteData = function (site, feature, bounds = null) {
		if (site) {
			if (!site.showTooltip) {
				if (!site.fullData) {
					$.getJSON(_geo_json_site_directory + site.id + ".json", function (site_details) {
						var site_amenities = Array();
						if (site_details.attributes.amenities) {
							site_details.attributes.amenities.data.groups.forEach(function (group) {
								var amenityGroup = {
									name: group.name,
									label: group.label.en.value,
									amenities: Array()
								};
								group.amenities.forEach(function (amenity) {
									amenityGroup.amenities.push({
										name: amenity.name,
										label: amenity.label.en.value,
										icon: amenity.icon
									});
								});
								site_amenities.push(amenityGroup);
							});
						}
						site.amenities = site_amenities;
						var images = Array();
						if (site_details.attributes.images) {
							var imageBaseUrl = "/content/campgrounds/" + vars.campgroundName + "/campmap/";
							if (site_details.attributes.images.primary) {
								images.push(imageBaseUrl + site_details.attributes.images.primary.filenames.processed.default + "." + site_details.attributes.images.primary.file_extension);
							}
							if (site_details.attributes.images.other && site_details.attributes.images.other.length > 0) {
								site_details.attributes.images.other.forEach(function (image) {
									images.push(imageBaseUrl + image.filenames.processed.default + "." + image.file_extension);
								});
							}
						}
						if (images.length == 0) {
							images.push("/content/images/campmap/default.jpg");
						}
						site.images = images;
						var shade;
						if (site_details.attributes.shade) {
							shade = site_details.attributes.shade.data.en.value;
						}
						site.shade = shade;
						var surface;
						if (site_details.attributes.surface) {
							surface = site_details.attributes.surface.data.en.value;
						}
						site.surface = surface;
						site.fullData = true;

						$(document).trigger("OnSiteTypeClicked", {
							site: site,
							showTooltip: false
						});
					});
				}
				else {
					$(document).trigger("OnSiteTypeClicked", {
						site: site,
						showTooltip: false
					});
				}
			}
			else {
				if (_displayMode == _DISPLAY_MODE.NONSMS) {
					var content = '<div class="availability-site-tooltip control"><button class="btn close-control border-0 p-0 text-white fa fa-times"></button><p class="site-number mt-4 mb-3">';
					content += site.tier.label;
					content += '</p><div class="btn-wrapper"><button class="availability-list-choose-site" data-id="';
					content += site.id;
					content += '"><span class="fa fa-check"></span><span class="pl-2">Choose Site Group</span></button><button class="availability-list-view-list"><span class="fa fa-list"></span><span class="pl-2">Back to List</span></button></div></div>';

					const control_content = document.createElement("div");
					control_content.innerHTML = content.trim();

					_map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(control_content.firstChild);
					_bottom_control_visible = true;
				}
				else {
					var content = '<div class="availability-site-tooltip"><p class="site-title">Site #</p><p class="site-number">';
					content += site.name;
					content += '</p><button class="availability-list-choose-site" data-id="';
					content += site.id;
					content += '"><span class="fa fa-check"></span><span class="pl-2">Choose Site</span></button><button class="availability-list-view-list"><span class="fa fa-list"></span><span class="pl-2">Back to List</span></button></div>';

					var infoWindow = new google.maps.InfoWindow({
						content: content,
						headerDisabled: true
					});
					infoWindow.setPosition(getObjectCenter(feature));
					infoWindow.open(_map);
					_selected_tooltip.push(infoWindow);
				}
				$(document).trigger("OnSiteTypeClicked", {
					site: site,
					showTooltip: true
				});
			}
		}
		return site;
	};

	var setSiteStyle = function (site) {
		var style = site.getProperty('styleRules');
		var zoom = _map.getZoom();
		var fillOpacity = 1;

		if (style && style.maxZoom >= 0 && style.maxZoom > zoom) {
			return _visible_false;
		}


		if (site.getProperty('selected')) {
			return _selected_feature_style;
		}
		else if (site.getProperty('isMatching') == false) {
			return _visible_false;
		}
		else if (site.getProperty('isAvailable') == false) {
			return _unavailable_style;
		}
		else if (style) {
			style.fillOpacity = fillOpacity;
			return style;
		}
		else {
			style = _site_style;
			style.fillOpacity = fillOpacity;
			return style;
		}
	};

	var featuresLoaded = function (features) {
		features.forEach(function (feature, index) {
			if (feature) {
				var featureId = feature.getId();
				var style = feature.getProperty('styleRules');
				var name = feature.getProperty('name');
				var images = Array();
				var imageBaseUrl = "/content/campgrounds/" + vars.campgroundName + "/campmap/features/" + featureId + "/";
				var primaryImage = feature.getProperty("defaultImage");
				if (primaryImage) {
					images.push(imageBaseUrl + primaryImage);
				}
				var secondaryImages = feature.getProperty("secondaryImages");
				if (secondaryImages && secondaryImages.length > 0) {
					secondaryImages.forEach(function (image) {
						images.push(imageBaseUrl + image);
					});
				}
				if (images.length == 0) {
					images.push("/content/images/campmap/default.jpg");
				}
				var categoryId = feature.getProperty('category_id');
				var categoryNameNormalized = "";
				if (categoryId && vars.categoryFilters) {
					var categoryNormalized = vars.categoryFilters.find(function (filter) {
						return filter.data.ids.includes(categoryId);
					});
					if (categoryNormalized) {
						categoryNameNormalized = categoryNormalized.name;
					}
				}
				_features_array.push({
					id: featureId,
					name: name ? name : feature.getProperty('title'),
					description: feature.getProperty('siteDescription'),
					title: feature.getProperty('title'),
					category: categoryId,
					categoryName: feature.getProperty('categoryName'),
					categoryNameNormalized: categoryNameNormalized,
					icon: style ? style.icon : null,
					images: images
				});

				if (style && style.icon) {
					var bounds = new google.maps.LatLngBounds();
					processPoints(feature.getGeometry(), bounds.extend, bounds);
					_data_layer_features.add({
						id: feature.getId() + '-marker',
						geometry: new google.maps.Data.Point(bounds.getCenter()),
						properties: {
							title: feature.getProperty('title'),
							styleRules: style
						}
					});
				}
			}
		});
		$(document).trigger("OnFeatureLoaded", [{ featureArray: _features_array }]);
	};

	var setFeatureStyle = function (feature) {
		var style = feature.getProperty('styleRules');
		var zoom = _map.getZoom();

		if (style && style.maxZoom >= 0 && style.maxZoom > zoom) {
			return _visible_false;
		}


		if (feature.getProperty('selected')) {
			var id = feature.getId();
			if (id.endsWith("-marker")) {
				return _visible_false;
			}
			else {
				return _selected_feature_style;
			}
		}
		else if (feature.getProperty('isMatching') == false) {
			return _visible_false;
		}
		else if (style) {
			style.fillOpacity = 0;
			style.strokeOpacity = 0;
			return style;
		}
		else {
			return _transparent_style;
		}
	};

	var poiLoaded = function (pois) {
		pois.forEach(function (poi, index) {
			if (poi) {
				var poiId = poi.getId();
				var style = poi.getProperty('styleRules');
				var name = poi.getProperty('name');
				var images = Array();
				var imageBaseUrl = "/content/campgrounds/" + vars.campgroundName + "/campmap/poi/" + poiId + "/";
				var primaryImage = poi.getProperty("defaultImage");
				if (primaryImage) {
					images.push(imageBaseUrl + primaryImage);
				}
				var secondaryImages = poi.getProperty("secondaryImages");
				if (secondaryImages && secondaryImages.length > 0) {
					secondaryImages.forEach(function (image) {
						images.push(imageBaseUrl + image);
					});
				}
				if (images.length == 0) {
					images.push("/content/images/campmap/default.jpg");
				}
				_features_array.push({
					id: poi.getId(),
					name: name ? name : poi.getProperty('title'),
					description: poi.getProperty('siteDescription'),
					title: poi.getProperty('title'),
					category: poi.getProperty('category_id'),
					icon: style ? style.icon : null,
					images: images
				});
			}
		});
		$(document).trigger("OnPoiLoaded");
	};

	var setPoiStyle = function (poi) {
		var style = poi.getProperty('styleRules');
		var zoom = _map.getZoom();

		if (style && style.maxZoom >= 0 && style.maxZoom > zoom) {
			return _visible_false;
		}

		if (poi.getProperty('selected')) {
			return _selected_feature_style;
		}
		else if (poi.getProperty('isMatching') == false) {
			return _visible_false;
		}
		else if (style) {
			return style;
		}
		else {
			return _poi_style;
		}
	};

	var onBoundsChanged = function () {
		if (document.fullscreenElement || document.webkitFullscreenElement) {
			_is_full_screen = true;
		}
		else {
			_is_full_screen = false;
		}
	};

	var onMapClicked = function (event) {
		if (_displayMode == _DISPLAY_MODE.VIEW) {
			$(document).trigger("OnMapClicked");
			deselectFeature();
		}
	};

	var onMapLoaded = function (event) {
		$(document).trigger("OnMapLoaded");
		google.maps.event.removeListener(_map_loaded_listener);
		createControls();
	};

	var onMapFeatureClicked = function (event, type) {
		//Scroll map
		if (_displayMode == _DISPLAY_MODE.VIEW || (_displayMode == _DISPLAY_MODE.NONSMS && type != "site")) {
			var bounds = new google.maps.LatLngBounds();
			processPoints(event.feature.getGeometry(), bounds.extend, bounds);
			_map.fitBounds(bounds);
			_bounds_listener = _map.addListener("bounds_changed", () => {
				_map.setZoom(_map.getZoom() - 1);
				google.maps.event.removeListener(_bounds_listener);
			});

			//Highlight Geojson
			selectFeature(event.feature, type);

			switch (type) {
				case "site":
					onSiteClicked(event);
					break;
				case "feature":
					onFeatureClicked(event);
					break;
				case "poi":
					onPoiClicked(event);
					break;
			}
		}
		else if (_displayMode == _DISPLAY_MODE.NONSMS && type == "site") {
			var bounds = new google.maps.LatLngBounds();
			var similarSites;

			if (event.feature) {
				var id = event.feature.getId();
				var site = _sites_array.find(x => x.id == id);
				similarSites = _sites_array.filter(x => x.tier.code == site.tier.code);
			}

			if (similarSites.length > 0) {
				deselectFeature();
				similarSites.forEach(function (item) {
					var feature = _data_layer_sites.getFeatureById(item.id);
					selectFeature(feature, type, false);
					processPoints(feature.getGeometry(), bounds.extend, bounds);
				});
			}
			else {
				processPoints(event.feature.getGeometry(), bounds.extend, bounds);
			}
			_map.fitBounds(bounds);
			_bounds_listener = _map.addListenerOnce("bounds_changed", () => {
				_map.setZoom(_map.getZoom() - 1);
				google.maps.event.removeListener(_bounds_listener);
			});

			onSiteClicked(event, bounds);
		}
	};

	var processPoints = function (geometry, callback, thisArg) {
		if (geometry instanceof google.maps.LatLng) {
			callback.call(thisArg, geometry);
		} else if (geometry instanceof google.maps.Data.Point) {
			callback.call(thisArg, geometry.get());
		} else {
			geometry.getArray().forEach(function (g) {
				processPoints(g, callback, thisArg);
			});
		}
	};

	var selectFeature = function (feature, type, deselect = true) {
		if (deselect) {
			deselectFeature();
		}

		if (type == "site") {
			feature.label.set('fontColor', '#000');
		}
		else if (type == "feature") {
			matchingFeature = getMatchingFeature(feature);

			if (matchingFeature) {
				matchingFeature.setProperty('selected', true);
				_selected_feature.push(matchingFeature);
			}
		}

		feature.setProperty('selected', true);
		_selected_feature.push(feature);
	};

	var getMatchingFeature = function (feature) {
		var id = feature.getId();
		var matchingId;
		if (id.endsWith("-marker")) {
			matchingId = id.replace("-marker", "");
		}
		else {
			matchingId = id + "-marker";
		}
		return _data_layer_features.getFeatureById(matchingId);
	};

	this.deselectFeature = function () {
		deselectFeature();
	};

	var deselectFeature = function () {
		if (_selected_feature.length > 0) {
			_selected_feature.forEach(function (feature) {
				feature.setProperty('selected', false);
				if (feature.label) {
					var style = feature.getProperty('styleRules');
					if (style.fillColor) {
						feature.label.set('fontColor', shouldTextBeBlack(style.fillColor) ? '#000' : '#fff');
					}
				}
			});
			_selected_feature = Array();
		}
		if (_selected_tooltip.length > 0) {
			_selected_tooltip.forEach(function (tooltip) {
				tooltip.close();
			})
			_selected_tooltip = Array();
		}
		if (_bottom_control_visible) {
			if (_map.controls[google.maps.ControlPosition.BOTTOM_CENTER].length > 0) {
				_map.controls[google.maps.ControlPosition.BOTTOM_CENTER].pop();
			}
			_bottom_control_visible = false;
		}
	};

	var onSiteClicked = function (event, bounds = null) {
		if (event.feature) {
			var id = event.feature.getId();
			var site = _sites_array.find(x => x.id == id);
			site.similarSites = _sites_array.filter(x => x.tier.code == site.tier.code).length;
			site.isAvailable = event.feature.getProperty('isAvailable');
			site.showTooltip = event.showTooltip;
			loadSiteData(site, event.feature, bounds);
		}
	};

	var onFeatureClicked = function (event) {
		if (event.feature) {
			var id = event.feature.getId();
			$(document).trigger("OnPoiClicked", _features_array.find(x => x.id == id.replace('-marker', '')));
		}
	};

	var onPoiClicked = function (event) {
		if (event.feature) {
			var id = event.feature.getId();
			$(document).trigger("OnPoiClicked", _features_array.find(x => x.id == id));
		}
	};

	this.applyFilters = function (filter_array, populateList = true, returnCount = false, availabilitySet = false) {
		$(document).trigger("OnFiltering");
		//Filter sites
		if (!$.isEmptyObject(filter_array)) {
			var unmatching_sites = _sites_array.slice(0); //Set equal to array of all sites
			var matching_sites = Array();
			if (filter_array.accomodations) {
				filter_array.accomodations.forEach(function (filter) {
					unmatching_sites.slice(0).forEach(function (site, index) {
						if (site) {
							if (site.category.code == filter) {
								matching_sites.push(site);
								var arrayIndex = unmatching_sites.indexOf(site);
								if (arrayIndex !== -1) {
									unmatching_sites.splice(arrayIndex, 1);
								}
							}
						}
					});
				});
			}
			if (filter_array.amenities) {
				filter_array.amenities.forEach(function (filter) {
					if (matching_sites.length > 0) {
						//Filter sites and remove not matching sites from temp array and add back to unmatching_sites
						matching_sites.slice(0).forEach(function (site, index) {
							if (site) {
								var matching = site.amenities.some(function (group) {
									return group.amenities.find((amenity) => amenity.name == filter);
								});
								if (!matching) {
									unmatching_sites.push(site);
									var arrayIndex = matching_sites.indexOf(site);
									if (arrayIndex !== -1) {
										matching_sites.splice(arrayIndex, 1);
									}
								}
							}
						});
					}
					else {
						//There was no accomodation filtering, so just add all matching to temp_matchiing_sites and remove from unmatching_sites
						unmatching_sites.slice(0).forEach(function (site, index) {
							if (site) {
								var matching = site.amenities.some(function (group) {
									return group.amenities.find((amenity) => amenity.name == filter);
								});
								if (matching) {
									matching_sites.push(site);
									var arrayIndex = unmatching_sites.indexOf(site);
									if (arrayIndex !== -1) {
										unmatching_sites.splice(arrayIndex, 1);
									}
								}
							}
						});
					}
				});
			}
			if (filter_array.tiers) {
				filter_array.tiers.forEach(function (filter) {
					if (matching_sites.length > 0) {
						//Filter sites and remove not matching sites from temp array and add back to unmatching_sites
						matching_sites.slice(0).forEach(function (site, index) {
							if (site) {
								if (site.tier.code != filter) {
									unmatching_sites.push(site);
									var arrayIndex = matching_sites.indexOf(site);
									if (arrayIndex !== -1) {
										matching_sites.splice(arrayIndex, 1);
									}
								}
							}
						});
					}
					else {
						//There was no accomodation filtering, so just add all matching to temp_matchiing_sites and remove from unmatching_sites
						unmatching_sites.slice(0).forEach(function (site, index) {
							if (site) {
								if (site.tier.code == filter) {
									matching_sites.push(site);
									var arrayIndex = unmatching_sites.indexOf(site);
									if (arrayIndex !== -1) {
										unmatching_sites.splice(arrayIndex, 1);
									}
								}
							}
						});
					}
				});
			}

			var unmatching_features = _features_array.slice(0); //Set equal to array of all pois/features
			var matching_features = Array();
			if (filter_array.features) {
				//Filter features based on if they match any of the recreation/facility filters

				filter_array.features.forEach(function (filter) {
					unmatching_features.slice(0).forEach(function (feature, index) {
						if (feature) {
							if ((Number.isInteger(filter) && feature.category == filter) || feature.categoryNameNormalized == filter || (feature.name && feature.name.replace(/\s/g, '') == filter)) {
								matching_features.push(feature);
								var arrayIndex = unmatching_features.indexOf(feature);
								if (arrayIndex !== -1) {
									unmatching_features.splice(arrayIndex, 1);
								}
							}
						}
					});
				});
			}
		}
		else {
			var matching_sites = _sites_array.slice(0);
			var unmatching_sites = Array();
			var matching_features = _features_array.slice(0);
			var unmatching_features = Array();
		}

		if (returnCount) {
			return matching_sites.length + matching_features.length;
		}
		//Set matching property on features, set bounds to features that match filter
		var bounds = new google.maps.LatLngBounds();
		//First deal with sites
		matching_sites.forEach(function (site) {
			var feature = _data_layer_sites.getFeatureById(site.id);
			feature.label.set('minZoom', 19);
			feature.setProperty('isMatching', true);
			processPoints(feature.getGeometry(), bounds.extend, bounds);
		});
		unmatching_sites.forEach(function (site) {
			var feature = _data_layer_sites.getFeatureById(site.id);
			feature.label.set('minZoom', 100);
			feature.setProperty('isMatching', false);
		});
		//Then deal with features (Features and POI layers)
		matching_features.forEach(function (feature) {
			var mapFeature = _data_layer_features.getFeatureById(feature.id);
			if (!mapFeature) {
				mapFeature = _data_layer_poi.getFeatureById(feature.id);
			}
			var matchingFeature = getMatchingFeature(mapFeature);
			mapFeature.setProperty('isMatching', true);
			if (matchingFeature) {
				matchingFeature.setProperty('isMatching', true);
			}
			processPoints(mapFeature.getGeometry(), bounds.extend, bounds);
		});
		unmatching_features.forEach(function (feature) {
			var mapFeature = _data_layer_features.getFeatureById(feature.id);
			if (!mapFeature) {
				mapFeature = _data_layer_poi.getFeatureById(feature.id);
			}
			var matchingFeature = getMatchingFeature(mapFeature);
			mapFeature.setProperty('isMatching', false);
			if (matchingFeature) {
				matchingFeature.setProperty('isMatching', false);
			}
		});

		$(document).trigger("OnFiltered", this.returnFilterList(populateList, availabilitySet));
		if (matching_sites.length > 0 || matching_features.length > 0) {
			_map.fitBounds(bounds);
		}
		else {
			_map.setZoom(vars.zoomStart);
			_map.setCenter(new google.maps.LatLng(vars.latitude, vars.longitude));
		}
	};

	this.returnFilterList = function (populateList, availabilitySet = false, returnCount = false) {
		var unmatching_sites = _sites_array.slice(0);
		var matching_sites = Array();
		var unmatching_features = _features_array.slice(0);
		var matching_features = Array();
		unmatching_sites.slice(0).forEach(function (site, index) {
			if (site) {
				var feature = _data_layer_sites.getFeatureById(site.id);
				if (feature.getProperty('isMatching') != false) {
					if (!availabilitySet || feature.getProperty('isAvailable') == true) {
						matching_sites.push(site);
						var arrayIndex = unmatching_sites.indexOf(site);
						if (arrayIndex !== -1) {
							unmatching_sites.splice(arrayIndex, 1);
						}
					}
				}
			}
		});
		if (!availabilitySet) {
			unmatching_features.slice(0).forEach(function (feature, index) {
				if (feature) {
					var mapFeature = _data_layer_features.getFeatureById(feature.id);
					if (!mapFeature) {
						mapFeature = _data_layer_poi.getFeatureById(feature.id);
					}
					if (mapFeature.getProperty('isMatching') != false) {
						matching_features.push(feature);
						var arrayIndex = unmatching_features.indexOf(feature);
						if (arrayIndex !== -1) {
							unmatching_features.splice(arrayIndex, 1);
						}
					}
				}
			});
		}

		if (returnCount) {
			return matching_sites.length + matching_features.length;
		}

		return {
			matchingSites: matching_sites,
			unmatchingSites: unmatching_sites,
			matchingFeatures: matching_features,
			unmatchingFeatures: unmatching_features,
			populateList: populateList
		};
	};

	//START: Font Color Calculator
	var shouldTextBeBlack = function (backgroundcolor) {
		return computeLuminence(backgroundcolor) > 0.179;
	};

	var isTextLighter = function (color, luminance) {
		return computeLuminence(color) > luminance;
	};

	var computeLuminence = function (backgroundcolor) {
		var colors = hexToRgb(backgroundcolor);

		var components = ['r', 'g', 'b'];
		for (var i in components) {
			var c = components[i];

			colors[c] = colors[c] / 255.0;

			if (colors[c] <= 0.03928) {
				colors[c] = colors[c] / 12.92;
			} else {
				colors[c] = Math.pow(((colors[c] + 0.055) / 1.055), 2.4);
			}
		}

		var luminence = 0.2126 * colors.r + 0.7152 * colors.g + 0.0722 * colors.b;

		return luminence;
	};

	var hexToRgb = function (hex) {
		var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
		return result ? {
			r: parseInt(result[1], 16),
			g: parseInt(result[2], 16),
			b: parseInt(result[3], 16)
		} : null;
	};
	//END: Font Color Calculator

	//START: Mercator Projections Conversion: https://wiki.openstreetmap.org/wiki/Mercator#JavaScript
	var y2lat = function (y) {
		PI = Math.PI;
		RAD2DEG = 180 / PI;
		DEG2RAD = PI / 180;
		R = 6378137.0
		return (2 * Math.atan(Math.exp(y / R)) - PI / 2) * RAD2DEG
	};

	var x2lon = function (x) {
		PI = Math.PI;
		RAD2DEG = 180 / PI;
		DEG2RAD = PI / 180;
		R = 6378137.0
		return RAD2DEG * (x / R);
	};
	//END: Mercator Projections Conversion

	this.selectSite = function (id, type, showTooltip = false) {
		switch (type) {
			case "site":
				google.maps.event.trigger(_data_layer_sites, 'click', { feature: _data_layer_sites.getFeatureById(id), fromList: true, showTooltip: showTooltip });
				break;
			case "feature":
				var feature = _data_layer_features.getFeatureById(id);
				if (feature) {
					google.maps.event.trigger(_data_layer_features, 'click', { feature: feature, fromList: true, showTooltip: showTooltip });
				}
				else {
					google.maps.event.trigger(_data_layer_poi, 'click', { feature: _data_layer_poi.getFeatureById(id), fromList: true, showTooltip: showTooltip });
				}
				break;
		}
	};

	this.updateSiteArrayAvailability = function (availableSiteTypes, campsiteSlideoutOpen = false, updateMap = false, populateList = true, availabilitySet = true) {
		if (!updateMap) {
			availableSiteTypes.forEach(function (siteObject) {
				_sites_array.filter(x => x.siteTypeID == siteObject.siteTypeId).forEach(function (site) {
					if (site.serializedSiteType == null) {
						site.serializedSiteType = siteObject.serialized;
					}
				});
			});
		}
		else {
			var unavailable_sites = _sites_array.slice(0);
			var available_sites = Array();
			availableSiteTypes.forEach(function (siteObject) {
				unavailable_sites.slice(0).forEach(function (site, index) {
					if (site) {
						if (site.siteTypeID == siteObject.siteTypeId) {
							site.serializedSiteType = siteObject.serialized;
							available_sites.push(site);
							var arrayIndex = unavailable_sites.indexOf(site);
							if (arrayIndex !== -1) {
								unavailable_sites.splice(arrayIndex, 1);
							}
						}
					}
				});
			});
			var bounds = new google.maps.LatLngBounds();
			available_sites.forEach(function (site) {
				if (site.serializedSiteType) {
					_sites_array.find(x => x.id == site.id).serializedSiteType = site.serializedSiteType;
				}
				var feature = _data_layer_sites.getFeatureById(site.id);
				feature.setProperty('isAvailable', true);
				processPoints(feature.getGeometry(), bounds.extend, bounds);
			});
			unavailable_sites.forEach(function (site) {
				if (site.serializedSiteType) {
					_sites_array.find(x => x.id == site.id).serializedSiteType = site.serializedSiteType;
				}
				var feature = _data_layer_sites.getFeatureById(site.id);
				feature.setProperty('isAvailable', false);
			});

			$(document).trigger("OnAvailabilityUpdated", this.returnFilterList(populateList, availabilitySet));

			if (!campsiteSlideoutOpen) {
				if (available_sites.length > 0) {
					_map.fitBounds(bounds);
				}
				else {
					_map.setZoom(vars.zoomStart);
					_map.setCenter(new google.maps.LatLng(vars.latitude, vars.longitude));
				}
			}
		}
	};

	this.updateAvailability = function (availableArray, campsiteSlideoutOpen = false, populateList = true) {
		var unavailable_sites = _sites_array.slice(0);
		var available_sites = Array();
		if (this.smsEnabled) {
			availableArray.forEach(function (siteObject) {
				unavailable_sites.slice(0).forEach(function (site, index) {
					if (site) {
						if (siteNameMatches(site, siteObject.siteId)) {
							site.serializedSiteType = siteObject.serialized;
							site.k2SiteId = siteObject.siteId;
							site.k2SiteName = siteObject.siteNumber;
							available_sites.push(site);
							var arrayIndex = unavailable_sites.indexOf(site);
							if (arrayIndex !== -1) {
								unavailable_sites.splice(arrayIndex, 1);
							}
						}
					}
				});
			});
		}
		else {
			availableArray.forEach(function (siteObject) {
				unavailable_sites.slice(0).forEach(function (site, index) {
					if (site) {
						if (site.siteTypeID == siteObject.siteTypeId) {
							site.serializedSiteType = siteObject.serialized;
							available_sites.push(site);
							var arrayIndex = unavailable_sites.indexOf(site);
							if (arrayIndex !== -1) {
								unavailable_sites.splice(arrayIndex, 1);
							}
						}
					}
				});
			});
		}
		var bounds = new google.maps.LatLngBounds();
		available_sites.forEach(function (site) {
			if (site.k2SiteId) {
				_sites_array.find(x => x.id == site.id).k2SiteId = site.k2SiteId;
			}
			if (site.k2SiteName) {
				_sites_array.find(x => x.id == site.id).k2SiteName = site.k2SiteName;
			}
			if (site.serializedSiteType) {
				_sites_array.find(x => x.id == site.id).serializedSiteType = site.serializedSiteType;
			}
			var feature = _data_layer_sites.getFeatureById(site.id);
			feature.setProperty('isAvailable', true);
			processPoints(feature.getGeometry(), bounds.extend, bounds);
		});
		unavailable_sites.forEach(function (site) {
			if (site.k2SiteId) {
				_sites_array.find(x => x.id == site.id).k2SiteId = site.k2SiteId;
			}
			if (site.k2SiteName) {
				_sites_array.find(x => x.id == site.id).k2SiteName = site.k2SiteName;
			}
			if (site.serializedSiteType) {
				_sites_array.find(x => x.id == site.id).serializedSiteType = site.serializedSiteType;
			}
			var feature = _data_layer_sites.getFeatureById(site.id);
			feature.setProperty('isAvailable', false);
		});

		$(document).trigger("OnAvailabilityUpdated", this.returnFilterList(populateList, availabilitySet));
		//TODO: Probably only want this when site slideout isn't open
		if (!campsiteSlideoutOpen) {
			if (available_sites.length > 0) {
				_map.fitBounds(bounds);
			}
			else {
				_map.setZoom(vars.zoomStart);
				_map.setCenter(new google.maps.LatLng(vars.latitude, vars.longitude));
			}
		}

		return available_sites;
	};

	this.clearAvailability = function () {
		var sites_list = _sites_array.slice(0);
		var bounds = new google.maps.LatLngBounds();
		sites_list.forEach(function (site) {
			var feature = _data_layer_sites.getFeatureById(site.id);
			feature.setProperty('isAvailable', null);
			processPoints(feature.getGeometry(), bounds.extend, bounds);
		});

		$(document).trigger("OnAvailabilityUpdated", this.returnFilterList(true, false));

		_map.fitBounds(bounds);
	};

	this.getAvailability = function (siteId) {
		var feature = _data_layer_sites.getFeatureById(siteId);
		if (feature) {
			return feature.getProperty('isAvailable');
		}
		return false;
	};

	this.getSiteTypeAvailability = function (siteId) {
		var hasAvailability = false;
		var site = _sites_array.find(x => x.id == siteId);
		if (site) {
			var sites = _sites_array.filter(x => x.siteTypeID == site.siteTypeID);
			sites.some(function (item) {
				var feature = _data_layer_sites.getFeatureById(item.id);
				if (feature) {
					if (feature.getProperty('isAvailable')) {
						hasAvailability = true;
						return true;
					}
				}
			});
		}
		return hasAvailability;
	};

	this.getSite = function (siteId) {
		return _sites_array.find(x => x.id == siteId);
	};

	this.getK2Id = function (siteId) {
		var site = _sites_array.find(x => x.id == siteId);
		if (site.k2SiteId) {
			return site.k2SiteId;
		}
	};

	this.getK2SiteName = function (siteId) {
		var site = _sites_array.find(x => x.id == siteId);
		if (site.k2SiteName) {
			return site.k2SiteName;
		}
	};

	this.getSiteTypeId = function (siteId) {
		var site = _sites_array.find(x => x.id == siteId);
		if (site.siteTypeID) {
			return site.siteTypeID;
		}
	};

	this.getSiteTypeSerialized = function (siteId) {
		var site = _sites_array.find(x => x.id == siteId);
		if (site.serializedSiteType) {
			return site.serializedSiteType;
		}
	};

	var siteNameMatches = function (site, siteId) {
		if (parseInt(site.siteID) == siteId) {
			return true;
		}
		return false;
	};

	this.getEquipment = function (equipmentCode) {
		return vars.campgroundEquipment.find(x => x.Code == equipmentCode);
	};

	this.getSiteTypeEquipment = function (siteType) {
		var equipment = vars.categoryEquipment.find(x => x.SiteCategoryCode == siteType);
		if (equipment) {
			return equipment.Equipment;
		}
	};

	this.GetCampgroundClosures = function () {
		return vars.campgroundClosures;
	};

	this.GetLastReservationDays = function () {
		return vars.lastReservationDateDays;
	};

	this.GetCulture = function () {
		return vars.culture;
	};

	this.SetCurrentReservationDetails = function (reservationDetails) {
		_current_reservation_details = reservationDetails;
	};

	this.GetCurrentReservationDetails = function () {
		return _current_reservation_details;
	};

	this.IsFullScreen = function () {
		return _is_full_screen;
	};

	this.AddSiteToShoppingCart = function (k2SiteId) {
		var site = _sites_array.find(x => x.k2SiteId == k2SiteId);
		if (site) {
			site.inShoppingCart = true;
		}
	};

	this.RemoveSiteFromShoppingCart = function (k2SiteId) {
		var site = _sites_array.find(x => x.k2SiteId == k2SiteId);
		if (site) {
			site.inShoppingCart = false;
		}
	};

	this.RemoveAllSitesFromShoppingCart = function () {
		var sites = _sites_array.filter(x => x.inShoppingCart == true);
		if (sites) {
			sites.forEach(function (site, index) {
				site.inShoppingCart = false;
			})
		}
	};

	this.construct(options);
}
;
var mainGa4DataLayer;

var ga4DataLayer = function () {

	this.init = function () {
		window.onload = function () {
			var siteButtons = document.getElementsByClassName('btn-sitetype-details');
			for (var i = 0; i < siteButtons.length; i++) {
				siteButtons[i].addEventListener("click", function (id) {
					var siteId = id.currentTarget.getAttribute('data-siteid');

					if (siteId) {
						mainGa4DataLayer.viewDetail(siteId);
					}
				});
			}
			var removeButtons = document.getElementsByClassName("removeFromCartCheckout");
			for (var i = 0; i < removeButtons.length; i++) {
				removeButtons[i].addEventListener("click", function (id) {
					
					var reservationId = $(this).val();
					if (reservationId) {						
						mainGa4DataLayer.removeFromCartCheckout(reservationId);
					}
				});
			}

		}
	}

	var _sites;	
	this.populateSites = function (sites) {		
		_sites = sites;		
	};

	var _cartItems;
	this.populateCartItems = function (cartItems) {
		_cartItems = cartItems;		
	};

	var _selectSites;
	this.populateSelectSites = function (selectSites) {
		_selectSites = selectSites;		
	}

	var _selectSitesCheckout;
	this.populateSelectSitesCheckout = function (selectSitesCheckout) {
		
		_selectSitesCheckout = selectSitesCheckout;		
		
	}



	this.ccTypeForRewards = function (ccType) {

		var creditCardType = "";
		if (ccType === 'VI') {
			creditCardType = "VISA";
		}
		if (ccType === 'MC') {
			creditCardType = "MC";
		}
		if (ccType === 'DI') {
			creditCardType = "DISC";
		}

		dataLayer.push({
			'event': 'checkout',
			'ecommerce': {
				'checkout': {
					'actionField': { 'step': 2, 'option': creditCardType },
				}
			}
		});

	}

	this.rewardsNewPurchase = function() {
		dataLayer.push({
			'event': 'detail',
			'ecommerce': {
				'detail': {
					'actionField': {},
					'products': [{
						'name': "KOA Rewards Purchase",
						'id': "VKR-stand-alone-purchase",
						'price': 39.00,
						'brand': "",
						'category': "VKR",
						'variant': "",
						'position': 1
					}]
				},
			}
		});
	}

	this.rewardsRenewal = function () {
		dataLayer.push({
			'event': 'detail',
			'ecommerce': {
				'detail': {
					'actionField': {},
					'products': [{
						'name': "KOA Rewards Renewal",
						'id': "VKR-stand-alone-renewal",
						'price': 39.00,
						'brand': "",
						'category': "VKR",
						'variant': "",
						'position': 2
					}]
				},
			}
		});
	}

	this.sensibleWeatherDetail = function (campgroundName, campgroundAlias, swTotal, swQuoteId) {
		dataLayer.push({
			'event': 'productClick',
			'ecommerce': {
				'click': {
					'actionField': {},
					'products': [{
						'name': "Sensible Weather Guarantee",
						'id': campgroundAlias,
						'price': swTotal,
						'brand': campgroundName,
						'category': "SW",
						'quoteId': swQuoteId,
						'quantity': 1
					}]
				},
			},
		});
	}

	this.viewDetail = function (siteId) {		
		var site = _sites.find(x => x.id == siteId);
		if (site) {
			dataLayer.push({
				'event': 'productClick',
				'ecommerce': {
					'click': {
						'actionField': { 'list': 'SiteType - ' + site.category },
						'products': [{
							'name': site.name,
							'id': site.id,
							'price': site.price,
							'brand': site.brand,
							'category': site.category,
							'variant': site.variant,
							'position': site.position
						}]
					},
				},
			});

			dataLayer.push({
			'ecommerce': {
					'impressions': [{
					'name': site.name,
					'id': site.id,
					'price': site.price,
					'brand': site.brand,
					'category': site.category,
					'variant': site.variant,
					'list': "SiteType - " + site.category,
					'position': site.position
					}]
				},
			'event': 'impression'
			});
		}
	}

	this.addToCart = function (siteId, nights) {
		var site = _sites.find(x => x.id == siteId);		
		if (site) {			
			dataLayer.push({
				'event': 'addToCart',
				'ecommerce': {
					'add': {
						'actionField': { 'list': 'SiteType - ' + site.category },
						'products': [{
							'name': site.name,
							'id': site.id,
							'price': site.price,
							'brand': site.brand,
							'category': site.category,
							'variant': site.variant,
							'quantity': nights,
							'dimension8': nights
						}]
					}
				}
			});			
		}
	};

	this.addToCartSelectSite = function (siteNumber, siteId, nights) {	
		var sSite = _selectSites.find(x => x.variant == siteNumber);
		var site = _sites.find(x => x.id == siteId);		
		if (site && sSite) {
			dataLayer.push({
				'event': 'addToCart',
				'ecommerce': {
					'add': {
						'actionField': { 'list': 'SiteType - ' + sSite.category },
						'products': [
							{
								'name': site.name,
								'id': site.id,
								'price': site.price,
								'brand': site.brand,
								'category': site.category,
								'variant': site.variant,
								'quantity': nights,
								'dimension8': nights
							},
							{
							'name': sSite.name,
							'id': sSite.id,
							'price': sSite.price,
							'brand': sSite.brand,
							'category': sSite.catetory,
							'variant': sSite.variant,
							'quantity': sSite.quantity
						}]
					}
				}
			});
		}
	};

	this.removeFromCartCheckout = function (cartReservationId) {
		
		var cartReservation = _cartItems.find(x => x.shoppingCartReservationID == cartReservationId);		
		var cartProducts = new Array();
		if (_selectSitesCheckout != null) {			
			var selectSiteCheckout = _selectSitesCheckout.find(y => y.shoppingCartReservationID = cartReservation.shoppingCartReservationID);			
			var site = {
				'name': selectSiteCheckout.name,
				'id': selectSiteCheckout.id,
				'price': selectSiteCheckout.price,
				'brand': selectSiteCheckout.brand,
				'category': selectSiteCheckout.catetory,
				'variant': selectSiteCheckout.variant,
				'quantity': selectSiteCheckout.quantity
			}
			
			cartProducts.push(site);
		}
		cartProducts.push({
			'name': cartReservation.name,
			'id': cartReservation.id,
			'price':cartReservation.price,
			'brand': cartReservation.brand,
			'category': cartReservation.category,
			'variant': cartReservation.variant,
			'quantity': cartReservation.dimension8,
			'dimension8': cartReservation.dimension8
		});

		dataLayer.push({
			'event': 'removeFromCart',
			'ecommerce': {
				'remove': {
					'products': cartProducts
				}
			}
		});
	};

	this.removeSelectMySite = function (campgroundId, campgroundName, siteNumber, smsFee, campgroundAlias) {		
		var smsProducts = new Array();
		smsProducts.push({
			'name': campgroundAlias + " - Select My Site",
			'id': "sms_" + campgroundId + "_" + siteNumber,
			'price': smsFee,
			'brand': campgroundName,
			'category': "SMS",
			'variant': siteNumber,
			'quantity': 1			
		});

		dataLayer.push({
			'event': 'removeFromCart',
			'ecommerce': {
				'remove': {
					'products': smsProducts
				}
			}
		});
		
	}

	this.removeFromCartDataLayer = function (reservation) {		
		var products = new Array();
		if (reservation.length > 1) {
			reservation.forEach(function (item, index) {				
				products.push({
					'name': item.CampgroundAlias + " - " + item.SiteTitle,
					'id': item.PricingGroupID,
					'price': Math.round(item.QuotedAmount / item.LengthOfStay),
					'brand': item.CampgroundName,
					'category': item.SiteCategoryCode,
					'variant': item.CampgroundName,
					'quantity': item.LengthOfStay,
					'dimension8': item.LengthOfStay
				});
				if (item.SiteNumber != null) {
					var selectSite = {
						'name': item.CampgroundAlias + " - Select My Site",
						'id': "sms_" + item.CampgroundID + "_" + item.SiteNumber,
						'price': item.QuotedSiteRequestFeeAmount,
						'brand': item.CampgroundName,
						'category': "SMS",
						'variant': item.SiteNumber,
						'quantity': 1
					}

					products.push(selectSite);
				}

			});
		}
		else {
			products.push({
				'name': reservation.CampgroundAlias + " - " + reservation.SiteTitle,
				'id': reservation.PricingGroupID,
				'price': Math.round(reservation.QuotedAmount / reservation.LengthOfStay),
				'brand': reservation.CampgroundName,
				'category': reservation.SiteCategoryCode,
				'variant': reservation.CampgroundName,
				'quantity': reservation.LengthOfStay,
				'dimension8': reservation.LengthOfStay
			});
			if (reservation.SiteNumber != null) {
				var selectSite = {
					'name': reservation.CampgroundAlias +  " - Select My Site",
					'id': "sms_" + reservation.CampgroundID + "_" + reservation.SiteNumber,
					'price': reservation.QuotedSiteRequestFeeAmount,
					'brand': reservation.CampgroundName,
					'category': "SMS",
					'variant': reservation.SiteNumber,
					'quantity': 1
				}

				products.push(selectSite);
			}			
		}
		dataLayer.push({
			'event': 'removeFromCart',
			'ecommerce': {
				'remove': {
					'products': products
				}
			}
		});
	}

	this.addRewardsToCart = function (campgroundAlias, rewardsPurchasePrice) {		
		dataLayer.push({
			'event': 'addToCart',
			'ecommerce': {
				'add': {
					'products': [{
						'name': 'KOA Rewards Purchase',
						'id': 'VKR-' + campgroundAlias,
						'price': rewardsPurchasePrice,
						'brand': '',
						'category': 'VKR',
						'variant': '',
						'quantity': 1
					}]
				}
			}
		});
	};

	this.removeRewardsFromCart = function (campgroundAlias, rewardsPurchasePrice) {		
		dataLayer.push({
			'event': 'removeFromCart',
			'ecommerce': {
				'remove': {
					'products': [{
						'name': 'KOA Rewards Purchase',
						'id': 'VKR-' + campgroundAlias,
						'price': rewardsPurchasePrice,
						'brand': '',
						'category': 'VKR',
						'variant': '',
						'quantity': 1
					}]
				}
			}
		});
	};

	this.addRewardsToCartNew = function () {
		dataLayer.push({
			'event': 'addToCart',
			'ecommerce': {
				'add': {
					'products': [{
						'name': "KOA Rewards Purchase",
						'id': "VKR-stand-alone-purchase",
						'price': 39.00,
						'brand': "",
						'category': "VKR",
						'variant': "",
						'quantity': 1
					}]
				}
			}
		});
	};

	this.addRewardsToCartRenew = function () {
		dataLayer.push({
			'event': 'addToCart',
			'ecommerce': {
				'add': {
					'products': [{
						'name': "KOA Rewards Renewal",
						'id': "VKR-stand-alone-renewal",
						'price': 39.00,
						'brand': "",
						'category': "VKR",
						'variant': "",
						'quantity': 1
					}]
				}
			}
		});
	};

	this.addToCartStep3 = function (checkoutData) {
		dataLayer.push({
			'event': 'addToCart',
			'ecommerce': {
				'add': {
					'actionField': { 'list': 'SiteType - ' + checkoutData.actionField },
					'products': [{
						'name': checkoutData.name,
						'id': checkoutData.id,
						'price': checkoutData.price,
						'brand': checkoutData.brand,
						'category': checkoutData.category,
						'variant': checkoutData.variant,
						'quantity': checkoutData.dimension8,
						'dimension8': checkoutData.dimension8
					}]
				}
			}
		});
	};

	this.init();
};

mainGa4DataLayer = new ga4DataLayer();
;
