/* Minification failed. Returning unminified contents.
(4036,11-12): run-time error JS1010: Expected identifier: .
(4036,11-12): run-time error JS1195: Expected expression: .
(6214,76-77): run-time error JS1010: Expected identifier: .
(6214,76-77): run-time error JS1195: Expected expression: .
(6214,89-93): run-time error JS1034: Unmatched 'else'; no 'if' defined: else
 */
/*! 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');
				});
			}
		}
	});

	$('.carousel').on('slide.bs.carousel', function () {
		$('.caption').hide();
	});
	$('.carousel').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');
	});

	$('#ways-to-stay-hamburger-button').on('click', function () {

		$('#ways-to-stay-hamburger').toggleClass('open');
	});
});

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);
	});
});
;
/**
 * 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));
;
// Constructor
var KOAAutoSuggest = (function () {
	return {
		init: function (options, elem) {
			// Mix in the passed in options with the default options
			this.options = $.extend({}, this.options, options);

			if (this.options.input == null || this.options.container == null) {
				return;
			}

			this.typeTimeout = null;
			this.xhr = null;
			this.camps = {};

			//disable native browser autocomplete
			$(this.options.input).attr('autocomplete', 'off');

			jQuery(this.options.input).bind("keyup", this, this.onInputKeyUp);
			jQuery(document).bind("mousedown", this, this.onDocumentMouseDown);
		},
		options: {
			container: null,
			input: null,
			truncateAt: null
		},
		// Prototype Members
		onInputKeyUp: function (e) {
			var $suggestList = jQuery(e.data.options.container + ' .find-results');

			var keyCode = e.keyCode || e.which;

			switch (keyCode) {
				case 27: // Escape
					$suggestList.hide();
				case 9:
				case 16:
					//case 17:
				case 18:
				case 19:
				case 33:
				case 34:
				case 35:
				case 36:
				case 37:
				case 38:
				case 39:
				case 40:
				case 44:
				case 46:
				case 91:
				case 92:
				case 145:
				case 13:
					//case 224:
					return;
				default:
			}

			e.data.clearTypeTimeout();

			e.data.abortAjax();

			var $this = jQuery(this);
			var query = $this.val();

			if (query.length >= 3) {
				e.data.typeTimeout = window.setTimeout(function () {
					e.data.xhr = jQuery.ajax(
					{
						url: '/handlers/autosuggest.ashx?q=' + encodeURIComponent(query),
						type: 'GET',
						success: function () {
							e.data.onAutoSuggestSuccess(e.data);
						},
						error: function (e, jqxhr, settings, exception) {
							console.log(exception);
						}
					});
				}, 100);
			}
			else {
				$suggestList.hide();
			}
		},
		clearTypeTimeout: function () {
			if (this.typeTimeout != null) {
				window.clearTimeout(this.typeTimeout);
				this.typeTimeout = null;
			}
		},
		abortAjax: function () {
			if (this.xhr != null) {
				try {
					if (typeof (this.xhr.abort) === 'function') {
						this.xhr.abort();
					}
					if (typeof (this.xhr.close) === 'function') {
						this.xhr.close();
					}
				}
				catch (e) { }
			}
		},
		onAutoSuggestSuccess: function (data) {
			var $suggestList = jQuery(data.options.container + ' .find-results');

			try {
				data = jQuery.parseJSON(data.xhr.responseText);
			}
			catch (e) {
				$suggestList.hide();
				return;
			}

			if (data.status == "success") {
				var ilen = data.list.length;

				if (ilen > 0) {
					var suggestHTML = [];
					for (var i = 0; i < ilen; i++) {
						// Store the camp data
						this.camps[data.list[i].id] = data.list[i];

						suggestHTML[suggestHTML.length] = '<li class="';
						if (i == 0) {
							//suggestHTML[suggestHTML.length] = 'first ';
							suggestHTML[suggestHTML.length] = 'first';
						}

						// Truncate camp name
						fullCampName = '';
						campName = data.list[i].name;
						if (this.options.truncateAt != null && !isNaN(this.options.truncateAt) && campName.length > this.options.truncateAt) {
							fullCampName = campName;
							campName = campName.substr(0, this.options.truncateAt - 3) + '...';
						}

						// Set country indicator
						var countryIndicator = '';
						switch (data.list[i].state) {
							case "AB":
							case "BC":
							case "SK":
							case "MB":
							case "ON":
							case "QC":
							case "NB":
							case "NS":
							case "NL":
							case "PE":
								countryIndicator = '<br />Canada';
								break;
							default:
								countryIndicator = '<br />USA';
								break;
						}

						suggestHTML[suggestHTML.length] = '"><div class="col-1"><a href="';
						suggestHTML[suggestHTML.length] = data.list[i].link;
						suggestHTML[suggestHTML.length] = '/" title="';
						suggestHTML[suggestHTML.length] = fullCampName;
						suggestHTML[suggestHTML.length] = '" class="link-arrow5 link-suggest-camp-name" rel="';
						suggestHTML[suggestHTML.length] = data.list[i].id;
						suggestHTML[suggestHTML.length] = '">';
						suggestHTML[suggestHTML.length] = campName;
						suggestHTML[suggestHTML.length] = countryIndicator + '</a></div><div class="col-2">';

						if (data.list[i].irez == 1) {
							suggestHTML[suggestHTML.length] = '<a class="link-book" href="';
							suggestHTML[suggestHTML.length] = data.list[i].link + "/reserve/";
							suggestHTML[suggestHTML.length] = '" rel="';
							suggestHTML[suggestHTML.length] = data.list[i].id;
							suggestHTML[suggestHTML.length] = '">Reserve</a>';
						}

						suggestHTML[suggestHTML.length] = '</div><div style="clear:both;"></div></li>';
					}
					$suggestList.find('ul').html(suggestHTML.join(''));
					$suggestList.animate({ height: 'show' }, 250);
				}
				else {
					$suggestList.hide();
				}
			}
			else {
				$suggestList.hide();
			}
		},
		onDocumentMouseDown: function (e) {
			var $container = jQuery(e.data.options.container);
			var $suggestList = jQuery(e.data.options.container + ' .find-results');
			if ($container.has(e.target).length <= 0 && $suggestList.is(':visible')) {
				$suggestList.hide();
			}
		}
	}
})();

$(function () {
	//load the plugins
	$.plugin("koaautosuggest", KOAAutoSuggest);

	$("#input-find").koaautosuggest({ container: '#find-koa', input: '#input-find', truncateAt: 29 });
	$("#find-a-koa-small-search").koaautosuggest({ container: '#find-koa-wrap-small', input: '#find-a-koa-small-search' });
});
;
$(document).ready(function () {
	$(document).click(function (e) {
		if (!$("#saved-reservations").is(e.target) && !$("#saved-reservations").has(e.target).length) {
			$('#saved-reservations').collapse('hide');
		}
	});

	$('.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-heart')
					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-heart')
					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);
			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);
			});
		}
	});

	$('.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);

		$(".wts-details-close").click();
	});

	$('.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');

			if (self == top) {
				var cartIcon = $('.shopping-cart-icon');
				mainShoppingCart.addToCart(cartIcon, siteInfo, addToCartButton);
			}
			else {
				var cartIcon = parent.$('.shopping-cart-icon');
				parent.mainShoppingCart.addToCart(cartIcon, siteInfo, addToCartButton);
			}

			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;
	});
});

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) {
				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 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(),
			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(),
			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(),
		};

		$.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;

						$("#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 () {

			});
	};

	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"));

		$.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");
						}
					});
				}
				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 () {
				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");
						}
					});
				});
			}
		});

		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;
	});
});
;
/* =============================================================
 * 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)});;
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: ""
        }
    }
})();;
/**
 * 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);
;
