/* Minification failed. Returning unminified contents.
(14035,40-41): run-time error JS1195: Expected expression: .
(14036,29-30): run-time error JS1004: Expected ';': :
(14037,31-32): run-time error JS1004: Expected ';': :
(14038,14-15): run-time error JS1006: Expected ')': ;
(14041,9-10): run-time error JS1002: Syntax error: }
(14044,20-21): run-time error JS1004: Expected ';': :
(14046,31-32): run-time error JS1004: Expected ';': :
(14058,41-42): run-time error JS1195: Expected expression: .
(14089,39-40): run-time error JS1195: Expected expression: .
(14103,48-49): run-time error JS1195: Expected expression: .
(14104,24-25): run-time error JS1004: Expected ';': :
(14105,17-24): run-time error JS1010: Expected identifier: 'items'
(14105,24-25): run-time error JS1004: Expected ';': :
(14108,5-6): run-time error JS1197: Too many errors. The file might not be a JavaScript file: }
 */
/* NUGET: BEGIN LICENSE TEXT
 *
 * Microsoft grants you the right to use these script files for the sole
 * purpose of either: (i) interacting through your browser with the Microsoft
 * website or online service, subject to the applicable licensing or use
 * terms; or (ii) using the files as included with a Microsoft product subject
 * to that product's license terms. Microsoft reserves all other rights to the
 * files not expressly granted by Microsoft, whether by implication, estoppel
 * or otherwise. Insofar as a script file is dual licensed under GPL,
 * Microsoft neither took the code under GPL nor distributes it thereunder but
 * under the terms set out in this paragraph. All notices and licenses
 * below are for informational purposes only.
 *
 * NUGET: END LICENSE TEXT */
/*
** Unobtrusive Ajax support library for jQuery
** Copyright (C) Microsoft Corporation. All rights reserved.
*/
(function(a){var b="unobtrusiveAjaxClick",d="unobtrusiveAjaxClickTarget",h="unobtrusiveValidation";function c(d,b){var a=window,c=(d||"").split(".");while(a&&c.length)a=a[c.shift()];if(typeof a==="function")return a;b.push(d);return Function.constructor.apply(null,b)}function e(a){return a==="GET"||a==="POST"}function g(b,a){!e(a)&&b.setRequestHeader("X-HTTP-Method-Override",a)}function i(c,b,e){var d;if(e.indexOf("application/x-javascript")!==-1)return;d=(c.getAttribute("data-ajax-mode")||"").toUpperCase();a(c.getAttribute("data-ajax-update")).each(function(f,c){var e;switch(d){case"BEFORE":e=c.firstChild;a("<div />").html(b).contents().each(function(){c.insertBefore(this,e)});break;case"AFTER":a("<div />").html(b).contents().each(function(){c.appendChild(this)});break;case"REPLACE-WITH":a(c).replaceWith(b);break;default:a(c).html(b)}})}function f(b,d){var j,k,f,h;j=b.getAttribute("data-ajax-confirm");if(j&&!window.confirm(j))return;k=a(b.getAttribute("data-ajax-loading"));h=parseInt(b.getAttribute("data-ajax-loading-duration"),10)||0;a.extend(d,{type:b.getAttribute("data-ajax-method")||undefined,url:b.getAttribute("data-ajax-url")||undefined,cache:!!b.getAttribute("data-ajax-cache"),beforeSend:function(d){var a;g(d,f);a=c(b.getAttribute("data-ajax-begin"),["xhr"]).apply(b,arguments);a!==false&&k.show(h);return a},complete:function(){k.hide(h);c(b.getAttribute("data-ajax-complete"),["xhr","status"]).apply(b,arguments)},success:function(a,e,d){i(b,a,d.getResponseHeader("Content-Type")||"text/html");c(b.getAttribute("data-ajax-success"),["data","status","xhr"]).apply(b,arguments)},error:function(){c(b.getAttribute("data-ajax-failure"),["xhr","status","error"]).apply(b,arguments)}});d.data.push({name:"X-Requested-With",value:"XMLHttpRequest"});f=d.type.toUpperCase();if(!e(f)){d.type="POST";d.data.push({name:"X-HTTP-Method-Override",value:f})}a.ajax(d)}function j(c){var b=a(c).data(h);return!b||!b.validate||b.validate()}a(document).on("click","a[data-ajax=true]",function(a){a.preventDefault();f(this,{url:this.href,type:"GET",data:[]})});a(document).on("click","form[data-ajax=true] input[type=image]",function(c){var g=c.target.name,e=a(c.target),f=a(e.parents("form")[0]),d=e.offset();f.data(b,[{name:g+".x",value:Math.round(c.pageX-d.left)},{name:g+".y",value:Math.round(c.pageY-d.top)}]);setTimeout(function(){f.removeData(b)},0)});a(document).on("click","form[data-ajax=true] :submit",function(e){var g=e.currentTarget.name,f=a(e.target),c=a(f.parents("form")[0]);c.data(b,g?[{name:g,value:e.currentTarget.value}]:[]);c.data(d,f);setTimeout(function(){c.removeData(b);c.removeData(d)},0)});a(document).on("submit","form[data-ajax=true]",function(h){var e=a(this).data(b)||[],c=a(this).data(d),g=c&&c.hasClass("cancel");h.preventDefault();if(!g&&!j(this))return;f(this,{url:this.action,type:this.method||"GET",data:e.concat(a(this).serializeArray())})})})(jQuery);;
/*! jQuery Validation Plugin - v1.13.1 - 10/14/2014
 * http://jqueryvalidation.org/
 * Copyright (c) 2014 Jörn Zaefferer; Licensed MIT */
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.validateDelegate(":submit","click",function(b){c.settings.submitHandler&&(c.submitButton=b.target),a(b.target).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(b.target).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.submit(function(b){function d(){var d,e;return c.settings.submitHandler?(c.submitButton&&(d=a("<input type='hidden'/>").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),e=c.settings.submitHandler.call(c,c.currentForm,b),c.submitButton&&d.remove(),void 0!==e?e:!1):!0}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c;return a(this[0]).is("form")?b=this.validate().form():(b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b})),b},removeAttrs:function(b){var c={},d=this;return a.each(b.split(/\s/),function(a,b){c[b]=d.attr(b),d.removeAttr(b)}),c},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(b,c){i[c]=f[c],delete f[c],"required"===c&&a(j).removeAttr("aria-required")}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g),a(j).attr("aria-required","true")),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}),a.extend(a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){return!!a.trim(""+a(b).val())},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(a,b){(9!==b.which||""!==this.elementValue(a))&&(a.name in this.submitted||a===this.lastElement)&&this.element(a)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date ( ISO ).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c=a.data(this[0].form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!this.is(e.ignore)&&e[d].call(c,this[0],b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).validateDelegate(":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'] ,[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox']","focusin focusout keyup",b).validateDelegate("select, option, [type='radio'], [type='checkbox']","click",b),this.settings.invalidHandler&&a(this.currentForm).bind("invalid-form.validate",this.settings.invalidHandler),a(this.currentForm).find("[required], [data-rule-required], .required").attr("aria-required","true")},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c=this.clean(b),d=this.validationTargetFor(c),e=!0;return this.lastElement=d,void 0===d?delete this.invalid[c.name]:(this.prepareElement(d),this.currentElements=a(d),e=this.check(d)!==!1,e?delete this.invalid[d.name]:this.invalid[d.name]=!0),a(b).attr("aria-invalid",!e),this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),e},showErrors:function(b){if(b){a.extend(this.errorMap,b),this.errorList=[];for(var c in b)this.errorList.push({message:b[c],element:this.findByName(c)[0]});this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.submitted={},this.lastElement=null,this.prepareForm(),this.hideErrors(),this.elements().removeClass(this.settings.errorClass).removeData("previousValue").removeAttr("aria-invalid")},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, [disabled], [readonly]").not(this.settings.ignore).filter(function(){return!this.name&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.name in c||!b.objectLength(a(this).rules())?!1:(c[this.name]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},reset:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([]),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d=a(b),e=b.type;return"radio"===e||"checkbox"===e?a("input[name='"+b.name+"']:checked").val():"number"===e&&"undefined"!=typeof b.validity?b.validity.badInput?!1:d.val():(c=d.val(),"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f=a(b).rules(),g=a.map(f,function(a,b){return b}).length,h=!1,i=this.elementValue(b);for(d in f){e={method:d,parameters:f[d]};try{if(c=a.validator.methods[d].call(this,i,b,e.parameters),"dependency-mismatch"===c&&1===g){h=!0;continue}if(h=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(j){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",j),j}}if(!h)return this.objectLength(f)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a];return void 0},defaultMessage:function(b,c){return this.findDefined(this.customMessage(b.name,c),this.customDataMessage(b,c),!this.settings.ignoreTitle&&b.title||void 0,a.validator.messages[c],"<strong>Warning: No message defined for "+b.name+"</strong>")},formatAndAdd:function(b,c){var d=this.defaultMessage(b,c.method),e=/\$?\{(\d+)\}/g;"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),this.errorList.push({message:d,element:b,method:c.method}),this.errorMap[b.name]=d,this.submitted[b.name]=d},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g=this.errorsFor(b),h=this.idOrName(b),i=a(b).attr("aria-describedby");g.length?(g.removeClass(this.settings.validClass).addClass(this.settings.errorClass),g.html(c)):(g=a("<"+this.settings.errorElement+">").attr("id",h+"-error").addClass(this.settings.errorClass).html(c||""),d=g,this.settings.wrapper&&(d=g.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement(d,a(b)):d.insertAfter(b),g.is("label")?g.attr("for",h):0===g.parents("label[for='"+h+"']").length&&(f=g.attr("id").replace(/(:|\.|\[|\])/g,"\\$1"),i?i.match(new RegExp("\\b"+f+"\\b"))||(i+=" "+f):i=f,a(b).attr("aria-describedby",i),e=this.groups[b.name],e&&a.each(this.groups,function(b,c){c===e&&a("[name='"+b+"']",this.currentForm).attr("aria-describedby",g.attr("id"))}))),!c&&this.settings.success&&(g.text(""),"string"==typeof this.settings.success?g.addClass(this.settings.success):this.settings.success(g,b)),this.toShow=this.toShow.add(g)},errorsFor:function(b){var c=this.idOrName(b),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+d.replace(/\s+/g,", #")),this.errors().filter(e)},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+b+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return this.dependTypes[typeof a]?this.dependTypes[typeof a](a,b):!0},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(a){this.pending[a.name]||(this.pendingRequest++,this.pending[a.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b){return a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,"remote")})}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),/min|max/.test(c)&&(null===g||/number|range|text/.test(g))&&(d=Number(d)),d||0===d?e[c]=d:g===c&&"range"!==g&&(e[c]=!0);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b);for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),void 0!==d&&(e[c]=d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0!==e.param?e.param:!0:delete b[d]}}),a.each(b,function(d,e){b[d]=a.isFunction(e)?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:a.trim(b).length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},creditcard:function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||d>=e},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||c>=a},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.unbind(".validate-equalTo").bind("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d){if(this.optional(c))return"dependency-mismatch";var e,f,g=this.previousValue(c);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),g.originalMessage=this.settings.messages[c.name].remote,this.settings.messages[c.name].remote=g.message,d="string"==typeof d&&{url:d}||d,g.old===b?g.valid:(g.old=b,e=this,this.startRequest(c),f={},f[c.name]=b,a.ajax(a.extend(!0,{url:d,mode:"abort",port:"validate"+c.name,dataType:"json",data:f,context:e.currentForm,success:function(d){var f,h,i,j=d===!0||"true"===d;e.settings.messages[c.name].remote=g.originalMessage,j?(i=e.formSubmitted,e.prepareElement(c),e.formSubmitted=i,e.successList.push(c),delete e.invalid[c.name],e.showErrors()):(f={},h=d||e.defaultMessage(c,"remote"),f[c.name]=g.message=a.isFunction(h)?h(b):h,e.invalid[c.name]=!0,e.showErrors(f)),g.valid=j,e.stopRequest(c,j)}},d)),"pending")}}}),a.format=function(){throw"$.format has been deprecated. Please use $.validator.format instead."};var b,c={};a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)}),a.extend(a.fn,{validateDelegate:function(b,c,d){return this.bind(c,function(c){var e=a(c.target);return e.is(b)?d.apply(e,arguments):void 0})}})});;
/* NUGET: BEGIN LICENSE TEXT
 *
 * Microsoft grants you the right to use these script files for the sole
 * purpose of either: (i) interacting through your browser with the Microsoft
 * website or online service, subject to the applicable licensing or use
 * terms; or (ii) using the files as included with a Microsoft product subject
 * to that product's license terms. Microsoft reserves all other rights to the
 * files not expressly granted by Microsoft, whether by implication, estoppel
 * or otherwise. Insofar as a script file is dual licensed under GPL,
 * Microsoft neither took the code under GPL nor distributes it thereunder but
 * under the terms set out in this paragraph. All notices and licenses
 * below are for informational purposes only.
 *
 * NUGET: END LICENSE TEXT */
/*
** Unobtrusive validation support library for jQuery and jQuery Validate
** Copyright (C) Microsoft Corporation. All rights reserved.
*/
(function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("<li />").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery);;
/*!
 * jQuery Cookie Plugin v1.4.1
 * https://github.com/carhartl/jquery-cookie
 *
 * Copyright 2006, 2014 Klaus Hartl
 * Released under the MIT license
 */
(function (factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD (Register as an anonymous module)
		define(['jquery'], factory);
	} else if (typeof exports === 'object') {
		// Node/CommonJS
		module.exports = factory(require('jquery'));
	} else {
		// Browser globals
		factory(jQuery);
	}
}(function ($) {

	var pluses = /\+/g;

	function encode(s) {
		return config.raw ? s : encodeURIComponent(s);
	}

	function decode(s) {
		return config.raw ? s : decodeURIComponent(s);
	}

	function stringifyCookieValue(value) {
		return encode(config.json ? JSON.stringify(value) : String(value));
	}

	function parseCookieValue(s) {
		if (s.indexOf('"') === 0) {
			// This is a quoted cookie as according to RFC2068, unescape...
			s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
		}

		try {
			// Replace server-side written pluses with spaces.
			// If we can't decode the cookie, ignore it, it's unusable.
			// If we can't parse the cookie, ignore it, it's unusable.
			s = decodeURIComponent(s.replace(pluses, ' '));
			return config.json ? JSON.parse(s) : s;
		} catch(e) {}
	}

	function read(s, converter) {
		var value = config.raw ? s : parseCookieValue(s);
		return $.isFunction(converter) ? converter(value) : value;
	}

	var config = $.cookie = function (key, value, options) {

		// Write

		if (arguments.length > 1 && !$.isFunction(value)) {
			options = $.extend({}, config.defaults, options);

			if (typeof options.expires === 'number') {
				var days = options.expires, t = options.expires = new Date();
				t.setMilliseconds(t.getMilliseconds() + days * 864e+5);
			}

			return (document.cookie = [
				encode(key), '=', stringifyCookieValue(value),
				options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
				options.path    ? '; path=' + options.path : '',
				options.domain  ? '; domain=' + options.domain : '',
				options.secure  ? '; secure' : ''
			].join(''));
		}

		// Read

		var result = key ? undefined : {},
			// To prevent the for loop in the first place assign an empty array
			// in case there are no cookies at all. Also prevents odd result when
			// calling $.cookie().
			cookies = document.cookie ? document.cookie.split('; ') : [],
			i = 0,
			l = cookies.length;

		for (; i < l; i++) {
			var parts = cookies[i].split('='),
				name = decode(parts.shift()),
				cookie = parts.join('=');

			if (key === name) {
				// If second argument (value) is a function it's a converter...
				result = read(cookie, value);
				break;
			}

			// Prevent storing a cookie that we couldn't decode.
			if (!key && (cookie = read(cookie)) !== undefined) {
				result[name] = cookie;
			}
		}

		return result;
	};

	config.defaults = {};

	$.removeCookie = function (key, options) {
		// Must not alter options, thus extending a fresh object...
		$.cookie(key, '', $.extend({}, options, { expires: -1 }));
		return !$.cookie(key);
	};

}));
;
//-- Source: https://github.com/AceMetrix/jquery-deparam
(function (deparam) {
    if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
        try {
            var jquery = require('jquery');
        } catch (e) {
        }
        module.exports = deparam(jquery);
    } else if (typeof define === 'function' && define.amd){
        define(['jquery'], function(jquery){
            return deparam(jquery);
        });
    } else {
        var global;
        try {
          global = (false || eval)('this'); // best cross-browser way to determine global for < ES5
        } catch (e) {
          global = window; // fails only if browser (https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives)
        }
        global.deparam = deparam(global.jQuery); // assume jQuery is in global namespace
    }
})(function ($) {
    var deparam = function( params, coerce ) {
        var obj = {},
        coerce_types = { 'true': !0, 'false': !1, 'null': null };

        // If params is an empty string or otherwise falsy, return obj.
        if (!params) {
            return obj;
        }

        // Iterate over all name=value pairs.
        params.replace(/\+/g, ' ').split('&').forEach(function(v){
            var param = v.split( '=' ),
            key = decodeURIComponent( param[0] ),
            val,
            cur = obj,
            i = 0,

            // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
            // into its component parts.
            keys = key.split( '][' ),
            keys_last = keys.length - 1;

            // If the first keys part contains [ and the last ends with ], then []
            // are correctly balanced.
            if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) {
                // Remove the trailing ] from the last keys part.
                keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' );

                // Split first keys part into two parts on the [ and add them back onto
                // the beginning of the keys array.
                keys = keys.shift().split('[').concat( keys );

                keys_last = keys.length - 1;
            } else {
                // Basic 'foo' style key.
                keys_last = 0;
            }

            // Are we dealing with a name=value pair, or just a name?
            if ( param.length === 2 ) {
                val = decodeURIComponent( param[1] );

                // Coerce values.
                if ( coerce ) {
                    val = val && !isNaN(val) && ((+val + '') === val) ? +val        // number
                    : val === 'undefined'                       ? undefined         // undefined
                    : coerce_types[val] !== undefined           ? coerce_types[val] // true, false, null
                    : val;                                                          // string
                }

                if ( keys_last ) {
                    // Complex key, build deep object structure based on a few rules:
                    // * The 'cur' pointer starts at the object top-level.
                    // * [] = array push (n is set to array length), [n] = array if n is
                    //   numeric, otherwise object.
                    // * If at the last keys part, set the value.
                    // * For each keys part, if the current level is undefined create an
                    //   object or array based on the type of the next keys part.
                    // * Move the 'cur' pointer to the next level.
                    // * Rinse & repeat.
                    for ( ; i <= keys_last; i++ ) {
                        key = keys[i] === '' ? cur.length : keys[i];
                        cur = cur[key] = i < keys_last
                        ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
                        : val;
                    }

                } else {
                    // Simple key, even simpler rules, since only scalars and shallow
                    // arrays are allowed.

                    if ( Object.prototype.toString.call( obj[key] ) === '[object Array]' ) {
                        // val is already an array, so push on the next value.
                        obj[key].push( val );

                    } else if ( {}.hasOwnProperty.call(obj, key) ) {
                        // val isn't an array, but since a second value has been specified,
                        // convert val into an array.
                        obj[key] = [ obj[key], val ];

                    } else {
                        // val is a scalar.
                        obj[key] = val;
                    }
                }

            } else if ( key ) {
                // No value was defined, so set something meaningful.
                obj[key] = coerce
                ? undefined
                : '';
            }
        });

        return obj;
    };
    if ($) {
      $.prototype.deparam = $.deparam = deparam;
    }
    return deparam;
});
;
/*! Magnific Popup - v1.0.0 - 2015-01-03
* http://dimsemenov.com/plugins/magnific-popup/
* Copyright (c) 2015 Dmitry Semenov; */
;(function (factory) { 
if (typeof define === 'function' && define.amd) { 
 // AMD. Register as an anonymous module. 
 define(['jquery'], factory); 
 } else if (typeof exports === 'object') { 
 // Node/CommonJS 
 factory(require('jquery')); 
 } else { 
 // Browser globals 
 factory(window.jQuery || window.Zepto); 
 } 
 }(function($) { 

/*>>core*/
/**
 * 
 * Magnific Popup Core JS file
 * 
 */


/**
 * Private static constants
 */
var CLOSE_EVENT = 'Close',
	BEFORE_CLOSE_EVENT = 'BeforeClose',
	AFTER_CLOSE_EVENT = 'AfterClose',
	BEFORE_APPEND_EVENT = 'BeforeAppend',
	MARKUP_PARSE_EVENT = 'MarkupParse',
	OPEN_EVENT = 'Open',
	CHANGE_EVENT = 'Change',
	NS = 'mfp',
	EVENT_NS = '.' + NS,
	READY_CLASS = 'mfp-ready',
	REMOVING_CLASS = 'mfp-removing',
	PREVENT_CLOSE_CLASS = 'mfp-prevent-close';


/**
 * Private vars 
 */
/*jshint -W079 */
var mfp, // As we have only one instance of MagnificPopup object, we define it locally to not to use 'this'
	MagnificPopup = function(){},
	_isJQ = !!(window.jQuery),
	_prevStatus,
	_window = $(window),
	_document,
	_prevContentType,
	_wrapClasses,
	_currPopupType;


/**
 * Private functions
 */
var _mfpOn = function(name, f) {
		mfp.ev.on(NS + name + EVENT_NS, f);
	},
	_getEl = function(className, appendTo, html, raw) {
		var el = document.createElement('div');
		el.className = 'mfp-'+className;
		if(html) {
			el.innerHTML = html;
		}
		if(!raw) {
			el = $(el);
			if(appendTo) {
				el.appendTo(appendTo);
			}
		} else if(appendTo) {
			appendTo.appendChild(el);
		}
		return el;
	},
	_mfpTrigger = function(e, data) {
		mfp.ev.triggerHandler(NS + e, data);

		if(mfp.st.callbacks) {
			// converts "mfpEventName" to "eventName" callback and triggers it if it's present
			e = e.charAt(0).toLowerCase() + e.slice(1);
			if(mfp.st.callbacks[e]) {
				mfp.st.callbacks[e].apply(mfp, $.isArray(data) ? data : [data]);
			}
		}
	},
	_getCloseBtn = function(type) {
		if(type !== _currPopupType || !mfp.currTemplate.closeBtn) {
			mfp.currTemplate.closeBtn = $( mfp.st.closeMarkup.replace('%title%', mfp.st.tClose ) );
			_currPopupType = type;
		}
		return mfp.currTemplate.closeBtn;
	},
	// Initialize Magnific Popup only when called at least once
	_checkInstance = function() {
		if(!$.magnificPopup.instance) {
			/*jshint -W020 */
			mfp = new MagnificPopup();
			mfp.init();
			$.magnificPopup.instance = mfp;
		}
	},
	// CSS transition detection, http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
	supportsTransitions = function() {
		var s = document.createElement('p').style, // 's' for style. better to create an element if body yet to exist
			v = ['ms','O','Moz','Webkit']; // 'v' for vendor

		if( s['transition'] !== undefined ) {
			return true; 
		}
			
		while( v.length ) {
			if( v.pop() + 'Transition' in s ) {
				return true;
			}
		}
				
		return false;
	};



/**
 * Public functions
 */
MagnificPopup.prototype = {

	constructor: MagnificPopup,

	/**
	 * Initializes Magnific Popup plugin. 
	 * This function is triggered only once when $.fn.magnificPopup or $.magnificPopup is executed
	 */
	init: function() {
		var appVersion = navigator.appVersion;
		mfp.isIE7 = appVersion.indexOf("MSIE 7.") !== -1; 
		mfp.isIE8 = appVersion.indexOf("MSIE 8.") !== -1;
		mfp.isLowIE = mfp.isIE7 || mfp.isIE8;
		mfp.isAndroid = (/android/gi).test(appVersion);
		mfp.isIOS = (/iphone|ipad|ipod/gi).test(appVersion);
		mfp.supportsTransition = supportsTransitions();

		// We disable fixed positioned lightbox on devices that don't handle it nicely.
		// If you know a better way of detecting this - let me know.
		mfp.probablyMobile = (mfp.isAndroid || mfp.isIOS || /(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent) );
		_document = $(document);

		mfp.popupsCache = {};
	},

	/**
	 * Opens popup
	 * @param  data [description]
	 */
	open: function(data) {

		var i;

		if(data.isObj === false) { 
			// convert jQuery collection to array to avoid conflicts later
			mfp.items = data.items.toArray();

			mfp.index = 0;
			var items = data.items,
				item;
			for(i = 0; i < items.length; i++) {
				item = items[i];
				if(item.parsed) {
					item = item.el[0];
				}
				if(item === data.el[0]) {
					mfp.index = i;
					break;
				}
			}
		} else {
			mfp.items = $.isArray(data.items) ? data.items : [data.items];
			mfp.index = data.index || 0;
		}

		// if popup is already opened - we just update the content
		if(mfp.isOpen) {
			mfp.updateItemHTML();
			return;
		}
		
		mfp.types = []; 
		_wrapClasses = '';
		if(data.mainEl && data.mainEl.length) {
			mfp.ev = data.mainEl.eq(0);
		} else {
			mfp.ev = _document;
		}

		if(data.key) {
			if(!mfp.popupsCache[data.key]) {
				mfp.popupsCache[data.key] = {};
			}
			mfp.currTemplate = mfp.popupsCache[data.key];
		} else {
			mfp.currTemplate = {};
		}



		mfp.st = $.extend(true, {}, $.magnificPopup.defaults, data ); 
		mfp.fixedContentPos = mfp.st.fixedContentPos === 'auto' ? !mfp.probablyMobile : mfp.st.fixedContentPos;

		if(mfp.st.modal) {
			mfp.st.closeOnContentClick = false;
			mfp.st.closeOnBgClick = false;
			mfp.st.showCloseBtn = false;
			mfp.st.enableEscapeKey = false;
		}
		

		// Building markup
		// main containers are created only once
		if(!mfp.bgOverlay) {

			// Dark overlay
			mfp.bgOverlay = _getEl('bg').on('click'+EVENT_NS, function() {
				mfp.close();
			});

			mfp.wrap = _getEl('wrap').attr('tabindex', -1).on('click'+EVENT_NS, function(e) {
				if(mfp._checkIfClose(e.target)) {
					mfp.close();
				}
			});

			mfp.container = _getEl('container', mfp.wrap);
		}

		mfp.contentContainer = _getEl('content');
		if(mfp.st.preloader) {
			mfp.preloader = _getEl('preloader', mfp.container, mfp.st.tLoading);
		}


		// Initializing modules
		var modules = $.magnificPopup.modules;
		for(i = 0; i < modules.length; i++) {
			var n = modules[i];
			n = n.charAt(0).toUpperCase() + n.slice(1);
			mfp['init'+n].call(mfp);
		}
		_mfpTrigger('BeforeOpen');


		if(mfp.st.showCloseBtn) {
			// Close button
			if(!mfp.st.closeBtnInside) {
				mfp.wrap.append( _getCloseBtn() );
			} else {
				_mfpOn(MARKUP_PARSE_EVENT, function(e, template, values, item) {
					values.close_replaceWith = _getCloseBtn(item.type);
				});
				_wrapClasses += ' mfp-close-btn-in';
			}
		}

		if(mfp.st.alignTop) {
			_wrapClasses += ' mfp-align-top';
		}

	

		if(mfp.fixedContentPos) {
			mfp.wrap.css({
				overflow: mfp.st.overflowY,
				overflowX: 'hidden',
				overflowY: mfp.st.overflowY
			});
		} else {
			mfp.wrap.css({ 
				top: _window.scrollTop(),
				position: 'absolute'
			});
		}
		if( mfp.st.fixedBgPos === false || (mfp.st.fixedBgPos === 'auto' && !mfp.fixedContentPos) ) {
			mfp.bgOverlay.css({
				height: _document.height(),
				position: 'absolute'
			});
		}

		

		if(mfp.st.enableEscapeKey) {
			// Close on ESC key
			_document.on('keyup' + EVENT_NS, function(e) {
				if(e.keyCode === 27) {
					mfp.close();
				}
			});
		}

		_window.on('resize' + EVENT_NS, function() {
			mfp.updateSize();
		});


		if(!mfp.st.closeOnContentClick) {
			_wrapClasses += ' mfp-auto-cursor';
		}
		
		if(_wrapClasses)
			mfp.wrap.addClass(_wrapClasses);


		// this triggers recalculation of layout, so we get it once to not to trigger twice
		var windowHeight = mfp.wH = _window.height();

		
		var windowStyles = {};

		if( mfp.fixedContentPos ) {
            if(mfp._hasScrollBar(windowHeight)){
                var s = mfp._getScrollbarSize();
                if(s) {
                    windowStyles.marginRight = s;
                }
            }
        }

		if(mfp.fixedContentPos) {
			if(!mfp.isIE7) {
				windowStyles.overflow = 'hidden';
			} else {
				// ie7 double-scroll bug
				$('body, html').css('overflow', 'hidden');
			}
		}

		
		
		var classesToadd = mfp.st.mainClass;
		if(mfp.isIE7) {
			classesToadd += ' mfp-ie7';
		}
		if(classesToadd) {
			mfp._addClassToMFP( classesToadd );
		}

		// add content
		mfp.updateItemHTML();

		_mfpTrigger('BuildControls');

		// remove scrollbar, add margin e.t.c
		$('html').css(windowStyles);
		
		// add everything to DOM
		mfp.bgOverlay.add(mfp.wrap).prependTo( mfp.st.prependTo || $(document.body) );

		// Save last focused element
		mfp._lastFocusedEl = document.activeElement;
		
		// Wait for next cycle to allow CSS transition
		setTimeout(function() {
			
			if(mfp.content) {
				mfp._addClassToMFP(READY_CLASS);
				mfp._setFocus();
			} else {
				// if content is not defined (not loaded e.t.c) we add class only for BG
				mfp.bgOverlay.addClass(READY_CLASS);
			}
			
			// Trap the focus in popup
			_document.on('focusin' + EVENT_NS, mfp._onFocusIn);

		}, 16);

		mfp.isOpen = true;
		mfp.updateSize(windowHeight);
		_mfpTrigger(OPEN_EVENT);

		return data;
	},

	/**
	 * Closes the popup
	 */
	close: function() {
		if(!mfp.isOpen) return;
		_mfpTrigger(BEFORE_CLOSE_EVENT);

		mfp.isOpen = false;
		// for CSS3 animation
		if(mfp.st.removalDelay && !mfp.isLowIE && mfp.supportsTransition )  {
			mfp._addClassToMFP(REMOVING_CLASS);
			setTimeout(function() {
				mfp._close();
			}, mfp.st.removalDelay);
		} else {
			mfp._close();
		}
	},

	/**
	 * Helper for close() function
	 */
	_close: function() {
		_mfpTrigger(CLOSE_EVENT);

		var classesToRemove = REMOVING_CLASS + ' ' + READY_CLASS + ' ';

		mfp.bgOverlay.detach();
		mfp.wrap.detach();
		mfp.container.empty();

		if(mfp.st.mainClass) {
			classesToRemove += mfp.st.mainClass + ' ';
		}

		mfp._removeClassFromMFP(classesToRemove);

		if(mfp.fixedContentPos) {
			var windowStyles = {marginRight: ''};
			if(mfp.isIE7) {
				$('body, html').css('overflow', '');
			} else {
				windowStyles.overflow = '';
			}
			$('html').css(windowStyles);
		}
		
		_document.off('keyup' + EVENT_NS + ' focusin' + EVENT_NS);
		mfp.ev.off(EVENT_NS);

		// clean up DOM elements that aren't removed
		mfp.wrap.attr('class', 'mfp-wrap').removeAttr('style');
		mfp.bgOverlay.attr('class', 'mfp-bg');
		mfp.container.attr('class', 'mfp-container');

		// remove close button from target element
		if(mfp.st.showCloseBtn &&
		(!mfp.st.closeBtnInside || mfp.currTemplate[mfp.currItem.type] === true)) {
			if(mfp.currTemplate.closeBtn)
				mfp.currTemplate.closeBtn.detach();
		}


		if(mfp._lastFocusedEl) {
			$(mfp._lastFocusedEl).focus(); // put tab focus back
		}
		mfp.currItem = null;	
		mfp.content = null;
		mfp.currTemplate = null;
		mfp.prevHeight = 0;

		_mfpTrigger(AFTER_CLOSE_EVENT);
	},
	
	updateSize: function(winHeight) {

		if(mfp.isIOS) {
			// fixes iOS nav bars https://github.com/dimsemenov/Magnific-Popup/issues/2
			var zoomLevel = document.documentElement.clientWidth / window.innerWidth;
			var height = window.innerHeight * zoomLevel;
			mfp.wrap.css('height', height);
			mfp.wH = height;
		} else {
			mfp.wH = winHeight || _window.height();
		}
		// Fixes #84: popup incorrectly positioned with position:relative on body
		if(!mfp.fixedContentPos) {
			mfp.wrap.css('height', mfp.wH);
		}

		_mfpTrigger('Resize');

	},

	/**
	 * Set content of popup based on current index
	 */
	updateItemHTML: function() {
		var item = mfp.items[mfp.index];

		// Detach and perform modifications
		mfp.contentContainer.detach();

		if(mfp.content)
			mfp.content.detach();

		if(!item.parsed) {
			item = mfp.parseEl( mfp.index );
		}

		var type = item.type;	

		_mfpTrigger('BeforeChange', [mfp.currItem ? mfp.currItem.type : '', type]);
		// BeforeChange event works like so:
		// _mfpOn('BeforeChange', function(e, prevType, newType) { });
		
		mfp.currItem = item;

		

		

		if(!mfp.currTemplate[type]) {
			var markup = mfp.st[type] ? mfp.st[type].markup : false;

			// allows to modify markup
			_mfpTrigger('FirstMarkupParse', markup);

			if(markup) {
				mfp.currTemplate[type] = $(markup);
			} else {
				// if there is no markup found we just define that template is parsed
				mfp.currTemplate[type] = true;
			}
		}

		if(_prevContentType && _prevContentType !== item.type) {
			mfp.container.removeClass('mfp-'+_prevContentType+'-holder');
		}
		
		var newContent = mfp['get' + type.charAt(0).toUpperCase() + type.slice(1)](item, mfp.currTemplate[type]);
		mfp.appendContent(newContent, type);

		item.preloaded = true;

		_mfpTrigger(CHANGE_EVENT, item);
		_prevContentType = item.type;
		
		// Append container back after its content changed
		mfp.container.prepend(mfp.contentContainer);

		_mfpTrigger('AfterChange');
	},


	/**
	 * Set HTML content of popup
	 */
	appendContent: function(newContent, type) {
		mfp.content = newContent;
		
		if(newContent) {
			if(mfp.st.showCloseBtn && mfp.st.closeBtnInside &&
				mfp.currTemplate[type] === true) {
				// if there is no markup, we just append close button element inside
				if(!mfp.content.find('.mfp-close').length) {
					mfp.content.append(_getCloseBtn());
				}
			} else {
				mfp.content = newContent;
			}
		} else {
			mfp.content = '';
		}

		_mfpTrigger(BEFORE_APPEND_EVENT);
		mfp.container.addClass('mfp-'+type+'-holder');

		mfp.contentContainer.append(mfp.content);
	},



	
	/**
	 * Creates Magnific Popup data object based on given data
	 * @param  {int} index Index of item to parse
	 */
	parseEl: function(index) {
		var item = mfp.items[index],
			type;

		if(item.tagName) {
			item = { el: $(item) };
		} else {
			type = item.type;
			item = { data: item, src: item.src };
		}

		if(item.el) {
			var types = mfp.types;

			// check for 'mfp-TYPE' class
			for(var i = 0; i < types.length; i++) {
				if( item.el.hasClass('mfp-'+types[i]) ) {
					type = types[i];
					break;
				}
			}

			item.src = item.el.attr('data-mfp-src');
			if(!item.src) {
				item.src = item.el.attr('href');
			}
		}

		item.type = type || mfp.st.type || 'inline';
		item.index = index;
		item.parsed = true;
		mfp.items[index] = item;
		_mfpTrigger('ElementParse', item);

		return mfp.items[index];
	},


	/**
	 * Initializes single popup or a group of popups
	 */
	addGroup: function(el, options) {
		var eHandler = function(e) {
			e.mfpEl = this;
			mfp._openClick(e, el, options);
		};

		if(!options) {
			options = {};
		} 

		var eName = 'click.magnificPopup';
		options.mainEl = el;
		
		if(options.items) {
			options.isObj = true;
			el.off(eName).on(eName, eHandler);
		} else {
			options.isObj = false;
			if(options.delegate) {
				el.off(eName).on(eName, options.delegate , eHandler);
			} else {
				options.items = el;
				el.off(eName).on(eName, eHandler);
			}
		}
	},
	_openClick: function(e, el, options) {
		var midClick = options.midClick !== undefined ? options.midClick : $.magnificPopup.defaults.midClick;


		if(!midClick && ( e.which === 2 || e.ctrlKey || e.metaKey ) ) {
			return;
		}

		var disableOn = options.disableOn !== undefined ? options.disableOn : $.magnificPopup.defaults.disableOn;

		if(disableOn) {
			if($.isFunction(disableOn)) {
				if( !disableOn.call(mfp) ) {
					return true;
				}
			} else { // else it's number
				if( _window.width() < disableOn ) {
					return true;
				}
			}
		}
		
		if(e.type) {
			e.preventDefault();

			// This will prevent popup from closing if element is inside and popup is already opened
			if(mfp.isOpen) {
				e.stopPropagation();
			}
		}
			

		options.el = $(e.mfpEl);
		if(options.delegate) {
			options.items = el.find(options.delegate);
		}
		mfp.open(options);
	},


	/**
	 * Updates text on preloader
	 */
	updateStatus: function(status, text) {

		if(mfp.preloader) {
			if(_prevStatus !== status) {
				mfp.container.removeClass('mfp-s-'+_prevStatus);
			}

			if(!text && status === 'loading') {
				text = mfp.st.tLoading;
			}

			var data = {
				status: status,
				text: text
			};
			// allows to modify status
			_mfpTrigger('UpdateStatus', data);

			status = data.status;
			text = data.text;

			mfp.preloader.html(text);

			mfp.preloader.find('a').on('click', function(e) {
				e.stopImmediatePropagation();
			});

			mfp.container.addClass('mfp-s-'+status);
			_prevStatus = status;
		}
	},


	/*
		"Private" helpers that aren't private at all
	 */
	// Check to close popup or not
	// "target" is an element that was clicked
	_checkIfClose: function(target) {

		if($(target).hasClass(PREVENT_CLOSE_CLASS)) {
			return;
		}

		var closeOnContent = mfp.st.closeOnContentClick;
		var closeOnBg = mfp.st.closeOnBgClick;

		if(closeOnContent && closeOnBg) {
			return true;
		} else {

			// We close the popup if click is on close button or on preloader. Or if there is no content.
			if(!mfp.content || $(target).hasClass('mfp-close') || (mfp.preloader && target === mfp.preloader[0]) ) {
				return true;
			}

			// if click is outside the content
			if(  (target !== mfp.content[0] && !$.contains(mfp.content[0], target))  ) {
				if(closeOnBg) {
					// last check, if the clicked element is in DOM, (in case it's removed onclick)
					if( $.contains(document, target) ) {
						return true;
					}
				}
			} else if(closeOnContent) {
				return true;
			}

		}
		return false;
	},
	_addClassToMFP: function(cName) {
		mfp.bgOverlay.addClass(cName);
		mfp.wrap.addClass(cName);
	},
	_removeClassFromMFP: function(cName) {
		this.bgOverlay.removeClass(cName);
		mfp.wrap.removeClass(cName);
	},
	_hasScrollBar: function(winHeight) {
		return (  (mfp.isIE7 ? _document.height() : document.body.scrollHeight) > (winHeight || _window.height()) );
	},
	_setFocus: function() {
		(mfp.st.focus ? mfp.content.find(mfp.st.focus).eq(0) : mfp.wrap).focus();
	},
	_onFocusIn: function(e) {
		if( e.target !== mfp.wrap[0] && !$.contains(mfp.wrap[0], e.target) ) {
			mfp._setFocus();
			return false;
		}
	},
	_parseMarkup: function(template, values, item) {
		var arr;
		if(item.data) {
			values = $.extend(item.data, values);
		}
		_mfpTrigger(MARKUP_PARSE_EVENT, [template, values, item] );

		$.each(values, function(key, value) {
			if(value === undefined || value === false) {
				return true;
			}
			arr = key.split('_');
			if(arr.length > 1) {
				var el = template.find(EVENT_NS + '-'+arr[0]);

				if(el.length > 0) {
					var attr = arr[1];
					if(attr === 'replaceWith') {
						if(el[0] !== value[0]) {
							el.replaceWith(value);
						}
					} else if(attr === 'img') {
						if(el.is('img')) {
							el.attr('src', value);
						} else {
							el.replaceWith( '<img src="'+value+'" class="' + el.attr('class') + '" />' );
						}
					} else {
						el.attr(arr[1], value);
					}
				}

			} else {
				template.find(EVENT_NS + '-'+key).html(value);
			}
		});
	},

	_getScrollbarSize: function() {
		// thx David
		if(mfp.scrollbarSize === undefined) {
			var scrollDiv = document.createElement("div");
			scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
			document.body.appendChild(scrollDiv);
			mfp.scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth;
			document.body.removeChild(scrollDiv);
		}
		return mfp.scrollbarSize;
	}

}; /* MagnificPopup core prototype end */




/**
 * Public static functions
 */
$.magnificPopup = {
	instance: null,
	proto: MagnificPopup.prototype,
	modules: [],

	open: function(options, index) {
		_checkInstance();	

		if(!options) {
			options = {};
		} else {
			options = $.extend(true, {}, options);
		}
			

		options.isObj = true;
		options.index = index || 0;
		return this.instance.open(options);
	},

	close: function() {
		return $.magnificPopup.instance && $.magnificPopup.instance.close();
	},

	registerModule: function(name, module) {
		if(module.options) {
			$.magnificPopup.defaults[name] = module.options;
		}
		$.extend(this.proto, module.proto);			
		this.modules.push(name);
	},

	defaults: {   

		// Info about options is in docs:
		// http://dimsemenov.com/plugins/magnific-popup/documentation.html#options
		
		disableOn: 0,	

		key: null,

		midClick: false,

		mainClass: '',

		preloader: true,

		focus: '', // CSS selector of input to focus after popup is opened
		
		closeOnContentClick: false,

		closeOnBgClick: true,

		closeBtnInside: true, 

		showCloseBtn: true,

		enableEscapeKey: true,

		modal: false,

		alignTop: false,
	
		removalDelay: 0,

		prependTo: null,
		
		fixedContentPos: 'auto', 
	
		fixedBgPos: 'auto',

		overflowY: 'auto',

		closeMarkup: '<button title="%title%" type="button" class="mfp-close">&times;</button>',

		tClose: 'Close (Esc)',

		tLoading: 'Loading...'

	}
};



$.fn.magnificPopup = function(options) {
	_checkInstance();

	var jqEl = $(this);

	// We call some API method of first param is a string
	if (typeof options === "string" ) {

		if(options === 'open') {
			var items,
				itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup,
				index = parseInt(arguments[1], 10) || 0;

			if(itemOpts.items) {
				items = itemOpts.items[index];
			} else {
				items = jqEl;
				if(itemOpts.delegate) {
					items = items.find(itemOpts.delegate);
				}
				items = items.eq( index );
			}
			mfp._openClick({mfpEl:items}, jqEl, itemOpts);
		} else {
			if(mfp.isOpen)
				mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1));
		}

	} else {
		// clone options obj
		options = $.extend(true, {}, options);
		
		/*
		 * As Zepto doesn't support .data() method for objects 
		 * and it works only in normal browsers
		 * we assign "options" object directly to the DOM element. FTW!
		 */
		if(_isJQ) {
			jqEl.data('magnificPopup', options);
		} else {
			jqEl[0].magnificPopup = options;
		}

		mfp.addGroup(jqEl, options);

	}
	return jqEl;
};


//Quick benchmark
/*
var start = performance.now(),
	i,
	rounds = 1000;

for(i = 0; i < rounds; i++) {

}
console.log('Test #1:', performance.now() - start);

start = performance.now();
for(i = 0; i < rounds; i++) {

}
console.log('Test #2:', performance.now() - start);
*/


/*>>core*/

/*>>inline*/

var INLINE_NS = 'inline',
	_hiddenClass,
	_inlinePlaceholder, 
	_lastInlineElement,
	_putInlineElementsBack = function() {
		if(_lastInlineElement) {
			_inlinePlaceholder.after( _lastInlineElement.addClass(_hiddenClass) ).detach();
			_lastInlineElement = null;
		}
	};

$.magnificPopup.registerModule(INLINE_NS, {
	options: {
		hiddenClass: 'hide', // will be appended with `mfp-` prefix
		markup: '',
		tNotFound: 'Content not found'
	},
	proto: {

		initInline: function() {
			mfp.types.push(INLINE_NS);

			_mfpOn(CLOSE_EVENT+'.'+INLINE_NS, function() {
				_putInlineElementsBack();
			});
		},

		getInline: function(item, template) {

			_putInlineElementsBack();

			if(item.src) {
				var inlineSt = mfp.st.inline,
					el = $(item.src);

				if(el.length) {

					// If target element has parent - we replace it with placeholder and put it back after popup is closed
					var parent = el[0].parentNode;
					if(parent && parent.tagName) {
						if(!_inlinePlaceholder) {
							_hiddenClass = inlineSt.hiddenClass;
							_inlinePlaceholder = _getEl(_hiddenClass);
							_hiddenClass = 'mfp-'+_hiddenClass;
						}
						// replace target inline element with placeholder
						_lastInlineElement = el.after(_inlinePlaceholder).detach().removeClass(_hiddenClass);
					}

					mfp.updateStatus('ready');
				} else {
					mfp.updateStatus('error', inlineSt.tNotFound);
					el = $('<div>');
				}

				item.inlineElement = el;
				return el;
			}

			mfp.updateStatus('ready');
			mfp._parseMarkup(template, {}, item);
			return template;
		}
	}
});

/*>>inline*/

/*>>ajax*/
var AJAX_NS = 'ajax',
	_ajaxCur,
	_removeAjaxCursor = function() {
		if(_ajaxCur) {
			$(document.body).removeClass(_ajaxCur);
		}
	},
	_destroyAjaxRequest = function() {
		_removeAjaxCursor();
		if(mfp.req) {
			mfp.req.abort();
		}
	};

$.magnificPopup.registerModule(AJAX_NS, {

	options: {
		settings: null,
		cursor: 'mfp-ajax-cur',
		tError: '<a href="%url%">The content</a> could not be loaded.'
	},

	proto: {
		initAjax: function() {
			mfp.types.push(AJAX_NS);
			_ajaxCur = mfp.st.ajax.cursor;

			_mfpOn(CLOSE_EVENT+'.'+AJAX_NS, _destroyAjaxRequest);
			_mfpOn('BeforeChange.' + AJAX_NS, _destroyAjaxRequest);
		},
		getAjax: function(item) {

			if(_ajaxCur) {
				$(document.body).addClass(_ajaxCur);
			}

			mfp.updateStatus('loading');

			var opts = $.extend({
				url: item.src,
				success: function(data, textStatus, jqXHR) {
					var temp = {
						data:data,
						xhr:jqXHR
					};

					_mfpTrigger('ParseAjax', temp);

					mfp.appendContent( $(temp.data), AJAX_NS );

					item.finished = true;

					_removeAjaxCursor();

					mfp._setFocus();

					setTimeout(function() {
						mfp.wrap.addClass(READY_CLASS);
					}, 16);

					mfp.updateStatus('ready');

					_mfpTrigger('AjaxContentAdded');
				},
				error: function() {
					_removeAjaxCursor();
					item.finished = item.loadError = true;
					mfp.updateStatus('error', mfp.st.ajax.tError.replace('%url%', item.src));
				}
			}, mfp.st.ajax.settings);

			mfp.req = $.ajax(opts);

			return '';
		}
	}
});





	

/*>>ajax*/

/*>>image*/
var _imgInterval,
	_getTitle = function(item) {
		if(item.data && item.data.title !== undefined) 
			return item.data.title;

		var src = mfp.st.image.titleSrc;

		if(src) {
			if($.isFunction(src)) {
				return src.call(mfp, item);
			} else if(item.el) {
				return item.el.attr(src) || '';
			}
		}
		return '';
	};

$.magnificPopup.registerModule('image', {

	options: {
		markup: '<div class="mfp-figure">'+
					'<div class="mfp-close"></div>'+
					'<figure>'+
						'<div class="mfp-img"></div>'+
						'<figcaption>'+
							'<div class="mfp-bottom-bar">'+
								'<div class="mfp-title"></div>'+
								'<div class="mfp-counter"></div>'+
							'</div>'+
						'</figcaption>'+
					'</figure>'+
				'</div>',
		cursor: 'mfp-zoom-out-cur',
		titleSrc: 'title', 
		verticalFit: true,
		tError: '<a href="%url%">The image</a> could not be loaded.'
	},

	proto: {
		initImage: function() {
			var imgSt = mfp.st.image,
				ns = '.image';

			mfp.types.push('image');

			_mfpOn(OPEN_EVENT+ns, function() {
				if(mfp.currItem.type === 'image' && imgSt.cursor) {
					$(document.body).addClass(imgSt.cursor);
				}
			});

			_mfpOn(CLOSE_EVENT+ns, function() {
				if(imgSt.cursor) {
					$(document.body).removeClass(imgSt.cursor);
				}
				_window.off('resize' + EVENT_NS);
			});

			_mfpOn('Resize'+ns, mfp.resizeImage);
			if(mfp.isLowIE) {
				_mfpOn('AfterChange', mfp.resizeImage);
			}
		},
		resizeImage: function() {
			var item = mfp.currItem;
			if(!item || !item.img) return;

			if(mfp.st.image.verticalFit) {
				var decr = 0;
				// fix box-sizing in ie7/8
				if(mfp.isLowIE) {
					decr = parseInt(item.img.css('padding-top'), 10) + parseInt(item.img.css('padding-bottom'),10);
				}
				item.img.css('max-height', mfp.wH-decr);
			}
		},
		_onImageHasSize: function(item) {
			if(item.img) {
				
				item.hasSize = true;

				if(_imgInterval) {
					clearInterval(_imgInterval);
				}
				
				item.isCheckingImgSize = false;

				_mfpTrigger('ImageHasSize', item);

				if(item.imgHidden) {
					if(mfp.content)
						mfp.content.removeClass('mfp-loading');
					
					item.imgHidden = false;
				}

			}
		},

		/**
		 * Function that loops until the image has size to display elements that rely on it asap
		 */
		findImageSize: function(item) {

			var counter = 0,
				img = item.img[0],
				mfpSetInterval = function(delay) {

					if(_imgInterval) {
						clearInterval(_imgInterval);
					}
					// decelerating interval that checks for size of an image
					_imgInterval = setInterval(function() {
						if(img.naturalWidth > 0) {
							mfp._onImageHasSize(item);
							return;
						}

						if(counter > 200) {
							clearInterval(_imgInterval);
						}

						counter++;
						if(counter === 3) {
							mfpSetInterval(10);
						} else if(counter === 40) {
							mfpSetInterval(50);
						} else if(counter === 100) {
							mfpSetInterval(500);
						}
					}, delay);
				};

			mfpSetInterval(1);
		},

		getImage: function(item, template) {

			var guard = 0,

				// image load complete handler
				onLoadComplete = function() {
					if(item) {
						if (item.img[0].complete) {
							item.img.off('.mfploader');
							
							if(item === mfp.currItem){
								mfp._onImageHasSize(item);

								mfp.updateStatus('ready');
							}

							item.hasSize = true;
							item.loaded = true;

							_mfpTrigger('ImageLoadComplete');
							
						}
						else {
							// if image complete check fails 200 times (20 sec), we assume that there was an error.
							guard++;
							if(guard < 200) {
								setTimeout(onLoadComplete,100);
							} else {
								onLoadError();
							}
						}
					}
				},

				// image error handler
				onLoadError = function() {
					if(item) {
						item.img.off('.mfploader');
						if(item === mfp.currItem){
							mfp._onImageHasSize(item);
							mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) );
						}

						item.hasSize = true;
						item.loaded = true;
						item.loadError = true;
					}
				},
				imgSt = mfp.st.image;


			var el = template.find('.mfp-img');
			if(el.length) {
				var img = document.createElement('img');
				img.className = 'mfp-img';
				if(item.el && item.el.find('img').length) {
					img.alt = item.el.find('img').attr('alt');
				}
				item.img = $(img).on('load.mfploader', onLoadComplete).on('error.mfploader', onLoadError);
				img.src = item.src;

				// without clone() "error" event is not firing when IMG is replaced by new IMG
				// TODO: find a way to avoid such cloning
				if(el.is('img')) {
					item.img = item.img.clone();
				}

				img = item.img[0];
				if(img.naturalWidth > 0) {
					item.hasSize = true;
				} else if(!img.width) {										
					item.hasSize = false;
				}
			}

			mfp._parseMarkup(template, {
				title: _getTitle(item),
				img_replaceWith: item.img
			}, item);

			mfp.resizeImage();

			if(item.hasSize) {
				if(_imgInterval) clearInterval(_imgInterval);

				if(item.loadError) {
					template.addClass('mfp-loading');
					mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) );
				} else {
					template.removeClass('mfp-loading');
					mfp.updateStatus('ready');
				}
				return template;
			}

			mfp.updateStatus('loading');
			item.loading = true;

			if(!item.hasSize) {
				item.imgHidden = true;
				template.addClass('mfp-loading');
				mfp.findImageSize(item);
			} 

			return template;
		}
	}
});



/*>>image*/

/*>>zoom*/
var hasMozTransform,
	getHasMozTransform = function() {
		if(hasMozTransform === undefined) {
			hasMozTransform = document.createElement('p').style.MozTransform !== undefined;
		}
		return hasMozTransform;		
	};

$.magnificPopup.registerModule('zoom', {

	options: {
		enabled: false,
		easing: 'ease-in-out',
		duration: 300,
		opener: function(element) {
			return element.is('img') ? element : element.find('img');
		}
	},

	proto: {

		initZoom: function() {
			var zoomSt = mfp.st.zoom,
				ns = '.zoom',
				image;
				
			if(!zoomSt.enabled || !mfp.supportsTransition) {
				return;
			}

			var duration = zoomSt.duration,
				getElToAnimate = function(image) {
					var newImg = image.clone().removeAttr('style').removeAttr('class').addClass('mfp-animated-image'),
						transition = 'all '+(zoomSt.duration/1000)+'s ' + zoomSt.easing,
						cssObj = {
							position: 'fixed',
							zIndex: 9999,
							left: 0,
							top: 0,
							'-webkit-backface-visibility': 'hidden'
						},
						t = 'transition';

					cssObj['-webkit-'+t] = cssObj['-moz-'+t] = cssObj['-o-'+t] = cssObj[t] = transition;

					newImg.css(cssObj);
					return newImg;
				},
				showMainContent = function() {
					mfp.content.css('visibility', 'visible');
				},
				openTimeout,
				animatedImg;

			_mfpOn('BuildControls'+ns, function() {
				if(mfp._allowZoom()) {

					clearTimeout(openTimeout);
					mfp.content.css('visibility', 'hidden');

					// Basically, all code below does is clones existing image, puts in on top of the current one and animated it
					
					image = mfp._getItemToZoom();

					if(!image) {
						showMainContent();
						return;
					}

					animatedImg = getElToAnimate(image); 
					
					animatedImg.css( mfp._getOffset() );

					mfp.wrap.append(animatedImg);

					openTimeout = setTimeout(function() {
						animatedImg.css( mfp._getOffset( true ) );
						openTimeout = setTimeout(function() {

							showMainContent();

							setTimeout(function() {
								animatedImg.remove();
								image = animatedImg = null;
								_mfpTrigger('ZoomAnimationEnded');
							}, 16); // avoid blink when switching images 

						}, duration); // this timeout equals animation duration

					}, 16); // by adding this timeout we avoid short glitch at the beginning of animation


					// Lots of timeouts...
				}
			});
			_mfpOn(BEFORE_CLOSE_EVENT+ns, function() {
				if(mfp._allowZoom()) {

					clearTimeout(openTimeout);

					mfp.st.removalDelay = duration;

					if(!image) {
						image = mfp._getItemToZoom();
						if(!image) {
							return;
						}
						animatedImg = getElToAnimate(image);
					}
					
					
					animatedImg.css( mfp._getOffset(true) );
					mfp.wrap.append(animatedImg);
					mfp.content.css('visibility', 'hidden');
					
					setTimeout(function() {
						animatedImg.css( mfp._getOffset() );
					}, 16);
				}

			});

			_mfpOn(CLOSE_EVENT+ns, function() {
				if(mfp._allowZoom()) {
					showMainContent();
					if(animatedImg) {
						animatedImg.remove();
					}
					image = null;
				}	
			});
		},

		_allowZoom: function() {
			return mfp.currItem.type === 'image';
		},

		_getItemToZoom: function() {
			if(mfp.currItem.hasSize) {
				return mfp.currItem.img;
			} else {
				return false;
			}
		},

		// Get element postion relative to viewport
		_getOffset: function(isLarge) {
			var el;
			if(isLarge) {
				el = mfp.currItem.img;
			} else {
				el = mfp.st.zoom.opener(mfp.currItem.el || mfp.currItem);
			}

			var offset = el.offset();
			var paddingTop = parseInt(el.css('padding-top'),10);
			var paddingBottom = parseInt(el.css('padding-bottom'),10);
			offset.top -= ( $(window).scrollTop() - paddingTop );


			/*
			
			Animating left + top + width/height looks glitchy in Firefox, but perfect in Chrome. And vice-versa.

			 */
			var obj = {
				width: el.width(),
				// fix Zepto height+padding issue
				height: (_isJQ ? el.innerHeight() : el[0].offsetHeight) - paddingBottom - paddingTop
			};

			// I hate to do this, but there is no another option
			if( getHasMozTransform() ) {
				obj['-moz-transform'] = obj['transform'] = 'translate(' + offset.left + 'px,' + offset.top + 'px)';
			} else {
				obj.left = offset.left;
				obj.top = offset.top;
			}
			return obj;
		}

	}
});



/*>>zoom*/

/*>>iframe*/

var IFRAME_NS = 'iframe',
	_emptyPage = '//about:blank',
	
	_fixIframeBugs = function(isShowing) {
		if(mfp.currTemplate[IFRAME_NS]) {
			var el = mfp.currTemplate[IFRAME_NS].find('iframe');
			if(el.length) { 
				// reset src after the popup is closed to avoid "video keeps playing after popup is closed" bug
				if(!isShowing) {
					el[0].src = _emptyPage;
				}

				// IE8 black screen bug fix
				if(mfp.isIE8) {
					el.css('display', isShowing ? 'block' : 'none');
				}
			}
		}
	};

$.magnificPopup.registerModule(IFRAME_NS, {

	options: {
		markup: '<div class="mfp-iframe-scaler">'+
					'<div class="mfp-close"></div>'+
					'<iframe class="mfp-iframe" src="//about:blank" frameborder="0" allowfullscreen></iframe>'+
				'</div>',

		srcAction: 'iframe_src',

		// we don't care and support only one default type of URL by default
		patterns: {
			youtube: {
				index: 'youtube.com', 
				id: 'v=', 
				src: '//www.youtube.com/embed/%id%?autoplay=1'
			},
			vimeo: {
				index: 'vimeo.com/',
				id: '/',
				src: '//player.vimeo.com/video/%id%?autoplay=1'
			},
			gmaps: {
				index: '//maps.google.',
				src: '%id%&output=embed'
			}
		}
	},

	proto: {
		initIframe: function() {
			mfp.types.push(IFRAME_NS);

			_mfpOn('BeforeChange', function(e, prevType, newType) {
				if(prevType !== newType) {
					if(prevType === IFRAME_NS) {
						_fixIframeBugs(); // iframe if removed
					} else if(newType === IFRAME_NS) {
						_fixIframeBugs(true); // iframe is showing
					} 
				}// else {
					// iframe source is switched, don't do anything
				//}
			});

			_mfpOn(CLOSE_EVENT + '.' + IFRAME_NS, function() {
				_fixIframeBugs();
			});
		},

		getIframe: function(item, template) {
			var embedSrc = item.src;
			var iframeSt = mfp.st.iframe;
				
			$.each(iframeSt.patterns, function() {
				if(embedSrc.indexOf( this.index ) > -1) {
					if(this.id) {
						if(typeof this.id === 'string') {
							embedSrc = embedSrc.substr(embedSrc.lastIndexOf(this.id)+this.id.length, embedSrc.length);
						} else {
							embedSrc = this.id.call( this, embedSrc );
						}
					}
					embedSrc = this.src.replace('%id%', embedSrc );
					return false; // break;
				}
			});
			
			var dataObj = {};
			if(iframeSt.srcAction) {
				dataObj[iframeSt.srcAction] = embedSrc;
			}
			mfp._parseMarkup(template, dataObj, item);

			mfp.updateStatus('ready');

			return template;
		}
	}
});



/*>>iframe*/

/*>>gallery*/
/**
 * Get looped index depending on number of slides
 */
var _getLoopedId = function(index) {
		var numSlides = mfp.items.length;
		if(index > numSlides - 1) {
			return index - numSlides;
		} else  if(index < 0) {
			return numSlides + index;
		}
		return index;
	},
	_replaceCurrTotal = function(text, curr, total) {
		return text.replace(/%curr%/gi, curr + 1).replace(/%total%/gi, total);
	};

$.magnificPopup.registerModule('gallery', {

	options: {
		enabled: false,
		arrowMarkup: '<button title="%title%" type="button" class="mfp-arrow mfp-arrow-%dir%"></button>',
		preload: [0,2],
		navigateByImgClick: true,
		arrows: true,

		tPrev: 'Previous (Left arrow key)',
		tNext: 'Next (Right arrow key)',
		tCounter: '%curr% of %total%'
	},

	proto: {
		initGallery: function() {

			var gSt = mfp.st.gallery,
				ns = '.mfp-gallery',
				supportsFastClick = Boolean($.fn.mfpFastClick);

			mfp.direction = true; // true - next, false - prev
			
			if(!gSt || !gSt.enabled ) return false;

			_wrapClasses += ' mfp-gallery';

			_mfpOn(OPEN_EVENT+ns, function() {

				if(gSt.navigateByImgClick) {
					mfp.wrap.on('click'+ns, '.mfp-img', function() {
						if(mfp.items.length > 1) {
							mfp.next();
							return false;
						}
					});
				}

				_document.on('keydown'+ns, function(e) {
					if (e.keyCode === 37) {
						mfp.prev();
					} else if (e.keyCode === 39) {
						mfp.next();
					}
				});
			});

			_mfpOn('UpdateStatus'+ns, function(e, data) {
				if(data.text) {
					data.text = _replaceCurrTotal(data.text, mfp.currItem.index, mfp.items.length);
				}
			});

			_mfpOn(MARKUP_PARSE_EVENT+ns, function(e, element, values, item) {
				var l = mfp.items.length;
				values.counter = l > 1 ? _replaceCurrTotal(gSt.tCounter, item.index, l) : '';
			});

			_mfpOn('BuildControls' + ns, function() {
				if(mfp.items.length > 1 && gSt.arrows && !mfp.arrowLeft) {
					var markup = gSt.arrowMarkup,
						arrowLeft = mfp.arrowLeft = $( markup.replace(/%title%/gi, gSt.tPrev).replace(/%dir%/gi, 'left') ).addClass(PREVENT_CLOSE_CLASS),			
						arrowRight = mfp.arrowRight = $( markup.replace(/%title%/gi, gSt.tNext).replace(/%dir%/gi, 'right') ).addClass(PREVENT_CLOSE_CLASS);

					var eName = supportsFastClick ? 'mfpFastClick' : 'click';
					arrowLeft[eName](function() {
						mfp.prev();
					});			
					arrowRight[eName](function() {
						mfp.next();
					});	

					// Polyfill for :before and :after (adds elements with classes mfp-a and mfp-b)
					if(mfp.isIE7) {
						_getEl('b', arrowLeft[0], false, true);
						_getEl('a', arrowLeft[0], false, true);
						_getEl('b', arrowRight[0], false, true);
						_getEl('a', arrowRight[0], false, true);
					}

					mfp.container.append(arrowLeft.add(arrowRight));
				}
			});

			_mfpOn(CHANGE_EVENT+ns, function() {
				if(mfp._preloadTimeout) clearTimeout(mfp._preloadTimeout);

				mfp._preloadTimeout = setTimeout(function() {
					mfp.preloadNearbyImages();
					mfp._preloadTimeout = null;
				}, 16);		
			});


			_mfpOn(CLOSE_EVENT+ns, function() {
				_document.off(ns);
				mfp.wrap.off('click'+ns);
			
				if(mfp.arrowLeft && supportsFastClick) {
					mfp.arrowLeft.add(mfp.arrowRight).destroyMfpFastClick();
				}
				mfp.arrowRight = mfp.arrowLeft = null;
			});

		}, 
		next: function() {
			mfp.direction = true;
			mfp.index = _getLoopedId(mfp.index + 1);
			mfp.updateItemHTML();
		},
		prev: function() {
			mfp.direction = false;
			mfp.index = _getLoopedId(mfp.index - 1);
			mfp.updateItemHTML();
		},
		goTo: function(newIndex) {
			mfp.direction = (newIndex >= mfp.index);
			mfp.index = newIndex;
			mfp.updateItemHTML();
		},
		preloadNearbyImages: function() {
			var p = mfp.st.gallery.preload,
				preloadBefore = Math.min(p[0], mfp.items.length),
				preloadAfter = Math.min(p[1], mfp.items.length),
				i;

			for(i = 1; i <= (mfp.direction ? preloadAfter : preloadBefore); i++) {
				mfp._preloadItem(mfp.index+i);
			}
			for(i = 1; i <= (mfp.direction ? preloadBefore : preloadAfter); i++) {
				mfp._preloadItem(mfp.index-i);
			}
		},
		_preloadItem: function(index) {
			index = _getLoopedId(index);

			if(mfp.items[index].preloaded) {
				return;
			}

			var item = mfp.items[index];
			if(!item.parsed) {
				item = mfp.parseEl( index );
			}

			_mfpTrigger('LazyLoad', item);

			if(item.type === 'image') {
				item.img = $('<img class="mfp-img" />').on('load.mfploader', function() {
					item.hasSize = true;
				}).on('error.mfploader', function() {
					item.hasSize = true;
					item.loadError = true;
					_mfpTrigger('LazyLoadError', item);
				}).attr('src', item.src);
			}


			item.preloaded = true;
		}
	}
});

/*
Touch Support that might be implemented some day

addSwipeGesture: function() {
	var startX,
		moved,
		multipleTouches;

		return;

	var namespace = '.mfp',
		addEventNames = function(pref, down, move, up, cancel) {
			mfp._tStart = pref + down + namespace;
			mfp._tMove = pref + move + namespace;
			mfp._tEnd = pref + up + namespace;
			mfp._tCancel = pref + cancel + namespace;
		};

	if(window.navigator.msPointerEnabled) {
		addEventNames('MSPointer', 'Down', 'Move', 'Up', 'Cancel');
	} else if('ontouchstart' in window) {
		addEventNames('touch', 'start', 'move', 'end', 'cancel');
	} else {
		return;
	}
	_window.on(mfp._tStart, function(e) {
		var oE = e.originalEvent;
		multipleTouches = moved = false;
		startX = oE.pageX || oE.changedTouches[0].pageX;
	}).on(mfp._tMove, function(e) {
		if(e.originalEvent.touches.length > 1) {
			multipleTouches = e.originalEvent.touches.length;
		} else {
			//e.preventDefault();
			moved = true;
		}
	}).on(mfp._tEnd + ' ' + mfp._tCancel, function(e) {
		if(moved && !multipleTouches) {
			var oE = e.originalEvent,
				diff = startX - (oE.pageX || oE.changedTouches[0].pageX);

			if(diff > 20) {
				mfp.next();
			} else if(diff < -20) {
				mfp.prev();
			}
		}
	});
},
*/


/*>>gallery*/

/*>>retina*/

var RETINA_NS = 'retina';

$.magnificPopup.registerModule(RETINA_NS, {
	options: {
		replaceSrc: function(item) {
			return item.src.replace(/\.\w+$/, function(m) { return '@2x' + m; });
		},
		ratio: 1 // Function or number.  Set to 1 to disable.
	},
	proto: {
		initRetina: function() {
			if(window.devicePixelRatio > 1) {

				var st = mfp.st.retina,
					ratio = st.ratio;

				ratio = !isNaN(ratio) ? ratio : ratio();

				if(ratio > 1) {
					_mfpOn('ImageHasSize' + '.' + RETINA_NS, function(e, item) {
						item.img.css({
							'max-width': item.img[0].naturalWidth / ratio,
							'width': '100%'
						});
					});
					_mfpOn('ElementParse' + '.' + RETINA_NS, function(e, item) {
						item.src = st.replaceSrc(item, ratio);
					});
				}
			}

		}
	}
});

/*>>retina*/

/*>>fastclick*/
/**
 * FastClick event implementation. (removes 300ms delay on touch devices)
 * Based on https://developers.google.com/mobile/articles/fast_buttons
 *
 * You may use it outside the Magnific Popup by calling just:
 *
 * $('.your-el').mfpFastClick(function() {
 *     console.log('Clicked!');
 * });
 *
 * To unbind:
 * $('.your-el').destroyMfpFastClick();
 * 
 * 
 * Note that it's a very basic and simple implementation, it blocks ghost click on the same element where it was bound.
 * If you need something more advanced, use plugin by FT Labs https://github.com/ftlabs/fastclick
 * 
 */

(function() {
	var ghostClickDelay = 1000,
		supportsTouch = 'ontouchstart' in window,
		unbindTouchMove = function() {
			_window.off('touchmove'+ns+' touchend'+ns);
		},
		eName = 'mfpFastClick',
		ns = '.'+eName;


	// As Zepto.js doesn't have an easy way to add custom events (like jQuery), so we implement it in this way
	$.fn.mfpFastClick = function(callback) {

		return $(this).each(function() {

			var elem = $(this),
				lock;

			if( supportsTouch ) {

				var timeout,
					startX,
					startY,
					pointerMoved,
					point,
					numPointers;

				elem.on('touchstart' + ns, function(e) {
					pointerMoved = false;
					numPointers = 1;

					point = e.originalEvent ? e.originalEvent.touches[0] : e.touches[0];
					startX = point.clientX;
					startY = point.clientY;

					_window.on('touchmove'+ns, function(e) {
						point = e.originalEvent ? e.originalEvent.touches : e.touches;
						numPointers = point.length;
						point = point[0];
						if (Math.abs(point.clientX - startX) > 10 ||
							Math.abs(point.clientY - startY) > 10) {
							pointerMoved = true;
							unbindTouchMove();
						}
					}).on('touchend'+ns, function(e) {
						unbindTouchMove();
						if(pointerMoved || numPointers > 1) {
							return;
						}
						lock = true;
						e.preventDefault();
						clearTimeout(timeout);
						timeout = setTimeout(function() {
							lock = false;
						}, ghostClickDelay);
						callback();
					});
				});

			}

			elem.on('click' + ns, function() {
				if(!lock) {
					callback();
				}
			});
		});
	};

	$.fn.destroyMfpFastClick = function() {
		$(this).off('touchstart' + ns + ' click' + ns);
		if(supportsTouch) _window.off('touchmove'+ns+' touchend'+ns);
	};
})();

/*>>fastclick*/
 _checkInstance(); }));;
// jQuery Mask Plugin v1.13.4
// github.com/igorescobar/jQuery-Mask-Plugin
(function(b){"function"===typeof define&&define.amd?define(["jquery"],b):"object"===typeof exports?module.exports=b(require("jquery")):b(jQuery||Zepto)})(function(b){var y=function(a,c,d){a=b(a);var g=this,k=a.val(),l;c="function"===typeof c?c(a.val(),void 0,a,d):c;var e={invalid:[],getCaret:function(){try{var q,b=0,e=a.get(0),f=document.selection,c=e.selectionStart;if(f&&-1===navigator.appVersion.indexOf("MSIE 10"))q=f.createRange(),q.moveStart("character",a.is("input")?-a.val().length:-a.text().length),
b=q.text.length;else if(c||"0"===c)b=c;return b}catch(d){}},setCaret:function(q){try{if(a.is(":focus")){var b,c=a.get(0);c.setSelectionRange?c.setSelectionRange(q,q):c.createTextRange&&(b=c.createTextRange(),b.collapse(!0),b.moveEnd("character",q),b.moveStart("character",q),b.select())}}catch(f){}},events:function(){a.on("input.mask keyup.mask",e.behaviour).on("paste.mask drop.mask",function(){setTimeout(function(){a.keydown().keyup()},100)}).on("change.mask",function(){a.data("changed",!0)}).on("blur.mask",
function(){k===a.val()||a.data("changed")||a.triggerHandler("change");a.data("changed",!1)}).on("blur.mask",function(){k=a.val()}).on("focus.mask",function(a){!0===d.selectOnFocus&&b(a.target).select()}).on("focusout.mask",function(){d.clearIfNotMatch&&!l.test(e.val())&&e.val("")})},getRegexMask:function(){for(var a=[],b,e,f,d,h=0;h<c.length;h++)(b=g.translation[c.charAt(h)])?(e=b.pattern.toString().replace(/.{1}$|^.{1}/g,""),f=b.optional,(b=b.recursive)?(a.push(c.charAt(h)),d={digit:c.charAt(h),
pattern:e}):a.push(f||b?e+"?":e)):a.push(c.charAt(h).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"));a=a.join("");d&&(a=a.replace(new RegExp("("+d.digit+"(.*"+d.digit+")?)"),"($1)?").replace(new RegExp(d.digit,"g"),d.pattern));return new RegExp(a)},destroyEvents:function(){a.off("input keydown keyup paste drop blur focusout ".split(" ").join(".mask "))},val:function(b){var c=a.is("input")?"val":"text";if(0<arguments.length){if(a[c]()!==b)a[c](b);c=a}else c=a[c]();return c},getMCharsBeforeCount:function(a,
b){for(var e=0,f=0,d=c.length;f<d&&f<a;f++)g.translation[c.charAt(f)]||(a=b?a+1:a,e++);return e},caretPos:function(a,b,d,f){return g.translation[c.charAt(Math.min(a-1,c.length-1))]?Math.min(a+d-b-f,d):e.caretPos(a+1,b,d,f)},behaviour:function(a){a=a||window.event;e.invalid=[];var c=a.keyCode||a.which;if(-1===b.inArray(c,g.byPassKeys)){var d=e.getCaret(),f=e.val().length,n=d<f,h=e.getMasked(),k=h.length,m=e.getMCharsBeforeCount(k-1)-e.getMCharsBeforeCount(f-1);e.val(h);!n||65===c&&a.ctrlKey||(8!==
c&&46!==c&&(d=e.caretPos(d,f,k,m)),e.setCaret(d));return e.callbacks(a)}},getMasked:function(a){var b=[],k=e.val(),f=0,n=c.length,h=0,l=k.length,m=1,p="push",u=-1,t,w;d.reverse?(p="unshift",m=-1,t=0,f=n-1,h=l-1,w=function(){return-1<f&&-1<h}):(t=n-1,w=function(){return f<n&&h<l});for(;w();){var x=c.charAt(f),v=k.charAt(h),r=g.translation[x];if(r)v.match(r.pattern)?(b[p](v),r.recursive&&(-1===u?u=f:f===t&&(f=u-m),t===u&&(f-=m)),f+=m):r.optional?(f+=m,h-=m):r.fallback?(b[p](r.fallback),f+=m,h-=m):e.invalid.push({p:h,
v:v,e:r.pattern}),h+=m;else{if(!a)b[p](x);v===x&&(h+=m);f+=m}}a=c.charAt(t);n!==l+1||g.translation[a]||b.push(a);return b.join("")},callbacks:function(b){var g=e.val(),l=g!==k,f=[g,b,a,d],n=function(a,b,c){"function"===typeof d[a]&&b&&d[a].apply(this,c)};n("onChange",!0===l,f);n("onKeyPress",!0===l,f);n("onComplete",g.length===c.length,f);n("onInvalid",0<e.invalid.length,[g,b,a,e.invalid,d])}};g.mask=c;g.options=d;g.remove=function(){var b=e.getCaret();e.destroyEvents();e.val(g.getCleanVal());e.setCaret(b-
e.getMCharsBeforeCount(b));return a};g.getCleanVal=function(){return e.getMasked(!0)};g.init=function(c){c=c||!1;d=d||{};g.byPassKeys=b.jMaskGlobals.byPassKeys;g.translation=b.jMaskGlobals.translation;g.translation=b.extend({},g.translation,d.translation);g=b.extend(!0,{},g,d);l=e.getRegexMask();!1===c?(d.placeholder&&a.attr("placeholder",d.placeholder),b("input").length&&!1==="oninput"in b("input")[0]&&"on"===a.attr("autocomplete")&&a.attr("autocomplete","off"),e.destroyEvents(),e.events(),c=e.getCaret(),
e.val(e.getMasked()),e.setCaret(c+e.getMCharsBeforeCount(c,!0))):(e.events(),e.val(e.getMasked()))};g.init(!a.is("input"))};b.maskWatchers={};var A=function(){var a=b(this),c={},d=a.attr("data-mask");a.attr("data-mask-reverse")&&(c.reverse=!0);a.attr("data-mask-clearifnotmatch")&&(c.clearIfNotMatch=!0);"true"===a.attr("data-mask-selectonfocus")&&(c.selectOnFocus=!0);if(z(a,d,c))return a.data("mask",new y(this,d,c))},z=function(a,c,d){d=d||{};var g=b(a).data("mask"),k=JSON.stringify;a=b(a).val()||
b(a).text();try{return"function"===typeof c&&(c=c(a)),"object"!==typeof g||k(g.options)!==k(d)||g.mask!==c}catch(l){}};b.fn.mask=function(a,c){c=c||{};var d=this.selector,g=b.jMaskGlobals,k=b.jMaskGlobals.watchInterval,l=function(){if(z(this,a,c))return b(this).data("mask",new y(this,a,c))};b(this).each(l);d&&""!==d&&g.watchInputs&&(clearInterval(b.maskWatchers[d]),b.maskWatchers[d]=setInterval(function(){b(document).find(d).each(l)},k));return this};b.fn.unmask=function(){clearInterval(b.maskWatchers[this.selector]);
delete b.maskWatchers[this.selector];return this.each(function(){var a=b(this).data("mask");a&&a.remove().removeData("mask")})};b.fn.cleanVal=function(){return this.data("mask").getCleanVal()};b.applyDataMask=function(a){a=a||b.jMaskGlobals.maskElements;(a instanceof b?a:b(a)).filter(b.jMaskGlobals.dataMaskAttr).each(A)};var p={maskElements:"input,td,span,div",dataMaskAttr:"*[data-mask]",dataMask:!0,watchInterval:300,watchInputs:!0,watchDataMask:!1,byPassKeys:[9,16,17,18,36,37,38,39,40,91],translation:{0:{pattern:/\d/},
9:{pattern:/\d/,optional:!0},"#":{pattern:/\d/,recursive:!0},A:{pattern:/[a-zA-Z0-9]/},S:{pattern:/[a-zA-Z]/}}};b.jMaskGlobals=b.jMaskGlobals||{};p=b.jMaskGlobals=b.extend(!0,{},p,b.jMaskGlobals);p.dataMask&&b.applyDataMask();setInterval(function(){b.jMaskGlobals.watchDataMask&&b.applyDataMask()},p.watchInterval)});;
/**
* jquery.matchHeight.js master
* http://brm.io/jquery-match-height/
* License: MIT
*/

;(function($) {
    /*
    *  internal
    */

    var _previousResizeWidth = -1,
        _updateTimeout = -1;

    /*
    *  _parse
    *  value parse utility function
    */

    var _parse = function(value) {
        // parse value and convert NaN to 0
        return parseFloat(value) || 0;
    };

    /*
    *  _rows
    *  utility function returns array of jQuery selections representing each row
    *  (as displayed after float wrapping applied by browser)
    */

    var _rows = function(elements) {
        var tolerance = 1,
            $elements = $(elements),
            lastTop = null,
            rows = [];

        // group elements by their top position
        $elements.each(function(){
            var $that = $(this),
                top = $that.offset().top - _parse($that.css('margin-top')),
                lastRow = rows.length > 0 ? rows[rows.length - 1] : null;

            if (lastRow === null) {
                // first item on the row, so just push it
                rows.push($that);
            } else {
                // if the row top is the same, add to the row group
                if (Math.floor(Math.abs(lastTop - top)) <= tolerance) {
                    rows[rows.length - 1] = lastRow.add($that);
                } else {
                    // otherwise start a new row group
                    rows.push($that);
                }
            }

            // keep track of the last row top
            lastTop = top;
        });

        return rows;
    };

    /*
    *  _parseOptions
    *  handle plugin options
    */

    var _parseOptions = function(options) {
        var opts = {
            byRow: true,
            property: 'height',
            target: null,
            remove: false
        };

        if (typeof options === 'object') {
            return $.extend(opts, options);
        }

        if (typeof options === 'boolean') {
            opts.byRow = options;
        } else if (options === 'remove') {
            opts.remove = true;
        }

        return opts;
    };

    /*
    *  matchHeight
    *  plugin definition
    */

    var matchHeight = $.fn.matchHeight = function(options) {
        var opts = _parseOptions(options);

        // handle remove
        if (opts.remove) {
            var that = this;

            // remove fixed height from all selected elements
            this.css(opts.property, '');

            // remove selected elements from all groups
            $.each(matchHeight._groups, function(key, group) {
                group.elements = group.elements.not(that);
            });

            // TODO: cleanup empty groups

            return this;
        }

        if (this.length <= 1 && !opts.target) {
            return this;
        }

        // keep track of this group so we can re-apply later on load and resize events
        matchHeight._groups.push({
            elements: this,
            options: opts
        });

        // match each element's height to the tallest element in the selection
        matchHeight._apply(this, opts);

        return this;
    };

    /*
    *  plugin global options
    */

    matchHeight._groups = [];
    matchHeight._throttle = 80;
    matchHeight._maintainScroll = false;
    matchHeight._beforeUpdate = null;
    matchHeight._afterUpdate = null;

    /*
    *  matchHeight._apply
    *  apply matchHeight to given elements
    */

    matchHeight._apply = function(elements, options) {
        var opts = _parseOptions(options),
            $elements = $(elements),
            rows = [$elements];

        // take note of scroll position
        var scrollTop = $(window).scrollTop(),
            htmlHeight = $('html').outerHeight(true);

        // get hidden parents
        var $hiddenParents = $elements.parents().filter(':hidden');

        // cache the original inline style
        $hiddenParents.each(function() {
            var $that = $(this);
            $that.data('style-cache', $that.attr('style'));
        });

        // temporarily must force hidden parents visible
        $hiddenParents.css('display', 'block');

        // get rows if using byRow, otherwise assume one row
        if (opts.byRow && !opts.target) {

            // must first force an arbitrary equal height so floating elements break evenly
            $elements.each(function() {
                var $that = $(this),
                    display = $that.css('display') === 'inline-block' ? 'inline-block' : 'block';

                // cache the original inline style
                $that.data('style-cache', $that.attr('style'));

                $that.css({
                    'display': display,
                    'padding-top': '0',
                    'padding-bottom': '0',
                    'margin-top': '0',
                    'margin-bottom': '0',
                    'border-top-width': '0',
                    'border-bottom-width': '0',
                    'height': '100px'
                });
            });

            // get the array of rows (based on element top position)
            rows = _rows($elements);

            // revert original inline styles
            $elements.each(function() {
                var $that = $(this);
                $that.attr('style', $that.data('style-cache') || '');
            });
        }

        $.each(rows, function(key, row) {
            var $row = $(row),
                targetHeight = 0;

            if (!opts.target) {
                // skip apply to rows with only one item
                if (opts.byRow && $row.length <= 1) {
                    $row.css(opts.property, '');
                    return;
                }

                // iterate the row and find the max height
                $row.each(function(){
                    var $that = $(this),
                        display = $that.css('display') === 'inline-block' ? 'inline-block' : 'block';

                    // ensure we get the correct actual height (and not a previously set height value)
                    var css = { 'display': display };
                    css[opts.property] = '';
                    $that.css(css);

                    // find the max height (including padding, but not margin)
                    if ($that.outerHeight(false) > targetHeight) {
                        targetHeight = $that.outerHeight(false);
                    }

                    // revert display block
                    $that.css('display', '');
                });
            } else {
                // if target set, use the height of the target element
                targetHeight = opts.target.outerHeight(false);
            }

            // iterate the row and apply the height to all elements
            $row.each(function(){
                var $that = $(this),
                    verticalPadding = 0;

                // don't apply to a target
                if (opts.target && $that.is(opts.target)) {
                    return;
                }

                // handle padding and border correctly (required when not using border-box)
                if ($that.css('box-sizing') !== 'border-box') {
                    verticalPadding += _parse($that.css('border-top-width')) + _parse($that.css('border-bottom-width'));
                    verticalPadding += _parse($that.css('padding-top')) + _parse($that.css('padding-bottom'));
                }

                // set the height (accounting for padding and border)
                $that.css(opts.property, targetHeight - verticalPadding);
            });
        });

        // revert hidden parents
        $hiddenParents.each(function() {
            var $that = $(this);
            $that.attr('style', $that.data('style-cache') || null);
        });

        // restore scroll position if enabled
        if (matchHeight._maintainScroll) {
            $(window).scrollTop((scrollTop / htmlHeight) * $('html').outerHeight(true));
        }

        return this;
    };

    /*
    *  matchHeight._applyDataApi
    *  applies matchHeight to all elements with a data-match-height attribute
    */

    matchHeight._applyDataApi = function() {
        var groups = {};

        // generate groups by their groupId set by elements using data-match-height
        $('[data-match-height], [data-mh]').each(function() {
            var $this = $(this),
                groupId = $this.attr('data-mh') || $this.attr('data-match-height');

            if (groupId in groups) {
                groups[groupId] = groups[groupId].add($this);
            } else {
                groups[groupId] = $this;
            }
        });

        // apply matchHeight to each group
        $.each(groups, function() {
            this.matchHeight(true);
        });
    };

    /*
    *  matchHeight._update
    *  updates matchHeight on all current groups with their correct options
    */

    var _update = function(event) {
        if (matchHeight._beforeUpdate) {
            matchHeight._beforeUpdate(event, matchHeight._groups);
        }

        $.each(matchHeight._groups, function() {
            matchHeight._apply(this.elements, this.options);
        });

        if (matchHeight._afterUpdate) {
            matchHeight._afterUpdate(event, matchHeight._groups);
        }
    };

    matchHeight._update = function(throttle, event) {
        // prevent update if fired from a resize event
        // where the viewport width hasn't actually changed
        // fixes an event looping bug in IE8
        if (event && event.type === 'resize') {
            var windowWidth = $(window).width();
            if (windowWidth === _previousResizeWidth) {
                return;
            }
            _previousResizeWidth = windowWidth;
        }

        // throttle updates
        if (!throttle) {
            _update(event);
        } else if (_updateTimeout === -1) {
            _updateTimeout = setTimeout(function() {
                _update(event);
                _updateTimeout = -1;
            }, matchHeight._throttle);
        }
    };

    /*
    *  bind events
    */

    // apply on DOM ready event
    $(matchHeight._applyDataApi);

    // update heights on load and resize events
    $(window).bind('load', function(event) {
        matchHeight._update(false, event);
    });

    // throttled update heights on resize events
    $(window).bind('resize orientationchange', function(event) {
        matchHeight._update(true, event);
    });

})(jQuery);;
/*
 * qTip2 - Pretty powerful tooltips - v2.2.1
 * http://qtip2.com
 *
 * Copyright (c) 2014 
 * Released under the MIT licenses
 * http://jquery.org/license
 *
 * Date: Sat Sep 6 2014 11:12 GMT+0100+0100
 * Plugins: tips modal viewport svg imagemap ie6
 * Styles: core basic css3
 */
/*global window: false, jQuery: false, console: false, define: false */

/* Cache window, document, undefined */
(function( window, document, undefined ) {

// Uses AMD or browser globals to create a jQuery plugin.
(function( factory ) {
	"use strict";
	if(typeof define === 'function' && define.amd) {
		define(['jquery'], factory);
	}
	else if(jQuery && !jQuery.fn.qtip) {
		factory(jQuery);
	}
}
(function($) {
	"use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
;// Munge the primitives - Paul Irish tip
var TRUE = true,
FALSE = false,
NULL = null,

// Common variables
X = 'x', Y = 'y',
WIDTH = 'width',
HEIGHT = 'height',

// Positioning sides
TOP = 'top',
LEFT = 'left',
BOTTOM = 'bottom',
RIGHT = 'right',
CENTER = 'center',

// Position adjustment types
FLIP = 'flip',
FLIPINVERT = 'flipinvert',
SHIFT = 'shift',

// Shortcut vars
QTIP, PROTOTYPE, CORNER, CHECKS,
PLUGINS = {},
NAMESPACE = 'qtip',
ATTR_HAS = 'data-hasqtip',
ATTR_ID = 'data-qtip-id',
WIDGET = ['ui-widget', 'ui-tooltip'],
SELECTOR = '.'+NAMESPACE,
INACTIVE_EVENTS = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '),

CLASS_FIXED = NAMESPACE+'-fixed',
CLASS_DEFAULT = NAMESPACE + '-default',
CLASS_FOCUS = NAMESPACE + '-focus',
CLASS_HOVER = NAMESPACE + '-hover',
CLASS_DISABLED = NAMESPACE+'-disabled',

replaceSuffix = '_replacedByqTip',
oldtitle = 'oldtitle',
trackingBound,

// Browser detection
BROWSER = {
	/*
	 * IE version detection
	 *
	 * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment
	 * Credit to James Padolsey for the original implemntation!
	 */
	ie: (function(){
		for (
			var v = 4, i = document.createElement("div");
			(i.innerHTML = "<!--[if gt IE " + v + "]><i></i><![endif]-->") && i.getElementsByTagName("i")[0];
			v+=1
		) {}
		return v > 4 ? v : NaN;
	}()),

	/*
	 * iOS version detection
	 */
	iOS: parseFloat(
		('' + (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
		.replace('undefined', '3_2').replace('_', '.').replace('_', '')
	) || FALSE
};
;function QTip(target, options, id, attr) {
	// Elements and ID
	this.id = id;
	this.target = target;
	this.tooltip = NULL;
	this.elements = { target: target };

	// Internal constructs
	this._id = NAMESPACE + '-' + id;
	this.timers = { img: {} };
	this.options = options;
	this.plugins = {};

	// Cache object
	this.cache = {
		event: {},
		target: $(),
		disabled: FALSE,
		attr: attr,
		onTooltip: FALSE,
		lastClass: ''
	};

	// Set the initial flags
	this.rendered = this.destroyed = this.disabled = this.waiting =
		this.hiddenDuringWait = this.positioning = this.triggering = FALSE;
}
PROTOTYPE = QTip.prototype;

PROTOTYPE._when = function(deferreds) {
	return $.when.apply($, deferreds);
};

PROTOTYPE.render = function(show) {
	if(this.rendered || this.destroyed) { return this; } // If tooltip has already been rendered, exit

	var self = this,
		options = this.options,
		cache = this.cache,
		elements = this.elements,
		text = options.content.text,
		title = options.content.title,
		button = options.content.button,
		posOptions = options.position,
		namespace = '.'+this._id+' ',
		deferreds = [],
		tooltip;

	// Add ARIA attributes to target
	$.attr(this.target[0], 'aria-describedby', this._id);

	// Create public position object that tracks current position corners
	cache.posClass = this._createPosClass(
		(this.position = { my: posOptions.my, at: posOptions.at }).my
	);

	// Create tooltip element
	this.tooltip = elements.tooltip = tooltip = $('<div/>', {
		'id': this._id,
		'class': [ NAMESPACE, CLASS_DEFAULT, options.style.classes, cache.posClass ].join(' '),
		'width': options.style.width || '',
		'height': options.style.height || '',
		'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,

		/* ARIA specific attributes */
		'role': 'alert',
		'aria-live': 'polite',
		'aria-atomic': FALSE,
		'aria-describedby': this._id + '-content',
		'aria-hidden': TRUE
	})
	.toggleClass(CLASS_DISABLED, this.disabled)
	.attr(ATTR_ID, this.id)
	.data(NAMESPACE, this)
	.appendTo(posOptions.container)
	.append(
		// Create content element
		elements.content = $('<div />', {
			'class': NAMESPACE + '-content',
			'id': this._id + '-content',
			'aria-atomic': TRUE
		})
	);

	// Set rendered flag and prevent redundant reposition calls for now
	this.rendered = -1;
	this.positioning = TRUE;

	// Create title...
	if(title) {
		this._createTitle();

		// Update title only if its not a callback (called in toggle if so)
		if(!$.isFunction(title)) {
			deferreds.push( this._updateTitle(title, FALSE) );
		}
	}

	// Create button
	if(button) { this._createButton(); }

	// Set proper rendered flag and update content if not a callback function (called in toggle)
	if(!$.isFunction(text)) {
		deferreds.push( this._updateContent(text, FALSE) );
	}
	this.rendered = TRUE;

	// Setup widget classes
	this._setWidget();

	// Initialize 'render' plugins
	$.each(PLUGINS, function(name) {
		var instance;
		if(this.initialize === 'render' && (instance = this(self))) {
			self.plugins[name] = instance;
		}
	});

	// Unassign initial events and assign proper events
	this._unassignEvents();
	this._assignEvents();

	// When deferreds have completed
	this._when(deferreds).then(function() {
		// tooltiprender event
		self._trigger('render');

		// Reset flags
		self.positioning = FALSE;

		// Show tooltip if not hidden during wait period
		if(!self.hiddenDuringWait && (options.show.ready || show)) {
			self.toggle(TRUE, cache.event, FALSE);
		}
		self.hiddenDuringWait = FALSE;
	});

	// Expose API
	QTIP.api[this.id] = this;

	return this;
};

PROTOTYPE.destroy = function(immediate) {
	// Set flag the signify destroy is taking place to plugins
	// and ensure it only gets destroyed once!
	if(this.destroyed) { return this.target; }

	function process() {
		if(this.destroyed) { return; }
		this.destroyed = TRUE;

		var target = this.target,
			title = target.attr(oldtitle),
			timer;

		// Destroy tooltip if rendered
		if(this.rendered) {
			this.tooltip.stop(1,0).find('*').remove().end().remove();
		}

		// Destroy all plugins
		$.each(this.plugins, function(name) {
			this.destroy && this.destroy();
		});

		// Clear timers
		for(timer in this.timers) {
			clearTimeout(this.timers[timer]);
		}

		// Remove api object and ARIA attributes
		target.removeData(NAMESPACE)
			.removeAttr(ATTR_ID)
			.removeAttr(ATTR_HAS)
			.removeAttr('aria-describedby');

		// Reset old title attribute if removed
		if(this.options.suppress && title) {
			target.attr('title', title).removeAttr(oldtitle);
		}

		// Remove qTip events associated with this API
		this._unassignEvents();

		// Remove ID from used id objects, and delete object references
		// for better garbage collection and leak protection
		this.options = this.elements = this.cache = this.timers =
			this.plugins = this.mouse = NULL;

		// Delete epoxsed API object
		delete QTIP.api[this.id];
	}

	// If an immediate destory is needed
	if((immediate !== TRUE || this.triggering === 'hide') && this.rendered) {
		this.tooltip.one('tooltiphidden', $.proxy(process, this));
		!this.triggering && this.hide();
	}

	// If we're not in the process of hiding... process
	else { process.call(this); }

	return this.target;
};
;function invalidOpt(a) {
	return a === NULL || $.type(a) !== 'object';
}

function invalidContent(c) {
	return !( $.isFunction(c) || (c && c.attr) || c.length || ($.type(c) === 'object' && (c.jquery || c.then) ));
}

// Option object sanitizer
function sanitizeOptions(opts) {
	var content, text, ajax, once;

	if(invalidOpt(opts)) { return FALSE; }

	if(invalidOpt(opts.metadata)) {
		opts.metadata = { type: opts.metadata };
	}

	if('content' in opts) {
		content = opts.content;

		if(invalidOpt(content) || content.jquery || content.done) {
			content = opts.content = {
				text: (text = invalidContent(content) ? FALSE : content)
			};
		}
		else { text = content.text; }

		// DEPRECATED - Old content.ajax plugin functionality
		// Converts it into the proper Deferred syntax
		if('ajax' in content) {
			ajax = content.ajax;
			once = ajax && ajax.once !== FALSE;
			delete content.ajax;

			content.text = function(event, api) {
				var loading = text || $(this).attr(api.options.content.attr) || 'Loading...',

				deferred = $.ajax(
					$.extend({}, ajax, { context: api })
				)
				.then(ajax.success, NULL, ajax.error)
				.then(function(content) {
					if(content && once) { api.set('content.text', content); }
					return content;
				},
				function(xhr, status, error) {
					if(api.destroyed || xhr.status === 0) { return; }
					api.set('content.text', status + ': ' + error);
				});

				return !once ? (api.set('content.text', loading), deferred) : loading;
			};
		}

		if('title' in content) {
			if($.isPlainObject(content.title)) {
				content.button = content.title.button;
				content.title = content.title.text;
			}

			if(invalidContent(content.title || FALSE)) {
				content.title = FALSE;
			}
		}
	}

	if('position' in opts && invalidOpt(opts.position)) {
		opts.position = { my: opts.position, at: opts.position };
	}

	if('show' in opts && invalidOpt(opts.show)) {
		opts.show = opts.show.jquery ? { target: opts.show } :
			opts.show === TRUE ? { ready: TRUE } : { event: opts.show };
	}

	if('hide' in opts && invalidOpt(opts.hide)) {
		opts.hide = opts.hide.jquery ? { target: opts.hide } : { event: opts.hide };
	}

	if('style' in opts && invalidOpt(opts.style)) {
		opts.style = { classes: opts.style };
	}

	// Sanitize plugin options
	$.each(PLUGINS, function() {
		this.sanitize && this.sanitize(opts);
	});

	return opts;
}

// Setup builtin .set() option checks
CHECKS = PROTOTYPE.checks = {
	builtin: {
		// Core checks
		'^id$': function(obj, o, v, prev) {
			var id = v === TRUE ? QTIP.nextid : v,
				new_id = NAMESPACE + '-' + id;

			if(id !== FALSE && id.length > 0 && !$('#'+new_id).length) {
				this._id = new_id;

				if(this.rendered) {
					this.tooltip[0].id = this._id;
					this.elements.content[0].id = this._id + '-content';
					this.elements.title[0].id = this._id + '-title';
				}
			}
			else { obj[o] = prev; }
		},
		'^prerender': function(obj, o, v) {
			v && !this.rendered && this.render(this.options.show.ready);
		},

		// Content checks
		'^content.text$': function(obj, o, v) {
			this._updateContent(v);
		},
		'^content.attr$': function(obj, o, v, prev) {
			if(this.options.content.text === this.target.attr(prev)) {
				this._updateContent( this.target.attr(v) );
			}
		},
		'^content.title$': function(obj, o, v) {
			// Remove title if content is null
			if(!v) { return this._removeTitle(); }

			// If title isn't already created, create it now and update
			v && !this.elements.title && this._createTitle();
			this._updateTitle(v);
		},
		'^content.button$': function(obj, o, v) {
			this._updateButton(v);
		},
		'^content.title.(text|button)$': function(obj, o, v) {
			this.set('content.'+o, v); // Backwards title.text/button compat
		},

		// Position checks
		'^position.(my|at)$': function(obj, o, v){
			'string' === typeof v && (this.position[o] = obj[o] = new CORNER(v, o === 'at'));
		},
		'^position.container$': function(obj, o, v){
			this.rendered && this.tooltip.appendTo(v);
		},

		// Show checks
		'^show.ready$': function(obj, o, v) {
			v && (!this.rendered && this.render(TRUE) || this.toggle(TRUE));
		},

		// Style checks
		'^style.classes$': function(obj, o, v, p) {
			this.rendered && this.tooltip.removeClass(p).addClass(v);
		},
		'^style.(width|height)': function(obj, o, v) {
			this.rendered && this.tooltip.css(o, v);
		},
		'^style.widget|content.title': function() {
			this.rendered && this._setWidget();
		},
		'^style.def': function(obj, o, v) {
			this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
		},

		// Events check
		'^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
			this.rendered && this.tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
		},

		// Properties which require event reassignment
		'^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
			if(!this.rendered) { return; }

			// Set tracking flag
			var posOptions = this.options.position;
			this.tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);

			// Reassign events
			this._unassignEvents();
			this._assignEvents();
		}
	}
};

// Dot notation converter
function convertNotation(options, notation) {
	var i = 0, obj, option = options,

	// Split notation into array
	levels = notation.split('.');

	// Loop through
	while( option = option[ levels[i++] ] ) {
		if(i < levels.length) { obj = option; }
	}

	return [obj || options, levels.pop()];
}

PROTOTYPE.get = function(notation) {
	if(this.destroyed) { return this; }

	var o = convertNotation(this.options, notation.toLowerCase()),
		result = o[0][ o[1] ];

	return result.precedance ? result.string() : result;
};

function setCallback(notation, args) {
	var category, rule, match;

	for(category in this.checks) {
		for(rule in this.checks[category]) {
			if(match = (new RegExp(rule, 'i')).exec(notation)) {
				args.push(match);

				if(category === 'builtin' || this.plugins[category]) {
					this.checks[category][rule].apply(
						this.plugins[category] || this, args
					);
				}
			}
		}
	}
}

var rmove = /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
	rrender = /^prerender|show\.ready/i;

PROTOTYPE.set = function(option, value) {
	if(this.destroyed) { return this; }

	var rendered = this.rendered,
		reposition = FALSE,
		options = this.options,
		checks = this.checks,
		name;

	// Convert singular option/value pair into object form
	if('string' === typeof option) {
		name = option; option = {}; option[name] = value;
	}
	else { option = $.extend({}, option); }

	// Set all of the defined options to their new values
	$.each(option, function(notation, value) {
		if(rendered && rrender.test(notation)) {
			delete option[notation]; return;
		}

		// Set new obj value
		var obj = convertNotation(options, notation.toLowerCase()), previous;
		previous = obj[0][ obj[1] ];
		obj[0][ obj[1] ] = value && value.nodeType ? $(value) : value;

		// Also check if we need to reposition
		reposition = rmove.test(notation) || reposition;

		// Set the new params for the callback
		option[notation] = [obj[0], obj[1], value, previous];
	});

	// Re-sanitize options
	sanitizeOptions(options);

	/*
	 * Execute any valid callbacks for the set options
	 * Also set positioning flag so we don't get loads of redundant repositioning calls.
	 */
	this.positioning = TRUE;
	$.each(option, $.proxy(setCallback, this));
	this.positioning = FALSE;

	// Update position if needed
	if(this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
		this.reposition( options.position.target === 'mouse' ? NULL : this.cache.event );
	}

	return this;
};
;PROTOTYPE._update = function(content, element, reposition) {
	var self = this,
		cache = this.cache;

	// Make sure tooltip is rendered and content is defined. If not return
	if(!this.rendered || !content) { return FALSE; }

	// Use function to parse content
	if($.isFunction(content)) {
		content = content.call(this.elements.target, cache.event, this) || '';
	}

	// Handle deferred content
	if($.isFunction(content.then)) {
		cache.waiting = TRUE;
		return content.then(function(c) {
			cache.waiting = FALSE;
			return self._update(c, element);
		}, NULL, function(e) {
			return self._update(e, element);
		});
	}

	// If content is null... return false
	if(content === FALSE || (!content && content !== '')) { return FALSE; }

	// Append new content if its a DOM array and show it if hidden
	if(content.jquery && content.length > 0) {
		element.empty().append(
			content.css({ display: 'block', visibility: 'visible' })
		);
	}

	// Content is a regular string, insert the new content
	else { element.html(content); }

	// Wait for content to be loaded, and reposition
	return this._waitForContent(element).then(function(images) {
		if(self.rendered && self.tooltip[0].offsetWidth > 0) {
			self.reposition(cache.event, !images.length);
		}
	});
};

PROTOTYPE._waitForContent = function(element) {
	var cache = this.cache;

	// Set flag
	cache.waiting = TRUE;

	// If imagesLoaded is included, ensure images have loaded and return promise
	return ( $.fn.imagesLoaded ? element.imagesLoaded() : $.Deferred().resolve([]) )
		.done(function() { cache.waiting = FALSE; })
		.promise();
};

PROTOTYPE._updateContent = function(content, reposition) {
	this._update(content, this.elements.content, reposition);
};

PROTOTYPE._updateTitle = function(content, reposition) {
	if(this._update(content, this.elements.title, reposition) === FALSE) {
		this._removeTitle(FALSE);
	}
};

PROTOTYPE._createTitle = function()
{
	var elements = this.elements,
		id = this._id+'-title';

	// Destroy previous title element, if present
	if(elements.titlebar) { this._removeTitle(); }

	// Create title bar and title elements
	elements.titlebar = $('<div />', {
		'class': NAMESPACE + '-titlebar ' + (this.options.style.widget ? createWidgetClass('header') : '')
	})
	.append(
		elements.title = $('<div />', {
			'id': id,
			'class': NAMESPACE + '-title',
			'aria-atomic': TRUE
		})
	)
	.insertBefore(elements.content)

	// Button-specific events
	.delegate('.qtip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
		$(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
	})
	.delegate('.qtip-close', 'mouseover mouseout', function(event){
		$(this).toggleClass('ui-state-hover', event.type === 'mouseover');
	});

	// Create button if enabled
	if(this.options.content.button) { this._createButton(); }
};

PROTOTYPE._removeTitle = function(reposition)
{
	var elements = this.elements;

	if(elements.title) {
		elements.titlebar.remove();
		elements.titlebar = elements.title = elements.button = NULL;

		// Reposition if enabled
		if(reposition !== FALSE) { this.reposition(); }
	}
};
;PROTOTYPE._createPosClass = function(my) {
	return NAMESPACE + '-pos-' + (my || this.options.position.my).abbrev();
};

PROTOTYPE.reposition = function(event, effect) {
	if(!this.rendered || this.positioning || this.destroyed) { return this; }

	// Set positioning flag
	this.positioning = TRUE;

	var cache = this.cache,
		tooltip = this.tooltip,
		posOptions = this.options.position,
		target = posOptions.target,
		my = posOptions.my,
		at = posOptions.at,
		viewport = posOptions.viewport,
		container = posOptions.container,
		adjust = posOptions.adjust,
		method = adjust.method.split(' '),
		tooltipWidth = tooltip.outerWidth(FALSE),
		tooltipHeight = tooltip.outerHeight(FALSE),
		targetWidth = 0,
		targetHeight = 0,
		type = tooltip.css('position'),
		position = { left: 0, top: 0 },
		visible = tooltip[0].offsetWidth > 0,
		isScroll = event && event.type === 'scroll',
		win = $(window),
		doc = container[0].ownerDocument,
		mouse = this.mouse,
		pluginCalculations, offset, adjusted, newClass;

	// Check if absolute position was passed
	if($.isArray(target) && target.length === 2) {
		// Force left top and set position
		at = { x: LEFT, y: TOP };
		position = { left: target[0], top: target[1] };
	}

	// Check if mouse was the target
	else if(target === 'mouse') {
		// Force left top to allow flipping
		at = { x: LEFT, y: TOP };

		// Use the mouse origin that caused the show event, if distance hiding is enabled
		if((!adjust.mouse || this.options.hide.distance) && cache.origin && cache.origin.pageX) {
			event =  cache.origin;
		}

		// Use cached event for resize/scroll events
		else if(!event || (event && (event.type === 'resize' || event.type === 'scroll'))) {
			event = cache.event;
		}

		// Otherwise, use the cached mouse coordinates if available
		else if(mouse && mouse.pageX) {
			event = mouse;
		}

		// Calculate body and container offset and take them into account below
		if(type !== 'static') { position = container.offset(); }
		if(doc.body.offsetWidth !== (window.innerWidth || doc.documentElement.clientWidth)) {
			offset = $(document.body).offset();
		}

		// Use event coordinates for position
		position = {
			left: event.pageX - position.left + (offset && offset.left || 0),
			top: event.pageY - position.top + (offset && offset.top || 0)
		};

		// Scroll events are a pain, some browsers
		if(adjust.mouse && isScroll && mouse) {
			position.left -= (mouse.scrollX || 0) - win.scrollLeft();
			position.top -= (mouse.scrollY || 0) - win.scrollTop();
		}
	}

	// Target wasn't mouse or absolute...
	else {
		// Check if event targetting is being used
		if(target === 'event') {
			if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
				cache.target = $(event.target);
			}
			else if(!event.target) {
				cache.target = this.elements.target;
			}
		}
		else if(target !== 'event'){
			cache.target = $(target.jquery ? target : this.elements.target);
		}
		target = cache.target;

		// Parse the target into a jQuery object and make sure there's an element present
		target = $(target).eq(0);
		if(target.length === 0) { return this; }

		// Check if window or document is the target
		else if(target[0] === document || target[0] === window) {
			targetWidth = BROWSER.iOS ? window.innerWidth : target.width();
			targetHeight = BROWSER.iOS ? window.innerHeight : target.height();

			if(target[0] === window) {
				position = {
					top: (viewport || target).scrollTop(),
					left: (viewport || target).scrollLeft()
				};
			}
		}

		// Check if the target is an <AREA> element
		else if(PLUGINS.imagemap && target.is('area')) {
			pluginCalculations = PLUGINS.imagemap(this, target, at, PLUGINS.viewport ? method : FALSE);
		}

		// Check if the target is an SVG element
		else if(PLUGINS.svg && target && target[0].ownerSVGElement) {
			pluginCalculations = PLUGINS.svg(this, target, at, PLUGINS.viewport ? method : FALSE);
		}

		// Otherwise use regular jQuery methods
		else {
			targetWidth = target.outerWidth(FALSE);
			targetHeight = target.outerHeight(FALSE);
			position = target.offset();
		}

		// Parse returned plugin values into proper variables
		if(pluginCalculations) {
			targetWidth = pluginCalculations.width;
			targetHeight = pluginCalculations.height;
			offset = pluginCalculations.offset;
			position = pluginCalculations.position;
		}

		// Adjust position to take into account offset parents
		position = this.reposition.offset(target, position, container);

		// Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
		if((BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1) ||
			(BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33) ||
			(!BROWSER.iOS && type === 'fixed')
		){
			position.left -= win.scrollLeft();
			position.top -= win.scrollTop();
		}

		// Adjust position relative to target
		if(!pluginCalculations || (pluginCalculations && pluginCalculations.adjustable !== FALSE)) {
			position.left += at.x === RIGHT ? targetWidth : at.x === CENTER ? targetWidth / 2 : 0;
			position.top += at.y === BOTTOM ? targetHeight : at.y === CENTER ? targetHeight / 2 : 0;
		}
	}

	// Adjust position relative to tooltip
	position.left += adjust.x + (my.x === RIGHT ? -tooltipWidth : my.x === CENTER ? -tooltipWidth / 2 : 0);
	position.top += adjust.y + (my.y === BOTTOM ? -tooltipHeight : my.y === CENTER ? -tooltipHeight / 2 : 0);

	// Use viewport adjustment plugin if enabled
	if(PLUGINS.viewport) {
		adjusted = position.adjusted = PLUGINS.viewport(
			this, position, posOptions, targetWidth, targetHeight, tooltipWidth, tooltipHeight
		);

		// Apply offsets supplied by positioning plugin (if used)
		if(offset && adjusted.left) { position.left += offset.left; }
		if(offset && adjusted.top) {  position.top += offset.top; }

		// Apply any new 'my' position
		if(adjusted.my) { this.position.my = adjusted.my; }
	}

	// Viewport adjustment is disabled, set values to zero
	else { position.adjusted = { left: 0, top: 0 }; }

	// Set tooltip position class if it's changed
	if(cache.posClass !== (newClass = this._createPosClass(this.position.my))) {
		tooltip.removeClass(cache.posClass).addClass( (cache.posClass = newClass) );
	}

	// tooltipmove event
	if(!this._trigger('move', [position, viewport.elem || viewport], event)) { return this; }
	delete position.adjusted;

	// If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
	if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
		tooltip.css(position);
	}

	// Use custom function if provided
	else if($.isFunction(posOptions.effect)) {
		posOptions.effect.call(tooltip, this, $.extend({}, position));
		tooltip.queue(function(next) {
			// Reset attributes to avoid cross-browser rendering bugs
			$(this).css({ opacity: '', height: '' });
			if(BROWSER.ie) { this.style.removeAttribute('filter'); }

			next();
		});
	}

	// Set positioning flag
	this.positioning = FALSE;

	return this;
};

// Custom (more correct for qTip!) offset calculator
PROTOTYPE.reposition.offset = function(elem, pos, container) {
	if(!container[0]) { return pos; }

	var ownerDocument = $(elem[0].ownerDocument),
		quirks = !!BROWSER.ie && document.compatMode !== 'CSS1Compat',
		parent = container[0],
		scrolled, position, parentOffset, overflow;

	function scroll(e, i) {
		pos.left += i * e.scrollLeft();
		pos.top += i * e.scrollTop();
	}

	// Compensate for non-static containers offset
	do {
		if((position = $.css(parent, 'position')) !== 'static') {
			if(position === 'fixed') {
				parentOffset = parent.getBoundingClientRect();
				scroll(ownerDocument, -1);
			}
			else {
				parentOffset = $(parent).position();
				parentOffset.left += (parseFloat($.css(parent, 'borderLeftWidth')) || 0);
				parentOffset.top += (parseFloat($.css(parent, 'borderTopWidth')) || 0);
			}

			pos.left -= parentOffset.left + (parseFloat($.css(parent, 'marginLeft')) || 0);
			pos.top -= parentOffset.top + (parseFloat($.css(parent, 'marginTop')) || 0);

			// If this is the first parent element with an overflow of "scroll" or "auto", store it
			if(!scrolled && (overflow = $.css(parent, 'overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = $(parent); }
		}
	}
	while((parent = parent.offsetParent));

	// Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)
	if(scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) {
		scroll(scrolled, 1);
	}

	return pos;
};

// Corner class
var C = (CORNER = PROTOTYPE.reposition.Corner = function(corner, forceY) {
	corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, CENTER).toLowerCase();
	this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
	this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
	this.forceY = !!forceY;

	var f = corner.charAt(0);
	this.precedance = (f === 't' || f === 'b' ? Y : X);
}).prototype;

C.invert = function(z, center) {
	this[z] = this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
};

C.string = function(join) {
	var x = this.x, y = this.y;

	var result = x !== y ?
		(x === 'center' || y !== 'center' && (this.precedance === Y || this.forceY) ? 
			[y,x] : [x,y]
		) :
	[x];

	return join !== false ? result.join(' ') : result;
};

C.abbrev = function() {
	var result = this.string(false);
	return result[0].charAt(0) + (result[1] && result[1].charAt(0) || '');
};

C.clone = function() {
	return new CORNER( this.string(), this.forceY );
};

;
PROTOTYPE.toggle = function(state, event) {
	var cache = this.cache,
		options = this.options,
		tooltip = this.tooltip;

	// Try to prevent flickering when tooltip overlaps show element
	if(event) {
		if((/over|enter/).test(event.type) && cache.event && (/out|leave/).test(cache.event.type) &&
			options.show.target.add(event.target).length === options.show.target.length &&
			tooltip.has(event.relatedTarget).length) {
			return this;
		}

		// Cache event
		cache.event = $.event.fix(event);
	}

	// If we're currently waiting and we've just hidden... stop it
	this.waiting && !state && (this.hiddenDuringWait = TRUE);

	// Render the tooltip if showing and it isn't already
	if(!this.rendered) { return state ? this.render(1) : this; }
	else if(this.destroyed || this.disabled) { return this; }

	var type = state ? 'show' : 'hide',
		opts = this.options[type],
		otherOpts = this.options[ !state ? 'show' : 'hide' ],
		posOptions = this.options.position,
		contentOptions = this.options.content,
		width = this.tooltip.css('width'),
		visible = this.tooltip.is(':visible'),
		animate = state || opts.target.length === 1,
		sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
		identicalState, allow, showEvent, delay, after;

	// Detect state if valid one isn't provided
	if((typeof state).search('boolean|number')) { state = !visible; }

	// Check if the tooltip is in an identical state to the new would-be state
	identicalState = !tooltip.is(':animated') && visible === state && sameTarget;

	// Fire tooltip(show/hide) event and check if destroyed
	allow = !identicalState ? !!this._trigger(type, [90]) : NULL;

	// Check to make sure the tooltip wasn't destroyed in the callback
	if(this.destroyed) { return this; }

	// If the user didn't stop the method prematurely and we're showing the tooltip, focus it
	if(allow !== FALSE && state) { this.focus(event); }

	// If the state hasn't changed or the user stopped it, return early
	if(!allow || identicalState) { return this; }

	// Set ARIA hidden attribute
	$.attr(tooltip[0], 'aria-hidden', !!!state);

	// Execute state specific properties
	if(state) {
		// Store show origin coordinates
		this.mouse && (cache.origin = $.event.fix(this.mouse));

		// Update tooltip content & title if it's a dynamic function
		if($.isFunction(contentOptions.text)) { this._updateContent(contentOptions.text, FALSE); }
		if($.isFunction(contentOptions.title)) { this._updateTitle(contentOptions.title, FALSE); }

		// Cache mousemove events for positioning purposes (if not already tracking)
		if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
			$(document).bind('mousemove.'+NAMESPACE, this._storeMouse);
			trackingBound = TRUE;
		}

		// Update the tooltip position (set width first to prevent viewport/max-width issues)
		if(!width) { tooltip.css('width', tooltip.outerWidth(FALSE)); }
		this.reposition(event, arguments[2]);
		if(!width) { tooltip.css('width', ''); }

		// Hide other tooltips if tooltip is solo
		if(!!opts.solo) {
			(typeof opts.solo === 'string' ? $(opts.solo) : $(SELECTOR, opts.solo))
				.not(tooltip).not(opts.target).qtip('hide', $.Event('tooltipsolo'));
		}
	}
	else {
		// Clear show timer if we're hiding
		clearTimeout(this.timers.show);

		// Remove cached origin on hide
		delete cache.origin;

		// Remove mouse tracking event if not needed (all tracking qTips are hidden)
		if(trackingBound && !$(SELECTOR+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
			$(document).unbind('mousemove.'+NAMESPACE);
			trackingBound = FALSE;
		}

		// Blur the tooltip
		this.blur(event);
	}

	// Define post-animation, state specific properties
	after = $.proxy(function() {
		if(state) {
			// Prevent antialias from disappearing in IE by removing filter
			if(BROWSER.ie) { tooltip[0].style.removeAttribute('filter'); }

			// Remove overflow setting to prevent tip bugs
			tooltip.css('overflow', '');

			// Autofocus elements if enabled
			if('string' === typeof opts.autofocus) {
				$(this.options.show.autofocus, tooltip).focus();
			}

			// If set, hide tooltip when inactive for delay period
			this.options.show.target.trigger('qtip-'+this.id+'-inactive');
		}
		else {
			// Reset CSS states
			tooltip.css({
				display: '',
				visibility: '',
				opacity: '',
				left: '',
				top: ''
			});
		}

		// tooltipvisible/tooltiphidden events
		this._trigger(state ? 'visible' : 'hidden');
	}, this);

	// If no effect type is supplied, use a simple toggle
	if(opts.effect === FALSE || animate === FALSE) {
		tooltip[ type ]();
		after();
	}

	// Use custom function if provided
	else if($.isFunction(opts.effect)) {
		tooltip.stop(1, 1);
		opts.effect.call(tooltip, this);
		tooltip.queue('fx', function(n) {
			after(); n();
		});
	}

	// Use basic fade function by default
	else { tooltip.fadeTo(90, state ? 1 : 0, after); }

	// If inactive hide method is set, active it
	if(state) { opts.target.trigger('qtip-'+this.id+'-inactive'); }

	return this;
};

PROTOTYPE.show = function(event) { return this.toggle(TRUE, event); };

PROTOTYPE.hide = function(event) { return this.toggle(FALSE, event); };
;PROTOTYPE.focus = function(event) {
	if(!this.rendered || this.destroyed) { return this; }

	var qtips = $(SELECTOR),
		tooltip = this.tooltip,
		curIndex = parseInt(tooltip[0].style.zIndex, 10),
		newIndex = QTIP.zindex + qtips.length,
		focusedElem;

	// Only update the z-index if it has changed and tooltip is not already focused
	if(!tooltip.hasClass(CLASS_FOCUS)) {
		// tooltipfocus event
		if(this._trigger('focus', [newIndex], event)) {
			// Only update z-index's if they've changed
			if(curIndex !== newIndex) {
				// Reduce our z-index's and keep them properly ordered
				qtips.each(function() {
					if(this.style.zIndex > curIndex) {
						this.style.zIndex = this.style.zIndex - 1;
					}
				});

				// Fire blur event for focused tooltip
				qtips.filter('.' + CLASS_FOCUS).qtip('blur', event);
			}

			// Set the new z-index
			tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
		}
	}

	return this;
};

PROTOTYPE.blur = function(event) {
	if(!this.rendered || this.destroyed) { return this; }

	// Set focused status to FALSE
	this.tooltip.removeClass(CLASS_FOCUS);

	// tooltipblur event
	this._trigger('blur', [ this.tooltip.css('zIndex') ], event);

	return this;
};
;PROTOTYPE.disable = function(state) {
	if(this.destroyed) { return this; }

	// If 'toggle' is passed, toggle the current state
	if(state === 'toggle') {
		state = !(this.rendered ? this.tooltip.hasClass(CLASS_DISABLED) : this.disabled);
	}

	// Disable if no state passed
	else if('boolean' !== typeof state) {
		state = TRUE;
	}

	if(this.rendered) {
		this.tooltip.toggleClass(CLASS_DISABLED, state)
			.attr('aria-disabled', state);
	}

	this.disabled = !!state;

	return this;
};

PROTOTYPE.enable = function() { return this.disable(FALSE); };
;PROTOTYPE._createButton = function()
{
	var self = this,
		elements = this.elements,
		tooltip = elements.tooltip,
		button = this.options.content.button,
		isString = typeof button === 'string',
		close = isString ? button : 'Close tooltip';

	if(elements.button) { elements.button.remove(); }

	// Use custom button if one was supplied by user, else use default
	if(button.jquery) {
		elements.button = button;
	}
	else {
		elements.button = $('<a />', {
			'class': 'qtip-close ' + (this.options.style.widget ? '' : NAMESPACE+'-icon'),
			'title': close,
			'aria-label': close
		})
		.prepend(
			$('<span />', {
				'class': 'ui-icon ui-icon-close',
				'html': '&times;'
			})
		);
	}

	// Create button and setup attributes
	elements.button.appendTo(elements.titlebar || tooltip)
		.attr('role', 'button')
		.click(function(event) {
			if(!tooltip.hasClass(CLASS_DISABLED)) { self.hide(event); }
			return FALSE;
		});
};

PROTOTYPE._updateButton = function(button)
{
	// Make sure tooltip is rendered and if not, return
	if(!this.rendered) { return FALSE; }

	var elem = this.elements.button;
	if(button) { this._createButton(); }
	else { elem.remove(); }
};
;// Widget class creator
function createWidgetClass(cls) {
	return WIDGET.concat('').join(cls ? '-'+cls+' ' : ' ');
}

// Widget class setter method
PROTOTYPE._setWidget = function()
{
	var on = this.options.style.widget,
		elements = this.elements,
		tooltip = elements.tooltip,
		disabled = tooltip.hasClass(CLASS_DISABLED);

	tooltip.removeClass(CLASS_DISABLED);
	CLASS_DISABLED = on ? 'ui-state-disabled' : 'qtip-disabled';
	tooltip.toggleClass(CLASS_DISABLED, disabled);

	tooltip.toggleClass('ui-helper-reset '+createWidgetClass(), on).toggleClass(CLASS_DEFAULT, this.options.style.def && !on);

	if(elements.content) {
		elements.content.toggleClass( createWidgetClass('content'), on);
	}
	if(elements.titlebar) {
		elements.titlebar.toggleClass( createWidgetClass('header'), on);
	}
	if(elements.button) {
		elements.button.toggleClass(NAMESPACE+'-icon', !on);
	}
};
;function delay(callback, duration) {
	// If tooltip has displayed, start hide timer
	if(duration > 0) {
		return setTimeout(
			$.proxy(callback, this), duration
		);
	}
	else{ callback.call(this); }
}

function showMethod(event) {
	if(this.tooltip.hasClass(CLASS_DISABLED)) { return; }

	// Clear hide timers
	clearTimeout(this.timers.show);
	clearTimeout(this.timers.hide);

	// Start show timer
	this.timers.show = delay.call(this,
		function() { this.toggle(TRUE, event); },
		this.options.show.delay
	);
}

function hideMethod(event) {
	if(this.tooltip.hasClass(CLASS_DISABLED) || this.destroyed) { return; }

	// Check if new target was actually the tooltip element
	var relatedTarget = $(event.relatedTarget),
		ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0],
		ontoTarget = relatedTarget[0] === this.options.show.target[0];

	// Clear timers and stop animation queue
	clearTimeout(this.timers.show);
	clearTimeout(this.timers.hide);

	// Prevent hiding if tooltip is fixed and event target is the tooltip.
	// Or if mouse positioning is enabled and cursor momentarily overlaps
	if(this !== relatedTarget[0] &&
		(this.options.position.target === 'mouse' && ontoTooltip) ||
		(this.options.hide.fixed && (
			(/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))
		))
	{
		try {
			event.preventDefault();
			event.stopImmediatePropagation();
		} catch(e) {}

		return;
	}

	// If tooltip has displayed, start hide timer
	this.timers.hide = delay.call(this,
		function() { this.toggle(FALSE, event); },
		this.options.hide.delay,
		this
	);
}

function inactiveMethod(event) {
	if(this.tooltip.hasClass(CLASS_DISABLED) || !this.options.hide.inactive) { return; }

	// Clear timer
	clearTimeout(this.timers.inactive);

	this.timers.inactive = delay.call(this,
		function(){ this.hide(event); },
		this.options.hide.inactive
	);
}

function repositionMethod(event) {
	if(this.rendered && this.tooltip[0].offsetWidth > 0) { this.reposition(event); }
}

// Store mouse coordinates
PROTOTYPE._storeMouse = function(event) {
	(this.mouse = $.event.fix(event)).type = 'mousemove';
	return this;
};

// Bind events
PROTOTYPE._bind = function(targets, events, method, suffix, context) {
	if(!targets || !method || !events.length) { return; }
	var ns = '.' + this._id + (suffix ? '-'+suffix : '');
	$(targets).bind(
		(events.split ? events : events.join(ns + ' ')) + ns,
		$.proxy(method, context || this)
	);
	return this;
};
PROTOTYPE._unbind = function(targets, suffix) {
	targets && $(targets).unbind('.' + this._id + (suffix ? '-'+suffix : ''));
	return this;
};

// Global delegation helper
function delegate(selector, events, method) {
	$(document.body).delegate(selector,
		(events.split ? events : events.join('.'+NAMESPACE + ' ')) + '.'+NAMESPACE,
		function() {
			var api = QTIP.api[ $.attr(this, ATTR_ID) ];
			api && !api.disabled && method.apply(api, arguments);
		}
	);
}
// Event trigger
PROTOTYPE._trigger = function(type, args, event) {
	var callback = $.Event('tooltip'+type);
	callback.originalEvent = (event && $.extend({}, event)) || this.cache.event || NULL;

	this.triggering = type;
	this.tooltip.trigger(callback, [this].concat(args || []));
	this.triggering = FALSE;

	return !callback.isDefaultPrevented();
};

PROTOTYPE._bindEvents = function(showEvents, hideEvents, showTargets, hideTargets, showMethod, hideMethod) {
	// Get tasrgets that lye within both
	var similarTargets = showTargets.filter( hideTargets ).add( hideTargets.filter(showTargets) ),
		toggleEvents = [];

	// If hide and show targets are the same...
	if(similarTargets.length) {

		// Filter identical show/hide events
		$.each(hideEvents, function(i, type) {
			var showIndex = $.inArray(type, showEvents);

			// Both events are identical, remove from both hide and show events
			// and append to toggleEvents
			showIndex > -1 && toggleEvents.push( showEvents.splice( showIndex, 1 )[0] );
		});

		// Toggle events are special case of identical show/hide events, which happen in sequence
		if(toggleEvents.length) {
			// Bind toggle events to the similar targets
			this._bind(similarTargets, toggleEvents, function(event) {
				var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false;
				(state ? hideMethod : showMethod).call(this, event);
			});

			// Remove the similar targets from the regular show/hide bindings
			showTargets = showTargets.not(similarTargets);
			hideTargets = hideTargets.not(similarTargets);
		}
	}

	// Apply show/hide/toggle events
	this._bind(showTargets, showEvents, showMethod);
	this._bind(hideTargets, hideEvents, hideMethod);
};

PROTOTYPE._assignInitialEvents = function(event) {
	var options = this.options,
		showTarget = options.show.target,
		hideTarget = options.hide.target,
		showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
		hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];

	// Catch remove/removeqtip events on target element to destroy redundant tooltips
	this._bind(this.elements.target, ['remove', 'removeqtip'], function(event) {
		this.destroy(true);
	}, 'destroy');

	/*
	 * Make sure hoverIntent functions properly by using mouseleave as a hide event if
	 * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
	 */
	if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) {
		hideEvents.push('mouseleave');
	}

	/*
	 * Also make sure initial mouse targetting works correctly by caching mousemove coords
	 * on show targets before the tooltip has rendered. Also set onTarget when triggered to
	 * keep mouse tracking working.
	 */
	this._bind(showTarget, 'mousemove', function(event) {
		this._storeMouse(event);
		this.cache.onTarget = TRUE;
	});

	// Define hoverIntent function
	function hoverIntent(event) {
		// Only continue if tooltip isn't disabled
		if(this.disabled || this.destroyed) { return FALSE; }

		// Cache the event data
		this.cache.event = event && $.event.fix(event);
		this.cache.target = event && $(event.target);

		// Start the event sequence
		clearTimeout(this.timers.show);
		this.timers.show = delay.call(this,
			function() { this.render(typeof event === 'object' || options.show.ready); },
			options.prerender ? 0 : options.show.delay
		);
	}

	// Filter and bind events
	this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, hoverIntent, function() {
		if(!this.timers) { return FALSE; }
		clearTimeout(this.timers.show);
	});

	// Prerendering is enabled, create tooltip now
	if(options.show.ready || options.prerender) { hoverIntent.call(this, event); }
};

// Event assignment method
PROTOTYPE._assignEvents = function() {
	var self = this,
		options = this.options,
		posOptions = options.position,

		tooltip = this.tooltip,
		showTarget = options.show.target,
		hideTarget = options.hide.target,
		containerTarget = posOptions.container,
		viewportTarget = posOptions.viewport,
		documentTarget = $(document),
		bodyTarget = $(document.body),
		windowTarget = $(window),

		showEvents = options.show.event ? $.trim('' + options.show.event).split(' ') : [],
		hideEvents = options.hide.event ? $.trim('' + options.hide.event).split(' ') : [];


	// Assign passed event callbacks
	$.each(options.events, function(name, callback) {
		self._bind(tooltip, name === 'toggle' ? ['tooltipshow','tooltiphide'] : ['tooltip'+name], callback, null, tooltip);
	});

	// Hide tooltips when leaving current window/frame (but not select/option elements)
	if(/mouse(out|leave)/i.test(options.hide.event) && options.hide.leave === 'window') {
		this._bind(documentTarget, ['mouseout', 'blur'], function(event) {
			if(!/select|option/.test(event.target.nodeName) && !event.relatedTarget) {
				this.hide(event);
			}
		});
	}

	// Enable hide.fixed by adding appropriate class
	if(options.hide.fixed) {
		hideTarget = hideTarget.add( tooltip.addClass(CLASS_FIXED) );
	}

	/*
	 * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
	 * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
	 */
	else if(/mouse(over|enter)/i.test(options.show.event)) {
		this._bind(hideTarget, 'mouseleave', function() {
			clearTimeout(this.timers.show);
		});
	}

	// Hide tooltip on document mousedown if unfocus events are enabled
	if(('' + options.hide.event).indexOf('unfocus') > -1) {
		this._bind(containerTarget.closest('html'), ['mousedown', 'touchstart'], function(event) {
			var elem = $(event.target),
				enabled = this.rendered && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0,
				isAncestor = elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0;

			if(elem[0] !== this.target[0] && elem[0] !== this.tooltip[0] && !isAncestor &&
				!this.target.has(elem[0]).length && enabled
			) {
				this.hide(event);
			}
		});
	}

	// Check if the tooltip hides when inactive
	if('number' === typeof options.hide.inactive) {
		// Bind inactive method to show target(s) as a custom event
		this._bind(showTarget, 'qtip-'+this.id+'-inactive', inactiveMethod, 'inactive');

		// Define events which reset the 'inactive' event handler
		this._bind(hideTarget.add(tooltip), QTIP.inactiveEvents, inactiveMethod);
	}

	// Filter and bind events
	this._bindEvents(showEvents, hideEvents, showTarget, hideTarget, showMethod, hideMethod);

	// Mouse movement bindings
	this._bind(showTarget.add(tooltip), 'mousemove', function(event) {
		// Check if the tooltip hides when mouse is moved a certain distance
		if('number' === typeof options.hide.distance) {
			var origin = this.cache.origin || {},
				limit = this.options.hide.distance,
				abs = Math.abs;

			// Check if the movement has gone beyond the limit, and hide it if so
			if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
				this.hide(event);
			}
		}

		// Cache mousemove coords on show targets
		this._storeMouse(event);
	});

	// Mouse positioning events
	if(posOptions.target === 'mouse') {
		// If mouse adjustment is on...
		if(posOptions.adjust.mouse) {
			// Apply a mouseleave event so we don't get problems with overlapping
			if(options.hide.event) {
				// Track if we're on the target or not
				this._bind(showTarget, ['mouseenter', 'mouseleave'], function(event) {
					if(!this.cache) {return FALSE; }
					this.cache.onTarget = event.type === 'mouseenter';
				});
			}

			// Update tooltip position on mousemove
			this._bind(documentTarget, 'mousemove', function(event) {
				// Update the tooltip position only if the tooltip is visible and adjustment is enabled
				if(this.rendered && this.cache.onTarget && !this.tooltip.hasClass(CLASS_DISABLED) && this.tooltip[0].offsetWidth > 0) {
					this.reposition(event);
				}
			});
		}
	}

	// Adjust positions of the tooltip on window resize if enabled
	if(posOptions.adjust.resize || viewportTarget.length) {
		this._bind( $.event.special.resize ? viewportTarget : windowTarget, 'resize', repositionMethod );
	}

	// Adjust tooltip position on scroll of the window or viewport element if present
	if(posOptions.adjust.scroll) {
		this._bind( windowTarget.add(posOptions.container), 'scroll', repositionMethod );
	}
};

// Un-assignment method
PROTOTYPE._unassignEvents = function() {
	var options = this.options,
		showTargets = options.show.target,
		hideTargets = options.hide.target,
		targets = $.grep([
			this.elements.target[0],
			this.rendered && this.tooltip[0],
			options.position.container[0],
			options.position.viewport[0],
			options.position.container.closest('html')[0], // unfocus
			window,
			document
		], function(i) {
			return typeof i === 'object';
		});

	// Add show and hide targets if they're valid
	if(showTargets && showTargets.toArray) {
		targets = targets.concat(showTargets.toArray());
	}
	if(hideTargets && hideTargets.toArray) {
		targets = targets.concat(hideTargets.toArray());
	}

	// Unbind the events
	this._unbind(targets)
		._unbind(targets, 'destroy')
		._unbind(targets, 'inactive');
};

// Apply common event handlers using delegate (avoids excessive .bind calls!)
$(function() {
	delegate(SELECTOR, ['mouseenter', 'mouseleave'], function(event) {
		var state = event.type === 'mouseenter',
			tooltip = $(event.currentTarget),
			target = $(event.relatedTarget || event.target),
			options = this.options;

		// On mouseenter...
		if(state) {
			// Focus the tooltip on mouseenter (z-index stacking)
			this.focus(event);

			// Clear hide timer on tooltip hover to prevent it from closing
			tooltip.hasClass(CLASS_FIXED) && !tooltip.hasClass(CLASS_DISABLED) && clearTimeout(this.timers.hide);
		}

		// On mouseleave...
		else {
			// When mouse tracking is enabled, hide when we leave the tooltip and not onto the show target (if a hide event is set)
			if(options.position.target === 'mouse' && options.position.adjust.mouse &&
				options.hide.event && options.show.target && !target.closest(options.show.target[0]).length) {
				this.hide(event);
			}
		}

		// Add hover class
		tooltip.toggleClass(CLASS_HOVER, state);
	});

	// Define events which reset the 'inactive' event handler
	delegate('['+ATTR_ID+']', INACTIVE_EVENTS, inactiveMethod);
});
;// Initialization method
function init(elem, id, opts) {
	var obj, posOptions, attr, config, title,

	// Setup element references
	docBody = $(document.body),

	// Use document body instead of document element if needed
	newTarget = elem[0] === document ? docBody : elem,

	// Grab metadata from element if plugin is present
	metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,

	// If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
	metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,

	// Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
	html5 = elem.data(opts.metadata.name || 'qtipopts');

	// If we don't get an object returned attempt to parse it manualyl without parseJSON
	try { html5 = typeof html5 === 'string' ? $.parseJSON(html5) : html5; } catch(e) {}

	// Merge in and sanitize metadata
	config = $.extend(TRUE, {}, QTIP.defaults, opts,
		typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
		sanitizeOptions(metadata5 || metadata));

	// Re-grab our positioning options now we've merged our metadata and set id to passed value
	posOptions = config.position;
	config.id = id;

	// Setup missing content if none is detected
	if('boolean' === typeof config.content.text) {
		attr = elem.attr(config.content.attr);

		// Grab from supplied attribute if available
		if(config.content.attr !== FALSE && attr) { config.content.text = attr; }

		// No valid content was found, abort render
		else { return FALSE; }
	}

	// Setup target options
	if(!posOptions.container.length) { posOptions.container = docBody; }
	if(posOptions.target === FALSE) { posOptions.target = newTarget; }
	if(config.show.target === FALSE) { config.show.target = newTarget; }
	if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
	if(config.hide.target === FALSE) { config.hide.target = newTarget; }
	if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }

	// Ensure we only use a single container
	posOptions.container = posOptions.container.eq(0);

	// Convert position corner values into x and y strings
	posOptions.at = new CORNER(posOptions.at, TRUE);
	posOptions.my = new CORNER(posOptions.my);

	// Destroy previous tooltip if overwrite is enabled, or skip element if not
	if(elem.data(NAMESPACE)) {
		if(config.overwrite) {
			elem.qtip('destroy', true);
		}
		else if(config.overwrite === FALSE) {
			return FALSE;
		}
	}

	// Add has-qtip attribute
	elem.attr(ATTR_HAS, id);

	// Remove title attribute and store it if present
	if(config.suppress && (title = elem.attr('title'))) {
		// Final attr call fixes event delegatiom and IE default tooltip showing problem
		elem.removeAttr('title').attr(oldtitle, title).attr('title', '');
	}

	// Initialize the tooltip and add API reference
	obj = new QTip(elem, config, id, !!attr);
	elem.data(NAMESPACE, obj);

	return obj;
}

// jQuery $.fn extension method
QTIP = $.fn.qtip = function(options, notation, newValue)
{
	var command = ('' + options).toLowerCase(), // Parse command
		returned = NULL,
		args = $.makeArray(arguments).slice(1),
		event = args[args.length - 1],
		opts = this[0] ? $.data(this[0], NAMESPACE) : NULL;

	// Check for API request
	if((!arguments.length && opts) || command === 'api') {
		return opts;
	}

	// Execute API command if present
	else if('string' === typeof options) {
		this.each(function() {
			var api = $.data(this, NAMESPACE);
			if(!api) { return TRUE; }

			// Cache the event if possible
			if(event && event.timeStamp) { api.cache.event = event; }

			// Check for specific API commands
			if(notation && (command === 'option' || command === 'options')) {
				if(newValue !== undefined || $.isPlainObject(notation)) {
					api.set(notation, newValue);
				}
				else {
					returned = api.get(notation);
					return FALSE;
				}
			}

			// Execute API command
			else if(api[command]) {
				api[command].apply(api, args);
			}
		});

		return returned !== NULL ? returned : this;
	}

	// No API commands. validate provided options and setup qTips
	else if('object' === typeof options || !arguments.length) {
		// Sanitize options first
		opts = sanitizeOptions($.extend(TRUE, {}, options));

		return this.each(function(i) {
			var api, id;

			// Find next available ID, or use custom ID if provided
			id = $.isArray(opts.id) ? opts.id[i] : opts.id;
			id = !id || id === FALSE || id.length < 1 || QTIP.api[id] ? QTIP.nextid++ : id;

			// Initialize the qTip and re-grab newly sanitized options
			api = init($(this), id, opts);
			if(api === FALSE) { return TRUE; }
			else { QTIP.api[id] = api; }

			// Initialize plugins
			$.each(PLUGINS, function() {
				if(this.initialize === 'initialize') { this(api); }
			});

			// Assign initial pre-render events
			api._assignInitialEvents(event);
		});
	}
};

// Expose class
$.qtip = QTip;

// Populated in render method
QTIP.api = {};
;$.each({
	/* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
	attr: function(attr, val) {
		if(this.length) {
			var self = this[0],
				title = 'title',
				api = $.data(self, 'qtip');

			if(attr === title && api && 'object' === typeof api && api.options.suppress) {
				if(arguments.length < 2) {
					return $.attr(self, oldtitle);
				}

				// If qTip is rendered and title was originally used as content, update it
				if(api && api.options.content.attr === title && api.cache.attr) {
					api.set('content.text', val);
				}

				// Use the regular attr method to set, then cache the result
				return this.attr(oldtitle, val);
			}
		}

		return $.fn['attr'+replaceSuffix].apply(this, arguments);
	},

	/* Allow clone to correctly retrieve cached title attributes */
	clone: function(keepData) {
		var titles = $([]), title = 'title',

		// Clone our element using the real clone method
		elems = $.fn['clone'+replaceSuffix].apply(this, arguments);

		// Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
		if(!keepData) {
			elems.filter('['+oldtitle+']').attr('title', function() {
				return $.attr(this, oldtitle);
			})
			.removeAttr(oldtitle);
		}

		return elems;
	}
}, function(name, func) {
	if(!func || $.fn[name+replaceSuffix]) { return TRUE; }

	var old = $.fn[name+replaceSuffix] = $.fn[name];
	$.fn[name] = function() {
		return func.apply(this, arguments) || old.apply(this, arguments);
	};
});

/* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
 * This snippet is taken directly from jQuery UI source code found here:
 *     http://code.jquery.com/ui/jquery-ui-git.js
 */
if(!$.ui) {
	$['cleanData'+replaceSuffix] = $.cleanData;
	$.cleanData = function( elems ) {
		for(var i = 0, elem; (elem = $( elems[i] )).length; i++) {
			if(elem.attr(ATTR_HAS)) {
				try { elem.triggerHandler('removeqtip'); }
				catch( e ) {}
			}
		}
		$['cleanData'+replaceSuffix].apply(this, arguments);
	};
}
;// qTip version
QTIP.version = '2.2.1';

// Base ID for all qTips
QTIP.nextid = 0;

// Inactive events array
QTIP.inactiveEvents = INACTIVE_EVENTS;

// Base z-index for all qTips
QTIP.zindex = 15000;

// Define configuration defaults
QTIP.defaults = {
	prerender: FALSE,
	id: FALSE,
	overwrite: TRUE,
	suppress: TRUE,
	content: {
		text: TRUE,
		attr: 'title',
		title: FALSE,
		button: FALSE
	},
	position: {
		my: 'top left',
		at: 'bottom right',
		target: FALSE,
		container: FALSE,
		viewport: FALSE,
		adjust: {
			x: 0, y: 0,
			mouse: TRUE,
			scroll: TRUE,
			resize: TRUE,
			method: 'flipinvert flipinvert'
		},
		effect: function(api, pos, viewport) {
			$(this).animate(pos, {
				duration: 200,
				queue: FALSE
			});
		}
	},
	show: {
		target: FALSE,
		event: 'mouseenter',
		effect: TRUE,
		delay: 90,
		solo: FALSE,
		ready: FALSE,
		autofocus: FALSE
	},
	hide: {
		target: FALSE,
		event: 'mouseleave',
		effect: TRUE,
		delay: 0,
		fixed: FALSE,
		inactive: FALSE,
		leave: 'window',
		distance: FALSE
	},
	style: {
		classes: '',
		widget: FALSE,
		width: FALSE,
		height: FALSE,
		def: TRUE
	},
	events: {
		render: NULL,
		move: NULL,
		show: NULL,
		hide: NULL,
		toggle: NULL,
		visible: NULL,
		hidden: NULL,
		focus: NULL,
		blur: NULL
	}
};
;var TIP,

// .bind()/.on() namespace
TIPNS = '.qtip-tip',

// Common CSS strings
MARGIN = 'margin',
BORDER = 'border',
COLOR = 'color',
BG_COLOR = 'background-color',
TRANSPARENT = 'transparent',
IMPORTANT = ' !important',

// Check if the browser supports <canvas/> elements
HASCANVAS = !!document.createElement('canvas').getContext,

// Invalid colour values used in parseColours()
INVALID = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i;

// Camel-case method, taken from jQuery source
// http://code.jquery.com/jquery-1.8.0.js
function camel(s) { return s.charAt(0).toUpperCase() + s.slice(1); }

/*
 * Modified from Modernizr's testPropsAll()
 * http://modernizr.com/downloads/modernizr-latest.js
 */
var cssProps = {}, cssPrefixes = ["Webkit", "O", "Moz", "ms"];
function vendorCss(elem, prop) {
	var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
		props = (prop + ' ' + cssPrefixes.join(ucProp + ' ') + ucProp).split(' '),
		cur, val, i = 0;

	// If the property has already been mapped...
	if(cssProps[prop]) { return elem.css(cssProps[prop]); }

	while((cur = props[i++])) {
		if((val = elem.css(cur)) !== undefined) {
			return cssProps[prop] = cur, val;
		}
	}
}

// Parse a given elements CSS property into an int
function intCss(elem, prop) {
	return Math.ceil(parseFloat(vendorCss(elem, prop)));
}


// VML creation (for IE only)
if(!HASCANVAS) {
	var createVML = function(tag, props, style) {
		return '<qtipvml:'+tag+' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" '+(props||'')+
			' style="behavior: url(#default#VML); '+(style||'')+ '" />';
	};
}

// Canvas only definitions
else {
	var PIXEL_RATIO = window.devicePixelRatio || 1,
		BACKING_STORE_RATIO = (function() {
			var context = document.createElement('canvas').getContext('2d');
			return context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio ||
					context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || 1;
		}()),
		SCALE = PIXEL_RATIO / BACKING_STORE_RATIO;
}


function Tip(qtip, options) {
	this._ns = 'tip';
	this.options = options;
	this.offset = options.offset;
	this.size = [ options.width, options.height ];

	// Initialize
	this.init( (this.qtip = qtip) );
}

$.extend(Tip.prototype, {
	init: function(qtip) {
		var context, tip;

		// Create tip element and prepend to the tooltip
		tip = this.element = qtip.elements.tip = $('<div />', { 'class': NAMESPACE+'-tip' }).prependTo(qtip.tooltip);

		// Create tip drawing element(s)
		if(HASCANVAS) {
			// save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
			context = $('<canvas />').appendTo(this.element)[0].getContext('2d');

			// Setup constant parameters
			context.lineJoin = 'miter';
			context.miterLimit = 100000;
			context.save();
		}
		else {
			context = createVML('shape', 'coordorigin="0,0"', 'position:absolute;');
			this.element.html(context + context);

			// Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML
			qtip._bind( $('*', tip).add(tip), ['click', 'mousedown'], function(event) { event.stopPropagation(); }, this._ns);
		}

		// Bind update events
		qtip._bind(qtip.tooltip, 'tooltipmove', this.reposition, this._ns, this);

		// Create it
		this.create();
	},

	_swapDimensions: function() {
		this.size[0] = this.options.height;
		this.size[1] = this.options.width;
	},
	_resetDimensions: function() {
		this.size[0] = this.options.width;
		this.size[1] = this.options.height;
	},

	_useTitle: function(corner) {
		var titlebar = this.qtip.elements.titlebar;
		return titlebar && (
			corner.y === TOP || (corner.y === CENTER && this.element.position().top + (this.size[1] / 2) + this.options.offset < titlebar.outerHeight(TRUE))
		);
	},

	_parseCorner: function(corner) {
		var my = this.qtip.options.position.my;

		// Detect corner and mimic properties
		if(corner === FALSE || my === FALSE) {
			corner = FALSE;
		}
		else if(corner === TRUE) {
			corner = new CORNER( my.string() );
		}
		else if(!corner.string) {
			corner = new CORNER(corner);
			corner.fixed = TRUE;
		}

		return corner;
	},

	_parseWidth: function(corner, side, use) {
		var elements = this.qtip.elements,
			prop = BORDER + camel(side) + 'Width';

		return (use ? intCss(use, prop) : (
			intCss(elements.content, prop) ||
			intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
			intCss(elements.tooltip, prop)
		)) || 0;
	},

	_parseRadius: function(corner) {
		var elements = this.qtip.elements,
			prop = BORDER + camel(corner.y) + camel(corner.x) + 'Radius';

		return BROWSER.ie < 9 ? 0 :
			intCss(this._useTitle(corner) && elements.titlebar || elements.content, prop) ||
			intCss(elements.tooltip, prop) || 0;
	},

	_invalidColour: function(elem, prop, compare) {
		var val = elem.css(prop);
		return !val || (compare && val === elem.css(compare)) || INVALID.test(val) ? FALSE : val;
	},

	_parseColours: function(corner) {
		var elements = this.qtip.elements,
			tip = this.element.css('cssText', ''),
			borderSide = BORDER + camel(corner[ corner.precedance ]) + camel(COLOR),
			colorElem = this._useTitle(corner) && elements.titlebar || elements.content,
			css = this._invalidColour, color = [];

		// Attempt to detect the background colour from various elements, left-to-right precedance
		color[0] = css(tip, BG_COLOR) || css(colorElem, BG_COLOR) || css(elements.content, BG_COLOR) ||
			css(elements.tooltip, BG_COLOR) || tip.css(BG_COLOR);

		// Attempt to detect the correct border side colour from various elements, left-to-right precedance
		color[1] = css(tip, borderSide, COLOR) || css(colorElem, borderSide, COLOR) ||
			css(elements.content, borderSide, COLOR) || css(elements.tooltip, borderSide, COLOR) || elements.tooltip.css(borderSide);

		// Reset background and border colours
		$('*', tip).add(tip).css('cssText', BG_COLOR+':'+TRANSPARENT+IMPORTANT+';'+BORDER+':0'+IMPORTANT+';');

		return color;
	},

	_calculateSize: function(corner) {
		var y = corner.precedance === Y,
			width = this.options['width'],
			height = this.options['height'],
			isCenter = corner.abbrev() === 'c',
			base = (y ? width: height) * (isCenter ? 0.5 : 1),
			pow = Math.pow,
			round = Math.round,
			bigHyp, ratio, result,

		smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),
		hyp = [ (this.border / base) * smallHyp, (this.border / height) * smallHyp ];

		hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(this.border, 2) );
		hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(this.border, 2) );

		bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
		ratio = bigHyp / smallHyp;

		result = [ round(ratio * width), round(ratio * height) ];
		return y ? result : result.reverse();
	},

	// Tip coordinates calculator
	_calculateTip: function(corner, size, scale) {
		scale = scale || 1;
		size = size || this.size;

		var width = size[0] * scale,
			height = size[1] * scale,
			width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),

		// Define tip coordinates in terms of height and width values
		tips = {
			br:	[0,0,		width,height,	width,0],
			bl:	[0,0,		width,0,		0,height],
			tr:	[0,height,	width,0,		width,height],
			tl:	[0,0,		0,height,		width,height],
			tc:	[0,height,	width2,0,		width,height],
			bc:	[0,0,		width,0,		width2,height],
			rc:	[0,0,		width,height2,	0,height],
			lc:	[width,0,	width,height,	0,height2]
		};

		// Set common side shapes
		tips.lt = tips.br; tips.rt = tips.bl;
		tips.lb = tips.tr; tips.rb = tips.tl;

		return tips[ corner.abbrev() ];
	},

	// Tip coordinates drawer (canvas)
	_drawCoords: function(context, coords) {
		context.beginPath();
		context.moveTo(coords[0], coords[1]);
		context.lineTo(coords[2], coords[3]);
		context.lineTo(coords[4], coords[5]);
		context.closePath();
	},

	create: function() {
		// Determine tip corner
		var c = this.corner = (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner);

		// If we have a tip corner...
		if( (this.enabled = !!this.corner && this.corner.abbrev() !== 'c') ) {
			// Cache it
			this.qtip.cache.corner = c.clone();

			// Create it
			this.update();
		}

		// Toggle tip element
		this.element.toggle(this.enabled);

		return this.corner;
	},

	update: function(corner, position) {
		if(!this.enabled) { return this; }

		var elements = this.qtip.elements,
			tip = this.element,
			inner = tip.children(),
			options = this.options,
			curSize = this.size,
			mimic = options.mimic,
			round = Math.round,
			color, precedance, context,
			coords, bigCoords, translate, newSize, border, BACKING_STORE_RATIO;

		// Re-determine tip if not already set
		if(!corner) { corner = this.qtip.cache.corner || this.corner; }

		// Use corner property if we detect an invalid mimic value
		if(mimic === FALSE) { mimic = corner; }

		// Otherwise inherit mimic properties from the corner object as necessary
		else {
			mimic = new CORNER(mimic);
			mimic.precedance = corner.precedance;

			if(mimic.x === 'inherit') { mimic.x = corner.x; }
			else if(mimic.y === 'inherit') { mimic.y = corner.y; }
			else if(mimic.x === mimic.y) {
				mimic[ corner.precedance ] = corner[ corner.precedance ];
			}
		}
		precedance = mimic.precedance;

		// Ensure the tip width.height are relative to the tip position
		if(corner.precedance === X) { this._swapDimensions(); }
		else { this._resetDimensions(); }

		// Update our colours
		color = this.color = this._parseColours(corner);

		// Detect border width, taking into account colours
		if(color[1] !== TRANSPARENT) {
			// Grab border width
			border = this.border = this._parseWidth(corner, corner[corner.precedance]);

			// If border width isn't zero, use border color as fill if it's not invalid (1.0 style tips)
			if(options.border && border < 1 && !INVALID.test(color[1])) { color[0] = color[1]; }

			// Set border width (use detected border width if options.border is true)
			this.border = border = options.border !== TRUE ? options.border : border;
		}

		// Border colour was invalid, set border to zero
		else { this.border = border = 0; }

		// Determine tip size
		newSize = this.size = this._calculateSize(corner);
		tip.css({
			width: newSize[0],
			height: newSize[1],
			lineHeight: newSize[1]+'px'
		});

		// Calculate tip translation
		if(corner.precedance === Y) {
			translate = [
				round(mimic.x === LEFT ? border : mimic.x === RIGHT ? newSize[0] - curSize[0] - border : (newSize[0] - curSize[0]) / 2),
				round(mimic.y === TOP ? newSize[1] - curSize[1] : 0)
			];
		}
		else {
			translate = [
				round(mimic.x === LEFT ? newSize[0] - curSize[0] : 0),
				round(mimic.y === TOP ? border : mimic.y === BOTTOM ? newSize[1] - curSize[1] - border : (newSize[1] - curSize[1]) / 2)
			];
		}

		// Canvas drawing implementation
		if(HASCANVAS) {
			// Grab canvas context and clear/save it
			context = inner[0].getContext('2d');
			context.restore(); context.save();
			context.clearRect(0,0,6000,6000);

			// Calculate coordinates
			coords = this._calculateTip(mimic, curSize, SCALE);
			bigCoords = this._calculateTip(mimic, this.size, SCALE);

			// Set the canvas size using calculated size
			inner.attr(WIDTH, newSize[0] * SCALE).attr(HEIGHT, newSize[1] * SCALE);
			inner.css(WIDTH, newSize[0]).css(HEIGHT, newSize[1]);

			// Draw the outer-stroke tip
			this._drawCoords(context, bigCoords);
			context.fillStyle = color[1];
			context.fill();

			// Draw the actual tip
			context.translate(translate[0] * SCALE, translate[1] * SCALE);
			this._drawCoords(context, coords);
			context.fillStyle = color[0];
			context.fill();
		}

		// VML (IE Proprietary implementation)
		else {
			// Calculate coordinates
			coords = this._calculateTip(mimic);

			// Setup coordinates string
			coords = 'm' + coords[0] + ',' + coords[1] + ' l' + coords[2] +
				',' + coords[3] + ' ' + coords[4] + ',' + coords[5] + ' xe';

			// Setup VML-specific offset for pixel-perfection
			translate[2] = border && /^(r|b)/i.test(corner.string()) ?
				BROWSER.ie === 8 ? 2 : 1 : 0;

			// Set initial CSS
			inner.css({
				coordsize: (newSize[0]+border) + ' ' + (newSize[1]+border),
				antialias: ''+(mimic.string().indexOf(CENTER) > -1),
				left: translate[0] - (translate[2] * Number(precedance === X)),
				top: translate[1] - (translate[2] * Number(precedance === Y)),
				width: newSize[0] + border,
				height: newSize[1] + border
			})
			.each(function(i) {
				var $this = $(this);

				// Set shape specific attributes
				$this[ $this.prop ? 'prop' : 'attr' ]({
					coordsize: (newSize[0]+border) + ' ' + (newSize[1]+border),
					path: coords,
					fillcolor: color[0],
					filled: !!i,
					stroked: !i
				})
				.toggle(!!(border || i));

				// Check if border is enabled and add stroke element
				!i && $this.html( createVML(
					'stroke', 'weight="'+(border*2)+'px" color="'+color[1]+'" miterlimit="1000" joinstyle="miter"'
				) );
			});
		}

		// Opera bug #357 - Incorrect tip position
		// https://github.com/Craga89/qTip2/issues/367
		window.opera && setTimeout(function() {
			elements.tip.css({
				display: 'inline-block',
				visibility: 'visible'
			});
		}, 1);

		// Position if needed
		if(position !== FALSE) { this.calculate(corner, newSize); }
	},

	calculate: function(corner, size) {
		if(!this.enabled) { return FALSE; }

		var self = this,
			elements = this.qtip.elements,
			tip = this.element,
			userOffset = this.options.offset,
			isWidget = elements.tooltip.hasClass('ui-widget'),
			position = {  },
			precedance, corners;

		// Inherit corner if not provided
		corner = corner || this.corner;
		precedance = corner.precedance;

		// Determine which tip dimension to use for adjustment
		size = size || this._calculateSize(corner);

		// Setup corners and offset array
		corners = [ corner.x, corner.y ];
		if(precedance === X) { corners.reverse(); }

		// Calculate tip position
		$.each(corners, function(i, side) {
			var b, bc, br;

			if(side === CENTER) {
				b = precedance === Y ? LEFT : TOP;
				position[ b ] = '50%';
				position[MARGIN+'-' + b] = -Math.round(size[ precedance === Y ? 0 : 1 ] / 2) + userOffset;
			}
			else {
				b = self._parseWidth(corner, side, elements.tooltip);
				bc = self._parseWidth(corner, side, elements.content);
				br = self._parseRadius(corner);

				position[ side ] = Math.max(-self.border, i ? bc : (userOffset + (br > b ? br : -b)));
			}
		});

		// Adjust for tip size
		position[ corner[precedance] ] -= size[ precedance === X ? 0 : 1 ];

		// Set and return new position
		tip.css({ margin: '', top: '', bottom: '', left: '', right: '' }).css(position);
		return position;
	},

	reposition: function(event, api, pos, viewport) {
		if(!this.enabled) { return; }

		var cache = api.cache,
			newCorner = this.corner.clone(),
			adjust = pos.adjusted,
			method = api.options.position.adjust.method.split(' '),
			horizontal = method[0],
			vertical = method[1] || method[0],
			shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
			offset, css = {}, props;

		function shiftflip(direction, precedance, popposite, side, opposite) {
			// Horizontal - Shift or flip method
			if(direction === SHIFT && newCorner.precedance === precedance && adjust[side] && newCorner[popposite] !== CENTER) {
				newCorner.precedance = newCorner.precedance === X ? Y : X;
			}
			else if(direction !== SHIFT && adjust[side]){
				newCorner[precedance] = newCorner[precedance] === CENTER ?
					(adjust[side] > 0 ? side : opposite) : (newCorner[precedance] === side ? opposite : side);
			}
		}

		function shiftonly(xy, side, opposite) {
			if(newCorner[xy] === CENTER) {
				css[MARGIN+'-'+side] = shift[xy] = offset[MARGIN+'-'+side] - adjust[side];
			}
			else {
				props = offset[opposite] !== undefined ?
					[ adjust[side], -offset[side] ] : [ -adjust[side], offset[side] ];

				if( (shift[xy] = Math.max(props[0], props[1])) > props[0] ) {
					pos[side] -= adjust[side];
					shift[side] = FALSE;
				}

				css[ offset[opposite] !== undefined ? opposite : side ] = shift[xy];
			}
		}

		// If our tip position isn't fixed e.g. doesn't adjust with viewport...
		if(this.corner.fixed !== TRUE) {
			// Perform shift/flip adjustments
			shiftflip(horizontal, X, Y, LEFT, RIGHT);
			shiftflip(vertical, Y, X, TOP, BOTTOM);

			// Update and redraw the tip if needed (check cached details of last drawn tip)
			if(newCorner.string() !== cache.corner.string() || cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left) {
				this.update(newCorner, FALSE);
			}
		}

		// Setup tip offset properties
		offset = this.calculate(newCorner);

		// Readjust offset object to make it left/top
		if(offset.right !== undefined) { offset.left = -offset.right; }
		if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
		offset.user = this.offset;

		// Perform shift adjustments
		if(shift.left = (horizontal === SHIFT && !!adjust.left)) { shiftonly(X, LEFT, RIGHT); }
		if(shift.top = (vertical === SHIFT && !!adjust.top)) { shiftonly(Y, TOP, BOTTOM); }

		/*
		* If the tip is adjusted in both dimensions, or in a
		* direction that would cause it to be anywhere but the
		* outer border, hide it!
		*/
		this.element.css(css).toggle(
			!((shift.x && shift.y) || (newCorner.x === CENTER && shift.y) || (newCorner.y === CENTER && shift.x))
		);

		// Adjust position to accomodate tip dimensions
		pos.left -= offset.left.charAt ? offset.user :
			horizontal !== SHIFT || shift.top || !shift.left && !shift.top ? offset.left + this.border : 0;
		pos.top -= offset.top.charAt ? offset.user :
			vertical !== SHIFT || shift.left || !shift.left && !shift.top ? offset.top + this.border : 0;

		// Cache details
		cache.cornerLeft = adjust.left; cache.cornerTop = adjust.top;
		cache.corner = newCorner.clone();
	},

	destroy: function() {
		// Unbind events
		this.qtip._unbind(this.qtip.tooltip, this._ns);

		// Remove the tip element(s)
		if(this.qtip.elements.tip) {
			this.qtip.elements.tip.find('*')
				.remove().end().remove();
		}
	}
});

TIP = PLUGINS.tip = function(api) {
	return new Tip(api, api.options.style.tip);
};

// Initialize tip on render
TIP.initialize = 'render';

// Setup plugin sanitization options
TIP.sanitize = function(options) {
	if(options.style && 'tip' in options.style) {
		var opts = options.style.tip;
		if(typeof opts !== 'object') { opts = options.style.tip = { corner: opts }; }
		if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; }
	}
};

// Add new option checks for the plugin
CHECKS.tip = {
	'^position.my|style.tip.(corner|mimic|border)$': function() {
		// Make sure a tip can be drawn
		this.create();

		// Reposition the tooltip
		this.qtip.reposition();
	},
	'^style.tip.(height|width)$': function(obj) {
		// Re-set dimensions and redraw the tip
		this.size = [ obj.width, obj.height ];
		this.update();

		// Reposition the tooltip
		this.qtip.reposition();
	},
	'^content.title|style.(classes|widget)$': function() {
		this.update();
	}
};

// Extend original qTip defaults
$.extend(TRUE, QTIP.defaults, {
	style: {
		tip: {
			corner: TRUE,
			mimic: FALSE,
			width: 6,
			height: 6,
			border: TRUE,
			offset: 0
		}
	}
});
;var MODAL, OVERLAY,
	MODALCLASS = 'qtip-modal',
	MODALSELECTOR = '.'+MODALCLASS;

OVERLAY = function()
{
	var self = this,
		focusableElems = {},
		current, onLast,
		prevState, elem;

	// Modified code from jQuery UI 1.10.0 source
	// http://code.jquery.com/ui/1.10.0/jquery-ui.js
	function focusable(element) {
		// Use the defined focusable checker when possible
		if($.expr[':'].focusable) { return $.expr[':'].focusable; }

		var isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')),
			nodeName = element.nodeName && element.nodeName.toLowerCase(),
			map, mapName, img;

		if('area' === nodeName) {
			map = element.parentNode;
			mapName = map.name;
			if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map') {
				return false;
			}
			img = $('img[usemap=#' + mapName + ']')[0];
			return !!img && img.is(':visible');
		}
		return (/input|select|textarea|button|object/.test( nodeName ) ?
				!element.disabled :
				'a' === nodeName ?
					element.href || isTabIndexNotNaN :
					isTabIndexNotNaN
			);
	}

	// Focus inputs using cached focusable elements (see update())
	function focusInputs(blurElems) {
		// Blurring body element in IE causes window.open windows to unfocus!
		if(focusableElems.length < 1 && blurElems.length) { blurElems.not('body').blur(); }

		// Focus the inputs
		else { focusableElems.first().focus(); }
	}

	// Steal focus from elements outside tooltip
	function stealFocus(event) {
		if(!elem.is(':visible')) { return; }

		var target = $(event.target),
			tooltip = current.tooltip,
			container = target.closest(SELECTOR),
			targetOnTop;

		// Determine if input container target is above this
		targetOnTop = container.length < 1 ? FALSE :
			(parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10));

		// If we're showing a modal, but focus has landed on an input below
		// this modal, divert focus to the first visible input in this modal
		// or if we can't find one... the tooltip itself
		if(!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) {
			focusInputs(target);
		}

		// Detect when we leave the last focusable element...
		onLast = event.target === focusableElems[focusableElems.length - 1];
	}

	$.extend(self, {
		init: function() {
			// Create document overlay
			elem = self.elem = $('<div />', {
				id: 'qtip-overlay',
				html: '<div></div>',
				mousedown: function() { return FALSE; }
			})
			.hide();

			// Make sure we can't focus anything outside the tooltip
			$(document.body).bind('focusin'+MODALSELECTOR, stealFocus);

			// Apply keyboard "Escape key" close handler
			$(document).bind('keydown'+MODALSELECTOR, function(event) {
				if(current && current.options.show.modal.escape && event.keyCode === 27) {
					current.hide(event);
				}
			});

			// Apply click handler for blur option
			elem.bind('click'+MODALSELECTOR, function(event) {
				if(current && current.options.show.modal.blur) {
					current.hide(event);
				}
			});

			return self;
		},

		update: function(api) {
			// Update current API reference
			current = api;

			// Update focusable elements if enabled
			if(api.options.show.modal.stealfocus !== FALSE) {
				focusableElems = api.tooltip.find('*').filter(function() {
					return focusable(this);
				});
			}
			else { focusableElems = []; }
		},

		toggle: function(api, state, duration) {
			var docBody = $(document.body),
				tooltip = api.tooltip,
				options = api.options.show.modal,
				effect = options.effect,
				type = state ? 'show': 'hide',
				visible = elem.is(':visible'),
				visibleModals = $(MODALSELECTOR).filter(':visible:not(:animated)').not(tooltip),
				zindex;

			// Set active tooltip API reference
			self.update(api);

			// If the modal can steal the focus...
			// Blur the current item and focus anything in the modal we an
			if(state && options.stealfocus !== FALSE) {
				focusInputs( $(':focus') );
			}

			// Toggle backdrop cursor style on show
			elem.toggleClass('blurs', options.blur);

			// Append to body on show
			if(state) {
				elem.appendTo(document.body);
			}

			// Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
			if((elem.is(':animated') && visible === state && prevState !== FALSE) || (!state && visibleModals.length)) {
				return self;
			}

			// Stop all animations
			elem.stop(TRUE, FALSE);

			// Use custom function if provided
			if($.isFunction(effect)) {
				effect.call(elem, state);
			}

			// If no effect type is supplied, use a simple toggle
			else if(effect === FALSE) {
				elem[ type ]();
			}

			// Use basic fade function
			else {
				elem.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
					if(!state) { elem.hide(); }
				});
			}

			// Reset position and detach from body on hide
			if(!state) {
				elem.queue(function(next) {
					elem.css({ left: '', top: '' });
					if(!$(MODALSELECTOR).length) { elem.detach(); }
					next();
				});
			}

			// Cache the state
			prevState = state;

			// If the tooltip is destroyed, set reference to null
			if(current.destroyed) { current = NULL; }

			return self;
		}
	});

	self.init();
};
OVERLAY = new OVERLAY();

function Modal(api, options) {
	this.options = options;
	this._ns = '-modal';

	this.init( (this.qtip = api) );
}

$.extend(Modal.prototype, {
	init: function(qtip) {
		var tooltip = qtip.tooltip;

		// If modal is disabled... return
		if(!this.options.on) { return this; }

		// Set overlay reference
		qtip.elements.overlay = OVERLAY.elem;

		// Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index
		tooltip.addClass(MODALCLASS).css('z-index', QTIP.modal_zindex + $(MODALSELECTOR).length);

		// Apply our show/hide/focus modal events
		qtip._bind(tooltip, ['tooltipshow', 'tooltiphide'], function(event, api, duration) {
			var oEvent = event.originalEvent;

			// Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
			if(event.target === tooltip[0]) {
				if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length) {
					try { event.preventDefault(); } catch(e) {}
				}
				else if(!oEvent || (oEvent && oEvent.type !== 'tooltipsolo')) {
					this.toggle(event, event.type === 'tooltipshow', duration);
				}
			}
		}, this._ns, this);

		// Adjust modal z-index on tooltip focus
		qtip._bind(tooltip, 'tooltipfocus', function(event, api) {
			// If focus was cancelled before it reached us, don't do anything
			if(event.isDefaultPrevented() || event.target !== tooltip[0]) { return; }

			var qtips = $(MODALSELECTOR),

			// Keep the modal's lower than other, regular qtips
			newIndex = QTIP.modal_zindex + qtips.length,
			curIndex = parseInt(tooltip[0].style.zIndex, 10);

			// Set overlay z-index
			OVERLAY.elem[0].style.zIndex = newIndex - 1;

			// Reduce modal z-index's and keep them properly ordered
			qtips.each(function() {
				if(this.style.zIndex > curIndex) {
					this.style.zIndex -= 1;
				}
			});

			// Fire blur event for focused tooltip
			qtips.filter('.' + CLASS_FOCUS).qtip('blur', event.originalEvent);

			// Set the new z-index
			tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;

			// Set current
			OVERLAY.update(api);

			// Prevent default handling
			try { event.preventDefault(); } catch(e) {}
		}, this._ns, this);

		// Focus any other visible modals when this one hides
		qtip._bind(tooltip, 'tooltiphide', function(event) {
			if(event.target === tooltip[0]) {
				$(MODALSELECTOR).filter(':visible').not(tooltip).last().qtip('focus', event);
			}
		}, this._ns, this);
	},

	toggle: function(event, state, duration) {
		// Make sure default event hasn't been prevented
		if(event && event.isDefaultPrevented()) { return this; }

		// Toggle it
		OVERLAY.toggle(this.qtip, !!state, duration);
	},

	destroy: function() {
		// Remove modal class
		this.qtip.tooltip.removeClass(MODALCLASS);

		// Remove bound events
		this.qtip._unbind(this.qtip.tooltip, this._ns);

		// Delete element reference
		OVERLAY.toggle(this.qtip, FALSE);
		delete this.qtip.elements.overlay;
	}
});


MODAL = PLUGINS.modal = function(api) {
	return new Modal(api, api.options.show.modal);
};

// Setup sanitiztion rules
MODAL.sanitize = function(opts) {
	if(opts.show) {
		if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
		else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
	}
};

// Base z-index for all modal tooltips (use qTip core z-index as a base)
QTIP.modal_zindex = QTIP.zindex - 200;

// Plugin needs to be initialized on render
MODAL.initialize = 'render';

// Setup option set checks
CHECKS.modal = {
	'^show.modal.(on|blur)$': function() {
		// Initialise
		this.destroy();
		this.init();

		// Show the modal if not visible already and tooltip is visible
		this.qtip.elems.overlay.toggle(
			this.qtip.tooltip[0].offsetWidth > 0
		);
	}
};

// Extend original api defaults
$.extend(TRUE, QTIP.defaults, {
	show: {
		modal: {
			on: FALSE,
			effect: TRUE,
			blur: TRUE,
			stealfocus: TRUE,
			escape: TRUE
		}
	}
});
;PLUGINS.viewport = function(api, position, posOptions, targetWidth, targetHeight, elemWidth, elemHeight)
{
	var target = posOptions.target,
		tooltip = api.elements.tooltip,
		my = posOptions.my,
		at = posOptions.at,
		adjust = posOptions.adjust,
		method = adjust.method.split(' '),
		methodX = method[0],
		methodY = method[1] || method[0],
		viewport = posOptions.viewport,
		container = posOptions.container,
		cache = api.cache,
		adjusted = { left: 0, top: 0 },
		fixed, newMy, containerOffset, containerStatic,
		viewportWidth, viewportHeight, viewportScroll, viewportOffset;

	// If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return
	if(!viewport.jquery || target[0] === window || target[0] === document.body || adjust.method === 'none') {
		return adjusted;
	}

	// Cach container details
	containerOffset = container.offset() || adjusted;
	containerStatic = container.css('position') === 'static';

	// Cache our viewport details
	fixed = tooltip.css('position') === 'fixed';
	viewportWidth = viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE);
	viewportHeight = viewport[0] === window ? viewport.height() : viewport.outerHeight(FALSE);
	viewportScroll = { left: fixed ? 0 : viewport.scrollLeft(), top: fixed ? 0 : viewport.scrollTop() };
	viewportOffset = viewport.offset() || adjusted;

	// Generic calculation method
	function calculate(side, otherSide, type, adjust, side1, side2, lengthName, targetLength, elemLength) {
		var initialPos = position[side1],
			mySide = my[side],
			atSide = at[side],
			isShift = type === SHIFT,
			myLength = mySide === side1 ? elemLength : mySide === side2 ? -elemLength : -elemLength / 2,
			atLength = atSide === side1 ? targetLength : atSide === side2 ? -targetLength : -targetLength / 2,
			sideOffset = viewportScroll[side1] + viewportOffset[side1] - (containerStatic ? 0 : containerOffset[side1]),
			overflow1 = sideOffset - initialPos,
			overflow2 = initialPos + elemLength - (lengthName === WIDTH ? viewportWidth : viewportHeight) - sideOffset,
			offset = myLength - (my.precedance === side || mySide === my[otherSide] ? atLength : 0) - (atSide === CENTER ? targetLength / 2 : 0);

		// shift
		if(isShift) {
			offset = (mySide === side1 ? 1 : -1) * myLength;

			// Adjust position but keep it within viewport dimensions
			position[side1] += overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
			position[side1] = Math.max(
				-containerOffset[side1] + viewportOffset[side1],
				initialPos - offset,
				Math.min(
					Math.max(
						-containerOffset[side1] + viewportOffset[side1] + (lengthName === WIDTH ? viewportWidth : viewportHeight),
						initialPos + offset
					),
					position[side1],

					// Make sure we don't adjust complete off the element when using 'center'
					mySide === 'center' ? initialPos - myLength : 1E9
				)
			);

		}

		// flip/flipinvert
		else {
			// Update adjustment amount depending on if using flipinvert or flip
			adjust *= (type === FLIPINVERT ? 2 : 0);

			// Check for overflow on the left/top
			if(overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
				position[side1] -= offset + adjust;
				newMy.invert(side, side1);
			}

			// Check for overflow on the bottom/right
			else if(overflow2 > 0 && (mySide !== side2 || overflow1 > 0)  ) {
				position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;
				newMy.invert(side, side2);
			}

			// Make sure we haven't made things worse with the adjustment and reset if so
			if(position[side1] < viewportScroll && -position[side1] > overflow2) {
				position[side1] = initialPos; newMy = my.clone();
			}
		}

		return position[side1] - initialPos;
	}

	// Set newMy if using flip or flipinvert methods
	if(methodX !== 'shift' || methodY !== 'shift') { newMy = my.clone(); }

	// Adjust position based onviewport and adjustment options
	adjusted = {
		left: methodX !== 'none' ? calculate( X, Y, methodX, adjust.x, LEFT, RIGHT, WIDTH, targetWidth, elemWidth ) : 0,
		top: methodY !== 'none' ? calculate( Y, X, methodY, adjust.y, TOP, BOTTOM, HEIGHT, targetHeight, elemHeight ) : 0,
		my: newMy
	};

	return adjusted;
};
;PLUGINS.polys = {
	// POLY area coordinate calculator
	//	Special thanks to Ed Cradock for helping out with this.
	//	Uses a binary search algorithm to find suitable coordinates.
	polygon: function(baseCoords, corner) {
		var result = {
			width: 0, height: 0,
			position: {
				top: 1e10, right: 0,
				bottom: 0, left: 1e10
			},
			adjustable: FALSE
		},
		i = 0, next,
		coords = [],
		compareX = 1, compareY = 1,
		realX = 0, realY = 0,
		newWidth, newHeight;

		// First pass, sanitize coords and determine outer edges
		i = baseCoords.length; while(i--) {
			next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];

			if(next[0] > result.position.right){ result.position.right = next[0]; }
			if(next[0] < result.position.left){ result.position.left = next[0]; }
			if(next[1] > result.position.bottom){ result.position.bottom = next[1]; }
			if(next[1] < result.position.top){ result.position.top = next[1]; }

			coords.push(next);
		}

		// Calculate height and width from outer edges
		newWidth = result.width = Math.abs(result.position.right - result.position.left);
		newHeight = result.height = Math.abs(result.position.bottom - result.position.top);

		// If it's the center corner...
		if(corner.abbrev() === 'c') {
			result.position = {
				left: result.position.left + (result.width / 2),
				top: result.position.top + (result.height / 2)
			};
		}
		else {
			// Second pass, use a binary search algorithm to locate most suitable coordinate
			while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
			{
				newWidth = Math.floor(newWidth / 2);
				newHeight = Math.floor(newHeight / 2);

				if(corner.x === LEFT){ compareX = newWidth; }
				else if(corner.x === RIGHT){ compareX = result.width - newWidth; }
				else{ compareX += Math.floor(newWidth / 2); }

				if(corner.y === TOP){ compareY = newHeight; }
				else if(corner.y === BOTTOM){ compareY = result.height - newHeight; }
				else{ compareY += Math.floor(newHeight / 2); }

				i = coords.length; while(i--)
				{
					if(coords.length < 2){ break; }

					realX = coords[i][0] - result.position.left;
					realY = coords[i][1] - result.position.top;

					if((corner.x === LEFT && realX >= compareX) ||
					(corner.x === RIGHT && realX <= compareX) ||
					(corner.x === CENTER && (realX < compareX || realX > (result.width - compareX))) ||
					(corner.y === TOP && realY >= compareY) ||
					(corner.y === BOTTOM && realY <= compareY) ||
					(corner.y === CENTER && (realY < compareY || realY > (result.height - compareY)))) {
						coords.splice(i, 1);
					}
				}
			}
			result.position = { left: coords[0][0], top: coords[0][1] };
		}

		return result;
	},

	rect: function(ax, ay, bx, by) {
		return {
			width: Math.abs(bx - ax),
			height: Math.abs(by - ay),
			position: {
				left: Math.min(ax, bx),
				top: Math.min(ay, by)
			}
		};
	},

	_angles: {
		tc: 3 / 2, tr: 7 / 4, tl: 5 / 4,
		bc: 1 / 2, br: 1 / 4, bl: 3 / 4,
		rc: 2, lc: 1, c: 0
	},
	ellipse: function(cx, cy, rx, ry, corner) {
		var c = PLUGINS.polys._angles[ corner.abbrev() ],
			rxc = c === 0 ? 0 : rx * Math.cos( c * Math.PI ),
			rys = ry * Math.sin( c * Math.PI );

		return {
			width: (rx * 2) - Math.abs(rxc),
			height: (ry * 2) - Math.abs(rys),
			position: {
				left: cx + rxc,
				top: cy + rys
			},
			adjustable: FALSE
		};
	},
	circle: function(cx, cy, r, corner) {
		return PLUGINS.polys.ellipse(cx, cy, r, r, corner);
	}
};
;PLUGINS.svg = function(api, svg, corner)
{
	var doc = $(document),
		elem = svg[0],
		root = $(elem.ownerSVGElement),
		ownerDocument = elem.ownerDocument,
		strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2,
		frameOffset, mtx, transformed, viewBox,
		len, next, i, points,
		result, position, dimensions;

	// Ascend the parentNode chain until we find an element with getBBox()
	while(!elem.getBBox) { elem = elem.parentNode; }
	if(!elem.getBBox || !elem.parentNode) { return FALSE; }

	// Determine which shape calculation to use
	switch(elem.nodeName) {
		case 'ellipse':
		case 'circle':
			result = PLUGINS.polys.ellipse(
				elem.cx.baseVal.value,
				elem.cy.baseVal.value,
				(elem.rx || elem.r).baseVal.value + strokeWidth2,
				(elem.ry || elem.r).baseVal.value + strokeWidth2,
				corner
			);
		break;

		case 'line':
		case 'polygon':
		case 'polyline':
			// Determine points object (line has none, so mimic using array)
			points = elem.points || [
				{ x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
				{ x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
			];

			for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
				next = points.getItem ? points.getItem(i) : points[i];
				result.push.apply(result, [next.x, next.y]);
			}

			result = PLUGINS.polys.polygon(result, corner);
		break;

		// Unknown shape or rectangle? Use bounding box
		default:
			result = elem.getBBox();
			result = {
				width: result.width,
				height: result.height,
				position: {
					left: result.x,
					top: result.y
				}
			};
		break;
	}

	// Shortcut assignments
	position = result.position;
	root = root[0];

	// Convert position into a pixel value
	if(root.createSVGPoint) {
		mtx = elem.getScreenCTM();
		points = root.createSVGPoint();

		points.x = position.left;
		points.y = position.top;
		transformed = points.matrixTransform( mtx );
		position.left = transformed.x;
		position.top = transformed.y;
	}

	// Check the element is not in a child document, and if so, adjust for frame elements offset
	if(ownerDocument !== document && api.position.target !== 'mouse') {
		frameOffset = $((ownerDocument.defaultView || ownerDocument.parentWindow).frameElement).offset();
		if(frameOffset) {
			position.left += frameOffset.left;
			position.top += frameOffset.top;
		}
	}

	// Adjust by scroll offset of owner document
	ownerDocument = $(ownerDocument);
	position.left += ownerDocument.scrollLeft();
	position.top += ownerDocument.scrollTop();

	return result;
};
;PLUGINS.imagemap = function(api, area, corner, adjustMethod)
{
	if(!area.jquery) { area = $(area); }

	var shape = (area.attr('shape') || 'rect').toLowerCase().replace('poly', 'polygon'),
		image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
		coordsString = $.trim(area.attr('coords')),
		coordsArray = coordsString.replace(/,$/, '').split(','),
		imageOffset, coords, i, next, result, len;

	// If we can't find the image using the map...
	if(!image.length) { return FALSE; }

	// Pass coordinates string if polygon
	if(shape === 'polygon') {
		result = PLUGINS.polys.polygon(coordsArray, corner);
	}

	// Otherwise parse the coordinates and pass them as arguments
	else if(PLUGINS.polys[shape]) {
		for(i = -1, len = coordsArray.length, coords = []; ++i < len;) {
			coords.push( parseInt(coordsArray[i], 10) );
		}

		result = PLUGINS.polys[shape].apply(
			this, coords.concat(corner)
		);
	}

	// If no shapre calculation method was found, return false
	else { return FALSE; }

	// Make sure we account for padding and borders on the image
	imageOffset = image.offset();
	imageOffset.left += Math.ceil((image.outerWidth(FALSE) - image.width()) / 2);
	imageOffset.top += Math.ceil((image.outerHeight(FALSE) - image.height()) / 2);

	// Add image position to offset coordinates
	result.position.left += imageOffset.left;
	result.position.top += imageOffset.top;

	return result;
};
;var IE6,

/*
 * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
 * Special thanks to Brandon Aaron
 */
BGIFRAME = '<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
	' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
		'-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>';

function Ie6(api, qtip) {
	this._ns = 'ie6';
	this.init( (this.qtip = api) );
}

$.extend(Ie6.prototype, {
	_scroll : function() {
		var overlay = this.qtip.elements.overlay;
		overlay && (overlay[0].style.top = $(window).scrollTop() + 'px');
	},

	init: function(qtip) {
		var tooltip = qtip.tooltip,
			scroll;

		// Create the BGIFrame element if needed
		if($('select, object').length < 1) {
			this.bgiframe = qtip.elements.bgiframe = $(BGIFRAME).appendTo(tooltip);

			// Update BGIFrame on tooltip move
			qtip._bind(tooltip, 'tooltipmove', this.adjustBGIFrame, this._ns, this);
		}

		// redraw() container for width/height calculations
		this.redrawContainer = $('<div/>', { id: NAMESPACE+'-rcontainer' })
			.appendTo(document.body);

		// Fixup modal plugin if present too
		if( qtip.elements.overlay && qtip.elements.overlay.addClass('qtipmodal-ie6fix') ) {
			qtip._bind(window, ['scroll', 'resize'], this._scroll, this._ns, this);
			qtip._bind(tooltip, ['tooltipshow'], this._scroll, this._ns, this);
		}

		// Set dimensions
		this.redraw();
	},

	adjustBGIFrame: function() {
		var tooltip = this.qtip.tooltip,
			dimensions = {
				height: tooltip.outerHeight(FALSE),
				width: tooltip.outerWidth(FALSE)
			},
			plugin = this.qtip.plugins.tip,
			tip = this.qtip.elements.tip,
			tipAdjust, offset;

		// Adjust border offset
		offset = parseInt(tooltip.css('borderLeftWidth'), 10) || 0;
		offset = { left: -offset, top: -offset };

		// Adjust for tips plugin
		if(plugin && tip) {
			tipAdjust = (plugin.corner.precedance === 'x') ? [WIDTH, LEFT] : [HEIGHT, TOP];
			offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
		}

		// Update bgiframe
		this.bgiframe.css(offset).css(dimensions);
	},

	// Max/min width simulator function
	redraw: function() {
		if(this.qtip.rendered < 1 || this.drawing) { return this; }

		var tooltip = this.qtip.tooltip,
			style = this.qtip.options.style,
			container = this.qtip.options.position.container,
			perc, width, max, min;

		// Set drawing flag
		this.qtip.drawing = 1;

		// If tooltip has a set height/width, just set it... like a boss!
		if(style.height) { tooltip.css(HEIGHT, style.height); }
		if(style.width) { tooltip.css(WIDTH, style.width); }

		// Simulate max/min width if not set width present...
		else {
			// Reset width and add fluid class
			tooltip.css(WIDTH, '').appendTo(this.redrawContainer);

			// Grab our tooltip width (add 1 if odd so we don't get wrapping problems.. huzzah!)
			width = tooltip.width();
			if(width % 2 < 1) { width += 1; }

			// Grab our max/min properties
			max = tooltip.css('maxWidth') || '';
			min = tooltip.css('minWidth') || '';

			// Parse into proper pixel values
			perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
		max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
			min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;

			// Determine new dimension size based on max/min/current values
			width = max + min ? Math.min(Math.max(width, min), max) : width;

			// Set the newly calculated width and remvoe fluid class
			tooltip.css(WIDTH, Math.round(width)).appendTo(container);
		}

		// Set drawing flag
		this.drawing = 0;

		return this;
	},

	destroy: function() {
		// Remove iframe
		this.bgiframe && this.bgiframe.remove();

		// Remove bound events
		this.qtip._unbind([window, this.qtip.tooltip], this._ns);
	}
});

IE6 = PLUGINS.ie6 = function(api) {
	// Proceed only if the browser is IE6
	return BROWSER.ie === 6 ? new Ie6(api) : FALSE;
};

IE6.initialize = 'render';

CHECKS.ie6 = {
	'^content|style$': function() {
		this.redraw();
	}
};
;}));
}( window, document ));
;
/*!
Waypoints - 4.0.1
Copyright © 2011-2016 Caleb Troughton
Licensed under the MIT license.
https://github.com/imakewebthings/waypoints/blob/master/licenses.txt
*/
!function(){"use strict";function t(o){if(!o)throw new Error("No options passed to Waypoint constructor");if(!o.element)throw new Error("No element option passed to Waypoint constructor");if(!o.handler)throw new Error("No handler option passed to Waypoint constructor");this.key="waypoint-"+e,this.options=t.Adapter.extend({},t.defaults,o),this.element=this.options.element,this.adapter=new t.Adapter(this.element),this.callback=o.handler,this.axis=this.options.horizontal?"horizontal":"vertical",this.enabled=this.options.enabled,this.triggerPoint=null,this.group=t.Group.findOrCreate({name:this.options.group,axis:this.axis}),this.context=t.Context.findOrCreateByElement(this.options.context),t.offsetAliases[this.options.offset]&&(this.options.offset=t.offsetAliases[this.options.offset]),this.group.add(this),this.context.add(this),i[this.key]=this,e+=1}var e=0,i={};t.prototype.queueTrigger=function(t){this.group.queueTrigger(this,t)},t.prototype.trigger=function(t){this.enabled&&this.callback&&this.callback.apply(this,t)},t.prototype.destroy=function(){this.context.remove(this),this.group.remove(this),delete i[this.key]},t.prototype.disable=function(){return this.enabled=!1,this},t.prototype.enable=function(){return this.context.refresh(),this.enabled=!0,this},t.prototype.next=function(){return this.group.next(this)},t.prototype.previous=function(){return this.group.previous(this)},t.invokeAll=function(t){var e=[];for(var o in i)e.push(i[o]);for(var n=0,r=e.length;r>n;n++)e[n][t]()},t.destroyAll=function(){t.invokeAll("destroy")},t.disableAll=function(){t.invokeAll("disable")},t.enableAll=function(){t.Context.refreshAll();for(var e in i)i[e].enabled=!0;return this},t.refreshAll=function(){t.Context.refreshAll()},t.viewportHeight=function(){return window.innerHeight||document.documentElement.clientHeight},t.viewportWidth=function(){return document.documentElement.clientWidth},t.adapters=[],t.defaults={context:window,continuous:!0,enabled:!0,group:"default",horizontal:!1,offset:0},t.offsetAliases={"bottom-in-view":function(){return this.context.innerHeight()-this.adapter.outerHeight()},"right-in-view":function(){return this.context.innerWidth()-this.adapter.outerWidth()}},window.Waypoint=t}(),function(){"use strict";function t(t){window.setTimeout(t,1e3/60)}function e(t){this.element=t,this.Adapter=n.Adapter,this.adapter=new this.Adapter(t),this.key="waypoint-context-"+i,this.didScroll=!1,this.didResize=!1,this.oldScroll={x:this.adapter.scrollLeft(),y:this.adapter.scrollTop()},this.waypoints={vertical:{},horizontal:{}},t.waypointContextKey=this.key,o[t.waypointContextKey]=this,i+=1,n.windowContext||(n.windowContext=!0,n.windowContext=new e(window)),this.createThrottledScrollHandler(),this.createThrottledResizeHandler()}var i=0,o={},n=window.Waypoint,r=window.onload;e.prototype.add=function(t){var e=t.options.horizontal?"horizontal":"vertical";this.waypoints[e][t.key]=t,this.refresh()},e.prototype.checkEmpty=function(){var t=this.Adapter.isEmptyObject(this.waypoints.horizontal),e=this.Adapter.isEmptyObject(this.waypoints.vertical),i=this.element==this.element.window;t&&e&&!i&&(this.adapter.off(".waypoints"),delete o[this.key])},e.prototype.createThrottledResizeHandler=function(){function t(){e.handleResize(),e.didResize=!1}var e=this;this.adapter.on("resize.waypoints",function(){e.didResize||(e.didResize=!0,n.requestAnimationFrame(t))})},e.prototype.createThrottledScrollHandler=function(){function t(){e.handleScroll(),e.didScroll=!1}var e=this;this.adapter.on("scroll.waypoints",function(){(!e.didScroll||n.isTouch)&&(e.didScroll=!0,n.requestAnimationFrame(t))})},e.prototype.handleResize=function(){n.Context.refreshAll()},e.prototype.handleScroll=function(){var t={},e={horizontal:{newScroll:this.adapter.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.adapter.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};for(var i in e){var o=e[i],n=o.newScroll>o.oldScroll,r=n?o.forward:o.backward;for(var s in this.waypoints[i]){var a=this.waypoints[i][s];if(null!==a.triggerPoint){var l=o.oldScroll<a.triggerPoint,h=o.newScroll>=a.triggerPoint,p=l&&h,u=!l&&!h;(p||u)&&(a.queueTrigger(r),t[a.group.id]=a.group)}}}for(var c in t)t[c].flushTriggers();this.oldScroll={x:e.horizontal.newScroll,y:e.vertical.newScroll}},e.prototype.innerHeight=function(){return this.element==this.element.window?n.viewportHeight():this.adapter.innerHeight()},e.prototype.remove=function(t){delete this.waypoints[t.axis][t.key],this.checkEmpty()},e.prototype.innerWidth=function(){return this.element==this.element.window?n.viewportWidth():this.adapter.innerWidth()},e.prototype.destroy=function(){var t=[];for(var e in this.waypoints)for(var i in this.waypoints[e])t.push(this.waypoints[e][i]);for(var o=0,n=t.length;n>o;o++)t[o].destroy()},e.prototype.refresh=function(){var t,e=this.element==this.element.window,i=e?void 0:this.adapter.offset(),o={};this.handleScroll(),t={horizontal:{contextOffset:e?0:i.left,contextScroll:e?0:this.oldScroll.x,contextDimension:this.innerWidth(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:e?0:i.top,contextScroll:e?0:this.oldScroll.y,contextDimension:this.innerHeight(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};for(var r in t){var s=t[r];for(var a in this.waypoints[r]){var l,h,p,u,c,d=this.waypoints[r][a],f=d.options.offset,w=d.triggerPoint,y=0,g=null==w;d.element!==d.element.window&&(y=d.adapter.offset()[s.offsetProp]),"function"==typeof f?f=f.apply(d):"string"==typeof f&&(f=parseFloat(f),d.options.offset.indexOf("%")>-1&&(f=Math.ceil(s.contextDimension*f/100))),l=s.contextScroll-s.contextOffset,d.triggerPoint=Math.floor(y+l-f),h=w<s.oldScroll,p=d.triggerPoint>=s.oldScroll,u=h&&p,c=!h&&!p,!g&&u?(d.queueTrigger(s.backward),o[d.group.id]=d.group):!g&&c?(d.queueTrigger(s.forward),o[d.group.id]=d.group):g&&s.oldScroll>=d.triggerPoint&&(d.queueTrigger(s.forward),o[d.group.id]=d.group)}}return n.requestAnimationFrame(function(){for(var t in o)o[t].flushTriggers()}),this},e.findOrCreateByElement=function(t){return e.findByElement(t)||new e(t)},e.refreshAll=function(){for(var t in o)o[t].refresh()},e.findByElement=function(t){return o[t.waypointContextKey]},window.onload=function(){r&&r(),e.refreshAll()},n.requestAnimationFrame=function(e){var i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||t;i.call(window,e)},n.Context=e}(),function(){"use strict";function t(t,e){return t.triggerPoint-e.triggerPoint}function e(t,e){return e.triggerPoint-t.triggerPoint}function i(t){this.name=t.name,this.axis=t.axis,this.id=this.name+"-"+this.axis,this.waypoints=[],this.clearTriggerQueues(),o[this.axis][this.name]=this}var o={vertical:{},horizontal:{}},n=window.Waypoint;i.prototype.add=function(t){this.waypoints.push(t)},i.prototype.clearTriggerQueues=function(){this.triggerQueues={up:[],down:[],left:[],right:[]}},i.prototype.flushTriggers=function(){for(var i in this.triggerQueues){var o=this.triggerQueues[i],n="up"===i||"left"===i;o.sort(n?e:t);for(var r=0,s=o.length;s>r;r+=1){var a=o[r];(a.options.continuous||r===o.length-1)&&a.trigger([i])}}this.clearTriggerQueues()},i.prototype.next=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints),o=i===this.waypoints.length-1;return o?null:this.waypoints[i+1]},i.prototype.previous=function(e){this.waypoints.sort(t);var i=n.Adapter.inArray(e,this.waypoints);return i?this.waypoints[i-1]:null},i.prototype.queueTrigger=function(t,e){this.triggerQueues[e].push(t)},i.prototype.remove=function(t){var e=n.Adapter.inArray(t,this.waypoints);e>-1&&this.waypoints.splice(e,1)},i.prototype.first=function(){return this.waypoints[0]},i.prototype.last=function(){return this.waypoints[this.waypoints.length-1]},i.findOrCreate=function(t){return o[t.axis][t.name]||new i(t)},n.Group=i}(),function(){"use strict";function t(t){this.$element=e(t)}var e=window.jQuery,i=window.Waypoint;e.each(["innerHeight","innerWidth","off","offset","on","outerHeight","outerWidth","scrollLeft","scrollTop"],function(e,i){t.prototype[i]=function(){var t=Array.prototype.slice.call(arguments);return this.$element[i].apply(this.$element,t)}}),e.each(["extend","inArray","isEmptyObject"],function(i,o){t[o]=e[o]}),i.adapters.push({name:"jquery",Adapter:t}),i.Adapter=t}(),function(){"use strict";function t(t){return function(){var i=[],o=arguments[0];return t.isFunction(arguments[0])&&(o=t.extend({},arguments[1]),o.handler=arguments[0]),this.each(function(){var n=t.extend({},o,{element:this});"string"==typeof n.context&&(n.context=t(this).closest(n.context)[0]),i.push(new e(n))}),i}}var e=window.Waypoint;window.jQuery&&(window.jQuery.fn.waypoint=t(window.jQuery)),window.Zepto&&(window.Zepto.fn.waypoint=t(window.Zepto))}();;
//--Needed by IE <= 8
if (!Date.prototype.toISOString) {
    (function () {

        function pad(number) {
            if (number < 10) {
                return '0' + number;
            }
            return number;
        }

        Date.prototype.toISOString = function () {
            return this.getUTCFullYear() +
              '-' + pad(this.getUTCMonth() + 1) +
              '-' + pad(this.getUTCDate()) +
              'T' + pad(this.getUTCHours()) +
              ':' + pad(this.getUTCMinutes()) +
              ':' + pad(this.getUTCSeconds()) +
              '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) +
              'Z';
        };

    }());
};
/*!
 * Knockout JavaScript library v3.3.0
 * (c) Steven Sanderson - http://knockoutjs.com/
 * License: MIT (http://www.opensource.org/licenses/mit-license.php)
 */

(function() {(function(p){var y=this||(0,eval)("this"),w=y.document,M=y.navigator,u=y.jQuery,E=y.JSON;(function(p){"function"===typeof define&&define.amd?define(["exports","require"],p):"function"===typeof require&&"object"===typeof exports&&"object"===typeof module?p(module.exports||exports):p(y.ko={})})(function(N,O){function J(a,d){return null===a||typeof a in Q?a===d:!1}function R(a,d){var c;return function(){c||(c=setTimeout(function(){c=p;a()},d))}}function S(a,d){var c;return function(){clearTimeout(c);
c=setTimeout(a,d)}}function K(b,d,c,e){a.d[b]={init:function(b,k,h,l,g){var m,x;a.w(function(){var q=a.a.c(k()),n=!c!==!q,r=!x;if(r||d||n!==m)r&&a.Z.oa()&&(x=a.a.la(a.e.childNodes(b),!0)),n?(r||a.e.T(b,a.a.la(x)),a.Ja(e?e(g,q):g,b)):a.e.ma(b),m=n},null,{q:b});return{controlsDescendantBindings:!0}}};a.h.ka[b]=!1;a.e.R[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,d){for(var c=b.split("."),e=a,f=0;f<c.length-1;f++)e=e[c[f]];e[c[c.length-1]]=d};a.D=function(a,d,c){a[d]=c};a.version="3.3.0";
a.b("version",a.version);a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function d(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function c(a,b){a.__proto__=b;return a}function e(b,c,g,d){var e=b[c].match(m)||[];a.a.o(g.match(m),function(b){a.a.ga(e,b,d)});b[c]=e.join(" ")}var f={__proto__:[]}instanceof Array,k={},h={};k[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];k.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");
b(k,function(a,b){if(b.length)for(var c=0,g=b.length;c<g;c++)h[b[c]]=a});var l={propertychange:!0},g=w&&function(){for(var a=3,b=w.createElement("div"),c=b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:p}(),m=/\S+/g;return{Bb:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],o:function(a,b){for(var c=0,g=a.length;c<g;c++)b(a[c],c)},m:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,
b);for(var c=0,g=a.length;c<g;c++)if(a[c]===b)return c;return-1},vb:function(a,b,c){for(var g=0,d=a.length;g<d;g++)if(b.call(c,a[g],g))return a[g];return null},ya:function(b,c){var g=a.a.m(b,c);0<g?b.splice(g,1):0===g&&b.shift()},wb:function(b){b=b||[];for(var c=[],g=0,d=b.length;g<d;g++)0>a.a.m(c,b[g])&&c.push(b[g]);return c},Ka:function(a,b){a=a||[];for(var c=[],g=0,d=a.length;g<d;g++)c.push(b(a[g],g));return c},xa:function(a,b){a=a||[];for(var c=[],g=0,d=a.length;g<d;g++)b(a[g],g)&&c.push(a[g]);
return c},ia:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,g=b.length;c<g;c++)a.push(b[c]);return a},ga:function(b,c,g){var d=a.a.m(a.a.cb(b),c);0>d?g&&b.push(c):g||b.splice(d,1)},za:f,extend:d,Fa:c,Ga:f?c:d,A:b,pa:function(a,b){if(!a)return a;var c={},g;for(g in a)a.hasOwnProperty(g)&&(c[g]=b(a[g],g,a));return c},Ra:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},Jb:function(b){b=a.a.O(b);for(var c=(b[0]&&b[0].ownerDocument||w).createElement("div"),g=0,d=b.length;g<
d;g++)c.appendChild(a.S(b[g]));return c},la:function(b,c){for(var g=0,d=b.length,e=[];g<d;g++){var m=b[g].cloneNode(!0);e.push(c?a.S(m):m)}return e},T:function(b,c){a.a.Ra(b);if(c)for(var g=0,d=c.length;g<d;g++)b.appendChild(c[g])},Qb:function(b,c){var g=b.nodeType?[b]:b;if(0<g.length){for(var d=g[0],e=d.parentNode,m=0,f=c.length;m<f;m++)e.insertBefore(c[m],d);m=0;for(f=g.length;m<f;m++)a.removeNode(g[m])}},na:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==
b;)a.splice(0,1);if(1<a.length){var c=a[0],g=a[a.length-1];for(a.length=0;c!==g;)if(a.push(c),c=c.nextSibling,!c)return;a.push(g)}}return a},Sb:function(a,b){7>g?a.setAttribute("selected",b):a.selected=b},ib:function(a){return null===a||a===p?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},Dc:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},jc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?
a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=b;)a=a.parentNode;return!!a},Qa:function(b){return a.a.jc(b,b.ownerDocument.documentElement)},tb:function(b){return!!a.a.vb(b,a.a.Qa)},v:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},n:function(b,c,d){var m=g&&l[c];if(!m&&u)u(b).bind(c,d);else if(m||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var e=function(a){d.call(b,a)},f="on"+c;b.attachEvent(f,e);a.a.C.fa(b,
function(){b.detachEvent(f,e)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,d,!1)},qa:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var g;"input"===a.a.v(b)&&b.type&&"click"==c.toLowerCase()?(g=b.type,g="checkbox"==g||"radio"==g):g=!1;if(u&&!g)u(b).trigger(c);else if("function"==typeof w.createEvent)if("function"==typeof b.dispatchEvent)g=w.createEvent(h[c]||"HTMLEvents"),g.initEvent(c,
!0,!0,y,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(g);else throw Error("The supplied element doesn't support dispatchEvent");else if(g&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");},c:function(b){return a.F(b)?b():b},cb:function(b){return a.F(b)?b.B():b},Ia:function(b,c,g){var d;c&&("object"===typeof b.classList?(d=b.classList[g?"add":"remove"],a.a.o(c.match(m),function(a){d.call(b.classList,a)})):"string"===
typeof b.className.baseVal?e(b.className,"baseVal",c,g):e(b,"className",c,g))},Ha:function(b,c){var g=a.a.c(c);if(null===g||g===p)g="";var d=a.e.firstChild(b);!d||3!=d.nodeType||a.e.nextSibling(d)?a.e.T(b,[b.ownerDocument.createTextNode(g)]):d.data=g;a.a.mc(b)},Rb:function(a,b){a.name=b;if(7>=g)try{a.mergeAttributes(w.createElement("<input name='"+a.name+"'/>"),!1)}catch(c){}},mc:function(a){9<=g&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},kc:function(a){if(g){var b=a.style.width;
a.style.width=0;a.style.width=b}},Bc:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var g=[],d=b;d<=c;d++)g.push(d);return g},O:function(a){for(var b=[],c=0,g=a.length;c<g;c++)b.push(a[c]);return b},Hc:6===g,Ic:7===g,M:g,Db:function(b,c){for(var g=a.a.O(b.getElementsByTagName("input")).concat(a.a.O(b.getElementsByTagName("textarea"))),d="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},m=[],e=g.length-1;0<=e;e--)d(g[e])&&m.push(g[e]);return m},yc:function(b){return"string"==
typeof b&&(b=a.a.ib(b))?E&&E.parse?E.parse(b):(new Function("return "+b))():null},jb:function(b,c,g){if(!E||!E.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");return E.stringify(a.a.c(b),c,g)},zc:function(c,g,d){d=d||{};var m=d.params||{},e=d.includeFields||this.Bb,f=c;if("object"==typeof c&&"form"===a.a.v(c))for(var f=c.action,
l=e.length-1;0<=l;l--)for(var k=a.a.Db(c,e[l]),h=k.length-1;0<=h;h--)m[k[h].name]=k[h].value;g=a.a.c(g);var s=w.createElement("form");s.style.display="none";s.action=f;s.method="post";for(var p in g)c=w.createElement("input"),c.type="hidden",c.name=p,c.value=a.a.jb(a.a.c(g[p])),s.appendChild(c);b(m,function(a,b){var c=w.createElement("input");c.type="hidden";c.name=a;c.value=b;s.appendChild(c)});w.body.appendChild(s);d.submitter?d.submitter(s):s.submit();setTimeout(function(){s.parentNode.removeChild(s)},
0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.o);a.b("utils.arrayFirst",a.a.vb);a.b("utils.arrayFilter",a.a.xa);a.b("utils.arrayGetDistinctValues",a.a.wb);a.b("utils.arrayIndexOf",a.a.m);a.b("utils.arrayMap",a.a.Ka);a.b("utils.arrayPushAll",a.a.ia);a.b("utils.arrayRemoveItem",a.a.ya);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",a.a.Bb);a.b("utils.getFormFields",a.a.Db);a.b("utils.peekObservable",a.a.cb);a.b("utils.postJson",a.a.zc);a.b("utils.parseJson",a.a.yc);a.b("utils.registerEventHandler",
a.a.n);a.b("utils.stringifyJson",a.a.jb);a.b("utils.range",a.a.Bc);a.b("utils.toggleDomNodeCssClass",a.a.Ia);a.b("utils.triggerEvent",a.a.qa);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.A);a.b("utils.addOrRemoveItem",a.a.ga);a.b("utils.setTextContent",a.a.Ha);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=function(a){var d=this;if(1===arguments.length)return function(){return d.apply(a,arguments)};var c=Array.prototype.slice.call(arguments,1);return function(){var e=
c.slice(0);e.push.apply(e,arguments);return d.apply(a,e)}});a.a.f=new function(){function a(b,k){var h=b[c];if(!h||"null"===h||!e[h]){if(!k)return p;h=b[c]="ko"+d++;e[h]={}}return e[h]}var d=0,c="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===p?p:e[d]},set:function(c,d,e){if(e!==p||a(c,!1)!==p)a(c,!0)[d]=e},clear:function(a){var b=a[c];return b?(delete e[b],a[c]=null,!0):!1},I:function(){return d++ +c}}};a.b("utils.domData",a.a.f);a.b("utils.domData.clear",a.a.f.clear);
a.a.C=new function(){function b(b,d){var e=a.a.f.get(b,c);e===p&&d&&(e=[],a.a.f.set(b,c,e));return e}function d(c){var e=b(c,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](c);a.a.f.clear(c);a.a.C.cleanExternalData(c);if(f[c.nodeType])for(e=c.firstChild;c=e;)e=c.nextSibling,8===c.nodeType&&d(c)}var c=a.a.f.I(),e={1:!0,8:!0,9:!0},f={1:!0,9:!0};return{fa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},Pb:function(d,e){var f=b(d,!1);f&&(a.a.ya(f,
e),0==f.length&&a.a.f.set(d,c,p))},S:function(b){if(e[b.nodeType]&&(d(b),f[b.nodeType])){var c=[];a.a.ia(c,b.getElementsByTagName("*"));for(var l=0,g=c.length;l<g;l++)d(c[l])}return b},removeNode:function(b){a.S(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){u&&"function"==typeof u.cleanData&&u.cleanData([a])}}};a.S=a.a.C.S;a.removeNode=a.a.C.removeNode;a.b("cleanNode",a.S);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.C);a.b("utils.domNodeDisposal.addDisposeCallback",
a.a.C.fa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.C.Pb);(function(){a.a.ca=function(b,d){var c;if(u)if(u.parseHTML)c=u.parseHTML(b,d)||[];else{if((c=u.clean([b],d))&&c[0]){for(var e=c[0];e.parentNode&&11!==e.parentNode.nodeType;)e=e.parentNode;e.parentNode&&e.parentNode.removeChild(e)}}else{(e=d)||(e=w);c=e.parentWindow||e.defaultView||y;var f=a.a.ib(b).toLowerCase(),e=e.createElement("div"),f=f.match(/^<(thead|tbody|tfoot)/)&&[1,"<table>","</table>"]||!f.indexOf("<tr")&&[2,"<table><tbody>",
"</tbody></table>"]||(!f.indexOf("<td")||!f.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||[0,"",""],k="ignored<div>"+f[1]+b+f[2]+"</div>";for("function"==typeof c.innerShiv?e.appendChild(c.innerShiv(k)):e.innerHTML=k;f[0]--;)e=e.lastChild;c=a.a.O(e.lastChild.childNodes)}return c};a.a.gb=function(b,d){a.a.Ra(b);d=a.a.c(d);if(null!==d&&d!==p)if("string"!=typeof d&&(d=d.toString()),u)u(b).html(d);else for(var c=a.a.ca(d,b.ownerDocument),e=0;e<c.length;e++)b.appendChild(c[e])}})();
a.b("utils.parseHtmlFragment",a.a.ca);a.b("utils.setHtml",a.a.gb);a.H=function(){function b(c,d){if(c)if(8==c.nodeType){var f=a.H.Lb(c.nodeValue);null!=f&&d.push({ic:c,wc:f})}else if(1==c.nodeType)for(var f=0,k=c.childNodes,h=k.length;f<h;f++)b(k[f],d)}var d={};return{$a:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);
d[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},Wb:function(a,b){var f=d[a];if(f===p)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),!0}finally{delete d[a]}},Xb:function(c,d){var f=[];b(c,f);for(var k=0,h=f.length;k<h;k++){var l=f[k].ic,g=[l];d&&a.a.ia(g,d);a.H.Wb(f[k].wc,g);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},Lb:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.H);
a.b("memoization.memoize",a.H.$a);a.b("memoization.unmemoize",a.H.Wb);a.b("memoization.parseMemoText",a.H.Lb);a.b("memoization.unmemoizeDomNodeAndDescendants",a.H.Xb);a.Sa={throttle:function(b,d){b.throttleEvaluation=d;var c=null;return a.j({read:b,write:function(a){clearTimeout(c);c=setTimeout(function(){b(a)},d)}})},rateLimit:function(a,d){var c,e,f;"number"==typeof d?c=d:(c=d.timeout,e=d.method);f="notifyWhenChangesStop"==e?S:R;a.Za(function(a){return f(a,c)})},notify:function(a,d){a.equalityComparer=
"always"==d?null:J}};var Q={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.Sa);a.Ub=function(b,d,c){this.da=b;this.La=d;this.hc=c;this.Gb=!1;a.D(this,"dispose",this.p)};a.Ub.prototype.p=function(){this.Gb=!0;this.hc()};a.Q=function(){a.a.Ga(this,a.Q.fn);this.G={};this.rb=1};var z={U:function(b,d,c){var e=this;c=c||"change";var f=new a.Ub(e,d?b.bind(d):b,function(){a.a.ya(e.G[c],f);e.ua&&e.ua(c)});e.ja&&e.ja(c);e.G[c]||(e.G[c]=[]);e.G[c].push(f);return f},notifySubscribers:function(b,
d){d=d||"change";"change"===d&&this.Yb();if(this.Ba(d))try{a.k.xb();for(var c=this.G[d].slice(0),e=0,f;f=c[e];++e)f.Gb||f.La(b)}finally{a.k.end()}},Aa:function(){return this.rb},pc:function(a){return this.Aa()!==a},Yb:function(){++this.rb},Za:function(b){var d=this,c=a.F(d),e,f,k;d.ta||(d.ta=d.notifySubscribers,d.notifySubscribers=function(a,b){b&&"change"!==b?"beforeChange"===b?d.pb(a):d.ta(a,b):d.qb(a)});var h=b(function(){c&&k===d&&(k=d());e=!1;d.Wa(f,k)&&d.ta(f=k)});d.qb=function(a){e=!0;k=a;
h()};d.pb=function(a){e||(f=a,d.ta(a,"beforeChange"))}},Ba:function(a){return this.G[a]&&this.G[a].length},nc:function(b){if(b)return this.G[b]&&this.G[b].length||0;var d=0;a.a.A(this.G,function(a,b){d+=b.length});return d},Wa:function(a,d){return!this.equalityComparer||!this.equalityComparer(a,d)},extend:function(b){var d=this;b&&a.a.A(b,function(b,e){var f=a.Sa[b];"function"==typeof f&&(d=f(d,e)||d)});return d}};a.D(z,"subscribe",z.U);a.D(z,"extend",z.extend);a.D(z,"getSubscriptionsCount",z.nc);
a.a.za&&a.a.Fa(z,Function.prototype);a.Q.fn=z;a.Hb=function(a){return null!=a&&"function"==typeof a.U&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.Q);a.b("isSubscribable",a.Hb);a.Z=a.k=function(){function b(a){c.push(e);e=a}function d(){e=c.pop()}var c=[],e,f=0;return{xb:b,end:d,Ob:function(b){if(e){if(!a.Hb(b))throw Error("Only subscribable things can act as dependencies");e.La(b,b.ac||(b.ac=++f))}},u:function(a,c,e){try{return b(),a.apply(c,e||[])}finally{d()}},oa:function(){if(e)return e.w.oa()},
Ca:function(){if(e)return e.Ca}}}();a.b("computedContext",a.Z);a.b("computedContext.getDependenciesCount",a.Z.oa);a.b("computedContext.isInitial",a.Z.Ca);a.b("computedContext.isSleeping",a.Z.Jc);a.b("ignoreDependencies",a.Gc=a.k.u);a.r=function(b){function d(){if(0<arguments.length)return d.Wa(c,arguments[0])&&(d.X(),c=arguments[0],d.W()),this;a.k.Ob(d);return c}var c=b;a.Q.call(d);a.a.Ga(d,a.r.fn);d.B=function(){return c};d.W=function(){d.notifySubscribers(c)};d.X=function(){d.notifySubscribers(c,
"beforeChange")};a.D(d,"peek",d.B);a.D(d,"valueHasMutated",d.W);a.D(d,"valueWillMutate",d.X);return d};a.r.fn={equalityComparer:J};var H=a.r.Ac="__ko_proto__";a.r.fn[H]=a.r;a.a.za&&a.a.Fa(a.r.fn,a.Q.fn);a.Ta=function(b,d){return null===b||b===p||b[H]===p?!1:b[H]===d?!0:a.Ta(b[H],d)};a.F=function(b){return a.Ta(b,a.r)};a.Da=function(b){return"function"==typeof b&&b[H]===a.r||"function"==typeof b&&b[H]===a.j&&b.qc?!0:!1};a.b("observable",a.r);a.b("isObservable",a.F);a.b("isWriteableObservable",a.Da);
a.b("isWritableObservable",a.Da);a.ba=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.r(b);a.a.Ga(b,a.ba.fn);return b.extend({trackArrayChanges:!0})};a.ba.fn={remove:function(b){for(var d=this.B(),c=[],e="function"!=typeof b||a.F(b)?function(a){return a===b}:b,f=0;f<d.length;f++){var k=d[f];e(k)&&(0===c.length&&this.X(),c.push(k),d.splice(f,1),f--)}c.length&&this.W();return c},
removeAll:function(b){if(b===p){var d=this.B(),c=d.slice(0);this.X();d.splice(0,d.length);this.W();return c}return b?this.remove(function(c){return 0<=a.a.m(b,c)}):[]},destroy:function(b){var d=this.B(),c="function"!=typeof b||a.F(b)?function(a){return a===b}:b;this.X();for(var e=d.length-1;0<=e;e--)c(d[e])&&(d[e]._destroy=!0);this.W()},destroyAll:function(b){return b===p?this.destroy(function(){return!0}):b?this.destroy(function(d){return 0<=a.a.m(b,d)}):[]},indexOf:function(b){var d=this();return a.a.m(d,
b)},replace:function(a,d){var c=this.indexOf(a);0<=c&&(this.X(),this.B()[c]=d,this.W())}};a.a.o("pop push reverse shift sort splice unshift".split(" "),function(b){a.ba.fn[b]=function(){var a=this.B();this.X();this.yb(a,b,arguments);a=a[b].apply(a,arguments);this.W();return a}});a.a.o(["slice"],function(b){a.ba.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.a.za&&a.a.Fa(a.ba.fn,a.r.fn);a.b("observableArray",a.ba);a.Sa.trackArrayChanges=function(b){function d(){if(!c){c=!0;var g=
b.notifySubscribers;b.notifySubscribers=function(a,b){b&&"change"!==b||++k;return g.apply(this,arguments)};var d=[].concat(b.B()||[]);e=null;f=b.U(function(c){c=[].concat(c||[]);if(b.Ba("arrayChange")){var g;if(!e||1<k)e=a.a.Ma(d,c,{sparse:!0});g=e}d=c;e=null;k=0;g&&g.length&&b.notifySubscribers(g,"arrayChange")})}}if(!b.yb){var c=!1,e=null,f,k=0,h=b.ja,l=b.ua;b.ja=function(a){h&&h.call(b,a);"arrayChange"===a&&d()};b.ua=function(a){l&&l.call(b,a);"arrayChange"!==a||b.Ba("arrayChange")||(f.p(),c=!1)};
b.yb=function(b,d,f){function l(a,b,c){return h[h.length]={status:a,value:b,index:c}}if(c&&!k){var h=[],r=b.length,v=f.length,t=0;switch(d){case "push":t=r;case "unshift":for(d=0;d<v;d++)l("added",f[d],t+d);break;case "pop":t=r-1;case "shift":r&&l("deleted",b[t],t);break;case "splice":d=Math.min(Math.max(0,0>f[0]?r+f[0]:f[0]),r);for(var r=1===v?r:Math.min(d+(f[1]||0),r),v=d+v-2,t=Math.max(r,v),G=[],A=[],p=2;d<t;++d,++p)d<r&&A.push(l("deleted",b[d],d)),d<v&&G.push(l("added",f[p],d));a.a.Cb(A,G);break;
default:return}e=h}}}};a.w=a.j=function(b,d,c){function e(a,b,c){if(I&&b===g)throw Error("A 'pure' computed must not be called recursively");B[a]=c;c.sa=F++;c.ea=b.Aa()}function f(){var a,b;for(a in B)if(B.hasOwnProperty(a)&&(b=B[a],b.da.pc(b.ea)))return!0}function k(){!s&&B&&a.a.A(B,function(a,b){b.p&&b.p()});B=null;F=0;G=!0;s=r=!1}function h(){var a=g.throttleEvaluation;a&&0<=a?(clearTimeout(z),z=setTimeout(function(){l(!0)},a)):g.nb?g.nb():l(!0)}function l(b){if(!v&&!G){if(y&&y()){if(!t){w();return}}else t=
!1;v=!0;try{var c=B,m=F,f=I?p:!F;a.k.xb({La:function(a,b){G||(m&&c[b]?(e(b,a,c[b]),delete c[b],--m):B[b]||e(b,a,s?{da:a}:a.U(h)))},w:g,Ca:f});B={};F=0;try{var l=d?A.call(d):A()}finally{a.k.end(),m&&!s&&a.a.A(c,function(a,b){b.p&&b.p()}),r=!1}g.Wa(n,l)&&(s||q(n,"beforeChange"),n=l,s?g.Yb():b&&q(n));f&&q(n,"awake")}finally{v=!1}F||w()}}function g(){if(0<arguments.length){if("function"===typeof C)C.apply(d,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
return this}a.k.Ob(g);(r||s&&f())&&l();return n}function m(){(r&&!F||s&&f())&&l();return n}function x(){return r||0<F}function q(a,b){g.notifySubscribers(a,b)}var n,r=!0,v=!1,t=!1,G=!1,A=b,I=!1,s=!1;A&&"object"==typeof A?(c=A,A=c.read):(c=c||{},A||(A=c.read));if("function"!=typeof A)throw Error("Pass a function that returns the value of the ko.computed");var C=c.write,D=c.disposeWhenNodeIsRemoved||c.q||null,u=c.disposeWhen||c.Pa,y=u,w=k,B={},F=0,z=null;d||(d=c.owner);a.Q.call(g);a.a.Ga(g,a.j.fn);
g.B=m;g.oa=function(){return F};g.qc="function"===typeof C;g.p=function(){w()};g.$=x;var T=g.Za;g.Za=function(a){T.call(g,a);g.nb=function(){g.pb(n);r=!0;g.qb(g)}};c.pure?(s=I=!0,g.ja=function(b){if(!G&&s&&"change"==b){s=!1;if(r||f())B=null,F=0,r=!0,l();else{var c=[];a.a.A(B,function(a,b){c[b.sa]=a});a.a.o(c,function(a,b){var c=B[a],g=c.da.U(h);g.sa=b;g.ea=c.ea;B[a]=g})}G||q(n,"awake")}},g.ua=function(b){G||"change"!=b||g.Ba("change")||(a.a.A(B,function(a,b){b.p&&(B[a]={da:b.da,sa:b.sa,ea:b.ea},b.p())}),
s=!0,q(p,"asleep"))},g.bc=g.Aa,g.Aa=function(){s&&(r||f())&&l();return g.bc()}):c.deferEvaluation&&(g.ja=function(a){"change"!=a&&"beforeChange"!=a||m()});a.D(g,"peek",g.B);a.D(g,"dispose",g.p);a.D(g,"isActive",g.$);a.D(g,"getDependenciesCount",g.oa);D&&(t=!0,D.nodeType&&(y=function(){return!a.a.Qa(D)||u&&u()}));s||c.deferEvaluation||l();D&&x()&&D.nodeType&&(w=function(){a.a.C.Pb(D,w);k()},a.a.C.fa(D,w));return g};a.sc=function(b){return a.Ta(b,a.j)};z=a.r.Ac;a.j[z]=a.r;a.j.fn={equalityComparer:J};
a.j.fn[z]=a.j;a.a.za&&a.a.Fa(a.j.fn,a.Q.fn);a.b("dependentObservable",a.j);a.b("computed",a.j);a.b("isComputed",a.sc);a.Nb=function(b,d){if("function"===typeof b)return a.w(b,d,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.w(b,d)};a.b("pureComputed",a.Nb);(function(){function b(a,f,k){k=k||new c;a=f(a);if("object"!=typeof a||null===a||a===p||a instanceof Date||a instanceof String||a instanceof Number||a instanceof Boolean)return a;var h=a instanceof Array?[]:{};k.save(a,h);d(a,function(c){var g=
f(a[c]);switch(typeof g){case "boolean":case "number":case "string":case "function":h[c]=g;break;case "object":case "undefined":var d=k.get(g);h[c]=d!==p?d:b(g,f,k)}});return h}function d(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function c(){this.keys=[];this.mb=[]}a.Vb=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");return b(c,function(b){for(var c=0;a.F(b)&&
10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.Vb(b);return a.a.jb(b,c,d)};c.prototype={save:function(b,c){var d=a.a.m(this.keys,b);0<=d?this.mb[d]=c:(this.keys.push(b),this.mb.push(c))},get:function(b){b=a.a.m(this.keys,b);return 0<=b?this.mb[b]:p}}})();a.b("toJS",a.Vb);a.b("toJSON",a.toJSON);(function(){a.i={s:function(b){switch(a.a.v(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.f.get(b,a.d.options.ab):7>=a.a.M?b.getAttributeNode("value")&&b.getAttributeNode("value").specified?
b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.i.s(b.options[b.selectedIndex]):p;default:return b.value}},Y:function(b,d,c){switch(a.a.v(b)){case "option":switch(typeof d){case "string":a.a.f.set(b,a.d.options.ab,p);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=d;break;default:a.a.f.set(b,a.d.options.ab,d),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof d?d:""}break;case "select":if(""===d||null===d)d=p;for(var e=-1,f=0,k=b.options.length,
h;f<k;++f)if(h=a.i.s(b.options[f]),h==d||""==h&&d===p){e=f;break}if(c||0<=e||d===p&&1<b.size)b.selectedIndex=e;break;default:if(null===d||d===p)d="";b.value=d}}}})();a.b("selectExtensions",a.i);a.b("selectExtensions.readValue",a.i.s);a.b("selectExtensions.writeValue",a.i.Y);a.h=function(){function b(b){b=a.a.ib(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),x,h=[],n=0;if(d){d.push(",");for(var r=0,v;v=d[r];++r){var t=v.charCodeAt(0);if(44===t){if(0>=n){c.push(x&&h.length?{key:x,
value:h.join("")}:{unknown:x||h.join("")});x=n=0;h=[];continue}}else if(58===t){if(!n&&!x&&1===h.length){x=h.pop();continue}}else 47===t&&r&&1<v.length?(t=d[r-1].match(f))&&!k[t[0]]&&(b=b.substr(b.indexOf(v)+1),d=b.match(e),d.push(","),r=-1,v="/"):40===t||123===t||91===t?++n:41===t||125===t||93===t?--n:x||h.length||34!==t&&39!==t||(v=v.slice(1,-1));h.push(v)}}return c}var d=["true","false","null","undefined"],c=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]",
"g"),f=/[\])"'A-Za-z0-9_$]+$/,k={"in":1,"return":1,"typeof":1},h={};return{ka:[],V:h,bb:b,Ea:function(e,g){function m(b,g){var e;if(!r){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(g=l.preprocess(g,b,m)))return;if(l=h[b])e=g,0<=a.a.m(d,e)?e=!1:(l=e.match(c),e=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:e),l=e;l&&k.push("'"+b+"':function(_z){"+e+"=_z}")}n&&(g="function(){return "+g+" }");f.push("'"+b+"':"+g)}g=g||{};var f=[],k=[],n=g.valueAccessors,r=g.bindingParams,v="string"===typeof e?b(e):e;
a.a.o(v,function(a){m(a.key||a.unknown,a.value)});k.length&&m("_ko_property_writers","{"+k.join(",")+" }");return f.join(",")},vc:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},ra:function(b,c,d,e,f){if(b&&a.F(b))!a.Da(b)||f&&b.B()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",a.h.ka);a.b("expressionRewriting.parseObjectLiteral",a.h.bb);a.b("expressionRewriting.preProcessBindings",
a.h.Ea);a.b("expressionRewriting._twoWayBindings",a.h.V);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Ea);(function(){function b(a){return 8==a.nodeType&&k.test(f?a.text:a.nodeValue)}function d(a){return 8==a.nodeType&&h.test(f?a.text:a.nodeValue)}function c(a,c){for(var e=a,f=1,l=[];e=e.nextSibling;){if(d(e)&&(f--,0===f))return l;l.push(e);b(e)&&f++}if(!c)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,
b){var d=c(a,b);return d?0<d.length?d[d.length-1].nextSibling:a.nextSibling:null}var f=w&&"\x3c!--test--\x3e"===w.createComment("test").text,k=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,h=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.e={R:{},childNodes:function(a){return b(a)?c(a):a.childNodes},ma:function(c){if(b(c)){c=a.e.childNodes(c);for(var d=0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.Ra(c)},T:function(c,d){if(b(c)){a.e.ma(c);for(var e=c.nextSibling,
f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],e)}else a.a.T(c,d)},Mb:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},Fb:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.e.Mb(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||d(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&
d(a.nextSibling)?null:a.nextSibling},oc:b,Fc:function(a){return(a=(f?a.text:a.nodeValue).match(k))?a[1]:null},Kb:function(c){if(l[a.a.v(c)]){var m=c.firstChild;if(m){do if(1===m.nodeType){var f;f=m.firstChild;var h=null;if(f){do if(h)h.push(f);else if(b(f)){var k=e(f,!0);k?f=k:h=[f]}else d(f)&&(h=[f]);while(f=f.nextSibling)}if(f=h)for(h=m.nextSibling,k=0;k<f.length;k++)h?c.insertBefore(f[k],h):c.appendChild(f[k])}while(m=m.nextSibling)}}}}})();a.b("virtualElements",a.e);a.b("virtualElements.allowedBindings",
a.e.R);a.b("virtualElements.emptyNode",a.e.ma);a.b("virtualElements.insertAfter",a.e.Fb);a.b("virtualElements.prepend",a.e.Mb);a.b("virtualElements.setDomNodeChildren",a.e.T);(function(){a.L=function(){this.ec={}};a.a.extend(a.L.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.e.oc(b);default:return!1}},getBindings:function(b,d){var c=this.getBindingsString(b,d),c=c?this.parseBindingsString(c,
d,b):null;return a.g.sb(c,b,d,!1)},getBindingAccessors:function(b,d){var c=this.getBindingsString(b,d),c=c?this.parseBindingsString(c,d,b,{valueAccessors:!0}):null;return a.g.sb(c,b,d,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.e.Fc(b);default:return null}},parseBindingsString:function(b,d,c,e){try{var f=this.ec,k=b+(e&&e.valueAccessors||""),h;if(!(h=f[k])){var l,g="with($context){with($data||{}){return{"+a.h.Ea(b,e)+"}}}";l=new Function("$context",
"$element",g);h=f[k]=l}return h(d,c)}catch(m){throw m.message="Unable to parse bindings.\nBindings value: "+b+"\nMessage: "+m.message,m;}}});a.L.instance=new a.L})();a.b("bindingProvider",a.L);(function(){function b(a){return function(){return a}}function d(a){return a()}function c(b){return a.a.pa(a.k.u(b),function(a,c){return function(){return b()[c]}})}function e(d,g,e){return"function"===typeof d?c(d.bind(null,g,e)):a.a.pa(d,b)}function f(a,b){return c(this.getBindings.bind(this,a,b))}function k(b,
c,d){var g,e=a.e.firstChild(c),f=a.L.instance,m=f.preprocessNode;if(m){for(;g=e;)e=a.e.nextSibling(g),m.call(f,g);e=a.e.firstChild(c)}for(;g=e;)e=a.e.nextSibling(g),h(b,g,d)}function h(b,c,d){var e=!0,f=1===c.nodeType;f&&a.e.Kb(c);if(f&&d||a.L.instance.nodeHasBindings(c))e=g(c,null,b,d).shouldBindDescendants;e&&!x[a.a.v(c)]&&k(b,c,!f)}function l(b){var c=[],d={},g=[];a.a.A(b,function I(e){if(!d[e]){var f=a.getBindingHandler(e);f&&(f.after&&(g.push(e),a.a.o(f.after,function(c){if(b[c]){if(-1!==a.a.m(g,
c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+g.join(", "));I(c)}}),g.length--),c.push({key:e,Eb:f}));d[e]=!0}});return c}function g(b,c,g,e){var m=a.a.f.get(b,q);if(!c){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.f.set(b,q,!0)}!m&&e&&a.Tb(b,g);var h;if(c&&"function"!==typeof c)h=c;else{var k=a.L.instance,x=k.getBindingAccessors||f,n=a.j(function(){(h=c?c(g,b):x.call(k,b,g))&&g.K&&g.K();return h},null,{q:b});
h&&n.$()||(n=null)}var u;if(h){var w=n?function(a){return function(){return d(n()[a])}}:function(a){return h[a]},y=function(){return a.a.pa(n?n():h,d)};y.get=function(a){return h[a]&&d(w(a))};y.has=function(a){return a in h};e=l(h);a.a.o(e,function(c){var d=c.Eb.init,e=c.Eb.update,f=c.key;if(8===b.nodeType&&!a.e.R[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.k.u(function(){var a=d(b,w(f),y,g.$data,g);if(a&&a.controlsDescendantBindings){if(u!==
p)throw Error("Multiple bindings ("+u+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");u=f}}),"function"==typeof e&&a.j(function(){e(b,w(f),y,g.$data,g)},null,{q:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+h[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:u===p}}function m(b){return b&&b instanceof a.N?b:new a.N(b)}a.d={};var x={script:!0,textarea:!0};a.getBindingHandler=function(b){return a.d[b]};
a.N=function(b,c,d,g){var e=this,f="function"==typeof b&&!a.F(b),m,l=a.j(function(){var m=f?b():b,h=a.a.c(m);c?(c.K&&c.K(),a.a.extend(e,c),l&&(e.K=l)):(e.$parents=[],e.$root=h,e.ko=a);e.$rawData=m;e.$data=h;d&&(e[d]=h);g&&g(e,c,h);return e.$data},null,{Pa:function(){return m&&!a.a.tb(m)},q:!0});l.$()&&(e.K=l,l.equalityComparer=null,m=[],l.Zb=function(b){m.push(b);a.a.C.fa(b,function(b){a.a.ya(m,b);m.length||(l.p(),e.K=l=p)})})};a.N.prototype.createChildContext=function(b,c,d){return new a.N(b,this,
c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);d&&d(a)})};a.N.prototype.extend=function(b){return new a.N(this.K||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};var q=a.a.f.I(),n=a.a.f.I();a.Tb=function(b,c){if(2==arguments.length)a.a.f.set(b,n,c),c.K&&c.K.Zb(b);else return a.a.f.get(b,n)};a.va=function(b,c,d){1===b.nodeType&&a.e.Kb(b);return g(b,c,m(d),!0)};a.cc=function(b,
c,d){d=m(d);return a.va(b,e(c,d,b),d)};a.Ja=function(a,b){1!==b.nodeType&&8!==b.nodeType||k(m(a),b,!0)};a.ub=function(a,b){!u&&y.jQuery&&(u=y.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||y.document.body;h(m(a),b,!0)};a.Oa=function(b){switch(b.nodeType){case 1:case 8:var c=a.Tb(b);if(c)return c;if(b.parentNode)return a.Oa(b.parentNode)}return p};a.gc=function(b){return(b=a.Oa(b))?
b.$data:p};a.b("bindingHandlers",a.d);a.b("applyBindings",a.ub);a.b("applyBindingsToDescendants",a.Ja);a.b("applyBindingAccessorsToNode",a.va);a.b("applyBindingsToNode",a.cc);a.b("contextFor",a.Oa);a.b("dataFor",a.gc)})();(function(b){function d(d,e){var g=f.hasOwnProperty(d)?f[d]:b,m;g?g.U(e):(g=f[d]=new a.Q,g.U(e),c(d,function(a,b){var c=!(!b||!b.synchronous);k[d]={definition:a,tc:c};delete f[d];m||c?g.notifySubscribers(a):setTimeout(function(){g.notifySubscribers(a)},0)}),m=!0)}function c(a,b){e("getConfig",
[a],function(c){c?e("loadComponent",[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,g,f){f||(f=a.g.loaders.slice(0));var k=f.shift();if(k){var q=k[c];if(q){var n=!1;if(q.apply(k,d.concat(function(a){n?g(null):null!==a?g(a):e(c,d,g,f)}))!==b&&(n=!0,!k.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,g,f)}else g(null)}var f={},k={};a.g={get:function(c,e){var g=k.hasOwnProperty(c)?k[c]:
b;g?g.tc?a.k.u(function(){e(g.definition)}):setTimeout(function(){e(g.definition)},0):d(c,e)},zb:function(a){delete k[a]},ob:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.zb)})();(function(){function b(b,c,d,e){function k(){0===--v&&e(h)}var h={},v=2,t=d.template;d=d.viewModel;t?f(c,t,function(c){a.g.ob("loadTemplate",[b,c],function(a){h.template=a;k()})}):k();d?f(c,d,function(c){a.g.ob("loadViewModel",[b,c],function(a){h[l]=a;k()})}):
k()}function d(a,b,c){if("function"===typeof b)c(function(a){return new b(a)});else if("function"===typeof b[l])c(b[l]);else if("instance"in b){var e=b.instance;c(function(){return e})}else"viewModel"in b?d(a,b.viewModel,c):a("Unknown viewModel value: "+b)}function c(b){switch(a.a.v(b)){case "script":return a.a.ca(b.text);case "textarea":return a.a.ca(b.value);case "template":if(e(b.content))return a.a.la(b.content.childNodes)}return a.a.la(b.childNodes)}function e(a){return y.DocumentFragment?a instanceof
DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?O||y.require?(O||y.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function k(a){return function(b){throw Error("Component '"+a+"': "+b);}}var h={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.Xa(b))throw Error("Component "+b+" is already registered");h[b]=c};a.g.Xa=function(a){return a in h};a.g.Ec=function(b){delete h[b];a.g.zb(b)};a.g.Ab={getConfig:function(a,
b){b(h.hasOwnProperty(a)?h[a]:null)},loadComponent:function(a,c,d){var e=k(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,d,f){b=k(b);if("string"===typeof d)f(a.a.ca(d));else if(d instanceof Array)f(d);else if(e(d))f(a.a.O(d.childNodes));else if(d.element)if(d=d.element,y.HTMLElement?d instanceof HTMLElement:d&&d.tagName&&1===d.nodeType)f(c(d));else if("string"===typeof d){var l=w.getElementById(d);l?f(c(l)):b("Cannot find element with ID "+d)}else b("Unknown element type: "+d);else b("Unknown template value: "+
d)},loadViewModel:function(a,b,c){d(k(a),b,c)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.Xa);a.b("components.unregister",a.g.Ec);a.b("components.defaultLoader",a.g.Ab);a.g.loaders.push(a.g.Ab);a.g.$b=h})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=d.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.pa(f,function(d){return a.w(d,null,{q:b})}),k=a.a.pa(f,function(d){var e=d.B();return d.$()?a.w({read:function(){return a.a.c(d())},
write:a.Da(e)&&function(a){d()(a)},q:b}):e});k.hasOwnProperty("$raw")||(k.$raw=f);return k}return{$raw:{}}}a.g.getComponentNameForNode=function(b){b=a.a.v(b);return a.g.Xa(b)&&b};a.g.sb=function(c,d,f,k){if(1===d.nodeType){var h=a.g.getComponentNameForNode(d);if(h){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');var l={name:h,params:b(d,f)};c.component=k?function(){return l}:l}}return c};var d=new a.L;9>a.a.M&&(a.g.register=function(a){return function(b){w.createElement(b);
return a.apply(this,arguments)}}(a.g.register),w.createDocumentFragment=function(b){return function(){var d=b(),f=a.g.$b,k;for(k in f)f.hasOwnProperty(k)&&d.createElement(k);return d}}(w.createDocumentFragment))})();(function(b){function d(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.la(c);a.e.T(d,b)}function c(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,k,h,l,g){function m(){var a=x&&
x.dispose;"function"===typeof a&&a.call(x);q=null}var x,q,n=a.a.O(a.e.childNodes(f));a.a.C.fa(f,m);a.w(function(){var l=a.a.c(k()),h,t;"string"===typeof l?h=l:(h=a.a.c(l.name),t=a.a.c(l.params));if(!h)throw Error("No component name specified");var p=q=++e;a.g.get(h,function(e){if(q===p){m();if(!e)throw Error("Unknown component '"+h+"'");d(h,e,f);var l=c(e,f,n,t);e=g.createChildContext(l,b,function(a){a.$component=l;a.$componentTemplateNodes=n});x=l;a.Ja(e,f)}})},null,{q:f});return{controlsDescendantBindings:!0}}};
a.e.R.component=!0})();var P={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,d){var c=a.a.c(d())||{};a.a.A(c,function(c,d){d=a.a.c(d);var k=!1===d||null===d||d===p;k&&b.removeAttribute(c);8>=a.a.M&&c in P?(c=P[c],k?b.removeAttribute(c):b[c]=d):k||b.setAttribute(c,d.toString());"name"===c&&a.a.Rb(b,k?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,d,c){function e(){var e=b.checked,f=x?k():e;if(!a.Z.Ca()&&(!l||e)){var h=a.k.u(d);g?m!==f?(e&&(a.a.ga(h,
f,!0),a.a.ga(h,m,!1)),m=f):a.a.ga(h,f,e):a.h.ra(h,c,"checked",f,!0)}}function f(){var c=a.a.c(d());b.checked=g?0<=a.a.m(c,k()):h?c:k()===c}var k=a.Nb(function(){return c.has("checkedValue")?a.a.c(c.get("checkedValue")):c.has("value")?a.a.c(c.get("value")):b.value}),h="checkbox"==b.type,l="radio"==b.type;if(h||l){var g=h&&a.a.c(d())instanceof Array,m=g?k():p,x=l||g;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.w(e,null,{q:b});a.a.n(b,"click",e);a.w(f,null,{q:b})}}};a.h.V.checked=!0;a.d.checkedValue=
{update:function(b,d){b.value=a.a.c(d())}}})();a.d.css={update:function(b,d){var c=a.a.c(d());null!==c&&"object"==typeof c?a.a.A(c,function(c,d){d=a.a.c(d);a.a.Ia(b,c,d)}):(c=String(c||""),a.a.Ia(b,b.__ko__cssValue,!1),b.__ko__cssValue=c,a.a.Ia(b,c,!0))}};a.d.enable={update:function(b,d){var c=a.a.c(d());c&&b.disabled?b.removeAttribute("disabled"):c||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,d){a.d.enable.update(b,function(){return!a.a.c(d())})}};a.d.event={init:function(b,d,c,
e,f){var k=d()||{};a.a.A(k,function(h){"string"==typeof h&&a.a.n(b,h,function(b){var g,m=d()[h];if(m){try{var k=a.a.O(arguments);e=f.$data;k.unshift(e);g=m.apply(e,k)}finally{!0!==g&&(b.preventDefault?b.preventDefault():b.returnValue=!1)}!1===c.get(h+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={Ib:function(b){return function(){var d=b(),c=a.a.cb(d);if(!c||"number"==typeof c.length)return{foreach:d,templateEngine:a.P.Va};a.a.c(d);return{foreach:c.data,as:c.as,
includeDestroyed:c.includeDestroyed,afterAdd:c.afterAdd,beforeRemove:c.beforeRemove,afterRender:c.afterRender,beforeMove:c.beforeMove,afterMove:c.afterMove,templateEngine:a.P.Va}}},init:function(b,d){return a.d.template.init(b,a.d.foreach.Ib(d))},update:function(b,d,c,e,f){return a.d.template.update(b,a.d.foreach.Ib(d),c,e,f)}};a.h.ka.foreach=!1;a.e.R.foreach=!0;a.d.hasfocus={init:function(b,d,c){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(m){g=
f.body}e=g===b}f=d();a.h.ra(f,c,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),k=e.bind(null,!1);a.a.n(b,"focus",f);a.a.n(b,"focusin",f);a.a.n(b,"blur",k);a.a.n(b,"focusout",k)},update:function(b,d){var c=!!a.a.c(d());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===c||(c?b.focus():b.blur(),a.k.u(a.a.qa,null,[b,c?"focusin":"focusout"]))}};a.h.V.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.V.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},
update:function(b,d){a.a.gb(b,d())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,d){return a.createChildContext(d)});var L={};a.d.options={init:function(b){if("select"!==a.a.v(b))throw Error("options binding applies only to SELECT elements");for(;0<b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,d,c){function e(){return a.a.xa(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function k(d,e){if(r&&
m)a.i.Y(b,a.a.c(c.get("value")),!0);else if(n.length){var g=0<=a.a.m(n,a.i.s(e[0]));a.a.Sb(e[0],g);r&&!g&&a.k.u(a.a.qa,null,[b,"change"])}}var h=b.multiple,l=0!=b.length&&h?b.scrollTop:null,g=a.a.c(d()),m=c.get("valueAllowUnset")&&c.has("value"),x=c.get("optionsIncludeDestroyed");d={};var q,n=[];m||(h?n=a.a.Ka(e(),a.i.s):0<=b.selectedIndex&&n.push(a.i.s(b.options[b.selectedIndex])));g&&("undefined"==typeof g.length&&(g=[g]),q=a.a.xa(g,function(b){return x||b===p||null===b||!a.a.c(b._destroy)}),c.has("optionsCaption")&&
(g=a.a.c(c.get("optionsCaption")),null!==g&&g!==p&&q.unshift(L)));var r=!1;d.beforeRemove=function(a){b.removeChild(a)};g=k;c.has("optionsAfterRender")&&"function"==typeof c.get("optionsAfterRender")&&(g=function(b,d){k(0,d);a.k.u(c.get("optionsAfterRender"),null,[d[0],b!==L?b:p])});a.a.fb(b,q,function(d,e,g){g.length&&(n=!m&&g[0].selected?[a.i.s(g[0])]:[],r=!0);e=b.ownerDocument.createElement("option");d===L?(a.a.Ha(e,c.get("optionsCaption")),a.i.Y(e,p)):(g=f(d,c.get("optionsValue"),d),a.i.Y(e,a.a.c(g)),
d=f(d,c.get("optionsText"),g),a.a.Ha(e,d));return[e]},d,g);a.k.u(function(){m?a.i.Y(b,a.a.c(c.get("value")),!0):(h?n.length&&e().length<n.length:n.length&&0<=b.selectedIndex?a.i.s(b.options[b.selectedIndex])!==n[0]:n.length||0<=b.selectedIndex)&&a.a.qa(b,"change")});a.a.kc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.ab=a.a.f.I();a.d.selectedOptions={after:["options","foreach"],init:function(b,d,c){a.a.n(b,"change",function(){var e=d(),f=[];a.a.o(b.getElementsByTagName("option"),
function(b){b.selected&&f.push(a.i.s(b))});a.h.ra(e,c,"selectedOptions",f)})},update:function(b,d){if("select"!=a.a.v(b))throw Error("values binding applies only to SELECT elements");var c=a.a.c(d());c&&"number"==typeof c.length&&a.a.o(b.getElementsByTagName("option"),function(b){var d=0<=a.a.m(c,a.i.s(b));a.a.Sb(b,d)})}};a.h.V.selectedOptions=!0;a.d.style={update:function(b,d){var c=a.a.c(d()||{});a.a.A(c,function(c,d){d=a.a.c(d);if(null===d||d===p||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,
d,c,e,f){if("function"!=typeof d())throw Error("The value for a submit binding must be a function");a.a.n(b,"submit",function(a){var c,e=d();try{c=e.call(f.$data,b)}finally{!0!==c&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,d){a.a.Ha(b,d())}};a.e.R.text=!0;(function(){if(y&&y.navigator)var b=function(a){if(a)return parseFloat(a[1])},d=y.opera&&y.opera.version&&parseInt(y.opera.version()),c=y.navigator.userAgent,
e=b(c.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(c.match(/Firefox\/([^ ]*)/));if(10>a.a.M)var k=a.a.f.I(),h=a.a.f.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.f.get(c,h))&&c(b)},g=function(b,c){var d=b.ownerDocument;a.a.f.get(d,k)||(a.a.f.set(d,k,!0),a.a.n(d,"selectionchange",l));a.a.f.set(b,h,c)};a.d.textInput={init:function(b,c,l){function h(c,d){a.a.n(b,c,d)}function k(){var d=a.a.c(c());if(null===d||d===p)d="";w!==p&&d===w?setTimeout(k,4):b.value!==d&&(u=d,b.value=d)}function v(){A||
(w=b.value,A=setTimeout(t,4))}function t(){clearTimeout(A);w=A=p;var d=b.value;u!==d&&(u=d,a.h.ra(c(),l,"textInput",d))}var u=b.value,A,w;10>a.a.M?(h("propertychange",function(a){"value"===a.propertyName&&t()}),8==a.a.M&&(h("keyup",t),h("keydown",t)),8<=a.a.M&&(g(b,t),h("dragend",v))):(h("input",t),5>e&&"textarea"===a.a.v(b)?(h("keydown",v),h("paste",v),h("cut",v)):11>d?h("keydown",v):4>f&&(h("DOMAutoComplete",t),h("dragdrop",t),h("drop",t)));h("change",t);a.w(k,null,{q:b})}};a.h.V.textInput=!0;a.d.textinput=
{preprocess:function(a,b,c){c("textInput",a)}}})();a.d.uniqueName={init:function(b,d){if(d()){var c="ko_unique_"+ ++a.d.uniqueName.fc;a.a.Rb(b,c)}}};a.d.uniqueName.fc=0;a.d.value={after:["options","foreach"],init:function(b,d,c){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=c.get("valueUpdate"),k=!1,h=null;f&&("string"==typeof f&&(f=[f]),a.a.ia(e,f),e=a.a.wb(e));var l=function(){h=null;k=!1;var e=d(),g=a.i.s(b);a.h.ra(e,c,"value",g)};!a.a.M||"input"!=
b.tagName.toLowerCase()||"text"!=b.type||"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.m(e,"propertychange")||(a.a.n(b,"propertychange",function(){k=!0}),a.a.n(b,"focus",function(){k=!1}),a.a.n(b,"blur",function(){k&&l()}));a.a.o(e,function(c){var d=l;a.a.Dc(c,"after")&&(d=function(){h=a.i.s(b);setTimeout(l,0)},c=c.substring(5));a.a.n(b,c,d)});var g=function(){var e=a.a.c(d()),f=a.i.s(b);if(null!==h&&e===h)setTimeout(g,0);else if(e!==f)if("select"===a.a.v(b)){var l=c.get("valueAllowUnset"),
f=function(){a.i.Y(b,e,l)};f();l||e===a.i.s(b)?setTimeout(f,0):a.k.u(a.a.qa,null,[b,"change"])}else a.i.Y(b,e)};a.w(g,null,{q:b})}else a.va(b,{checkedValue:d})},update:function(){}};a.h.V.value=!0;a.d.visible={update:function(b,d){var c=a.a.c(d()),e="none"!=b.style.display;c&&!e?b.style.display="":!c&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(d,c,e,f,k){return a.d.event.init.call(this,d,function(){var a={};a[b]=c();return a},e,f,k)}}})("click");a.J=function(){};a.J.prototype.renderTemplateSource=
function(){throw Error("Override renderTemplateSource");};a.J.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.J.prototype.makeTemplateSource=function(b,d){if("string"==typeof b){d=d||w;var c=d.getElementById(b);if(!c)throw Error("Cannot find template with ID "+b);return new a.t.l(c)}if(1==b.nodeType||8==b.nodeType)return new a.t.ha(b);throw Error("Unknown template type: "+b);};a.J.prototype.renderTemplate=function(a,d,c,e){a=this.makeTemplateSource(a,
e);return this.renderTemplateSource(a,d,c,e)};a.J.prototype.isTemplateRewritten=function(a,d){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,d).data("isRewritten")};a.J.prototype.rewriteTemplate=function(a,d,c){a=this.makeTemplateSource(a,c);d=d(a.text());a.text(d);a.data("isRewritten",!0)};a.b("templateEngine",a.J);a.kb=function(){function b(b,c,d,h){b=a.h.bb(b);for(var l=a.h.ka,g=0;g<b.length;g++){var m=b[g].key;if(l.hasOwnProperty(m)){var x=l[m];if("function"===typeof x){if(m=
x(b[g].value))throw Error(m);}else if(!x)throw Error("This template engine does not support the '"+m+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Ea(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return h.createJavaScriptEvaluatorBlock(d)+c}var d=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,c=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{lc:function(b,
c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.kb.xc(b,c)},d)},xc:function(a,f){return a.replace(d,function(a,c,d,e,m){return b(m,c,d,f)}).replace(c,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},dc:function(b,c){return a.H.$a(function(d,h){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.va(l,b,h)})}}}();a.b("__tr_ambtns",a.kb.dc);(function(){a.t={};a.t.l=function(a){this.l=a};a.t.l.prototype.text=function(){var b=a.a.v(this.l),b="script"===b?"text":
"textarea"===b?"value":"innerHTML";if(0==arguments.length)return this.l[b];var d=arguments[0];"innerHTML"===b?a.a.gb(this.l,d):this.l[b]=d};var b=a.a.f.I()+"_";a.t.l.prototype.data=function(c){if(1===arguments.length)return a.a.f.get(this.l,b+c);a.a.f.set(this.l,b+c,arguments[1])};var d=a.a.f.I();a.t.ha=function(a){this.l=a};a.t.ha.prototype=new a.t.l;a.t.ha.prototype.text=function(){if(0==arguments.length){var b=a.a.f.get(this.l,d)||{};b.lb===p&&b.Na&&(b.lb=b.Na.innerHTML);return b.lb}a.a.f.set(this.l,
d,{lb:arguments[0]})};a.t.l.prototype.nodes=function(){if(0==arguments.length)return(a.a.f.get(this.l,d)||{}).Na;a.a.f.set(this.l,d,{Na:arguments[0]})};a.b("templateSources",a.t);a.b("templateSources.domElement",a.t.l);a.b("templateSources.anonymousTemplate",a.t.ha)})();(function(){function b(b,c,d){var e;for(c=a.e.nextSibling(c);b&&(e=b)!==c;)b=a.e.nextSibling(e),d(e,b)}function d(c,d){if(c.length){var e=c[0],f=c[c.length-1],h=e.parentNode,k=a.L.instance,r=k.preprocessNode;if(r){b(e,f,function(a,
b){var c=a.previousSibling,d=r.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.na(c,h))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.ub(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.H.Xb(b,[d])});a.a.na(c,h)}}function c(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,e,f,h,q){q=q||{};var n=(b&&c(b)||f||{}).ownerDocument,r=q.templateEngine||k;a.kb.lc(f,r,n);f=r.renderTemplate(f,h,q,n);if("number"!=
typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");n=!1;switch(e){case "replaceChildren":a.e.T(b,f);n=!0;break;case "replaceNode":a.a.Qb(b,f);n=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}n&&(d(f,h),q.afterRender&&a.k.u(q.afterRender,null,[f,h.$data]));return f}function f(b,c,d){return a.F(b)?b():"function"===typeof b?b(c,d):b}var k;a.hb=function(b){if(b!=p&&!(b instanceof a.J))throw Error("templateEngine must inherit from ko.templateEngine");
k=b};a.eb=function(b,d,h,x,q){h=h||{};if((h.templateEngine||k)==p)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(x){var n=c(x);return a.j(function(){var k=d&&d instanceof a.N?d:new a.N(a.a.c(d)),p=f(b,k.$data,k),k=e(x,q,p,k,h);"replaceNode"==q&&(x=k,n=c(x))},null,{Pa:function(){return!n||!a.a.Qa(n)},q:n&&"replaceNode"==q?n.parentNode:n})}return a.H.$a(function(c){a.eb(b,d,h,c,"replaceNode")})};a.Cc=function(b,c,h,k,q){function n(a,b){d(b,v);h.afterRender&&
h.afterRender(b,a);v=null}function r(a,c){v=q.createChildContext(a,h.as,function(a){a.$index=c});var d=f(b,a,v);return e(null,"ignoreTargetNode",d,v,h)}var v;return a.j(function(){var b=a.a.c(c)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.xa(b,function(b){return h.includeDestroyed||b===p||null===b||!a.a.c(b._destroy)});a.k.u(a.a.fb,null,[k,b,r,h,n])},null,{q:k})};var h=a.a.f.I();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.e.ma(b);else{if("nodes"in d){if(d=
d.nodes||[],a.F(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.e.childNodes(b);d=a.a.Jb(d);(new a.t.ha(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var k=c(),r;c=a.a.c(k);d=!0;e=null;"string"==typeof c?c={}:(k=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in c&&(d=!a.a.c(c.ifnot)),r=a.a.c(c.data));"foreach"in c?e=a.Cc(k||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.createChildContext(r,c.as):f,e=a.eb(k||b,f,c,b)):a.e.ma(b);f=
e;(r=a.a.f.get(b,h))&&"function"==typeof r.p&&r.p();a.a.f.set(b,h,f&&f.$()?f:p)}};a.h.ka.template=function(b){b=a.h.bb(b);return 1==b.length&&b[0].unknown||a.h.vc(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.e.R.template=!0})();a.b("setTemplateEngine",a.hb);a.b("renderTemplate",a.eb);a.a.Cb=function(a,d,c){if(a.length&&d.length){var e,f,k,h,l;for(e=f=0;(!c||e<c)&&(h=a[f]);++f){for(k=0;l=d[k];++k)if(h.value===l.value){h.moved=l.index;l.moved=
h.index;d.splice(k,1);e=k=0;break}e+=k}}};a.a.Ma=function(){function b(b,c,e,f,k){var h=Math.min,l=Math.max,g=[],m,p=b.length,q,n=c.length,r=n-p||1,v=p+n+1,t,u,w;for(m=0;m<=p;m++)for(u=t,g.push(t=[]),w=h(n,m+r),q=l(0,m-1);q<=w;q++)t[q]=q?m?b[m-1]===c[q-1]?u[q-1]:h(u[q]||v,t[q-1]||v)+1:q+1:m+1;h=[];l=[];r=[];m=p;for(q=n;m||q;)n=g[m][q]-1,q&&n===g[m][q-1]?l.push(h[h.length]={status:e,value:c[--q],index:q}):m&&n===g[m-1][q]?r.push(h[h.length]={status:f,value:b[--m],index:m}):(--q,--m,k.sparse||h.push({status:"retained",
value:c[q]}));a.a.Cb(l,r,10*p);return h.reverse()}return function(a,c,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];c=c||[];return a.length<=c.length?b(a,c,"added","deleted",e):b(c,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.Ma);(function(){function b(b,d,f,k,h){var l=[],g=a.j(function(){var g=d(f,h,a.a.na(l,b))||[];0<l.length&&(a.a.Qb(l,g),k&&a.k.u(k,null,[f,g,h]));l.length=0;a.a.ia(l,g)},null,{q:b,Pa:function(){return!a.a.tb(l)}});return{aa:l,j:g.$()?g:p}}var d=a.a.f.I();
a.a.fb=function(c,e,f,k,h){function l(b,d){s=u[d];t!==d&&(z[b]=s);s.Ua(t++);a.a.na(s.aa,c);r.push(s);y.push(s)}function g(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.o(c[d].aa,function(a){b(a,d,c[d].wa)})}e=e||[];k=k||{};var m=a.a.f.get(c,d)===p,u=a.a.f.get(c,d)||[],q=a.a.Ka(u,function(a){return a.wa}),n=a.a.Ma(q,e,k.dontLimitMoves),r=[],v=0,t=0,w=[],y=[];e=[];for(var z=[],q=[],s,C=0,D,E;D=n[C];C++)switch(E=D.moved,D.status){case "deleted":E===p&&(s=u[v],s.j&&s.j.p(),w.push.apply(w,a.a.na(s.aa,
c)),k.beforeRemove&&(e[C]=s,y.push(s)));v++;break;case "retained":l(C,v++);break;case "added":E!==p?l(C,E):(s={wa:D.value,Ua:a.r(t++)},r.push(s),y.push(s),m||(q[C]=s))}g(k.beforeMove,z);a.a.o(w,k.beforeRemove?a.S:a.removeNode);for(var C=0,m=a.e.firstChild(c),H;s=y[C];C++){s.aa||a.a.extend(s,b(c,f,s.wa,h,s.Ua));for(v=0;n=s.aa[v];m=n.nextSibling,H=n,v++)n!==m&&a.e.Fb(c,n,H);!s.rc&&h&&(h(s.wa,s.aa,s.Ua),s.rc=!0)}g(k.beforeRemove,e);g(k.afterMove,z);g(k.afterAdd,q);a.a.f.set(c,d,r)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",
a.a.fb);a.P=function(){this.allowTemplateRewriting=!1};a.P.prototype=new a.J;a.P.prototype.renderTemplateSource=function(b,d,c,e){if(d=(9>a.a.M?0:b.nodes)?b.nodes():null)return a.a.O(d.cloneNode(!0).childNodes);b=b.text();return a.a.ca(b,e)};a.P.Va=new a.P;a.hb(a.P.Va);a.b("nativeTemplateEngine",a.P);(function(){a.Ya=function(){var a=this.uc=function(){if(!u||!u.tmpl)return 0;try{if(0<=u.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,
e,f,k){k=k||w;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var h=b.data("precompiled");h||(h=b.text()||"",h=u.template(null,"{{ko_with $item.koBindingContext}}"+h+"{{/ko_with}}"),b.data("precompiled",h));b=[e.$data];e=u.extend({koBindingContext:e},f.templateOptions);e=u.tmpl(h,b,e);e.appendTo(k.createElement("div"));u.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+
a+" })()) }}"};this.addTemplate=function(a,b){w.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(u.tmpl.tag.ko_code={open:"__.push($1 || '');"},u.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.Ya.prototype=new a.J;var b=new a.Ya;0<b.uc&&a.hb(b);a.b("jqueryTmplTemplateEngine",a.Ya)})()})})();})();
;
/*!
 * enquire.js v2.1.2 - Awesome Media Queries in JavaScript
 * Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/enquire.js
 * License: MIT (http://www.opensource.org/licenses/mit-license.php)
 */

;(function (name, context, factory) {
	var matchMedia = window.matchMedia;

	if (typeof module !== 'undefined' && module.exports) {
		module.exports = factory(matchMedia);
	}
	else if (typeof define === 'function' && define.amd) {
		define(function() {
			return (context[name] = factory(matchMedia));
		});
	}
	else {
		context[name] = factory(matchMedia);
	}
}('enquire', this, function (matchMedia) {

	'use strict';

    /*jshint unused:false */
    /**
     * Helper function for iterating over a collection
     *
     * @param collection
     * @param fn
     */
    function each(collection, fn) {
        var i      = 0,
            length = collection.length,
            cont;

        for(i; i < length; i++) {
            cont = fn(collection[i], i);
            if(cont === false) {
                break; //allow early exit
            }
        }
    }

    /**
     * Helper function for determining whether target object is an array
     *
     * @param target the object under test
     * @return {Boolean} true if array, false otherwise
     */
    function isArray(target) {
        return Object.prototype.toString.apply(target) === '[object Array]';
    }

    /**
     * Helper function for determining whether target object is a function
     *
     * @param target the object under test
     * @return {Boolean} true if function, false otherwise
     */
    function isFunction(target) {
        return typeof target === 'function';
    }

    /**
     * Delegate to handle a media query being matched and unmatched.
     *
     * @param {object} options
     * @param {function} options.match callback for when the media query is matched
     * @param {function} [options.unmatch] callback for when the media query is unmatched
     * @param {function} [options.setup] one-time callback triggered the first time a query is matched
     * @param {boolean} [options.deferSetup=false] should the setup callback be run immediately, rather than first time query is matched?
     * @constructor
     */
    function QueryHandler(options) {
        this.options = options;
        !options.deferSetup && this.setup();
    }
    QueryHandler.prototype = {

        /**
         * coordinates setup of the handler
         *
         * @function
         */
        setup : function() {
            if(this.options.setup) {
                this.options.setup();
            }
            this.initialised = true;
        },

        /**
         * coordinates setup and triggering of the handler
         *
         * @function
         */
        on : function() {
            !this.initialised && this.setup();
            this.options.match && this.options.match();
        },

        /**
         * coordinates the unmatch event for the handler
         *
         * @function
         */
        off : function() {
            this.options.unmatch && this.options.unmatch();
        },

        /**
         * called when a handler is to be destroyed.
         * delegates to the destroy or unmatch callbacks, depending on availability.
         *
         * @function
         */
        destroy : function() {
            this.options.destroy ? this.options.destroy() : this.off();
        },

        /**
         * determines equality by reference.
         * if object is supplied compare options, if function, compare match callback
         *
         * @function
         * @param {object || function} [target] the target for comparison
         */
        equals : function(target) {
            return this.options === target || this.options.match === target;
        }

    };
    /**
     * Represents a single media query, manages it's state and registered handlers for this query
     *
     * @constructor
     * @param {string} query the media query string
     * @param {boolean} [isUnconditional=false] whether the media query should run regardless of whether the conditions are met. Primarily for helping older browsers deal with mobile-first design
     */
    function MediaQuery(query, isUnconditional) {
        this.query = query;
        this.isUnconditional = isUnconditional;
        this.handlers = [];
        this.mql = matchMedia(query);

        var self = this;
        this.listener = function(mql) {
            self.mql = mql;
            self.assess();
        };
        this.mql.addListener(this.listener);
    }
    MediaQuery.prototype = {

        /**
         * add a handler for this query, triggering if already active
         *
         * @param {object} handler
         * @param {function} handler.match callback for when query is activated
         * @param {function} [handler.unmatch] callback for when query is deactivated
         * @param {function} [handler.setup] callback for immediate execution when a query handler is registered
         * @param {boolean} [handler.deferSetup=false] should the setup callback be deferred until the first time the handler is matched?
         */
        addHandler : function(handler) {
            var qh = new QueryHandler(handler);
            this.handlers.push(qh);

            this.matches() && qh.on();
        },

        /**
         * removes the given handler from the collection, and calls it's destroy methods
         * 
         * @param {object || function} handler the handler to remove
         */
        removeHandler : function(handler) {
            var handlers = this.handlers;
            each(handlers, function(h, i) {
                if(h.equals(handler)) {
                    h.destroy();
                    return !handlers.splice(i,1); //remove from array and exit each early
                }
            });
        },

        /**
         * Determine whether the media query should be considered a match
         * 
         * @return {Boolean} true if media query can be considered a match, false otherwise
         */
        matches : function() {
            return this.mql.matches || this.isUnconditional;
        },

        /**
         * Clears all handlers and unbinds events
         */
        clear : function() {
            each(this.handlers, function(handler) {
                handler.destroy();
            });
            this.mql.removeListener(this.listener);
            this.handlers.length = 0; //clear array
        },

        /*
         * Assesses the query, turning on all handlers if it matches, turning them off if it doesn't match
         */
        assess : function() {
            var action = this.matches() ? 'on' : 'off';

            each(this.handlers, function(handler) {
                handler[action]();
            });
        }
    };
    /**
     * Allows for registration of query handlers.
     * Manages the query handler's state and is responsible for wiring up browser events
     *
     * @constructor
     */
    function MediaQueryDispatch () {
        if(!matchMedia) {
            throw new Error('matchMedia not present, legacy browsers require a polyfill');
        }

        this.queries = {};
        this.browserIsIncapable = !matchMedia('only all').matches;
    }

    MediaQueryDispatch.prototype = {

        /**
         * Registers a handler for the given media query
         *
         * @param {string} q the media query
         * @param {object || Array || Function} options either a single query handler object, a function, or an array of query handlers
         * @param {function} options.match fired when query matched
         * @param {function} [options.unmatch] fired when a query is no longer matched
         * @param {function} [options.setup] fired when handler first triggered
         * @param {boolean} [options.deferSetup=false] whether setup should be run immediately or deferred until query is first matched
         * @param {boolean} [shouldDegrade=false] whether this particular media query should always run on incapable browsers
         */
        register : function(q, options, shouldDegrade) {
            var queries         = this.queries,
                isUnconditional = shouldDegrade && this.browserIsIncapable;

            if(!queries[q]) {
                queries[q] = new MediaQuery(q, isUnconditional);
            }

            //normalise to object in an array
            if(isFunction(options)) {
                options = { match : options };
            }
            if(!isArray(options)) {
                options = [options];
            }
            each(options, function(handler) {
                if (isFunction(handler)) {
                    handler = { match : handler };
                }
                queries[q].addHandler(handler);
            });

            return this;
        },

        /**
         * unregisters a query and all it's handlers, or a specific handler for a query
         *
         * @param {string} q the media query to target
         * @param {object || function} [handler] specific handler to unregister
         */
        unregister : function(q, handler) {
            var query = this.queries[q];

            if(query) {
                if(handler) {
                    query.removeHandler(handler);
                }
                else {
                    query.clear();
                    delete this.queries[q];
                }
            }

            return this;
        }
    };

	return new MediaQueryDispatch();

}));;
/*
    FILE IS NO LONGER IN SYNC WITH SELECTIZE DIST
    We applied a pull requet fix for a bug with IE that prevented tabbing into the field and then starting to type
    https://github.com/brianreavis/selectize.js/pull/724
*/

/**
 * sifter.js
 * Copyright (c) 2013 Brian Reavis & contributors
 *
 * 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.
 *
 * @author Brian Reavis <brian@thirdroute.com>
 */

(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		define('sifter', factory);
	} else if (typeof exports === 'object') {
		module.exports = factory();
	} else {
		root.Sifter = factory();
	}
}(this, function() {

	/**
	 * Textually searches arrays and hashes of objects
	 * by property (or multiple properties). Designed
	 * specifically for autocomplete.
	 *
	 * @constructor
	 * @param {array|object} items
	 * @param {object} items
	 */
	var Sifter = function(items, settings) {
		this.items = items;
		this.settings = settings || {diacritics: true};
	};

	/**
	 * Splits a search string into an array of individual
	 * regexps to be used to match results.
	 *
	 * @param {string} query
	 * @returns {array}
	 */
	Sifter.prototype.tokenize = function(query) {
		query = trim(String(query || '').toLowerCase());
		if (!query || !query.length) return [];

		var i, n, regex, letter;
		var tokens = [];
		var words = query.split(/ +/);

		for (i = 0, n = words.length; i < n; i++) {
			regex = escape_regex(words[i]);
			if (this.settings.diacritics) {
				for (letter in DIACRITICS) {
					if (DIACRITICS.hasOwnProperty(letter)) {
						regex = regex.replace(new RegExp(letter, 'g'), DIACRITICS[letter]);
					}
				}
			}
			tokens.push({
				string : words[i],
				regex  : new RegExp(regex, 'i')
			});
		}

		return tokens;
	};

	/**
	 * Iterates over arrays and hashes.
	 *
	 * ```
	 * this.iterator(this.items, function(item, id) {
	 *    // invoked for each item
	 * });
	 * ```
	 *
	 * @param {array|object} object
	 */
	Sifter.prototype.iterator = function(object, callback) {
		var iterator;
		if (is_array(object)) {
			iterator = Array.prototype.forEach || function(callback) {
				for (var i = 0, n = this.length; i < n; i++) {
					callback(this[i], i, this);
				}
			};
		} else {
			iterator = function(callback) {
				for (var key in this) {
					if (this.hasOwnProperty(key)) {
						callback(this[key], key, this);
					}
				}
			};
		}

		iterator.apply(object, [callback]);
	};

	/**
	 * Returns a function to be used to score individual results.
	 *
	 * Good matches will have a higher score than poor matches.
	 * If an item is not a match, 0 will be returned by the function.
	 *
	 * @param {object|string} search
	 * @param {object} options (optional)
	 * @returns {function}
	 */
	Sifter.prototype.getScoreFunction = function(search, options) {
		var self, fields, tokens, token_count;

		self        = this;
		search      = self.prepareSearch(search, options);
		tokens      = search.tokens;
		fields      = search.options.fields;
		token_count = tokens.length;

		/**
		 * Calculates how close of a match the
		 * given value is against a search token.
		 *
		 * @param {mixed} value
		 * @param {object} token
		 * @return {number}
		 */
		var scoreValue = function(value, token) {
			var score, pos;

			if (!value) return 0;
			value = String(value || '');
			pos = value.search(token.regex);
			if (pos === -1) return 0;
			score = token.string.length / value.length;
			if (pos === 0) score += 0.5;
			return score;
		};

		/**
		 * Calculates the score of an object
		 * against the search query.
		 *
		 * @param {object} token
		 * @param {object} data
		 * @return {number}
		 */
		var scoreObject = (function() {
			var field_count = fields.length;
			if (!field_count) {
				return function() { return 0; };
			}
			if (field_count === 1) {
				return function(token, data) {
					return scoreValue(data[fields[0]], token);
				};
			}
			return function(token, data) {
				for (var i = 0, sum = 0; i < field_count; i++) {
					sum += scoreValue(data[fields[i]], token);
				}
				return sum / field_count;
			};
		})();

		if (!token_count) {
			return function() { return 0; };
		}
		if (token_count === 1) {
			return function(data) {
				return scoreObject(tokens[0], data);
			};
		}

		if (search.options.conjunction === 'and') {
			return function(data) {
				var score;
				for (var i = 0, sum = 0; i < token_count; i++) {
					score = scoreObject(tokens[i], data);
					if (score <= 0) return 0;
					sum += score;
				}
				return sum / token_count;
			};
		} else {
			return function(data) {
				for (var i = 0, sum = 0; i < token_count; i++) {
					sum += scoreObject(tokens[i], data);
				}
				return sum / token_count;
			};
		}
	};

	/**
	 * Returns a function that can be used to compare two
	 * results, for sorting purposes. If no sorting should
	 * be performed, `null` will be returned.
	 *
	 * @param {string|object} search
	 * @param {object} options
	 * @return function(a,b)
	 */
	Sifter.prototype.getSortFunction = function(search, options) {
		var i, n, self, field, fields, fields_count, multiplier, multipliers, get_field, implicit_score, sort;

		self   = this;
		search = self.prepareSearch(search, options);
		sort   = (!search.query && options.sort_empty) || options.sort;

		/**
		 * Fetches the specified sort field value
		 * from a search result item.
		 *
		 * @param  {string} name
		 * @param  {object} result
		 * @return {mixed}
		 */
		get_field = function(name, result) {
			if (name === '$score') return result.score;
			return self.items[result.id][name];
		};

		// parse options
		fields = [];
		if (sort) {
			for (i = 0, n = sort.length; i < n; i++) {
				if (search.query || sort[i].field !== '$score') {
					fields.push(sort[i]);
				}
			}
		}

		// the "$score" field is implied to be the primary
		// sort field, unless it's manually specified
		if (search.query) {
			implicit_score = true;
			for (i = 0, n = fields.length; i < n; i++) {
				if (fields[i].field === '$score') {
					implicit_score = false;
					break;
				}
			}
			if (implicit_score) {
				fields.unshift({field: '$score', direction: 'desc'});
			}
		} else {
			for (i = 0, n = fields.length; i < n; i++) {
				if (fields[i].field === '$score') {
					fields.splice(i, 1);
					break;
				}
			}
		}

		multipliers = [];
		for (i = 0, n = fields.length; i < n; i++) {
			multipliers.push(fields[i].direction === 'desc' ? -1 : 1);
		}

		// build function
		fields_count = fields.length;
		if (!fields_count) {
			return null;
		} else if (fields_count === 1) {
			field = fields[0].field;
			multiplier = multipliers[0];
			return function(a, b) {
				return multiplier * cmp(
					get_field(field, a),
					get_field(field, b)
				);
			};
		} else {
			return function(a, b) {
				var i, result, a_value, b_value, field;
				for (i = 0; i < fields_count; i++) {
					field = fields[i].field;
					result = multipliers[i] * cmp(
						get_field(field, a),
						get_field(field, b)
					);
					if (result) return result;
				}
				return 0;
			};
		}
	};

	/**
	 * Parses a search query and returns an object
	 * with tokens and fields ready to be populated
	 * with results.
	 *
	 * @param {string} query
	 * @param {object} options
	 * @returns {object}
	 */
	Sifter.prototype.prepareSearch = function(query, options) {
		if (typeof query === 'object') return query;

		options = extend({}, options);

		var option_fields     = options.fields;
		var option_sort       = options.sort;
		var option_sort_empty = options.sort_empty;

		if (option_fields && !is_array(option_fields)) options.fields = [option_fields];
		if (option_sort && !is_array(option_sort)) options.sort = [option_sort];
		if (option_sort_empty && !is_array(option_sort_empty)) options.sort_empty = [option_sort_empty];

		return {
			options : options,
			query   : String(query || '').toLowerCase(),
			tokens  : this.tokenize(query),
			total   : 0,
			items   : []
		};
	};

	/**
	 * Searches through all items and returns a sorted array of matches.
	 *
	 * The `options` parameter can contain:
	 *
	 *   - fields {string|array}
	 *   - sort {array}
	 *   - score {function}
	 *   - filter {bool}
	 *   - limit {integer}
	 *
	 * Returns an object containing:
	 *
	 *   - options {object}
	 *   - query {string}
	 *   - tokens {array}
	 *   - total {int}
	 *   - items {array}
	 *
	 * @param {string} query
	 * @param {object} options
	 * @returns {object}
	 */
	Sifter.prototype.search = function(query, options) {
		var self = this, value, score, search, calculateScore;
		var fn_sort;
		var fn_score;

		search  = this.prepareSearch(query, options);
		options = search.options;
		query   = search.query;

		// generate result scoring function
		fn_score = options.score || self.getScoreFunction(search);

		// perform search and sort
		if (query.length) {
			self.iterator(self.items, function(item, id) {
				score = fn_score(item);
				if (options.filter === false || score > 0) {
					search.items.push({'score': score, 'id': id});
				}
			});
		} else {
			self.iterator(self.items, function(item, id) {
				search.items.push({'score': 1, 'id': id});
			});
		}

		fn_sort = self.getSortFunction(search, options);
		if (fn_sort) search.items.sort(fn_sort);

		// apply limits
		search.total = search.items.length;
		if (typeof options.limit === 'number') {
			search.items = search.items.slice(0, options.limit);
		}

		return search;
	};

	// utilities
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	var cmp = function(a, b) {
		if (typeof a === 'number' && typeof b === 'number') {
			return a > b ? 1 : (a < b ? -1 : 0);
		}
		a = asciifold(String(a || ''));
		b = asciifold(String(b || ''));
		if (a > b) return 1;
		if (b > a) return -1;
		return 0;
	};

	var extend = function(a, b) {
		var i, n, k, object;
		for (i = 1, n = arguments.length; i < n; i++) {
			object = arguments[i];
			if (!object) continue;
			for (k in object) {
				if (object.hasOwnProperty(k)) {
					a[k] = object[k];
				}
			}
		}
		return a;
	};

	var trim = function(str) {
		return (str + '').replace(/^\s+|\s+$|/g, '');
	};

	var escape_regex = function(str) {
		return (str + '').replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
	};

	var is_array = Array.isArray || ($ && $.isArray) || function(object) {
		return Object.prototype.toString.call(object) === '[object Array]';
	};

	var DIACRITICS = {
		'a': '[aÀÁÂÃÄÅàáâãäåĀāąĄ]',
		'c': '[cÇçćĆčČ]',
		'd': '[dđĐďĎ]',
		'e': '[eÈÉÊËèéêëěĚĒēęĘ]',
		'i': '[iÌÍÎÏìíîïĪī]',
		'l': '[lłŁ]',
		'n': '[nÑñňŇńŃ]',
		'o': '[oÒÓÔÕÕÖØòóôõöøŌō]',
		'r': '[rřŘ]',
		's': '[sŠšśŚ]',
		't': '[tťŤ]',
		'u': '[uÙÚÛÜùúûüůŮŪū]',
		'y': '[yŸÿýÝ]',
		'z': '[zŽžżŻźŹ]'
	};

	var asciifold = (function() {
		var i, n, k, chunk;
		var foreignletters = '';
		var lookup = {};
		for (k in DIACRITICS) {
			if (DIACRITICS.hasOwnProperty(k)) {
				chunk = DIACRITICS[k].substring(2, DIACRITICS[k].length - 1);
				foreignletters += chunk;
				for (i = 0, n = chunk.length; i < n; i++) {
					lookup[chunk.charAt(i)] = k;
				}
			}
		}
		var regexp = new RegExp('[' +  foreignletters + ']', 'g');
		return function(str) {
			return str.replace(regexp, function(foreignletter) {
				return lookup[foreignletter];
			}).toLowerCase();
		};
	})();


	// export
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

	return Sifter;
}));



/**
 * microplugin.js
 * Copyright (c) 2013 Brian Reavis & contributors
 *
 * 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.
 *
 * @author Brian Reavis <brian@thirdroute.com>
 */

(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		define('microplugin', factory);
	} else if (typeof exports === 'object') {
		module.exports = factory();
	} else {
		root.MicroPlugin = factory();
	}
}(this, function() {
	var MicroPlugin = {};

	MicroPlugin.mixin = function(Interface) {
		Interface.plugins = {};

		/**
		 * Initializes the listed plugins (with options).
		 * Acceptable formats:
		 *
		 * List (without options):
		 *   ['a', 'b', 'c']
		 *
		 * List (with options):
		 *   [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
		 *
		 * Hash (with options):
		 *   {'a': { ... }, 'b': { ... }, 'c': { ... }}
		 *
		 * @param {mixed} plugins
		 */
		Interface.prototype.initializePlugins = function(plugins) {
			var i, n, key;
			var self  = this;
			var queue = [];

			self.plugins = {
				names     : [],
				settings  : {},
				requested : {},
				loaded    : {}
			};

			if (utils.isArray(plugins)) {
				for (i = 0, n = plugins.length; i < n; i++) {
					if (typeof plugins[i] === 'string') {
						queue.push(plugins[i]);
					} else {
						self.plugins.settings[plugins[i].name] = plugins[i].options;
						queue.push(plugins[i].name);
					}
				}
			} else if (plugins) {
				for (key in plugins) {
					if (plugins.hasOwnProperty(key)) {
						self.plugins.settings[key] = plugins[key];
						queue.push(key);
					}
				}
			}

			while (queue.length) {
				self.require(queue.shift());
			}
		};

		Interface.prototype.loadPlugin = function(name) {
			var self    = this;
			var plugins = self.plugins;
			var plugin  = Interface.plugins[name];

			if (!Interface.plugins.hasOwnProperty(name)) {
				throw new Error('Unable to find "' +  name + '" plugin');
			}

			plugins.requested[name] = true;
			plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
			plugins.names.push(name);
		};

		/**
		 * Initializes a plugin.
		 *
		 * @param {string} name
		 */
		Interface.prototype.require = function(name) {
			var self = this;
			var plugins = self.plugins;

			if (!self.plugins.loaded.hasOwnProperty(name)) {
				if (plugins.requested[name]) {
					throw new Error('Plugin has circular dependency ("' + name + '")');
				}
				self.loadPlugin(name);
			}

			return plugins.loaded[name];
		};

		/**
		 * Registers a plugin.
		 *
		 * @param {string} name
		 * @param {function} fn
		 */
		Interface.define = function(name, fn) {
			Interface.plugins[name] = {
				'name' : name,
				'fn'   : fn
			};
		};
	};

	var utils = {
		isArray: Array.isArray || function(vArg) {
			return Object.prototype.toString.call(vArg) === '[object Array]';
		}
	};

	return MicroPlugin;
}));

/**
 * selectize.js (v0.12.1)
 * Copyright (c) 2013–2015 Brian Reavis & contributors
 *
 * 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.
 *
 * @author Brian Reavis <brian@thirdroute.com>
 */

/*jshint curly:false */
/*jshint browser:true */

(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		define('selectize', ['jquery','sifter','microplugin'], factory);
	} else if (typeof exports === 'object') {
		module.exports = factory(require('jquery'), require('sifter'), require('microplugin'));
	} else {
		root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin);
	}
}(this, function($, Sifter, MicroPlugin) {
	'use strict';

	var highlight = function($element, pattern) {
		if (typeof pattern === 'string' && !pattern.length) return;
		var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern;
	
		var highlight = function(node) {
			var skip = 0;
			if (node.nodeType === 3) {
				var pos = node.data.search(regex);
				if (pos >= 0 && node.data.length > 0) {
					var match = node.data.match(regex);
					var spannode = document.createElement('span');
					spannode.className = 'highlight';
					var middlebit = node.splitText(pos);
					var endbit = middlebit.splitText(match[0].length);
					var middleclone = middlebit.cloneNode(true);
					spannode.appendChild(middleclone);
					middlebit.parentNode.replaceChild(spannode, middlebit);
					skip = 1;
				}
			} else if (node.nodeType === 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
				for (var i = 0; i < node.childNodes.length; ++i) {
					i += highlight(node.childNodes[i]);
				}
			}
			return skip;
		};
	
		return $element.each(function() {
			highlight(this);
		});
	};
	
	var MicroEvent = function() {};
	MicroEvent.prototype = {
		on: function(event, fct){
			this._events = this._events || {};
			this._events[event] = this._events[event] || [];
			this._events[event].push(fct);
		},
		off: function(event, fct){
			var n = arguments.length;
			if (n === 0) return delete this._events;
			if (n === 1) return delete this._events[event];
	
			this._events = this._events || {};
			if (event in this._events === false) return;
			this._events[event].splice(this._events[event].indexOf(fct), 1);
		},
		trigger: function(event /* , args... */){
			this._events = this._events || {};
			if (event in this._events === false) return;
			for (var i = 0; i < this._events[event].length; i++){
				this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
			}
		}
	};
	
	/**
	 * Mixin will delegate all MicroEvent.js function in the destination object.
	 *
	 * - MicroEvent.mixin(Foobar) will make Foobar able to use MicroEvent
	 *
	 * @param {object} the object which will support MicroEvent
	 */
	MicroEvent.mixin = function(destObject){
		var props = ['on', 'off', 'trigger'];
		for (var i = 0; i < props.length; i++){
			destObject.prototype[props[i]] = MicroEvent.prototype[props[i]];
		}
	};
	
	var IS_MAC        = /Mac/.test(navigator.userAgent);
	
	var KEY_A         = 65;
	var KEY_COMMA     = 188;
	var KEY_RETURN    = 13;
	var KEY_ESC       = 27;
	var KEY_LEFT      = 37;
	var KEY_UP        = 38;
	var KEY_P         = 80;
	var KEY_RIGHT     = 39;
	var KEY_DOWN      = 40;
	var KEY_N         = 78;
	var KEY_BACKSPACE = 8;
	var KEY_DELETE    = 46;
	var KEY_SHIFT     = 16;
	var KEY_CMD       = IS_MAC ? 91 : 17;
	var KEY_CTRL      = IS_MAC ? 18 : 17;
	var KEY_TAB       = 9;
	
	var TAG_SELECT    = 1;
	var TAG_INPUT     = 2;
	
	// for now, android support in general is too spotty to support validity
	var SUPPORTS_VALIDITY_API = !/android/i.test(window.navigator.userAgent) && !!document.createElement('form').validity;
	
	var isset = function(object) {
		return typeof object !== 'undefined';
	};
	
	/**
	 * Converts a scalar to its best string representation
	 * for hash keys and HTML attribute values.
	 *
	 * Transformations:
	 *   'str'     -> 'str'
	 *   null      -> ''
	 *   undefined -> ''
	 *   true      -> '1'
	 *   false     -> '0'
	 *   0         -> '0'
	 *   1         -> '1'
	 *
	 * @param {string} value
	 * @returns {string|null}
	 */
	var hash_key = function(value) {
		if (typeof value === 'undefined' || value === null) return null;
		if (typeof value === 'boolean') return value ? '1' : '0';
		return value + '';
	};
	
	/**
	 * Escapes a string for use within HTML.
	 *
	 * @param {string} str
	 * @returns {string}
	 */
	var escape_html = function(str) {
		return (str + '')
			.replace(/&/g, '&amp;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;')
			.replace(/"/g, '&quot;');
	};
	
	/**
	 * Escapes "$" characters in replacement strings.
	 *
	 * @param {string} str
	 * @returns {string}
	 */
	var escape_replace = function(str) {
		return (str + '').replace(/\$/g, '$$$$');
	};
	
	var hook = {};
	
	/**
	 * Wraps `method` on `self` so that `fn`
	 * is invoked before the original method.
	 *
	 * @param {object} self
	 * @param {string} method
	 * @param {function} fn
	 */
	hook.before = function(self, method, fn) {
		var original = self[method];
		self[method] = function() {
			fn.apply(self, arguments);
			return original.apply(self, arguments);
		};
	};
	
	/**
	 * Wraps `method` on `self` so that `fn`
	 * is invoked after the original method.
	 *
	 * @param {object} self
	 * @param {string} method
	 * @param {function} fn
	 */
	hook.after = function(self, method, fn) {
		var original = self[method];
		self[method] = function() {
			var result = original.apply(self, arguments);
			fn.apply(self, arguments);
			return result;
		};
	};
	
	/**
	 * Wraps `fn` so that it can only be invoked once.
	 *
	 * @param {function} fn
	 * @returns {function}
	 */
	var once = function(fn) {
		var called = false;
		return function() {
			if (called) return;
			called = true;
			fn.apply(this, arguments);
		};
	};
	
	/**
	 * Wraps `fn` so that it can only be called once
	 * every `delay` milliseconds (invoked on the falling edge).
	 *
	 * @param {function} fn
	 * @param {int} delay
	 * @returns {function}
	 */
	var debounce = function(fn, delay) {
		var timeout;
		return function() {
			var self = this;
			var args = arguments;
			window.clearTimeout(timeout);
			timeout = window.setTimeout(function() {
				fn.apply(self, args);
			}, delay);
		};
	};
	
	/**
	 * Debounce all fired events types listed in `types`
	 * while executing the provided `fn`.
	 *
	 * @param {object} self
	 * @param {array} types
	 * @param {function} fn
	 */
	var debounce_events = function(self, types, fn) {
		var type;
		var trigger = self.trigger;
		var event_args = {};
	
		// override trigger method
		self.trigger = function() {
			var type = arguments[0];
			if (types.indexOf(type) !== -1) {
				event_args[type] = arguments;
			} else {
				return trigger.apply(self, arguments);
			}
		};
	
		// invoke provided function
		fn.apply(self, []);
		self.trigger = trigger;
	
		// trigger queued events
		for (type in event_args) {
			if (event_args.hasOwnProperty(type)) {
				trigger.apply(self, event_args[type]);
			}
		}
	};
	
	/**
	 * A workaround for http://bugs.jquery.com/ticket/6696
	 *
	 * @param {object} $parent - Parent element to listen on.
	 * @param {string} event - Event name.
	 * @param {string} selector - Descendant selector to filter by.
	 * @param {function} fn - Event handler.
	 */
	var watchChildEvent = function($parent, event, selector, fn) {
		$parent.on(event, selector, function(e) {
			var child = e.target;
			while (child && child.parentNode !== $parent[0]) {
				child = child.parentNode;
			}
			e.currentTarget = child;
			return fn.apply(this, [e]);
		});
	};
	
	/**
	 * Determines the current selection within a text input control.
	 * Returns an object containing:
	 *   - start
	 *   - length
	 *
	 * @param {object} input
	 * @returns {object}
	 */
	var getSelection = function(input) {
		var result = {};
		if ('selectionStart' in input) {
			result.start = input.selectionStart;
			result.length = input.selectionEnd - result.start;
		} else if (document.selection) {
			input.focus();
			var sel = document.selection.createRange();
			var selLen = document.selection.createRange().text.length;
			sel.moveStart('character', -input.value.length);
			result.start = sel.text.length - selLen;
			result.length = selLen;
		}
		return result;
	};
	
	/**
	 * Copies CSS properties from one element to another.
	 *
	 * @param {object} $from
	 * @param {object} $to
	 * @param {array} properties
	 */
	var transferStyles = function($from, $to, properties) {
		var i, n, styles = {};
		if (properties) {
			for (i = 0, n = properties.length; i < n; i++) {
				styles[properties[i]] = $from.css(properties[i]);
			}
		} else {
			styles = $from.css();
		}
		$to.css(styles);
	};
	
	/**
	 * Measures the width of a string within a
	 * parent element (in pixels).
	 *
	 * @param {string} str
	 * @param {object} $parent
	 * @returns {int}
	 */
	var measureString = function(str, $parent) {
		if (!str) {
			return 0;
		}
	
		var $test = $('<test>').css({
			position: 'absolute',
			top: -99999,
			left: -99999,
			width: 'auto',
			padding: 0,
			whiteSpace: 'pre'
		}).text(str).appendTo('body');
	
		transferStyles($parent, $test, [
			'letterSpacing',
			'fontSize',
			'fontFamily',
			'fontWeight',
			'textTransform'
		]);
	
		var width = $test.width();
		$test.remove();
	
		return width;
	};
	
	/**
	 * Sets up an input to grow horizontally as the user
	 * types. If the value is changed manually, you can
	 * trigger the "update" handler to resize:
	 *
	 * $input.trigger('update');
	 *
	 * @param {object} $input
	 */
	var autoGrow = function($input) {
		var currentWidth = null;
	
		var update = function(e, options) {
			var value, keyCode, printable, placeholder, width;
			var shift, character, selection;
			e = e || window.event || {};
			options = options || {};
	
			if (e.metaKey || e.altKey) return;
			if (!options.force && $input.data('grow') === false) return;
	
			value = $input.val();
			if (e.type && e.type.toLowerCase() === 'keydown') {
				keyCode = e.keyCode;
				printable = (
					(keyCode >= 97 && keyCode <= 122) || // a-z
					(keyCode >= 65 && keyCode <= 90)  || // A-Z
					(keyCode >= 48 && keyCode <= 57)  || // 0-9
					keyCode === 32 // space
				);
	
				if (keyCode === KEY_DELETE || keyCode === KEY_BACKSPACE) {
					selection = getSelection($input[0]);
					if (selection.length) {
						value = value.substring(0, selection.start) + value.substring(selection.start + selection.length);
					} else if (keyCode === KEY_BACKSPACE && selection.start) {
						value = value.substring(0, selection.start - 1) + value.substring(selection.start + 1);
					} else if (keyCode === KEY_DELETE && typeof selection.start !== 'undefined') {
						value = value.substring(0, selection.start) + value.substring(selection.start + 1);
					}
				} else if (printable) {
					shift = e.shiftKey;
					character = String.fromCharCode(e.keyCode);
					if (shift) character = character.toUpperCase();
					else character = character.toLowerCase();
					value += character;
				}
			}
	
			placeholder = $input.attr('placeholder');
			if (!value && placeholder) {
				value = placeholder;
			}
	
			width = measureString(value, $input) + 4;
			if (width !== currentWidth) {
				currentWidth = width;
				$input.width(width);
				$input.triggerHandler('resize');
			}
		};
	
		$input.on('keydown keyup update blur', update);
		update();
	};
	
	var Selectize = function($input, settings) {
		var key, i, n, dir, input, self = this;
		input = $input[0];
		input.selectize = self;
	
		// detect rtl environment
		var computedStyle = window.getComputedStyle && window.getComputedStyle(input, null);
		dir = computedStyle ? computedStyle.getPropertyValue('direction') : input.currentStyle && input.currentStyle.direction;
		dir = dir || $input.parents('[dir]:first').attr('dir') || '';
	
		// setup default state
		$.extend(self, {
			order            : 0,
			settings         : settings,
			$input           : $input,
			tabIndex         : $input.attr('tabindex') || '',
			tagType          : input.tagName.toLowerCase() === 'select' ? TAG_SELECT : TAG_INPUT,
			rtl              : /rtl/i.test(dir),
	
			eventNS          : '.selectize' + (++Selectize.count),
			highlightedValue : null,
			isOpen           : false,
			isDisabled       : false,
			isRequired       : $input.is('[required]'),
			isInvalid        : false,
			isLocked         : false,
			isFocused        : false,
			isInputHidden    : false,
			isSetup          : false,
			isShiftDown      : false,
			isCmdDown        : false,
			isCtrlDown       : false,
			ignoreFocus      : false,
			ignoreBlur       : false,
			ignoreHover      : false,
			hasOptions       : false,
			currentResults   : null,
			lastValue        : '',
			caretPos         : 0,
			loading          : 0,
			loadedSearches   : {},
	
			$activeOption    : null,
			$activeItems     : [],
	
			optgroups        : {},
			options          : {},
			userOptions      : {},
			items            : [],
			renderCache      : {},
			onSearchChange   : settings.loadThrottle === null ? self.onSearchChange : debounce(self.onSearchChange, settings.loadThrottle)
		});
	
		// search system
		self.sifter = new Sifter(this.options, {diacritics: settings.diacritics});
	
		// build options table
		if (self.settings.options) {
			for (i = 0, n = self.settings.options.length; i < n; i++) {
				self.registerOption(self.settings.options[i]);
			}
			delete self.settings.options;
		}
	
		// build optgroup table
		if (self.settings.optgroups) {
			for (i = 0, n = self.settings.optgroups.length; i < n; i++) {
				self.registerOptionGroup(self.settings.optgroups[i]);
			}
			delete self.settings.optgroups;
		}
	
		// option-dependent defaults
		self.settings.mode = self.settings.mode || (self.settings.maxItems === 1 ? 'single' : 'multi');
		if (typeof self.settings.hideSelected !== 'boolean') {
			self.settings.hideSelected = self.settings.mode === 'multi';
		}
	
		self.initializePlugins(self.settings.plugins);
		self.setupCallbacks();
		self.setupTemplates();
		self.setup();
	};
	
	// mixins
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	MicroEvent.mixin(Selectize);
	MicroPlugin.mixin(Selectize);
	
	// methods
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	
	$.extend(Selectize.prototype, {
	
		/**
		 * Creates all elements and sets up event bindings.
		 */
		setup: function() {
			var self      = this;
			var settings  = self.settings;
			var eventNS   = self.eventNS;
			var $window   = $(window);
			var $document = $(document);
			var $input    = self.$input;
	
			var $wrapper;
			var $control;
			var $control_input;
			var $dropdown;
			var $dropdown_content;
			var $dropdown_parent;
			var inputMode;
			var timeout_blur;
			var timeout_focus;
			var classes;
			var classes_plugins;
	
			inputMode         = self.settings.mode;
			classes           = $input.attr('class') || '';
	
			$wrapper          = $('<div>').addClass(settings.wrapperClass).addClass(classes).addClass(inputMode);
			$control          = $('<div>').addClass(settings.inputClass).addClass('items').appendTo($wrapper);
			$control_input    = $('<input type="text" autocomplete="off" />').appendTo($control).attr('tabindex', $input.is(':disabled') ? '-1' : self.tabIndex);
			$dropdown_parent  = $(settings.dropdownParent || $wrapper);
			$dropdown         = $('<div>').addClass(settings.dropdownClass).addClass(inputMode).hide().appendTo($dropdown_parent);
			$dropdown_content = $('<div tabindex="-1">').addClass(settings.dropdownContentClass).appendTo($dropdown);
	
			if(self.settings.copyClassesToDropdown) {
				$dropdown.addClass(classes);
			}
	
			$wrapper.css({
				width: $input[0].style.width
			});
	
			if (self.plugins.names.length) {
				classes_plugins = 'plugin-' + self.plugins.names.join(' plugin-');
				$wrapper.addClass(classes_plugins);
				$dropdown.addClass(classes_plugins);
			}
	
			if ((settings.maxItems === null || settings.maxItems > 1) && self.tagType === TAG_SELECT) {
				$input.attr('multiple', 'multiple');
			}
	
			if (self.settings.placeholder) {
				$control_input.attr('placeholder', settings.placeholder);
			}
	
			// if splitOn was not passed in, construct it from the delimiter to allow pasting universally
			if (!self.settings.splitOn && self.settings.delimiter) {
				var delimiterEscaped = self.settings.delimiter.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
				self.settings.splitOn = new RegExp('\\s*' + delimiterEscaped + '+\\s*');
			}
	
			if ($input.attr('autocorrect')) {
				$control_input.attr('autocorrect', $input.attr('autocorrect'));
			}
	
			if ($input.attr('autocapitalize')) {
				$control_input.attr('autocapitalize', $input.attr('autocapitalize'));
			}
	
			self.$wrapper          = $wrapper;
			self.$control          = $control;
			self.$control_input    = $control_input;
			self.$dropdown         = $dropdown;
			self.$dropdown_content = $dropdown_content;
	
			$dropdown.on('mouseenter', '[data-selectable]', function() { return self.onOptionHover.apply(self, arguments); });
			$dropdown.on('mousedown click', '[data-selectable]', function() { return self.onOptionSelect.apply(self, arguments); });
			watchChildEvent($control, 'mousedown', '*:not(input)', function() { return self.onItemSelect.apply(self, arguments); });
			autoGrow($control_input);
	
			$control.on({
				mousedown : function() { return self.onMouseDown.apply(self, arguments); },
				click     : function() { return self.onClick.apply(self, arguments); }
			});
	
			$control_input.on({
				mousedown : function(e) { e.stopPropagation(); },
				keydown   : function() { return self.onKeyDown.apply(self, arguments); },
				keyup     : function() { return self.onKeyUp.apply(self, arguments); },
				keypress  : function() { return self.onKeyPress.apply(self, arguments); },
				resize    : function() { self.positionDropdown.apply(self, []); },
				blur      : function() { return self.onBlur.apply(self, arguments); },
				focus     : function() { self.ignoreBlur = false; return self.onFocus.apply(self, arguments); },
				paste     : function() { return self.onPaste.apply(self, arguments); }
			});
	
			$document.on('keydown' + eventNS, function(e) {
				self.isCmdDown = e[IS_MAC ? 'metaKey' : 'ctrlKey'];
				self.isCtrlDown = e[IS_MAC ? 'altKey' : 'ctrlKey'];
				self.isShiftDown = e.shiftKey;
			});
	
			$document.on('keyup' + eventNS, function(e) {
				if (e.keyCode === KEY_CTRL) self.isCtrlDown = false;
				if (e.keyCode === KEY_SHIFT) self.isShiftDown = false;
				if (e.keyCode === KEY_CMD) self.isCmdDown = false;
			});
	
			$document.on('mousedown' + eventNS, function(e) {
				if (self.isFocused) {
					// prevent events on the dropdown scrollbar from causing the control to blur
					if (e.target === self.$dropdown[0] || e.target.parentNode === self.$dropdown[0]) {
						return false;
					}
					// blur on click outside
					if (!self.$control.has(e.target).length && e.target !== self.$control[0]) {
						self.blur(e.target);
					}
				}
			});
	
			$window.on(['scroll' + eventNS, 'resize' + eventNS].join(' '), function() {
				if (self.isOpen) {
					self.positionDropdown.apply(self, arguments);
				}
			});
			$window.on('mousemove' + eventNS, function() {
				self.ignoreHover = false;
			});
	
			// store original children and tab index so that they can be
			// restored when the destroy() method is called.
			this.revertSettings = {
				$children : $input.children().detach(),
				tabindex  : $input.attr('tabindex')
			};
	
			$input.attr('tabindex', -1).hide().after(self.$wrapper);
	
			if ($.isArray(settings.items)) {
				self.setValue(settings.items);
				delete settings.items;
			}
	
			// feature detect for the validation API
			if (SUPPORTS_VALIDITY_API) {
				$input.on('invalid' + eventNS, function(e) {
					e.preventDefault();
					self.isInvalid = true;
					self.refreshState();
				});
			}
	
			self.updateOriginalInput();
			self.refreshItems();
			self.refreshState();
			self.updatePlaceholder();
			self.isSetup = true;
	
			if ($input.is(':disabled')) {
				self.disable();
			}
	
			self.on('change', this.onChange);
	
			$input.data('selectize', self);
			$input.addClass('selectized');
			self.trigger('initialize');
	
			// preload options
			if (settings.preload === true) {
				self.onSearchChange('');
			}
	
		},
	
		/**
		 * Sets up default rendering functions.
		 */
		setupTemplates: function() {
			var self = this;
			var field_label = self.settings.labelField;
			var field_optgroup = self.settings.optgroupLabelField;
	
			var templates = {
				'optgroup': function(data) {
					return '<div class="optgroup">' + data.html + '</div>';
				},
				'optgroup_header': function(data, escape) {
					return '<div class="optgroup-header">' + escape(data[field_optgroup]) + '</div>';
				},
                'option': function (data, escape) {
                    var cssClasses = 'option';
                    if (data.DropdownDisplay && data.DropdownDisplay.indexOf('CLASS FULL') > -1) {
                        cssClasses = cssClasses + ' ' + 'course-overview-disabled-option';
                    }
                    return '<div class="' + cssClasses + '">' + escape(data[field_label]) + '</div>';
                },
				'item': function (data, escape) {
					if (data.DropdownDisplay) {
						return '<div class="item" title="' + data.DropdownDisplay + '">' + escape(data[field_label]) + '</div>';
                    }
					return '<div class="item">' + escape(data[field_label]) + '</div>';
				},
				'option_create': function(data, escape) {
					return '<div class="create">Add <strong>' + escape(data.input) + '</strong>&hellip;</div>';
				}
			};
	
			self.settings.render = $.extend({}, templates, self.settings.render);
		},
	
		/**
		 * Maps fired events to callbacks provided
		 * in the settings used when creating the control.
		 */
		setupCallbacks: function() {
			var key, fn, callbacks = {
				'initialize'      : 'onInitialize',
				'change'          : 'onChange',
				'item_add'        : 'onItemAdd',
				'item_remove'     : 'onItemRemove',
				'clear'           : 'onClear',
				'option_add'      : 'onOptionAdd',
				'option_remove'   : 'onOptionRemove',
				'option_clear'    : 'onOptionClear',
				'optgroup_add'    : 'onOptionGroupAdd',
				'optgroup_remove' : 'onOptionGroupRemove',
				'optgroup_clear'  : 'onOptionGroupClear',
				'dropdown_open'   : 'onDropdownOpen',
				'dropdown_close'  : 'onDropdownClose',
				'type'            : 'onType',
				'load'            : 'onLoad',
				'focus'           : 'onFocus',
				'blur'            : 'onBlur'
			};
	
			for (key in callbacks) {
				if (callbacks.hasOwnProperty(key)) {
					fn = this.settings[callbacks[key]];
					if (fn) this.on(key, fn);
				}
			}
		},
	
		/**
		 * Triggered when the main control element
		 * has a click event.
		 *
		 * @param {object} e
		 * @return {boolean}
		 */
		onClick: function(e) {
			var self = this;
	
			// necessary for mobile webkit devices (manual focus triggering
			// is ignored unless invoked within a click event)
			if (!self.isFocused) {
				self.focus();
				e.preventDefault();
			}
		},
	
		/**
		 * Triggered when the main control element
		 * has a mouse down event.
		 *
		 * @param {object} e
		 * @return {boolean}
		 */
		onMouseDown: function(e) {
			var self = this;
			var defaultPrevented = e.isDefaultPrevented();
			var $target = $(e.target);
	
			if (self.isFocused) {
				// retain focus by preventing native handling. if the
				// event target is the input it should not be modified.
				// otherwise, text selection within the input won't work.
				if (e.target !== self.$control_input[0]) {
					if (self.settings.mode === 'single') {
						// toggle dropdown
						self.isOpen ? self.close() : self.open();
					} else if (!defaultPrevented) {
						self.setActiveItem(null);
					}
					return false;
				}
			} else {
				// give control focus
				if (!defaultPrevented) {
					window.setTimeout(function() {
						self.focus();
					}, 0);
				}
			}
		},
	
		/**
		 * Triggered when the value of the control has been changed.
		 * This should propagate the event to the original DOM
		 * input / select element.
		 */
		onChange: function() {
			this.$input.trigger('change');
		},
	
		/**
		 * Triggered on <input> paste.
		 *
		 * @param {object} e
		 * @returns {boolean}
		 */
		onPaste: function(e) {
			var self = this;
			if (self.isFull() || self.isInputHidden || self.isLocked) {
				e.preventDefault();
			} else {
				// If a regex or string is included, this will split the pasted
				// input and create Items for each separate value
				if (self.settings.splitOn) {
					setTimeout(function() {
						var splitInput = $.trim(self.$control_input.val() || '').split(self.settings.splitOn);
						for (var i = 0, n = splitInput.length; i < n; i++) {
							self.createItem(splitInput[i]);
						}
					}, 0);
				}
			}
		},
	
		/**
		 * Triggered on <input> keypress.
		 *
		 * @param {object} e
		 * @returns {boolean}
		 */
		onKeyPress: function(e) {
			if (this.isLocked) return e && e.preventDefault();
			var character = String.fromCharCode(e.keyCode || e.which);
			if (this.settings.create && this.settings.mode === 'multi' && character === this.settings.delimiter) {
				this.createItem();
				e.preventDefault();
				return false;
			}
		},
	
		/**
		 * Triggered on <input> keydown.
		 *
		 * @param {object} e
		 * @returns {boolean}
		 */
		onKeyDown: function(e) {
			var isInput = e.target === this.$control_input[0];
			var self = this;
	
			if (self.isLocked) {
				if (e.keyCode !== KEY_TAB) {
					e.preventDefault();
				}
				return;
			}
	
			switch (e.keyCode) {
				case KEY_A:
					if (self.isCmdDown) {
						self.selectAll();
						return;
					}
					break;
				case KEY_ESC:
					if (self.isOpen) {
						e.preventDefault();
						e.stopPropagation();
						self.close();
					}
					return;
				case KEY_N:
					if (!e.ctrlKey || e.altKey) break;
				case KEY_DOWN:
					if (!self.isOpen && self.hasOptions) {
						self.open();
					} else if (self.$activeOption) {
						self.ignoreHover = true;
						var $next = self.getAdjacentOption(self.$activeOption, 1);
						if ($next.length) self.setActiveOption($next, true, true);
					}
					e.preventDefault();
					return;
				case KEY_P:
					if (!e.ctrlKey || e.altKey) break;
				case KEY_UP:
					if (self.$activeOption) {
						self.ignoreHover = true;
						var $prev = self.getAdjacentOption(self.$activeOption, -1);
						if ($prev.length) self.setActiveOption($prev, true, true);
					}
					e.preventDefault();
					return;
				case KEY_RETURN:
					if (self.isOpen && self.$activeOption) {
						self.onOptionSelect({currentTarget: self.$activeOption});
						e.preventDefault();
					}
					return;
				case KEY_LEFT:
					self.advanceSelection(-1, e);
					return;
				case KEY_RIGHT:
					self.advanceSelection(1, e);
					return;
				case KEY_TAB:
					if (self.settings.selectOnTab && self.isOpen && self.$activeOption) {
						self.onOptionSelect({currentTarget: self.$activeOption});
	
						// Default behaviour is to jump to the next field, we only want this
						// if the current field doesn't accept any more entries
						if (!self.isFull()) {
							e.preventDefault();
						}
					}
					if (self.settings.create && self.createItem()) {
						e.preventDefault();
					}
					return;
				case KEY_BACKSPACE:
				case KEY_DELETE:
					self.deleteSelection(e);
					return;
			}
	
			if ((self.isFull() || self.isInputHidden) && !(IS_MAC ? e.metaKey : e.ctrlKey)) {
			    self.settings.mode === 'single' && self.settings.searchOnKeypress && printable ? self.clear() : e.preventDefault();
				return;
			}
		},
	
		/**
		 * Triggered on <input> keyup.
		 *
		 * @param {object} e
		 * @returns {boolean}
		 */
		onKeyUp: function(e) {
			var self = this;
	
			if (self.isLocked) return e && e.preventDefault();
			var value = self.$control_input.val() || '';
			if (self.lastValue !== value) {
				self.lastValue = value;
				self.onSearchChange(value);
				self.refreshOptions();
				self.trigger('type', value);
			}
		},
	
		/**
		 * Invokes the user-provide option provider / loader.
		 *
		 * Note: this function is debounced in the Selectize
		 * constructor (by `settings.loadDelay` milliseconds)
		 *
		 * @param {string} value
		 */
		onSearchChange: function(value) {
			var self = this;
			var fn = self.settings.load;
			if (!fn) return;
			if (self.loadedSearches.hasOwnProperty(value)) return;
			self.loadedSearches[value] = true;
			self.load(function(callback) {
				fn.apply(self, [value, callback]);
			});
		},
	
		/**
		 * Triggered on <input> focus.
		 *
		 * @param {object} e (optional)
		 * @returns {boolean}
		 */
		onFocus: function(e) {
			var self = this;
			var wasFocused = self.isFocused;

            if (self.isDisabled) {
				self.blur();
				e && e.preventDefault();
				return false;
			}
	
			if (self.ignoreFocus) return;
			self.isFocused = true;
			if (self.settings.preload === 'focus') self.onSearchChange('');
	
			if (!wasFocused) self.trigger('focus');
	
			if (!self.$activeItems.length) {
				self.showInput();
				self.setActiveItem(null);
				self.refreshOptions(!!self.settings.openOnFocus);
			}
	
			self.refreshState();
		},
	
		/**
		 * Triggered on <input> blur.
		 *
		 * @param {object} e
		 * @param {Element} dest
		 */
		onBlur: function(e, dest) {
			var self = this;
			if (!self.isFocused) return;
			self.isFocused = false;
	
			if (self.ignoreFocus) {
				return;
			} else if (!self.ignoreBlur && document.activeElement === self.$dropdown_content[0]) {
				// necessary to prevent IE closing the dropdown when the scrollbar is clicked
				self.ignoreBlur = true;
				self.onFocus(e);
				return;
			}
	
			var deactivate = function() {
				self.close();
				self.setTextboxValue('');
				self.setActiveItem(null);
				self.setActiveOption(null);
				self.setCaret(self.items.length);
				self.refreshState();
	
				if (dest) {
				    dest.focus();
				}
	
				self.ignoreFocus = false;
				self.trigger('blur');
			};
	
			self.ignoreFocus = true;
			if (self.settings.create && self.settings.createOnBlur) {
				self.createItem(null, false, deactivate);
			} else {
				deactivate();
			}
		},
	
		/**
		 * Triggered when the user rolls over
		 * an option in the autocomplete dropdown menu.
		 *
		 * @param {object} e
		 * @returns {boolean}
		 */
		onOptionHover: function(e) {
			if (this.ignoreHover) return;
			this.setActiveOption(e.currentTarget, false);
		},
	
		/**
		 * Triggered when the user clicks on an option
		 * in the autocomplete dropdown menu.
		 *
		 * @param {object} e
		 * @returns {boolean}
		 */
		onOptionSelect: function(e) {
			var value, $target, $option, self = this;
	
			if (e.preventDefault) {
				e.preventDefault();
				e.stopPropagation();
			}
	
			$target = $(e.currentTarget);
			if ($target.hasClass('create')) {
				self.createItem(null, function() {
					if (self.settings.closeAfterSelect) {
						self.close();
					}
				});
			} else {
				value = $target.attr('data-value');
				if (typeof value !== 'undefined') {
					self.lastQuery = null;
					self.setTextboxValue('');
					self.addItem(value);
					if (self.settings.closeAfterSelect) {
						self.close();
					} else if (!self.settings.hideSelected && e.type && /mouse/.test(e.type)) {
						self.setActiveOption(self.getOption(value));
					}
				}
			}
		},
	
		/**
		 * Triggered when the user clicks on an item
		 * that has been selected.
		 *
		 * @param {object} e
		 * @returns {boolean}
		 */
		onItemSelect: function(e) {
			var self = this;
	
			if (self.isLocked) return;
			if (self.settings.mode === 'multi') {
				e.preventDefault();
				self.setActiveItem(e.currentTarget, e);
			}
		},
	
		/**
		 * Invokes the provided method that provides
		 * results to a callback---which are then added
		 * as options to the control.
		 *
		 * @param {function} fn
		 */
		load: function(fn) {
			var self = this;
			var $wrapper = self.$wrapper.addClass(self.settings.loadingClass);
	
			self.loading++;
			fn.apply(self, [function(results) {
				self.loading = Math.max(self.loading - 1, 0);
				if (results && results.length) {
					self.addOption(results);
					self.refreshOptions(self.isFocused && !self.isInputHidden);
				}
				if (!self.loading) {
					$wrapper.removeClass(self.settings.loadingClass);
				}
				self.trigger('load', results);
			}]);
		},
	
		/**
		 * Sets the input field of the control to the specified value.
		 *
		 * @param {string} value
		 */
		setTextboxValue: function(value) {
			var $input = this.$control_input;
			var changed = $input.val() !== value;
			if (changed) {
				$input.val(value).triggerHandler('update');
				this.lastValue = value;
			}
		},
	
		/**
		 * Returns the value of the control. If multiple items
		 * can be selected (e.g. <select multiple>), this returns
		 * an array. If only one item can be selected, this
		 * returns a string.
		 *
		 * @returns {mixed}
		 */
		getValue: function() {
			if (this.tagType === TAG_SELECT && this.$input.attr('multiple')) {
				return this.items;
			} else {
				return this.items.join(this.settings.delimiter);
			}
		},
	
		/**
		 * Resets the selected items to the given value.
		 *
		 * @param {mixed} value
		 */
		setValue: function(value, silent) {
			var events = silent ? [] : ['change'];
	
			debounce_events(this, events, function() {
				this.clear(silent);
				this.addItems(value, silent);
			});
		},
	
		/**
		 * Sets the selected item.
		 *
		 * @param {object} $item
		 * @param {object} e (optional)
		 */
		setActiveItem: function($item, e) {
			var self = this;
			var eventName;
			var i, idx, begin, end, item, swap;
			var $last;
	
			if (self.settings.mode === 'single') return;
			$item = $($item);
	
			// clear the active selection
			if (!$item.length) {
				$(self.$activeItems).removeClass('active');
				self.$activeItems = [];
				if (self.isFocused) {
					self.showInput();
				}
				return;
			}
	
			// modify selection
			eventName = e && e.type.toLowerCase();
	
			if (eventName === 'mousedown' && self.isShiftDown && self.$activeItems.length) {
				$last = self.$control.children('.active:last');
				begin = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$last[0]]);
				end   = Array.prototype.indexOf.apply(self.$control[0].childNodes, [$item[0]]);
				if (begin > end) {
					swap  = begin;
					begin = end;
					end   = swap;
				}
				for (i = begin; i <= end; i++) {
					item = self.$control[0].childNodes[i];
					if (self.$activeItems.indexOf(item) === -1) {
                        $(item).addClass('active');
                        self.$activeItems.push(item);
                    }
				}
				e.preventDefault();
			} else if ((eventName === 'mousedown' && self.isCtrlDown) || (eventName === 'keydown' && this.isShiftDown)) {
				if ($item.hasClass('active')) {
					idx = self.$activeItems.indexOf($item[0]);
					self.$activeItems.splice(idx, 1);
					$item.removeClass('active');
				} else {
					self.$activeItems.push($item.addClass('active')[0]);
				}
			} else {
				$(self.$activeItems).removeClass('active');
				self.$activeItems = [$item.addClass('active')[0]];
			}
	
			// ensure control has focus
			self.hideInput();
			if (!this.isFocused) {
				self.focus();
			}
		},
	
		/**
		 * Sets the selected item in the dropdown menu
		 * of available options.
		 *
		 * @param {object} $object
		 * @param {boolean} scroll
		 * @param {boolean} animate
		 */
		setActiveOption: function($option, scroll, animate) {
			var height_menu, height_item, y;
			var scroll_top, scroll_bottom;
			var self = this;
	
			if (self.$activeOption) self.$activeOption.removeClass('active');
			self.$activeOption = null;
	
			$option = $($option);
			if (!$option.length) return;
	
			self.$activeOption = $option.addClass('active');
	
			if (scroll || !isset(scroll)) {
	
				height_menu   = self.$dropdown_content.height();
				height_item   = self.$activeOption.outerHeight(true);
				scroll        = self.$dropdown_content.scrollTop() || 0;
				y             = self.$activeOption.offset().top - self.$dropdown_content.offset().top + scroll;
				scroll_top    = y;
				scroll_bottom = y - height_menu + height_item;
	
				if (y + height_item > height_menu + scroll) {
					self.$dropdown_content.stop().animate({scrollTop: scroll_bottom}, animate ? self.settings.scrollDuration : 0);
				} else if (y < scroll) {
					self.$dropdown_content.stop().animate({scrollTop: scroll_top}, animate ? self.settings.scrollDuration : 0);
				}
	
			}
		},
	
		/**
		 * Selects all items (CTRL + A).
		 */
		selectAll: function() {
			var self = this;
			if (self.settings.mode === 'single') return;
	
			self.$activeItems = Array.prototype.slice.apply(self.$control.children(':not(input)').addClass('active'));
			if (self.$activeItems.length) {
				self.hideInput();
				self.close();
			}
			self.focus();
		},
	
		/**
		 * Hides the input element out of view, while
		 * retaining its focus.
		 */
		hideInput: function() {
			var self = this;
	
			self.setTextboxValue('');
			self.$control_input.css({opacity: 0, position: 'absolute', left: self.rtl ? 10000 : -10000});
			self.isInputHidden = true;
		},
	
		/**
		 * Restores input visibility.
		 */
		showInput: function() {
			this.$control_input.css({opacity: 1, position: 'relative', left: 0});
			this.isInputHidden = false;
		},
	
		/**
		 * Gives the control focus.
		 */
		focus: function() {
			var self = this;
			if (self.isDisabled) return;
	
			self.ignoreFocus = true;
			self.$control_input[0].focus();
			window.setTimeout(function() {
				self.ignoreFocus = false;
				self.onFocus();
			}, 0);
		},
	
		/**
		 * Forces the control out of focus.
		 *
		 * @param {Element} dest
		 */
		blur: function(dest) {
			this.$control_input[0].blur();
			this.onBlur(null, dest);
		},
	
		/**
		 * Returns a function that scores an object
		 * to show how good of a match it is to the
		 * provided query.
		 *
		 * @param {string} query
		 * @param {object} options
		 * @return {function}
		 */
		getScoreFunction: function(query) {
			return this.sifter.getScoreFunction(query, this.getSearchOptions());
		},
	
		/**
		 * Returns search options for sifter (the system
		 * for scoring and sorting results).
		 *
		 * @see https://github.com/brianreavis/sifter.js
		 * @return {object}
		 */
		getSearchOptions: function() {
			var settings = this.settings;
			var sort = settings.sortField;
			if (typeof sort === 'string') {
				sort = [{field: sort}];
			}
	
			return {
				fields      : settings.searchField,
				conjunction : settings.searchConjunction,
				sort        : sort
			};
		},
	
		/**
		 * Searches through available options and returns
		 * a sorted array of matches.
		 *
		 * Returns an object containing:
		 *
		 *   - query {string}
		 *   - tokens {array}
		 *   - total {int}
		 *   - items {array}
		 *
		 * @param {string} query
		 * @returns {object}
		 */
		search: function(query) {
			var i, value, score, result, calculateScore;
			var self     = this;
			var settings = self.settings;
			var options  = this.getSearchOptions();
	
			// validate user-provided result scoring function
			if (settings.score) {
				calculateScore = self.settings.score.apply(this, [query]);
				if (typeof calculateScore !== 'function') {
					throw new Error('Selectize "score" setting must be a function that returns a function');
				}
			}
	
			// perform search
			if (query !== self.lastQuery) {
				self.lastQuery = query;
				result = self.sifter.search(query, $.extend(options, {score: calculateScore}));
				self.currentResults = result;
			} else {
				result = $.extend(true, {}, self.currentResults);
			}
	
			// filter out selected items
			if (settings.hideSelected) {
				for (i = result.items.length - 1; i >= 0; i--) {
					if (self.items.indexOf(hash_key(result.items[i].id)) !== -1) {
						result.items.splice(i, 1);
					}
				}
			}
	
			return result;
		},
	
		/**
		 * Refreshes the list of available options shown
		 * in the autocomplete dropdown menu.
		 *
		 * @param {boolean} triggerDropdown
		 */
		refreshOptions: function(triggerDropdown) {
			var i, j, k, n, groups, groups_order, option, option_html, optgroup, optgroups, html, html_children, has_create_option;
			var $active, $active_before, $create;
	
			if (typeof triggerDropdown === 'undefined') {
				triggerDropdown = true;
			}
	
			var self              = this;
			var query             = $.trim(self.$control_input.val());
			var results           = self.search(query);
			var $dropdown_content = self.$dropdown_content;
			var active_before     = self.$activeOption && hash_key(self.$activeOption.attr('data-value'));
	
			// build markup
			n = results.items.length;
			if (typeof self.settings.maxOptions === 'number') {
				n = Math.min(n, self.settings.maxOptions);
			}
	
			// render and group available options individually
			groups = {};
			groups_order = [];
	
			for (i = 0; i < n; i++) {
				option      = self.options[results.items[i].id];
                option_html = self.render('option', option);
                if (option.DropdownDisplay && option.DropdownDisplay.indexOf('CLASS FULL') > -1) {
                    option_html = option_html.replace('data-selectable', '');
                }
				optgroup    = option[self.settings.optgroupField] || '';
				optgroups   = $.isArray(optgroup) ? optgroup : [optgroup];
	
				for (j = 0, k = optgroups && optgroups.length; j < k; j++) {
					optgroup = optgroups[j];
					if (!self.optgroups.hasOwnProperty(optgroup)) {
						optgroup = '';
					}
					if (!groups.hasOwnProperty(optgroup)) {
						groups[optgroup] = [];
						groups_order.push(optgroup);
					}
					groups[optgroup].push(option_html);
				}
			}
	
			// sort optgroups
			if (this.settings.lockOptgroupOrder) {
				groups_order.sort(function(a, b) {
					var a_order = self.optgroups[a].$order || 0;
					var b_order = self.optgroups[b].$order || 0;
					return a_order - b_order;
				});
			}
	
			// render optgroup headers & join groups
			html = [];
			for (i = 0, n = groups_order.length; i < n; i++) {
				optgroup = groups_order[i];
				if (self.optgroups.hasOwnProperty(optgroup) && groups[optgroup].length) {
					// render the optgroup header and options within it,
					// then pass it to the wrapper template
					html_children = self.render('optgroup_header', self.optgroups[optgroup]) || '';
					html_children += groups[optgroup].join('');
					html.push(self.render('optgroup', $.extend({}, self.optgroups[optgroup], {
						html: html_children
					})));
				} else {
					html.push(groups[optgroup].join(''));
				}
			}
	
			$dropdown_content.html(html.join(''));
	
			// highlight matching terms inline
			if (self.settings.highlight && results.query.length && results.tokens.length) {
				for (i = 0, n = results.tokens.length; i < n; i++) {
					highlight($dropdown_content, results.tokens[i].regex);
				}
			}
	
			// add "selected" class to selected options
			if (!self.settings.hideSelected) {
                for (i = 0, n = self.items.length; i < n; i++) {
                        self.getOption(self.items[i]).addClass('selected');
				}
			}
	
			// add create option
			has_create_option = self.canCreate(query);
			if (has_create_option) {
				$dropdown_content.prepend(self.render('option_create', {input: query}));
				$create = $($dropdown_content[0].childNodes[0]);
			}
	
			// activate
			self.hasOptions = results.items.length > 0 || has_create_option;
			if (self.hasOptions) {
				if (results.items.length > 0) {
					$active_before = active_before && self.getOption(active_before);
					if ($active_before && $active_before.length) {
						$active = $active_before;
					} else if (self.settings.mode === 'single' && self.items.length) {
						$active = self.getOption(self.items[0]);
					}
					if (!$active || !$active.length) {
						if ($create && !self.settings.addPrecedence) {
							$active = self.getAdjacentOption($create, 1);
						} else {
							$active = $dropdown_content.find('[data-selectable]:first');
						}
					}
				} else {
					$active = $create;
				}
				self.setActiveOption($active);
				if (triggerDropdown && !self.isOpen) { self.open(); }
			} else {
				self.setActiveOption(null);
				if (triggerDropdown && self.isOpen) { self.close(); }
			}
		},
	
		/**
		 * Adds an available option. If it already exists,
		 * nothing will happen. Note: this does not refresh
		 * the options list dropdown (use `refreshOptions`
		 * for that).
		 *
		 * Usage:
		 *
		 *   this.addOption(data)
		 *
		 * @param {object|array} data
		 */
		addOption: function(data) {
			var i, n, value, self = this;
	
			if ($.isArray(data)) {
				for (i = 0, n = data.length; i < n; i++) {
					self.addOption(data[i]);
				}
				return;
			}
	
			if (value = self.registerOption(data)) {
				self.userOptions[value] = true;
				self.lastQuery = null;
				self.trigger('option_add', value, data);
			}
		},
	
		/**
		 * Registers an option to the pool of options.
		 *
		 * @param {object} data
		 * @return {boolean|string}
		 */
		registerOption: function(data) {
			var key = hash_key(data[this.settings.valueField]);
			if (!key || this.options.hasOwnProperty(key)) return false;
			data.$order = data.$order || ++this.order;
			this.options[key] = data;
			return key;
		},
	
		/**
		 * Registers an option group to the pool of option groups.
		 *
		 * @param {object} data
		 * @return {boolean|string}
		 */
		registerOptionGroup: function(data) {
			var key = hash_key(data[this.settings.optgroupValueField]);
			if (!key) return false;
	
			data.$order = data.$order || ++this.order;
			this.optgroups[key] = data;
			return key;
		},
	
		/**
		 * Registers a new optgroup for options
		 * to be bucketed into.
		 *
		 * @param {string} id
		 * @param {object} data
		 */
		addOptionGroup: function(id, data) {
			data[this.settings.optgroupValueField] = id;
			if (id = this.registerOptionGroup(data)) {
				this.trigger('optgroup_add', id, data);
			}
		},
	
		/**
		 * Removes an existing option group.
		 *
		 * @param {string} id
		 */
		removeOptionGroup: function(id) {
			if (this.optgroups.hasOwnProperty(id)) {
				delete this.optgroups[id];
				this.renderCache = {};
				this.trigger('optgroup_remove', id);
			}
		},
	
		/**
		 * Clears all existing option groups.
		 */
		clearOptionGroups: function() {
			this.optgroups = {};
			this.renderCache = {};
			this.trigger('optgroup_clear');
		},
	
		/**
		 * Updates an option available for selection. If
		 * it is visible in the selected items or options
		 * dropdown, it will be re-rendered automatically.
		 *
		 * @param {string} value
		 * @param {object} data
		 */
		updateOption: function(value, data) {
			var self = this;
			var $item, $item_new;
			var value_new, index_item, cache_items, cache_options, order_old;
	
			value     = hash_key(value);
			value_new = hash_key(data[self.settings.valueField]);
	
			// sanity checks
			if (value === null) return;
			if (!self.options.hasOwnProperty(value)) return;
			if (typeof value_new !== 'string') throw new Error('Value must be set in option data');
	
			order_old = self.options[value].$order;
	
			// update references
			if (value_new !== value) {
				delete self.options[value];
				index_item = self.items.indexOf(value);
				if (index_item !== -1) {
					self.items.splice(index_item, 1, value_new);
				}
			}
			data.$order = data.$order || order_old;
			self.options[value_new] = data;
	
			// invalidate render cache
			cache_items = self.renderCache['item'];
			cache_options = self.renderCache['option'];
	
			if (cache_items) {
				delete cache_items[value];
				delete cache_items[value_new];
			}
			if (cache_options) {
				delete cache_options[value];
				delete cache_options[value_new];
			}
	
			// update the item if it's selected
			if (self.items.indexOf(value_new) !== -1) {
				$item = self.getItem(value);
				$item_new = $(self.render('item', data));
				if ($item.hasClass('active')) $item_new.addClass('active');
				$item.replaceWith($item_new);
			}
	
			// invalidate last query because we might have updated the sortField
			self.lastQuery = null;
	
			// update dropdown contents
			if (self.isOpen) {
				self.refreshOptions(false);
			}
		},
	
		/**
		 * Removes a single option.
		 *
		 * @param {string} value
		 * @param {boolean} silent
		 */
		removeOption: function(value, silent) {
			var self = this;
			value = hash_key(value);
	
			var cache_items = self.renderCache['item'];
			var cache_options = self.renderCache['option'];
			if (cache_items) delete cache_items[value];
			if (cache_options) delete cache_options[value];
	
			delete self.userOptions[value];
			delete self.options[value];
			self.lastQuery = null;
			self.trigger('option_remove', value);
			self.removeItem(value, silent);
		},
	
		/**
		 * Clears all options.
		 */
		clearOptions: function() {
			var self = this;
	
			self.loadedSearches = {};
			self.userOptions = {};
			self.renderCache = {};
			self.options = self.sifter.items = {};
			self.lastQuery = null;
			self.trigger('option_clear');
			self.clear();
		},
	
		/**
		 * Returns the jQuery element of the option
		 * matching the given value.
		 *
		 * @param {string} value
		 * @returns {object}
		 */
		getOption: function(value) {
			return this.getElementWithValue(value, this.$dropdown_content.find('[data-selectable]'));
		},
	
		/**
		 * Returns the jQuery element of the next or
		 * previous selectable option.
		 *
		 * @param {object} $option
		 * @param {int} direction  can be 1 for next or -1 for previous
		 * @return {object}
		 */
		getAdjacentOption: function($option, direction) {
			var $options = this.$dropdown.find('[data-selectable]');
			var index    = $options.index($option) + direction;
	
			return index >= 0 && index < $options.length ? $options.eq(index) : $();
		},
	
		/**
		 * Finds the first element with a "data-value" attribute
		 * that matches the given value.
		 *
		 * @param {mixed} value
		 * @param {object} $els
		 * @return {object}
		 */
		getElementWithValue: function(value, $els) {
			value = hash_key(value);
	
			if (typeof value !== 'undefined' && value !== null) {
				for (var i = 0, n = $els.length; i < n; i++) {
					if ($els[i].getAttribute('data-value') === value) {
						return $($els[i]);
					}
				}
			}
	
			return $();
		},
	
		/**
		 * Returns the jQuery element of the item
		 * matching the given value.
		 *
		 * @param {string} value
		 * @returns {object}
		 */
		getItem: function(value) {
			return this.getElementWithValue(value, this.$control.children());
		},
	
		/**
		 * "Selects" multiple items at once. Adds them to the list
		 * at the current caret position.
		 *
		 * @param {string} value
		 * @param {boolean} silent
		 */
		addItems: function(values, silent) {
			var items = $.isArray(values) ? values : [values];
			for (var i = 0, n = items.length; i < n; i++) {
				this.isPending = (i < n - 1);
				this.addItem(items[i], silent);
			}
		},
	
		/**
		 * "Selects" an item. Adds it to the list
		 * at the current caret position.
		 *
		 * @param {string} value
		 * @param {boolean} silent
		 */
		addItem: function(value, silent) {
			var events = silent ? [] : ['change'];
	
			debounce_events(this, events, function() {
				var $item, $option, $options;
				var self = this;
				var inputMode = self.settings.mode;
				var i, active, value_next, wasFull;
				value = hash_key(value);
	
				if (self.items.indexOf(value) !== -1) {
					if (inputMode === 'single') self.close();
					return;
				}
	
				if (!self.options.hasOwnProperty(value)) return;
				if (inputMode === 'single') self.clear(silent);
				if (inputMode === 'multi' && self.isFull()) return;
	
				$item = $(self.render('item', self.options[value]));
				wasFull = self.isFull();
				self.items.splice(self.caretPos, 0, value);
				self.insertAtCaret($item);
				if (!self.isPending || (!wasFull && self.isFull())) {
					self.refreshState();
				}
	
				if (self.isSetup) {
					$options = self.$dropdown_content.find('[data-selectable]');
	
					// update menu / remove the option (if this is not one item being added as part of series)
					if (!self.isPending) {
						$option = self.getOption(value);
						value_next = self.getAdjacentOption($option, 1).attr('data-value');
						self.refreshOptions(self.isFocused && inputMode !== 'single');
						if (value_next) {
							self.setActiveOption(self.getOption(value_next));
						}
					}
	
					// hide the menu if the maximum number of items have been selected or no options are left
					if (!$options.length || self.isFull()) {
						self.close();
					} else {
						self.positionDropdown();
					}
	
					self.updatePlaceholder();
					self.trigger('item_add', value, $item);
					self.updateOriginalInput({silent: silent});
				}
			});
		},
	
		/**
		 * Removes the selected item matching
		 * the provided value.
		 *
		 * @param {string} value
		 */
		removeItem: function(value, silent) {
			var self = this;
			var $item, i, idx;
	
			$item = (typeof value === 'object') ? value : self.getItem(value);
			value = hash_key($item.attr('data-value'));
			i = self.items.indexOf(value);
	
			if (i !== -1) {
				$item.remove();
				if ($item.hasClass('active')) {
					idx = self.$activeItems.indexOf($item[0]);
					self.$activeItems.splice(idx, 1);
				}
	
				self.items.splice(i, 1);
				self.lastQuery = null;
				if (!self.settings.persist && self.userOptions.hasOwnProperty(value)) {
					self.removeOption(value, silent);
				}
	
				if (i < self.caretPos) {
					self.setCaret(self.caretPos - 1);
				}
	
				self.refreshState();
				self.updatePlaceholder();
				self.updateOriginalInput({silent: silent});
				self.positionDropdown();
				self.trigger('item_remove', value, $item);
			}
		},
	
		/**
		 * Invokes the `create` method provided in the
		 * selectize options that should provide the data
		 * for the new item, given the user input.
		 *
		 * Once this completes, it will be added
		 * to the item list.
		 *
		 * @param {string} value
		 * @param {boolean} [triggerDropdown]
		 * @param {function} [callback]
		 * @return {boolean}
		 */
		createItem: function(input, triggerDropdown) {
			var self  = this;
			var caret = self.caretPos;
			input = input || $.trim(self.$control_input.val() || '');
	
			var callback = arguments[arguments.length - 1];
			if (typeof callback !== 'function') callback = function() {};
	
			if (typeof triggerDropdown !== 'boolean') {
				triggerDropdown = true;
			}
	
			if (!self.canCreate(input)) {
				callback();
				return false;
			}
	
			self.lock();
	
			var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
				var data = {};
				data[self.settings.labelField] = input;
				data[self.settings.valueField] = input;
				return data;
			};
	
			var create = once(function(data) {
				self.unlock();
	
				if (!data || typeof data !== 'object') return callback();
				var value = hash_key(data[self.settings.valueField]);
				if (typeof value !== 'string') return callback();
	
				self.setTextboxValue('');
				self.addOption(data);
				self.setCaret(caret);
				self.addItem(value);
				self.refreshOptions(triggerDropdown && self.settings.mode !== 'single');
				callback(data);
			});
	
			var output = setup.apply(this, [input, create]);
			if (typeof output !== 'undefined') {
				create(output);
			}
	
			return true;
		},
	
		/**
		 * Re-renders the selected item lists.
		 */
		refreshItems: function() {
			this.lastQuery = null;
	
			if (this.isSetup) {
				this.addItem(this.items);
			}
	
			this.refreshState();
			this.updateOriginalInput();
		},
	
		/**
		 * Updates all state-dependent attributes
		 * and CSS classes.
		 */
		refreshState: function() {
			var invalid, self = this;
			if (self.isRequired) {
				if (self.items.length) self.isInvalid = false;
				self.$control_input.prop('required', invalid);
			}
			self.refreshClasses();
		},
	
		/**
		 * Updates all state-dependent CSS classes.
		 */
		refreshClasses: function() {
			var self     = this;
			var isFull   = self.isFull();
			var isLocked = self.isLocked;
	
			self.$wrapper
				.toggleClass('rtl', self.rtl);
	
			self.$control
				.toggleClass('focus', self.isFocused)
				.toggleClass('disabled', self.isDisabled)
				.toggleClass('required', self.isRequired)
				.toggleClass('invalid', self.isInvalid)
				.toggleClass('locked', isLocked)
				.toggleClass('full', isFull).toggleClass('not-full', !isFull)
				.toggleClass('input-active', self.isFocused && !self.isInputHidden)
				.toggleClass('dropdown-active', self.isOpen)
				.toggleClass('has-options', !$.isEmptyObject(self.options))
				.toggleClass('has-items', self.items.length > 0);
	
			self.$control_input.data('grow', !isFull && !isLocked);
		},
	
		/**
		 * Determines whether or not more items can be added
		 * to the control without exceeding the user-defined maximum.
		 *
		 * @returns {boolean}
		 */
		isFull: function() {
			return this.settings.maxItems !== null && this.items.length >= this.settings.maxItems;
		},
	
		/**
		 * Refreshes the original <select> or <input>
		 * element to reflect the current state.
		 */
		updateOriginalInput: function(opts) {
			var i, n, options, label, self = this;
			opts = opts || {};
	
			if (self.tagType === TAG_SELECT) {
				options = [];
				for (i = 0, n = self.items.length; i < n; i++) {
					label = self.options[self.items[i]][self.settings.labelField] || '';
					options.push('<option value="' + escape_html(self.items[i]) + '" selected="selected">' + escape_html(label) + '</option>');
				}
				if (!options.length && !this.$input.attr('multiple')) {
					options.push('<option value="" selected="selected"></option>');
				}
				self.$input.html(options.join(''));
			} else {
				self.$input.val(self.getValue());
				self.$input.attr('value',self.$input.val());
			}
	
			if (self.isSetup) {
				if (!opts.silent) {
					self.trigger('change', self.$input.val());
				}
			}
		},
	
		/**
		 * Shows/hide the input placeholder depending
		 * on if there items in the list already.
		 */
		updatePlaceholder: function() {
			if (!this.settings.placeholder) return;
			var $input = this.$control_input;
	
			if (this.items.length) {
				$input.removeAttr('placeholder');
			} else {
				$input.attr('placeholder', this.settings.placeholder);
			}
			$input.triggerHandler('update', {force: true});
		},
	
		/**
		 * Shows the autocomplete dropdown containing
		 * the available options.
		 */
		open: function() {
			var self = this;
	
			if (self.isLocked || self.isOpen || (self.settings.mode === 'multi' && self.isFull())) return;
			self.focus();
			self.isOpen = true;
			self.refreshState();
			self.$dropdown.css({visibility: 'hidden', display: 'block'});
			self.positionDropdown();
			self.$dropdown.css({visibility: 'visible'});
			self.trigger('dropdown_open', self.$dropdown);
		},
	
		/**
		 * Closes the autocomplete dropdown menu.
		 */
		close: function() {
			var self = this;
			var trigger = self.isOpen;
	
			if (self.settings.mode === 'single' && self.items.length) {
				self.hideInput();
			}
	
			self.isOpen = false;
			self.$dropdown.hide();
			self.setActiveOption(null);
			self.refreshState();
	
			if (trigger) self.trigger('dropdown_close', self.$dropdown);
		},
	
		/**
		 * Calculates and applies the appropriate
		 * position of the dropdown.
		 */
		positionDropdown: function() {
			var $control = this.$control;
			var offset = this.settings.dropdownParent === 'body' ? $control.offset() : $control.position();
			offset.top += $control.outerHeight(true);
	
			this.$dropdown.css({
				width : $control.outerWidth(),
				top   : offset.top,
				left  : offset.left
			});
		},
	
		/**
		 * Resets / clears all selected items
		 * from the control.
		 *
		 * @param {boolean} silent
		 */
		clear: function(silent) {
			var self = this;
	
			if (!self.items.length) return;
			self.$control.children(':not(input)').remove();
			self.items = [];
			self.lastQuery = null;
			self.setCaret(0);
			self.setActiveItem(null);
			self.updatePlaceholder();
			self.updateOriginalInput({silent: silent});
			self.refreshState();
			self.showInput();
			self.trigger('clear');
		},
	
		/**
		 * A helper method for inserting an element
		 * at the current caret position.
		 *
		 * @param {object} $el
		 */
		insertAtCaret: function($el) {
			var caret = Math.min(this.caretPos, this.items.length);
			if (caret === 0) {
				this.$control.prepend($el);
			} else {
				$(this.$control[0].childNodes[caret]).before($el);
			}
			this.setCaret(caret + 1);
		},
	
		/**
		 * Removes the current selected item(s).
		 *
		 * @param {object} e (optional)
		 * @returns {boolean}
		 */
		deleteSelection: function(e) {
			var i, n, direction, selection, values, caret, option_select, $option_select, $tail;
			var self = this;
	
			direction = (e && e.keyCode === KEY_BACKSPACE) ? -1 : 1;
			selection = getSelection(self.$control_input[0]);
	
			if (self.$activeOption && !self.settings.hideSelected) {
				option_select = self.getAdjacentOption(self.$activeOption, -1).attr('data-value');
			}
	
			// determine items that will be removed
			values = [];
	
			if (self.$activeItems.length) {
				$tail = self.$control.children('.active:' + (direction > 0 ? 'last' : 'first'));
				caret = self.$control.children(':not(input)').index($tail);
				if (direction > 0) { caret++; }
	
				for (i = 0, n = self.$activeItems.length; i < n; i++) {
					values.push($(self.$activeItems[i]).attr('data-value'));
				}
				if (e) {
					e.preventDefault();
					e.stopPropagation();
				}
			} else if ((self.isFocused || self.settings.mode === 'single') && self.items.length) {
				if (direction < 0 && selection.start === 0 && selection.length === 0) {
					values.push(self.items[self.caretPos - 1]);
				} else if (direction > 0 && selection.start === self.$control_input.val().length) {
					values.push(self.items[self.caretPos]);
				}
			}
	
			// allow the callback to abort
			if (!values.length || (typeof self.settings.onDelete === 'function' && self.settings.onDelete.apply(self, [values]) === false)) {
				return false;
			}
	
			// perform removal
			if (typeof caret !== 'undefined') {
				self.setCaret(caret);
			}
			while (values.length) {
				self.removeItem(values.pop());
			}
	
			self.showInput();
			self.positionDropdown();
			self.refreshOptions(true);
	
			// select previous option
			if (option_select) {
				$option_select = self.getOption(option_select);
				if ($option_select.length) {
					self.setActiveOption($option_select);
				}
			}
	
			return true;
		},
	
		/**
		 * Selects the previous / next item (depending
		 * on the `direction` argument).
		 *
		 * > 0 - right
		 * < 0 - left
		 *
		 * @param {int} direction
		 * @param {object} e (optional)
		 */
		advanceSelection: function(direction, e) {
			var tail, selection, idx, valueLength, cursorAtEdge, $tail;
			var self = this;
	
			if (direction === 0) return;
			if (self.rtl) direction *= -1;
	
			tail = direction > 0 ? 'last' : 'first';
			selection = getSelection(self.$control_input[0]);
	
			if (self.isFocused && !self.isInputHidden) {
				valueLength = self.$control_input.val().length;
				cursorAtEdge = direction < 0
					? selection.start === 0 && selection.length === 0
					: selection.start === valueLength;
	
				if (cursorAtEdge && !valueLength) {
					self.advanceCaret(direction, e);
				}
			} else {
				$tail = self.$control.children('.active:' + tail);
				if ($tail.length) {
					idx = self.$control.children(':not(input)').index($tail);
					self.setActiveItem(null);
					self.setCaret(direction > 0 ? idx + 1 : idx);
				}
			}
		},
	
		/**
		 * Moves the caret left / right.
		 *
		 * @param {int} direction
		 * @param {object} e (optional)
		 */
		advanceCaret: function(direction, e) {
			var self = this, fn, $adj;
	
			if (direction === 0) return;
	
			fn = direction > 0 ? 'next' : 'prev';
			if (self.isShiftDown) {
				$adj = self.$control_input[fn]();
				if ($adj.length) {
					self.hideInput();
					self.setActiveItem($adj);
					e && e.preventDefault();
				}
			} else {
				self.setCaret(self.caretPos + direction);
			}
		},
	
		/**
		 * Moves the caret to the specified index.
		 *
		 * @param {int} i
		 */
		setCaret: function(i) {
			var self = this;
	
			if (self.settings.mode === 'single') {
				i = self.items.length;
			} else {
				i = Math.max(0, Math.min(self.items.length, i));
			}
	
			if(!self.isPending) {
				// the input must be moved by leaving it in place and moving the
				// siblings, due to the fact that focus cannot be restored once lost
				// on mobile webkit devices
				var j, n, fn, $children, $child;
				$children = self.$control.children(':not(input)');
				for (j = 0, n = $children.length; j < n; j++) {
					$child = $($children[j]).detach();
					if (j <  i) {
						self.$control_input.before($child);
					} else {
						self.$control.append($child);
					}
				}
			}
	
			self.caretPos = i;
		},
	
		/**
		 * Disables user input on the control. Used while
		 * items are being asynchronously created.
		 */
		lock: function() {
			this.close();
			this.isLocked = true;
			this.refreshState();
		},
	
		/**
		 * Re-enables user input on the control.
		 */
		unlock: function() {
			this.isLocked = false;
			this.refreshState();
		},
	
		/**
		 * Disables user input on the control completely.
		 * While disabled, it cannot receive focus.
		 */
		disable: function() {
			var self = this;
			self.$input.prop('disabled', true);
			self.$control_input.prop('disabled', true).prop('tabindex', -1);
			self.isDisabled = true;
			self.lock();
		},
	
		/**
		 * Enables the control so that it can respond
		 * to focus and user input.
		 */
		enable: function() {
			var self = this;
			self.$input.prop('disabled', false);
			self.$control_input.prop('disabled', false).prop('tabindex', self.tabIndex);
			self.isDisabled = false;
			self.unlock();
		},
	
		/**
		 * Completely destroys the control and
		 * unbinds all event listeners so that it can
		 * be garbage collected.
		 */
		destroy: function() {
			var self = this;
			var eventNS = self.eventNS;
			var revertSettings = self.revertSettings;
	
			self.trigger('destroy');
			self.off();
			self.$wrapper.remove();
			self.$dropdown.remove();
	
			self.$input
				.html('')
				.append(revertSettings.$children)
				.removeAttr('tabindex')
				.removeClass('selectized')
				.attr({tabindex: revertSettings.tabindex})
				.show();
	
			self.$control_input.removeData('grow');
			self.$input.removeData('selectize');
	
			$(window).off(eventNS);
			$(document).off(eventNS);
			$(document.body).off(eventNS);
	
			delete self.$input[0].selectize;
		},
	
		/**
		 * A helper method for rendering "item" and
		 * "option" templates, given the data.
		 *
		 * @param {string} templateName
		 * @param {object} data
		 * @returns {string}
		 */
		render: function(templateName, data) {
			var value, id, label;
			var html = '';
			var cache = false;
			var self = this;
			var regex_tag = /^[\t \r\n]*<([a-z][a-z0-9\-_]*(?:\:[a-z][a-z0-9\-_]*)?)/i;
	
			if (templateName === 'option' || templateName === 'item') {
				value = hash_key(data[self.settings.valueField]);
				cache = !!value;
			}
	
			// pull markup from cache if it exists
			if (cache) {
				if (!isset(self.renderCache[templateName])) {
					self.renderCache[templateName] = {};
				}
				if (self.renderCache[templateName].hasOwnProperty(value)) {
					return self.renderCache[templateName][value];
				}
			}
	
			// render markup
			html = self.settings.render[templateName].apply(this, [data, escape_html]);
	
			// add mandatory attributes
            if (templateName === 'option' || templateName === 'option_create') {
				html = html.replace(regex_tag, '<$1 data-selectable');
			}
			if (templateName === 'optgroup') {
				id = data[self.settings.optgroupValueField] || '';
				html = html.replace(regex_tag, '<$1 data-group="' + escape_replace(escape_html(id)) + '"');
			}
			if (templateName === 'option' || templateName === 'item') {
				html = html.replace(regex_tag, '<$1 data-value="' + escape_replace(escape_html(value || '')) + '"');
			}
	
			// update cache
			if (cache) {
				self.renderCache[templateName][value] = html;
			}
	
			return html;
		},
	
		/**
		 * Clears the render cache for a template. If
		 * no template is given, clears all render
		 * caches.
		 *
		 * @param {string} templateName
		 */
		clearCache: function(templateName) {
			var self = this;
			if (typeof templateName === 'undefined') {
				self.renderCache = {};
			} else {
				delete self.renderCache[templateName];
			}
		},
	
		/**
		 * Determines whether or not to display the
		 * create item prompt, given a user input.
		 *
		 * @param {string} input
		 * @return {boolean}
		 */
		canCreate: function(input) {
			var self = this;
			if (!self.settings.create) return false;
			var filter = self.settings.createFilter;
			return input.length
				&& (typeof filter !== 'function' || filter.apply(self, [input]))
				&& (typeof filter !== 'string' || new RegExp(filter).test(input))
				&& (!(filter instanceof RegExp) || filter.test(input));
		}
	
	});
	
	
	Selectize.count = 0;
	Selectize.defaults = {
		options: [],
		optgroups: [],
	
		plugins: [],
		delimiter: ',',
		splitOn: null, // regexp or string for splitting up values from a paste command
		persist: true,
		diacritics: true,
		create: false,
		createOnBlur: false,
		createFilter: null,
		highlight: true,
		openOnFocus: true,
		maxOptions: 1000,
		maxItems: null,
		hideSelected: null,
		addPrecedence: false,
		selectOnTab: false,
		preload: false,
		allowEmptyOption: false,
		closeAfterSelect: false,
		searchOnKeypress: true,

		scrollDuration: 60,
		loadThrottle: 300,
		loadingClass: 'loading',
	
		dataAttr: 'data-data',
		optgroupField: 'optgroup',
		valueField: 'value',
		labelField: 'text',
		optgroupLabelField: 'label',
		optgroupValueField: 'value',
		lockOptgroupOrder: false,
	
		sortField: '$order',
		searchField: ['text'],
		searchConjunction: 'and',
	
		mode: null,
		wrapperClass: 'selectize-control',
		inputClass: 'selectize-input',
		dropdownClass: 'selectize-dropdown',
		dropdownContentClass: 'selectize-dropdown-content',
	
		dropdownParent: null,
	
		copyClassesToDropdown: true,
	
		/*
		load                 : null, // function(query, callback) { ... }
		score                : null, // function(search) { ... }
		onInitialize         : null, // function() { ... }
		onChange             : null, // function(value) { ... }
		onItemAdd            : null, // function(value, $item) { ... }
		onItemRemove         : null, // function(value) { ... }
		onClear              : null, // function() { ... }
		onOptionAdd          : null, // function(value, data) { ... }
		onOptionRemove       : null, // function(value) { ... }
		onOptionClear        : null, // function() { ... }
		onOptionGroupAdd     : null, // function(id, data) { ... }
		onOptionGroupRemove  : null, // function(id) { ... }
		onOptionGroupClear   : null, // function() { ... }
		onDropdownOpen       : null, // function($dropdown) { ... }
		onDropdownClose      : null, // function($dropdown) { ... }
		onType               : null, // function(str) { ... }
		onDelete             : null, // function(values) { ... }
		*/
	
		render: {
			/*
			item: null,
			optgroup: null,
			optgroup_header: null,
			option: null,
			option_create: null
			*/
		}
	};
	
	
	$.fn.selectize = function(settings_user) {
		var defaults             = $.fn.selectize.defaults;
		var settings             = $.extend({}, defaults, settings_user);
		var attr_data            = settings.dataAttr;
		var field_label          = settings.labelField;
		var field_value          = settings.valueField;
		var field_optgroup       = settings.optgroupField;
		var field_optgroup_label = settings.optgroupLabelField;
		var field_optgroup_value = settings.optgroupValueField;
	
		/**
		 * Initializes selectize from a <input type="text"> element.
		 *
		 * @param {object} $input
		 * @param {object} settings_element
		 */
		var init_textbox = function($input, settings_element) {
			var i, n, values, option;
	
			var data_raw = $input.attr(attr_data);
	
			if (!data_raw) {
				var value = $.trim($input.val() || '');
				if (!settings.allowEmptyOption && !value.length) return;
				values = value.split(settings.delimiter);
				for (i = 0, n = values.length; i < n; i++) {
					option = {};
					option[field_label] = values[i];
                    option[field_value] = values[i];
					settings_element.options.push(option);
				}
				settings_element.items = values;
			} else {
				settings_element.options = JSON.parse(data_raw);
				for (i = 0, n = settings_element.options.length; i < n; i++) {
					settings_element.items.push(settings_element.options[i][field_value]);
				}
			}
		};
	
		/**
		 * Initializes selectize from a <select> element.
		 *
		 * @param {object} $input
		 * @param {object} settings_element
		 */
		var init_select = function($input, settings_element) {
			var i, n, tagName, $children, order = 0;
			var options = settings_element.options;
			var optionsMap = {};
	
			var readData = function($el) {
				var data = attr_data && $el.attr(attr_data);
				if (typeof data === 'string' && data.length) {
					return JSON.parse(data);
				}
				return null;
			};
	
			var addOption = function($option, group) {
				$option = $($option);
	
				var value = hash_key($option.attr('value'));
				if (!value && !settings.allowEmptyOption) return;
	
				// if the option already exists, it's probably been
				// duplicated in another optgroup. in this case, push
				// the current group to the "optgroup" property on the
				// existing option so that it's rendered in both places.
				if (optionsMap.hasOwnProperty(value)) {
					if (group) {
						var arr = optionsMap[value][field_optgroup];
						if (!arr) {
							optionsMap[value][field_optgroup] = group;
						} else if (!$.isArray(arr)) {
							optionsMap[value][field_optgroup] = [arr, group];
						} else {
							arr.push(group);
						}
					}
					return;
				}
	
				var option             = readData($option) || {};
				option[field_label]    = option[field_label] || $option.text();
				option[field_value]    = option[field_value] || value;
				option[field_optgroup] = option[field_optgroup] || group;
	
				optionsMap[value] = option;
				options.push(option);
	
				if ($option.is(':selected')) {
					settings_element.items.push(value);
				}
			};
	
			var addGroup = function($optgroup) {
				var i, n, id, optgroup, $options;
	
				$optgroup = $($optgroup);
				id = $optgroup.attr('label');
	
				if (id) {
					optgroup = readData($optgroup) || {};
					optgroup[field_optgroup_label] = id;
					optgroup[field_optgroup_value] = id;
					settings_element.optgroups.push(optgroup);
				}
	
				$options = $('option', $optgroup);
				for (i = 0, n = $options.length; i < n; i++) {
					addOption($options[i], id);
				}
			};
	
			settings_element.maxItems = $input.attr('multiple') ? null : 1;
	
			$children = $input.children();
			for (i = 0, n = $children.length; i < n; i++) {
				tagName = $children[i].tagName.toLowerCase();
				if (tagName === 'optgroup') {
					addGroup($children[i]);
				} else if (tagName === 'option') {
					addOption($children[i]);
				}
			}
		};
	
		return this.each(function() {
			if (this.selectize) return;
	
			var instance;
			var $input = $(this);
			var tag_name = this.tagName.toLowerCase();
			var placeholder = $input.attr('placeholder') || $input.attr('data-placeholder');
			if (!placeholder && !settings.allowEmptyOption) {
				placeholder = $input.children('option[value=""]').text();
			}
	
			var settings_element = {
				'placeholder' : placeholder,
				'options'     : [],
				'optgroups'   : [],
				'items'       : []
			};
	
			if (tag_name === 'select') {
				init_select($input, settings_element);
			} else {
				init_textbox($input, settings_element);
			}
	
			instance = new Selectize($input, $.extend(true, {}, defaults, settings_element, settings_user));
		});
	};
	
	$.fn.selectize.defaults = Selectize.defaults;
	$.fn.selectize.support = {
		validity: SUPPORTS_VALIDITY_API
	};
	
	
	Selectize.define('drag_drop', function(options) {
		if (!$.fn.sortable) throw new Error('The "drag_drop" plugin requires jQuery UI "sortable".');
		if (this.settings.mode !== 'multi') return;
		var self = this;
	
		self.lock = (function() {
			var original = self.lock;
			return function() {
				var sortable = self.$control.data('sortable');
				if (sortable) sortable.disable();
				return original.apply(self, arguments);
			};
		})();
	
		self.unlock = (function() {
			var original = self.unlock;
			return function() {
				var sortable = self.$control.data('sortable');
				if (sortable) sortable.enable();
				return original.apply(self, arguments);
			};
		})();
	
		self.setup = (function() {
			var original = self.setup;
			return function() {
				original.apply(this, arguments);
	
				var $control = self.$control.sortable({
					items: '[data-value]',
					forcePlaceholderSize: true,
					disabled: self.isLocked,
					start: function(e, ui) {
						ui.placeholder.css('width', ui.helper.css('width'));
						$control.css({overflow: 'visible'});
					},
					stop: function() {
						$control.css({overflow: 'hidden'});
						var active = self.$activeItems ? self.$activeItems.slice() : null;
						var values = [];
						$control.children('[data-value]').each(function() {
							values.push($(this).attr('data-value'));
						});
						self.setValue(values);
						self.setActiveItem(active);
					}
				});
			};
		})();
	
	});
	
	Selectize.define('dropdown_header', function(options) {
		var self = this;
	
		options = $.extend({
			title         : 'Untitled',
			headerClass   : 'selectize-dropdown-header',
			titleRowClass : 'selectize-dropdown-header-title',
			labelClass    : 'selectize-dropdown-header-label',
			closeClass    : 'selectize-dropdown-header-close',
	
			html: function(data) {
				return (
					'<div class="' + data.headerClass + '">' +
						'<div class="' + data.titleRowClass + '">' +
							'<span class="' + data.labelClass + '">' + data.title + '</span>' +
							'<a href="javascript:void(0)" class="' + data.closeClass + '">&times;</a>' +
						'</div>' +
					'</div>'
				);
			}
		}, options);
	
		self.setup = (function() {
			var original = self.setup;
			return function() {
				original.apply(self, arguments);
				self.$dropdown_header = $(options.html(options));
				self.$dropdown.prepend(self.$dropdown_header);
			};
		})();
	
	});
	
	Selectize.define('optgroup_columns', function(options) {
		var self = this;
	
		options = $.extend({
			equalizeWidth  : true,
			equalizeHeight : true
		}, options);
	
		this.getAdjacentOption = function($option, direction) {
			var $options = $option.closest('[data-group]').find('[data-selectable]');
			var index    = $options.index($option) + direction;
	
			return index >= 0 && index < $options.length ? $options.eq(index) : $();
		};
	
		this.onKeyDown = (function() {
			var original = self.onKeyDown;
			return function(e) {
				var index, $option, $options, $optgroup;
	
				if (this.isOpen && (e.keyCode === KEY_LEFT || e.keyCode === KEY_RIGHT)) {
					self.ignoreHover = true;
					$optgroup = this.$activeOption.closest('[data-group]');
					index = $optgroup.find('[data-selectable]').index(this.$activeOption);
	
					if(e.keyCode === KEY_LEFT) {
						$optgroup = $optgroup.prev('[data-group]');
					} else {
						$optgroup = $optgroup.next('[data-group]');
					}
	
					$options = $optgroup.find('[data-selectable]');
					$option  = $options.eq(Math.min($options.length - 1, index));
					if ($option.length) {
						this.setActiveOption($option);
					}
					return;
				}
	
				return original.apply(this, arguments);
			};
		})();
	
		var getScrollbarWidth = function() {
			var div;
			var width = getScrollbarWidth.width;
			var doc = document;
	
			if (typeof width === 'undefined') {
				div = doc.createElement('div');
				div.innerHTML = '<div style="width:50px;height:50px;position:absolute;left:-50px;top:-50px;overflow:auto;"><div style="width:1px;height:100px;"></div></div>';
				div = div.firstChild;
				doc.body.appendChild(div);
				width = getScrollbarWidth.width = div.offsetWidth - div.clientWidth;
				doc.body.removeChild(div);
			}
			return width;
		};
	
		var equalizeSizes = function() {
			var i, n, height_max, width, width_last, width_parent, $optgroups;
	
			$optgroups = $('[data-group]', self.$dropdown_content);
			n = $optgroups.length;
			if (!n || !self.$dropdown_content.width()) return;
	
			if (options.equalizeHeight) {
				height_max = 0;
				for (i = 0; i < n; i++) {
					height_max = Math.max(height_max, $optgroups.eq(i).height());
				}
				$optgroups.css({height: height_max});
			}
	
			if (options.equalizeWidth) {
				width_parent = self.$dropdown_content.innerWidth() - getScrollbarWidth();
				width = Math.round(width_parent / n);
				$optgroups.css({width: width});
				if (n > 1) {
					width_last = width_parent - width * (n - 1);
					$optgroups.eq(n - 1).css({width: width_last});
				}
			}
		};
	
		if (options.equalizeHeight || options.equalizeWidth) {
			hook.after(this, 'positionDropdown', equalizeSizes);
			hook.after(this, 'refreshOptions', equalizeSizes);
		}
	
	
	});
	
	Selectize.define('remove_button', function(options) {
		if (this.settings.mode === 'single') return;
	
		options = $.extend({
			label     : '&times;',
			title     : 'Remove',
			className : 'remove',
			append    : true
		}, options);
	
		var self = this;
		var html = '<a href="javascript:void(0)" class="' + options.className + '" tabindex="-1" title="' + escape_html(options.title) + '">' + options.label + '</a>';
	
		/**
		 * Appends an element as a child (with raw HTML).
		 *
		 * @param {string} html_container
		 * @param {string} html_element
		 * @return {string}
		 */
		var append = function(html_container, html_element) {
			var pos = html_container.search(/(<\/[^>]+>\s*)$/);
			return html_container.substring(0, pos) + html_element + html_container.substring(pos);
		};
	
		this.setup = (function() {
			var original = self.setup;
			return function() {
				// override the item rendering method to add the button to each
				if (options.append) {
					var render_item = self.settings.render.item;
					self.settings.render.item = function(data) {
						return append(render_item.apply(this, arguments), html);
					};
				}
	
				original.apply(this, arguments);
	
				// add event listener
				this.$control.on('click', '.' + options.className, function(e) {
					e.preventDefault();
					if (self.isLocked) return;
	
					var $item = $(e.currentTarget).parent();
					self.setActiveItem($item);
					if (self.deleteSelection()) {
						self.setCaret(self.items.length);
					}
				});
	
			};
		})();
	
	});
	
	Selectize.define('restore_on_backspace', function(options) {
		var self = this;
	
		options.text = options.text || function(option) {
			return option[this.settings.labelField];
		};
	
		this.onKeyDown = (function() {
			var original = self.onKeyDown;
			return function(e) {
				var index, option;
				if (e.keyCode === KEY_BACKSPACE && this.$control_input.val() === '' && !this.$activeItems.length) {
					index = this.caretPos - 1;
					if (index >= 0 && index < this.items.length) {
						option = this.options[this.items[index]];
						if (this.deleteSelection(e)) {
							this.setTextboxValue(options.text.apply(this, [option]));
							this.refreshOptions(true);
						}
						e.preventDefault();
						return;
					}
				}
				return original.apply(this, arguments);
			};
		})();
	});
	

	return Selectize;
}));;
/*
    FILE IS NO LONGER IN SYNC
    Added checkForSelectAttributeChange function. If MutationObserver not undefined then use original code.
    else if use el.addEventListener("DOMAttrModified", function (ev) {
*/

/**
 * Modified from:
 * https://github.com/MrTrick/knockout-selectize
 */

(function (factory) {
    if (typeof define === 'function' && define.amd) {
        define(['knockout', 'selectize'], factory);
    } else if (typeof exports === 'object' && typeof module === 'object') {
        module.exports = factory;
    } else {
        factory(ko);
    }
})(function (ko) {

    var checkForSelectAttributeChange = function (el) {
        if (!el.selectize) {
            return;
        }
        var disabled = el.selectize.$input.is(':disabled'),
            required = el.selectize.$input.is(':required');

        //Check disabled
        //(The mutation may fire multiple times, so fence against loops)
        if (disabled !== el.selectize.isDisabled) {
            //Disable/enable selectize to match the real select element
            el.selectize[disabled ? 'disable' : 'enable']();

            //When the real element is disabled, disable the control input too.
            //This is a selectize bug - #307
            el.selectize.$control_input.prop('disabled', disabled);
        }

        //Check required
        //(The mutation may fire multiple times, so fence against loops)
        if (required !== el.selectize.isRequired) {
            el.selectize.isRequired = required;
            el.selectize.refreshState();
        }
    };

    ko.bindingHandlers.selectize = {
        init: function (el, bindingValue, bindings, vm, context) {
          
            var $el = $(el);
            if (!$el.is('select')) throw new Error('The selectize knockout binding is only valid on <select> elements. (because of the options binding)');
        
            //If there are other selectized selects on the page then it is OK to selectize this one
            if ($('.selectize-control').length > 0
                && !el.selectize) {
                $el.selectize();
            }
            var setSelectizeValue = function(value) {
                if (!el.selectize) {
                    return;
                }

                //If the observable value doesn't match the element value, update the element;
                if (value !== el.selectize.getValue()) {
                    if (typeof el.selectize.loadOption === "function") { el.selectize.loadOption(value); }
                    else { el.selectize.setValue(value); }
                }
            }

            //What options are being given?
            var options = bindings.get('options');
            var optionsSubscription = false;

            var valueAccessor;
            var textAccessor;
            if (bindings.has('optionsValue')) {
                valueAccessor = bindings.get('optionsValue');
            }
            if (bindings.has('optionsText')) {
                textAccessor = bindings.get('optionsText');
            }

            //What is the input's value?
            var value = bindings.get('value');
            var valueSubscription = false;
            //If the value is observable, synchronise
            if (ko.isObservable(value)) {
                valueSubscription = value.subscribe(setSelectizeValue);
            }

            //If the options are observable, synchronise add/remove events between selectize and knockout
            if (ko.isObservable(options)) {

                //Whenever the observable has an element added/removed, copy to the options
                optionsSubscription = options.subscribe(function (changes) {
                    if (!el.selectize) {
                        return;
                    }

                    var newOptions = [];
                    changes.forEach(function (change) {
                        var value;
                        if (!valueAccessor) {
                            value = change.value;
                        }
                        else if (typeof valueAccessor === 'function') {
                            value = valueAccessor(change.value);
                        }
                        else {
                            value = change.value[valueAccessor];
                        }

                        if (change.status === 'added') {
                            var option = {
                                value: value
                            };

                            if (!textAccessor) {
                                option.text = change.value;
                            }
                            else if (typeof textAccessor === 'function') {
                                option.text = textAccessor(change.value);
                            }
                            else {
                                option.text = change.value[textAccessor];
                            }

                            newOptions.push(option);
                        } else if (change.status === 'deleted') {
                            el.selectize.removeOption(value);
                        }
                    });

                    newOptions.forEach(function (item) {
                        el.selectize.addOption(item);
                    });

                    setSelectizeValue(value());
                    $el.change();
                }, null, 'arrayChange');
            }

            //Selectize bug; doesn't disable the control input if the parent is disabled, even at startup. Fix. (after startup, handled by the observer ahead)
            if (el.selectize
                && el.selectize.$input.is(':disabled')) el.selectize.$control_input.prop('disabled', true);

            //For knockout bindings to work, we need to observe when the attributes of the <select> element are changed - `required`, `disabled`.
            //This is to update the selectize control to match the <select> element
            //(NOTE: `visible` should not be bound directly to <select>. Bind `visible` to an enclosing <div> or similar.)
            if (typeof MutationObserver !== 'undefined' && MutationObserver) {
                observer = new MutationObserver(function(mutations) {
                    checkForSelectAttributeChange(el);
                });

                observer.observe(el, { attributes: true });
            }//FOR IE10
            else if (typeof MutationObserver === 'undefined') {
                el.addEventListener("DOMAttrModified", function (ev) {
                    checkForSelectAttributeChange(el);
                }, false);
            }

            //When the dom node is removed, ensure that the selectize node is too, as well as the mutation observer
            ko.utils.domNodeDisposal.addDisposeCallback(el, function () {
                if (el.selectize) {
                    el.selectize.destroy();
                }
                if (typeof observer !== 'undefined' && observer) observer.disconnect();
                if (typeof optionsSubscription !== 'undefined' && optionsSubscription) optionsSubscription.dispose();
                if (typeof valueSubscription !== 'undefined' && valueSubscription) valueSubscription.dispose();
            });
        }
    };
});


// ----------------------- from https://gist.github.com/xtranophilist/8001624

var inject_binding = function (allBindings, key, value) {
    //https://github.com/knockout/knockout/pull/932#issuecomment-26547528
    return {
        has: function (bindingKey) {
            return (bindingKey == key) || allBindings.has(bindingKey);
        },
        get: function (bindingKey) {
            var binding = allBindings.get(bindingKey);
            if (bindingKey == key) {
                binding = binding ? [].concat(binding, value) : value;
            }
            return binding;
        }
    };
}

ko.bindingHandlers.selectize2 = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        if (!allBindingsAccessor.has('optionsText'))
            allBindingsAccessor = inject_binding(allBindingsAccessor, 'optionsText', 'name');
        if (!allBindingsAccessor.has('optionsValue'))
            allBindingsAccessor = inject_binding(allBindingsAccessor, 'optionsValue', 'id');
        if (typeof allBindingsAccessor.get('optionsCaption') == 'undefined')
            allBindingsAccessor = inject_binding(allBindingsAccessor, 'optionsCaption', 'Choose...');

        ko.bindingHandlers.options.update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);

        var options = {
            valueField: allBindingsAccessor.get('optionsValue'),
            labelField: allBindingsAccessor.get('optionsText'),
            searchField: allBindingsAccessor.get('optionsText')
        }

        if (allBindingsAccessor.has('options')) {
            var passed_options = allBindingsAccessor.get('options')
            for (var attr_name in passed_options) {
                options[attr_name] = passed_options[attr_name];
            }
        }

        var $select = $(element).selectize(options)[0].selectize;

        if (typeof allBindingsAccessor.get('value') == 'function') {
            $select.addItem(allBindingsAccessor.get('value')());
            allBindingsAccessor.get('value').subscribe(function (new_val) {
                $select.addItem(new_val);
            })
        }

        if (typeof allBindingsAccessor.get('selectedOptions') == 'function') {
            allBindingsAccessor.get('selectedOptions').subscribe(function (new_val) {
                // Removing items which are not in new value
                var values = $select.getValue();
                var items_to_remove = [];
                for (var k in values) {
                    if (new_val.indexOf(values[k]) == -1) {
                        items_to_remove.push(values[k]);
                    }
                }

                for (var k in items_to_remove) {
                    $select.removeItem(items_to_remove[k]);
                }

                for (var k in new_val) {
                    $select.addItem(new_val[k]);
                }

            });
            var selected = allBindingsAccessor.get('selectedOptions')();
            for (var k in selected) {
                $select.addItem(selected[k]);
            }
        }


        if (typeof init_selectize == 'function') {
            init_selectize($select);
        }

        if (typeof valueAccessor().subscribe == 'function') {
            valueAccessor().subscribe(function (changes) {
                // To avoid having duplicate keys, all delete operations will go first
                var addedItems = new Array();
                changes.forEach(function (change) {
                    switch (change.status) {
                        case 'added':
                            addedItems.push(change.value);
                            break;
                        case 'deleted':
                            var itemId = change.value[options.valueField];
                            if (itemId != null) $select.removeOption(itemId);
                    }
                });
                addedItems.forEach(function (item) {
                    $select.addOption(item);
                });

            }, null, "arrayChange");
        }

    },
    update: function (element, valueAccessor, allBindingsAccessor) {

        if (allBindingsAccessor.has('object')) {
            var optionsValue = allBindingsAccessor.get('optionsValue') || 'id';
            var value_accessor = valueAccessor();
            var selected_obj = $.grep(value_accessor(), function (i) {
                if (typeof i[optionsValue] == 'function')
                    var id = i[optionsValue]
                else
                    var id = i[optionsValue]
                return id == allBindingsAccessor.get('value')();
            })[0];

            if (selected_obj) {
                allBindingsAccessor.get('object')(selected_obj);
            }
        }
    }
};
//! moment.js
//! version : 2.10.2
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT
//! momentjs.com
!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Ac.apply(null,arguments)}function b(a){Ac=a}function c(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function d(a){return"[object Array]"===Object.prototype.toString.call(a)}function e(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function f(a,b){var c,d=[];for(c=0;c<a.length;++c)d.push(b(a[c],c));return d}function g(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function h(a,b){for(var c in b)g(b,c)&&(a[c]=b[c]);return g(b,"toString")&&(a.toString=b.toString),g(b,"valueOf")&&(a.valueOf=b.valueOf),a}function i(a,b,c,d){return ya(a,b,c,d,!0).utc()}function j(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length&&void 0===a._pf.bigHour)),a._isValid}function k(a){var b=i(0/0);return null!=a?h(b._pf,a):b._pf.userInvalidated=!0,b}function l(a,b){var c,d,e;if("undefined"!=typeof b._isAMomentObject&&(a._isAMomentObject=b._isAMomentObject),"undefined"!=typeof b._i&&(a._i=b._i),"undefined"!=typeof b._f&&(a._f=b._f),"undefined"!=typeof b._l&&(a._l=b._l),"undefined"!=typeof b._strict&&(a._strict=b._strict),"undefined"!=typeof b._tzm&&(a._tzm=b._tzm),"undefined"!=typeof b._isUTC&&(a._isUTC=b._isUTC),"undefined"!=typeof b._offset&&(a._offset=b._offset),"undefined"!=typeof b._pf&&(a._pf=b._pf),"undefined"!=typeof b._locale&&(a._locale=b._locale),Cc.length>0)for(c in Cc)d=Cc[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function m(b){l(this,b),this._d=new Date(+b._d),Dc===!1&&(Dc=!0,a.updateOffset(this),Dc=!1)}function n(a){return a instanceof m||null!=a&&g(a,"_isAMomentObject")}function o(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function p(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&o(a[d])!==o(b[d]))&&g++;return g+f}function q(){}function r(a){return a?a.toLowerCase().replace("_","-"):a}function s(a){for(var b,c,d,e,f=0;f<a.length;){for(e=r(a[f]).split("-"),b=e.length,c=r(a[f+1]),c=c?c.split("-"):null;b>0;){if(d=t(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&p(e,c,!0)>=b-1)break;b--}f++}return null}function t(a){var b=null;if(!Ec[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=Bc._abbr,require("./locale/"+a),u(b)}catch(c){}return Ec[a]}function u(a,b){var c;return a&&(c="undefined"==typeof b?w(a):v(a,b),c&&(Bc=c)),Bc._abbr}function v(a,b){return null!==b?(b.abbr=a,Ec[a]||(Ec[a]=new q),Ec[a].set(b),u(a),Ec[a]):(delete Ec[a],null)}function w(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return Bc;if(!d(a)){if(b=t(a))return b;a=[a]}return s(a)}function x(a,b){var c=a.toLowerCase();Fc[c]=Fc[c+"s"]=Fc[b]=a}function y(a){return"string"==typeof a?Fc[a]||Fc[a.toLowerCase()]:void 0}function z(a){var b,c,d={};for(c in a)g(a,c)&&(b=y(c),b&&(d[b]=a[c]));return d}function A(b,c){return function(d){return null!=d?(C(this,b,d),a.updateOffset(this,c),this):B(this,b)}}function B(a,b){return a._d["get"+(a._isUTC?"UTC":"")+b]()}function C(a,b,c){return a._d["set"+(a._isUTC?"UTC":"")+b](c)}function D(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=y(a),"function"==typeof this[a])return this[a](b);return this}function E(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.length<b;)d="0"+d;return(e?c?"+":"":"-")+d}function F(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Jc[a]=e),b&&(Jc[b[0]]=function(){return E(e.apply(this,arguments),b[1],b[2])}),c&&(Jc[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function G(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function H(a){var b,c,d=a.match(Gc);for(b=0,c=d.length;c>b;b++)d[b]=Jc[d[b]]?Jc[d[b]]:G(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function I(a,b){return a.isValid()?(b=J(b,a.localeData()),Ic[b]||(Ic[b]=H(b)),Ic[b](a)):a.localeData().invalidDate()}function J(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Hc.lastIndex=0;d>=0&&Hc.test(a);)a=a.replace(Hc,c),Hc.lastIndex=0,d-=1;return a}function K(a,b,c){Yc[a]="function"==typeof b?b:function(a){return a&&c?c:b}}function L(a,b){return g(Yc,a)?Yc[a](b._strict,b._locale):new RegExp(M(a))}function M(a){return a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}).replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function N(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=o(a)}),c=0;c<a.length;c++)Zc[a[c]]=d}function O(a,b){N(a,function(a,c,d,e){d._w=d._w||{},b(a,d._w,d,e)})}function P(a,b,c){null!=b&&g(Zc,a)&&Zc[a](b,c._a,c,a)}function Q(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function R(a){return this._months[a.month()]}function S(a){return this._monthsShort[a.month()]}function T(a,b,c){var d,e,f;for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=i([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function U(a,b){var c;return"string"==typeof b&&(b=a.localeData().monthsParse(b),"number"!=typeof b)?a:(c=Math.min(a.date(),Q(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a)}function V(b){return null!=b?(U(this,b),a.updateOffset(this,!0),this):B(this,"Month")}function W(){return Q(this.year(),this.month())}function X(a){var b,c=a._a;return c&&-2===a._pf.overflow&&(b=c[_c]<0||c[_c]>11?_c:c[ad]<1||c[ad]>Q(c[$c],c[_c])?ad:c[bd]<0||c[bd]>24||24===c[bd]&&(0!==c[cd]||0!==c[dd]||0!==c[ed])?bd:c[cd]<0||c[cd]>59?cd:c[dd]<0||c[dd]>59?dd:c[ed]<0||c[ed]>999?ed:-1,a._pf._overflowDayOfYear&&($c>b||b>ad)&&(b=ad),a._pf.overflow=b),a}function Y(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function Z(a,b){var c=!0;return h(function(){return c&&(Y(a),c=!1),b.apply(this,arguments)},b)}function $(a,b){hd[a]||(Y(b),hd[a]=!0)}function _(a){var b,c,d=a._i,e=id.exec(d);if(e){for(a._pf.iso=!0,b=0,c=jd.length;c>b;b++)if(jd[b][1].exec(d)){a._f=jd[b][0]+(e[6]||" ");break}for(b=0,c=kd.length;c>b;b++)if(kd[b][1].exec(d)){a._f+=kd[b][0];break}d.match(Vc)&&(a._f+="Z"),sa(a)}else a._isValid=!1}function aa(b){var c=ld.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(_(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function ba(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function ca(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function da(a){return ea(a)?366:365}function ea(a){return a%4===0&&a%100!==0||a%400===0}function fa(){return ea(this.year())}function ga(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=za(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ha(a){return ga(a,this._week.dow,this._week.doy).week}function ia(){return this._week.dow}function ja(){return this._week.doy}function ka(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function la(a){var b=ga(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function ma(a,b,c,d,e){var f,g,h=ca(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:da(a-1)+g}}function na(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function oa(a,b,c){return null!=a?a:null!=b?b:c}function pa(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function qa(a){var b,c,d,e,f=[];if(!a._d){for(d=pa(a),a._w&&null==a._a[ad]&&null==a._a[_c]&&ra(a),a._dayOfYear&&(e=oa(a._a[$c],d[$c]),a._dayOfYear>da(e)&&(a._pf._overflowDayOfYear=!0),c=ca(e,0,a._dayOfYear),a._a[_c]=c.getUTCMonth(),a._a[ad]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[bd]&&0===a._a[cd]&&0===a._a[dd]&&0===a._a[ed]&&(a._nextDay=!0,a._a[bd]=0),a._d=(a._useUTC?ca:ba).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[bd]=24)}}function ra(a){var b,c,d,e,f,g,h;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=oa(b.GG,a._a[$c],ga(za(),1,4).year),d=oa(b.W,1),e=oa(b.E,1)):(f=a._locale._week.dow,g=a._locale._week.doy,c=oa(b.gg,a._a[$c],ga(za(),f,g).year),d=oa(b.w,1),null!=b.d?(e=b.d,f>e&&++d):e=null!=b.e?b.e+f:f),h=ma(c,d,e,g,f),a._a[$c]=h.year,a._dayOfYear=h.dayOfYear}function sa(b){if(b._f===a.ISO_8601)return void _(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=J(b._f,b._locale).match(Gc)||[],c=0;c<e.length;c++)f=e[c],d=(h.match(L(f,b))||[])[0],d&&(g=h.substr(0,h.indexOf(d)),g.length>0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),Jc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),P(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[bd]<=12&&(b._pf.bigHour=void 0),b._a[bd]=ta(b._locale,b._a[bd],b._meridiem),qa(b),X(b)}function ta(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function ua(a){var b,d,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;f<a._f.length;f++)g=0,b=l({},a),null!=a._useUTC&&(b._useUTC=a._useUTC),b._pf=c(),b._f=a._f[f],sa(b),j(b)&&(g+=b._pf.charsLeftOver,g+=10*b._pf.unusedTokens.length,b._pf.score=g,(null==e||e>g)&&(e=g,d=b));h(a,d||b)}function va(a){if(!a._d){var b=z(a._i);a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],qa(a)}}function wa(a){var b,c=a._i,e=a._f;return a._locale=a._locale||w(a._l),null===c||void 0===e&&""===c?k({nullInput:!0}):("string"==typeof c&&(a._i=c=a._locale.preparse(c)),n(c)?new m(X(c)):(d(e)?ua(a):e?sa(a):xa(a),b=new m(X(a)),b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b))}function xa(b){var c=b._i;void 0===c?b._d=new Date:e(c)?b._d=new Date(+c):"string"==typeof c?aa(b):d(c)?(b._a=f(c.slice(0),function(a){return parseInt(a,10)}),qa(b)):"object"==typeof c?va(b):"number"==typeof c?b._d=new Date(c):a.createFromInputFallback(b)}function ya(a,b,d,e,f){var g={};return"boolean"==typeof d&&(e=d,d=void 0),g._isAMomentObject=!0,g._useUTC=g._isUTC=f,g._l=d,g._i=a,g._f=b,g._strict=e,g._pf=c(),wa(g)}function za(a,b,c,d){return ya(a,b,c,d,!1)}function Aa(a,b){var c,e;if(1===b.length&&d(b[0])&&(b=b[0]),!b.length)return za();for(c=b[0],e=1;e<b.length;++e)b[e][a](c)&&(c=b[e]);return c}function Ba(){var a=[].slice.call(arguments,0);return Aa("isBefore",a)}function Ca(){var a=[].slice.call(arguments,0);return Aa("isAfter",a)}function Da(a){var b=z(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+36e5*h,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=w(),this._bubble()}function Ea(a){return a instanceof Da}function Fa(a,b){F(a,0,0,function(){var a=this.utcOffset(),c="+";return 0>a&&(a=-a,c="-"),c+E(~~(a/60),2)+b+E(~~a%60,2)})}function Ga(a){var b=(a||"").match(Vc)||[],c=b[b.length-1]||[],d=(c+"").match(qd)||["-",0,0],e=+(60*d[1])+o(d[2]);return"+"===d[0]?e:-e}function Ha(b,c){var d,f;return c._isUTC?(d=c.clone(),f=(n(b)||e(b)?+b:+za(b))-+d,d._d.setTime(+d._d+f),a.updateOffset(d,!1),d):za(b).local();return c._isUTC?za(b).zone(c._offset||0):za(b).local()}function Ia(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ja(b,c){var d,e=this._offset||0;return null!=b?("string"==typeof b&&(b=Ga(b)),Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ia(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?Za(this,Ua(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ia(this)}function Ka(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function La(a){return this.utcOffset(0,a)}function Ma(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ia(this),"m")),this}function Na(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ga(this._i)),this}function Oa(a){return a=a?za(a).utcOffset():0,(this.utcOffset()-a)%60===0}function Pa(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Qa(){if(this._a){var a=this._isUTC?i(this._a):za(this._a);return this.isValid()&&p(this._a,a.toArray())>0}return!1}function Ra(){return!this._isUTC}function Sa(){return this._isUTC}function Ta(){return this._isUTC&&0===this._offset}function Ua(a,b){var c,d,e,f=a,h=null;return Ea(a)?f={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(f={},b?f[b]=a:f.milliseconds=a):(h=rd.exec(a))?(c="-"===h[1]?-1:1,f={y:0,d:o(h[ad])*c,h:o(h[bd])*c,m:o(h[cd])*c,s:o(h[dd])*c,ms:o(h[ed])*c}):(h=sd.exec(a))?(c="-"===h[1]?-1:1,f={y:Va(h[2],c),M:Va(h[3],c),d:Va(h[4],c),h:Va(h[5],c),m:Va(h[6],c),s:Va(h[7],c),w:Va(h[8],c)}):null==f?f={}:"object"==typeof f&&("from"in f||"to"in f)&&(e=Xa(za(f.from),za(f.to)),f={},f.ms=e.milliseconds,f.M=e.months),d=new Da(f),Ea(a)&&g(a,"_locale")&&(d._locale=a._locale),d}function Va(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function Wa(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Xa(a,b){var c;return b=Ha(b,a),a.isBefore(b)?c=Wa(a,b):(c=Wa(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c}function Ya(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||($(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ua(c,d),Za(this,e,a),this}}function Za(b,c,d,e){var f=c._milliseconds,g=c._days,h=c._months;e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&C(b,"Date",B(b,"Date")+g*d),h&&U(b,B(b,"Month")+h*d),e&&a.updateOffset(b,g||h)}function $a(a){var b=a||za(),c=Ha(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,za(b)))}function _a(){return new m(this)}function ab(a,b){var c;return b=y("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+this>+a):(c=n(a)?+a:+za(a),c<+this.clone().startOf(b))}function bb(a,b){var c;return b=y("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+a>+this):(c=n(a)?+a:+za(a),+this.clone().endOf(b)<c)}function cb(a,b,c){return this.isAfter(a,c)&&this.isBefore(b,c)}function db(a,b){var c;return b=y(b||"millisecond"),"millisecond"===b?(a=n(a)?a:za(a),+this===+a):(c=+za(a),+this.clone().startOf(b)<=c&&c<=+this.clone().endOf(b))}function eb(a){return 0>a?Math.ceil(a):Math.floor(a)}function fb(a,b,c){var d,e,f=Ha(a,this),g=6e4*(f.utcOffset()-this.utcOffset());return b=y(b),"year"===b||"month"===b||"quarter"===b?(e=gb(this,f),"quarter"===b?e/=3:"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:eb(e)}function gb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function hb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ib(){var a=this.clone().utc();return 0<a.year()&&a.year()<=9999?"function"==typeof Date.prototype.toISOString?this.toDate().toISOString():I(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):I(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function jb(b){var c=I(this,b||a.defaultFormat);return this.localeData().postformat(c)}function kb(a,b){return Ua({to:this,from:a}).locale(this.locale()).humanize(!b)}function lb(a){return this.from(za(),a)}function mb(a){var b;return void 0===a?this._locale._abbr:(b=w(a),null!=b&&(this._locale=b),this)}function nb(){return this._locale}function ob(a){switch(a=y(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a&&this.weekday(0),"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this}function pb(a){return a=y(a),void 0===a||"millisecond"===a?this:this.startOf(a).add(1,"isoWeek"===a?"week":a).subtract(1,"ms")}function qb(){return+this._d-6e4*(this._offset||0)}function rb(){return Math.floor(+this/1e3)}function sb(){return this._offset?new Date(+this):this._d}function tb(){var a=this;return[a.year(),a.month(),a.date(),a.hour(),a.minute(),a.second(),a.millisecond()]}function ub(){return j(this)}function vb(){return h({},this._pf)}function wb(){return this._pf.overflow}function xb(a,b){F(0,[a,a.length],0,b)}function yb(a,b,c){return ga(za([a,11,31+b-c]),b,c).week}function zb(a){var b=ga(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return null==a?b:this.add(a-b,"y")}function Ab(a){var b=ga(this,1,4).year;return null==a?b:this.add(a-b,"y")}function Bb(){return yb(this.year(),1,4)}function Cb(){var a=this.localeData()._week;return yb(this.year(),a.dow,a.doy)}function Db(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Eb(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function Fb(a){return this._weekdays[a.day()]}function Gb(a){return this._weekdaysShort[a.day()]}function Hb(a){return this._weekdaysMin[a.day()]}function Ib(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=za([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b}function Jb(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Eb(a,this.localeData()),this.add(a-b,"d")):b}function Kb(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Lb(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)}function Mb(a,b){F(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function Nb(a,b){return b._meridiemParse}function Ob(a){return"p"===(a+"").toLowerCase().charAt(0)}function Pb(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Qb(a){F(0,[a,3],0,"millisecond")}function Rb(){return this._isUTC?"UTC":""}function Sb(){return this._isUTC?"Coordinated Universal Time":""}function Tb(a){return za(1e3*a)}function Ub(){return za.apply(null,arguments).parseZone()}function Vb(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.call(b,c):d}function Wb(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b}function Xb(){return this._invalidDate}function Yb(a){return this._ordinal.replace("%d",a)}function Zb(a){return a}function $b(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)}function _b(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)}function ac(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function bc(a,b,c,d){var e=w(),f=i().set(d,b);return e[c](f,a)}function cc(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return bc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=bc(a,f,c,e);return g}function dc(a,b){return cc(a,b,"months",12,"month")}function ec(a,b){return cc(a,b,"monthsShort",12,"month")}function fc(a,b){return cc(a,b,"weekdays",7,"day")}function gc(a,b){return cc(a,b,"weekdaysShort",7,"day")}function hc(a,b){return cc(a,b,"weekdaysMin",7,"day")}function ic(){var a=this._data;return this._milliseconds=Od(this._milliseconds),this._days=Od(this._days),this._months=Od(this._months),a.milliseconds=Od(a.milliseconds),a.seconds=Od(a.seconds),a.minutes=Od(a.minutes),a.hours=Od(a.hours),a.months=Od(a.months),a.years=Od(a.years),this}function jc(a,b,c,d){var e=Ua(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function kc(a,b){return jc(this,a,b,1)}function lc(a,b){return jc(this,a,b,-1)}function mc(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;return g.milliseconds=d%1e3,a=eb(d/1e3),g.seconds=a%60,b=eb(a/60),g.minutes=b%60,c=eb(b/60),g.hours=c%24,e+=eb(c/24),h=eb(nc(e)),e-=eb(oc(h)),f+=eb(e/30),e%=30,h+=eb(f/12),f%=12,g.days=e,g.months=f,g.years=h,this}function nc(a){return 400*a/146097}function oc(a){return 146097*a/400}function pc(a){var b,c,d=this._milliseconds;if(a=y(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+12*nc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(oc(this._months/12)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 24*b*60+d/6e4;case"second":return 24*b*60*60+d/1e3;case"millisecond":return Math.floor(24*b*60*60*1e3)+d;default:throw new Error("Unknown unit "+a)}}function qc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*o(this._months/12)}function rc(a){return function(){return this.as(a)}}function sc(a){return a=y(a),this[a+"s"]()}function tc(a){return function(){return this._data[a]}}function uc(){return eb(this.days()/7)}function vc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function wc(a,b,c){var d=Ua(a).abs(),e=ce(d.as("s")),f=ce(d.as("m")),g=ce(d.as("h")),h=ce(d.as("d")),i=ce(d.as("M")),j=ce(d.as("y")),k=e<de.s&&["s",e]||1===f&&["m"]||f<de.m&&["mm",f]||1===g&&["h"]||g<de.h&&["hh",g]||1===h&&["d"]||h<de.d&&["dd",h]||1===i&&["M"]||i<de.M&&["MM",i]||1===j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,vc.apply(null,k)}function xc(a,b){return void 0===de[a]?!1:void 0===b?de[a]:(de[a]=b,!0)}function yc(a){var b=this.localeData(),c=wc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function zc(){var a=ee(this.years()),b=ee(this.months()),c=ee(this.days()),d=ee(this.hours()),e=ee(this.minutes()),f=ee(this.seconds()+this.milliseconds()/1e3),g=this.asSeconds();return g?(0>g?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"}var Ac,Bc,Cc=a.momentProperties=[],Dc=!1,Ec={},Fc={},Gc=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|x|X|zz?|ZZ?|.)/g,Hc=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,Ic={},Jc={},Kc=/\d/,Lc=/\d\d/,Mc=/\d{3}/,Nc=/\d{4}/,Oc=/[+-]?\d{6}/,Pc=/\d\d?/,Qc=/\d{1,3}/,Rc=/\d{1,4}/,Sc=/[+-]?\d{1,6}/,Tc=/\d+/,Uc=/[+-]?\d+/,Vc=/Z|[+-]\d\d:?\d\d/gi,Wc=/[+-]?\d+(\.\d{1,3})?/,Xc=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Yc={},Zc={},$c=0,_c=1,ad=2,bd=3,cd=4,dd=5,ed=6;F("M",["MM",2],"Mo",function(){return this.month()+1}),F("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),F("MMMM",0,0,function(a){return this.localeData().months(this,a)}),x("month","M"),K("M",Pc),K("MM",Pc,Lc),K("MMM",Xc),K("MMMM",Xc),N(["M","MM"],function(a,b){b[_c]=o(a)-1}),N(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[_c]=e:c._pf.invalidMonth=a});var fd="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),gd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),hd={};a.suppressDeprecationWarnings=!1;var id=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,jd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],kd=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ld=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=Z("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),F(0,["YY",2],0,function(){return this.year()%100}),F(0,["YYYY",4],0,"year"),F(0,["YYYYY",5],0,"year"),F(0,["YYYYYY",6,!0],0,"year"),x("year","y"),K("Y",Uc),K("YY",Pc,Lc),K("YYYY",Rc,Nc),K("YYYYY",Sc,Oc),K("YYYYYY",Sc,Oc),N(["YYYY","YYYYY","YYYYYY"],$c),N("YY",function(b,c){c[$c]=a.parseTwoDigitYear(b)}),a.parseTwoDigitYear=function(a){return o(a)+(o(a)>68?1900:2e3)};var md=A("FullYear",!1);F("w",["ww",2],"wo","week"),F("W",["WW",2],"Wo","isoWeek"),x("week","w"),x("isoWeek","W"),K("w",Pc),K("ww",Pc,Lc),K("W",Pc),K("WW",Pc,Lc),O(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=o(a)});var nd={dow:0,doy:6};F("DDD",["DDDD",3],"DDDo","dayOfYear"),x("dayOfYear","DDD"),K("DDD",Qc),K("DDDD",Mc),N(["DDD","DDDD"],function(a,b,c){c._dayOfYear=o(a)}),a.ISO_8601=function(){};var od=Z("moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=za.apply(null,arguments);return this>a?this:a}),pd=Z("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=za.apply(null,arguments);return a>this?this:a});Fa("Z",":"),Fa("ZZ",""),K("Z",Vc),K("ZZ",Vc),N(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ga(a)});var qd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var rd=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,sd=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/;Ua.fn=Da.prototype;var td=Ya(1,"add"),ud=Ya(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var vd=Z("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});F(0,["gg",2],0,function(){return this.weekYear()%100}),F(0,["GG",2],0,function(){return this.isoWeekYear()%100}),xb("gggg","weekYear"),xb("ggggg","weekYear"),xb("GGGG","isoWeekYear"),xb("GGGGG","isoWeekYear"),x("weekYear","gg"),x("isoWeekYear","GG"),K("G",Uc),K("g",Uc),K("GG",Pc,Lc),K("gg",Pc,Lc),K("GGGG",Rc,Nc),K("gggg",Rc,Nc),K("GGGGG",Sc,Oc),K("ggggg",Sc,Oc),O(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=o(a)}),O(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),F("Q",0,0,"quarter"),x("quarter","Q"),K("Q",Kc),N("Q",function(a,b){b[_c]=3*(o(a)-1)}),F("D",["DD",2],"Do","date"),x("date","D"),K("D",Pc),K("DD",Pc,Lc),K("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),N(["D","DD"],ad),N("Do",function(a,b){b[ad]=o(a.match(Pc)[0],10)});var wd=A("Date",!0);F("d",0,"do","day"),F("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),F("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),F("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),F("e",0,0,"weekday"),F("E",0,0,"isoWeekday"),x("day","d"),x("weekday","e"),x("isoWeekday","E"),K("d",Pc),K("e",Pc),K("E",Pc),K("dd",Xc),K("ddd",Xc),K("dddd",Xc),O(["dd","ddd","dddd"],function(a,b,c){var d=c._locale.weekdaysParse(a);null!=d?b.d=d:c._pf.invalidWeekday=a}),O(["d","e","E"],function(a,b,c,d){b[d]=o(a)});var xd="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),yd="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),zd="Su_Mo_Tu_We_Th_Fr_Sa".split("_");F("H",["HH",2],0,"hour"),F("h",["hh",2],0,function(){return this.hours()%12||12}),Mb("a",!0),Mb("A",!1),x("hour","h"),K("a",Nb),K("A",Nb),K("H",Pc),K("h",Pc),K("HH",Pc,Lc),K("hh",Pc,Lc),N(["H","HH"],bd),N(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),N(["h","hh"],function(a,b,c){b[bd]=o(a),c._pf.bigHour=!0});var Ad=/[ap]\.?m?\.?/i,Bd=A("Hours",!0);F("m",["mm",2],0,"minute"),x("minute","m"),K("m",Pc),K("mm",Pc,Lc),N(["m","mm"],cd);var Cd=A("Minutes",!1);F("s",["ss",2],0,"second"),x("second","s"),K("s",Pc),K("ss",Pc,Lc),N(["s","ss"],dd);var Dd=A("Seconds",!1);F("S",0,0,function(){return~~(this.millisecond()/100)}),F(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),Qb("SSS"),Qb("SSSS"),x("millisecond","ms"),K("S",Qc,Kc),K("SS",Qc,Lc),K("SSS",Qc,Mc),K("SSSS",Tc),N(["S","SS","SSS","SSSS"],function(a,b){b[ed]=o(1e3*("0."+a))});var Ed=A("Milliseconds",!1);F("z",0,0,"zoneAbbr"),F("zz",0,0,"zoneName");var Fd=m.prototype;Fd.add=td,Fd.calendar=$a,Fd.clone=_a,Fd.diff=fb,Fd.endOf=pb,Fd.format=jb,Fd.from=kb,Fd.fromNow=lb,Fd.get=D,Fd.invalidAt=wb,Fd.isAfter=ab,Fd.isBefore=bb,Fd.isBetween=cb,Fd.isSame=db,Fd.isValid=ub,Fd.lang=vd,Fd.locale=mb,Fd.localeData=nb,Fd.max=pd,Fd.min=od,Fd.parsingFlags=vb,Fd.set=D,Fd.startOf=ob,Fd.subtract=ud,Fd.toArray=tb,Fd.toDate=sb,Fd.toISOString=ib,Fd.toJSON=ib,Fd.toString=hb,Fd.unix=rb,Fd.valueOf=qb,Fd.year=md,Fd.isLeapYear=fa,Fd.weekYear=zb,Fd.isoWeekYear=Ab,Fd.quarter=Fd.quarters=Db,Fd.month=V,Fd.daysInMonth=W,Fd.week=Fd.weeks=ka,Fd.isoWeek=Fd.isoWeeks=la,Fd.weeksInYear=Cb,Fd.isoWeeksInYear=Bb,Fd.date=wd,Fd.day=Fd.days=Jb,Fd.weekday=Kb,Fd.isoWeekday=Lb,Fd.dayOfYear=na,Fd.hour=Fd.hours=Bd,Fd.minute=Fd.minutes=Cd,Fd.second=Fd.seconds=Dd,Fd.millisecond=Fd.milliseconds=Ed,Fd.utcOffset=Ja,Fd.utc=La,Fd.local=Ma,Fd.parseZone=Na,Fd.hasAlignedHourOffset=Oa,Fd.isDST=Pa,Fd.isDSTShifted=Qa,Fd.isLocal=Ra,Fd.isUtcOffset=Sa,Fd.isUtc=Ta,Fd.isUTC=Ta,Fd.zoneAbbr=Rb,Fd.zoneName=Sb,Fd.dates=Z("dates accessor is deprecated. Use date instead.",wd),Fd.months=Z("months accessor is deprecated. Use month instead",V),Fd.years=Z("years accessor is deprecated. Use year instead",md),Fd.zone=Z("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Ka);var Gd=Fd,Hd={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Id={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},Jd="Invalid date",Kd="%d",Ld=/\d{1,2}/,Md={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Nd=q.prototype;Nd._calendar=Hd,Nd.calendar=Vb,Nd._longDateFormat=Id,Nd.longDateFormat=Wb,Nd._invalidDate=Jd,Nd.invalidDate=Xb,Nd._ordinal=Kd,Nd.ordinal=Yb,Nd._ordinalParse=Ld,
Nd.preparse=Zb,Nd.postformat=Zb,Nd._relativeTime=Md,Nd.relativeTime=$b,Nd.pastFuture=_b,Nd.set=ac,Nd.months=R,Nd._months=fd,Nd.monthsShort=S,Nd._monthsShort=gd,Nd.monthsParse=T,Nd.week=ha,Nd._week=nd,Nd.firstDayOfYear=ja,Nd.firstDayOfWeek=ia,Nd.weekdays=Fb,Nd._weekdays=xd,Nd.weekdaysMin=Hb,Nd._weekdaysMin=zd,Nd.weekdaysShort=Gb,Nd._weekdaysShort=yd,Nd.weekdaysParse=Ib,Nd.isPM=Ob,Nd._meridiemParse=Ad,Nd.meridiem=Pb,u("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===o(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=Z("moment.lang is deprecated. Use moment.locale instead.",u),a.langData=Z("moment.langData is deprecated. Use moment.localeData instead.",w);var Od=Math.abs,Pd=rc("ms"),Qd=rc("s"),Rd=rc("m"),Sd=rc("h"),Td=rc("d"),Ud=rc("w"),Vd=rc("M"),Wd=rc("y"),Xd=tc("milliseconds"),Yd=tc("seconds"),Zd=tc("minutes"),$d=tc("hours"),_d=tc("days"),ae=tc("months"),be=tc("years"),ce=Math.round,de={s:45,m:45,h:22,d:26,M:11},ee=Math.abs,fe=Da.prototype;fe.abs=ic,fe.add=kc,fe.subtract=lc,fe.as=pc,fe.asMilliseconds=Pd,fe.asSeconds=Qd,fe.asMinutes=Rd,fe.asHours=Sd,fe.asDays=Td,fe.asWeeks=Ud,fe.asMonths=Vd,fe.asYears=Wd,fe.valueOf=qc,fe._bubble=mc,fe.get=sc,fe.milliseconds=Xd,fe.seconds=Yd,fe.minutes=Zd,fe.hours=$d,fe.days=_d,fe.weeks=uc,fe.months=ae,fe.years=be,fe.humanize=yc,fe.toISOString=zc,fe.toString=zc,fe.toJSON=zc,fe.locale=mb,fe.localeData=nb,fe.toIsoString=Z("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",zc),fe.lang=vd,F("X",0,0,"unix"),F("x",0,0,"valueOf"),K("x",Uc),K("X",Wc),N("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),N("x",function(a,b,c){c._d=new Date(o(a))}),a.version="2.10.2",b(za),a.fn=Gd,a.min=Ba,a.max=Ca,a.utc=i,a.unix=Tb,a.months=dc,a.isDate=e,a.locale=u,a.invalid=k,a.duration=Ua,a.isMoment=n,a.weekdays=fc,a.parseZone=Ub,a.localeData=w,a.isDuration=Ea,a.monthsShort=ec,a.weekdaysMin=hc,a.defineLocale=v,a.weekdaysShort=gc,a.normalizeUnits=y,a.relativeTimeThreshold=xc;var ge=a;return ge});;
/*!
 * pickadate.js v3.5.6, 2015/04/20
 * By Amsul, http://amsul.ca
 * Hosted on http://amsul.github.io/pickadate.js
 * Licensed under MIT
 */

(function ( factory ) {

    // AMD.
    if ( typeof define == 'function' && define.amd )
        define( 'picker', ['jquery'], factory )

    // Node.js/browserify.
    else if ( typeof exports == 'object' )
        module.exports = factory( require('jquery') )

    // Browser globals.
    else this.Picker = factory( jQuery )

}(function( $ ) {

var $window = $( window )
var $document = $( document )
var $html = $( document.documentElement )
var supportsTransitions = document.documentElement.style.transition != null


/**
 * The picker constructor that creates a blank picker.
 */
function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {

    // If there’s no element, return the picker constructor.
    if ( !ELEMENT ) return PickerConstructor


    var
        IS_DEFAULT_THEME = false,


        // The state of the picker.
        STATE = {
            id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) )
        },


        // Merge the defaults and options passed.
        SETTINGS = COMPONENT ? $.extend( true, {}, COMPONENT.defaults, OPTIONS ) : OPTIONS || {},


        // Merge the default classes with the settings classes.
        CLASSES = $.extend( {}, PickerConstructor.klasses(), SETTINGS.klass ),


        // The element node wrapper into a jQuery object.
        $ELEMENT = $( ELEMENT ),


        // Pseudo picker constructor.
        PickerInstance = function() {
            return this.start()
        },


        // The picker prototype.
        P = PickerInstance.prototype = {

            constructor: PickerInstance,

            $node: $ELEMENT,


            /**
             * Initialize everything
             */
            start: function() {

                // If it’s already started, do nothing.
                if ( STATE && STATE.start ) return P


                // Update the picker states.
                STATE.methods = {}
                STATE.start = true
                STATE.open = false
                STATE.type = ELEMENT.type


                // Confirm focus state, convert into text input to remove UA stylings,
                // and set as readonly to prevent keyboard popup.
                ELEMENT.autofocus = ELEMENT == getActiveElement()
                ELEMENT.readOnly = !SETTINGS.editable
                ELEMENT.id = ELEMENT.id || STATE.id
                if ( ELEMENT.type != 'text' ) {
                    ELEMENT.type = 'text'
                }


                // Create a new picker component with the settings.
                P.component = new COMPONENT(P, SETTINGS)


                // Create the picker root and then prepare it.
                P.$root = $( '<div class="' + CLASSES.picker + '" id="' + ELEMENT.id + '_root" />' )
                prepareElementRoot()


                // Create the picker holder and then prepare it.
                P.$holder = $( createWrappedComponent() ).appendTo( P.$root )
                prepareElementHolder()


                // If there’s a format for the hidden input element, create the element.
                if ( SETTINGS.formatSubmit ) {
                    prepareElementHidden()
                }


                // Prepare the input element.
                prepareElement()


                // Insert the hidden input as specified in the settings.
                if ( SETTINGS.containerHidden ) $( SETTINGS.containerHidden ).append( P._hidden )
                else $ELEMENT.after( P._hidden )


                // Insert the root as specified in the settings.
                if ( SETTINGS.container ) $( SETTINGS.container ).append( P.$root )
                else $ELEMENT.after( P.$root )


                // Bind the default component and settings events.
                P.on({
                    start: P.component.onStart,
                    render: P.component.onRender,
                    stop: P.component.onStop,
                    open: P.component.onOpen,
                    close: P.component.onClose,
                    set: P.component.onSet
                }).on({
                    start: SETTINGS.onStart,
                    render: SETTINGS.onRender,
                    stop: SETTINGS.onStop,
                    open: SETTINGS.onOpen,
                    close: SETTINGS.onClose,
                    set: SETTINGS.onSet
                })


                // Once we’re all set, check the theme in use.
                IS_DEFAULT_THEME = isUsingDefaultTheme( P.$holder[0] )


                // If the element has autofocus, open the picker.
                if ( ELEMENT.autofocus ) {
                    P.open()
                }


                // Trigger queued the “start” and “render” events.
                return P.trigger( 'start' ).trigger( 'render' )
            }, //start


            /**
             * Render a new picker
             */
            render: function( entireComponent ) {

                // Insert a new component holder in the root or box.
                if ( entireComponent ) {
                    P.$holder = $( createWrappedComponent() )
                    prepareElementHolder()
                    P.$root.html( P.$holder )
                }
                else P.$root.find( '.' + CLASSES.box ).html( P.component.nodes( STATE.open ) )

                // Trigger the queued “render” events.
                return P.trigger( 'render' )
            }, //render


            /**
             * Destroy everything
             */
            stop: function() {

                // If it’s already stopped, do nothing.
                if ( !STATE.start ) return P

                // Then close the picker.
                P.close()

                // Remove the hidden field.
                if ( P._hidden ) {
                    P._hidden.parentNode.removeChild( P._hidden )
                }

                // Remove the root.
                P.$root.remove()

                // Remove the input class, remove the stored data, and unbind
                // the events (after a tick for IE - see `P.close`).
                $ELEMENT.removeClass( CLASSES.input ).removeData( NAME )
                setTimeout( function() {
                    $ELEMENT.off( '.' + STATE.id )
                }, 0)

                // Restore the element state
                ELEMENT.type = STATE.type
                ELEMENT.readOnly = false

                // Trigger the queued “stop” events.
                P.trigger( 'stop' )

                // Reset the picker states.
                STATE.methods = {}
                STATE.start = false

                return P
            }, //stop


            /**
             * Open up the picker
             */
            open: function( dontGiveFocus ) {

                // If it’s already open, do nothing.
                if ( STATE.open ) return P

                // Add the “active” class.
                $ELEMENT.addClass( CLASSES.active )
                aria( ELEMENT, 'expanded', true )

                // * A Firefox bug, when `html` has `overflow:hidden`, results in
                //   killing transitions :(. So add the “opened” state on the next tick.
                //   Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289
                setTimeout( function() {

                    // Add the “opened” class to the picker root.
                    P.$root.addClass( CLASSES.opened )
                    aria( P.$root[0], 'hidden', false )

                }, 0 )

                // If we have to give focus, bind the element and doc events.
                if ( dontGiveFocus !== false ) {

                    // Set it as open.
                    STATE.open = true

                    // Prevent the page from scrolling.
                    if ( IS_DEFAULT_THEME ) {
                        $html.
                            css( 'overflow', 'hidden' ).
                            css( 'padding-right', '+=' + getScrollbarWidth() )
                    }

                    // Pass focus to the root element’s jQuery object.
                    focusPickerOnceOpened()

                    // Bind the document events.
                    $document.on( 'click.' + STATE.id + ' focusin.' + STATE.id, function( event ) {

                        var target = event.target

                        // If the target of the event is not the element, close the picker picker.
                        // * Don’t worry about clicks or focusins on the root because those don’t bubble up.
                        //   Also, for Firefox, a click on an `option` element bubbles up directly
                        //   to the doc. So make sure the target wasn't the doc.
                        // * In Firefox stopPropagation() doesn’t prevent right-click events from bubbling,
                        //   which causes the picker to unexpectedly close when right-clicking it. So make
                        //   sure the event wasn’t a right-click.
                        if ( target != ELEMENT && target != document && event.which != 3 ) {

                            // If the target was the holder that covers the screen,
                            // keep the element focused to maintain tabindex.
                            P.close( target === P.$holder[0] )
                        }

                    }).on( 'keydown.' + STATE.id, function( event ) {

                        var
                            // Get the keycode.
                            keycode = event.keyCode,

                            // Translate that to a selection change.
                            keycodeToMove = P.component.key[ keycode ],

                            // Grab the target.
                            target = event.target


                        // On escape, close the picker and give focus.
                        if ( keycode == 27 ) {
                            P.close( true )
                        }


                        // Check if there is a key movement or “enter” keypress on the element.
                        else if ( target == P.$holder[0] && ( keycodeToMove || keycode == 13 ) ) {

                            // Prevent the default action to stop page movement.
                            event.preventDefault()

                            // Trigger the key movement action.
                            if ( keycodeToMove ) {
                                PickerConstructor._.trigger( P.component.key.go, P, [ PickerConstructor._.trigger( keycodeToMove ) ] )
                            }

                            // On “enter”, if the highlighted item isn’t disabled, set the value and close.
                            else if ( !P.$root.find( '.' + CLASSES.highlighted ).hasClass( CLASSES.disabled ) ) {
                                P.set( 'select', P.component.item.highlight )
                                if ( SETTINGS.closeOnSelect ) {
                                    P.close( true )
                                }
                            }
                        }


                        // If the target is within the root and “enter” is pressed,
                        // prevent the default action and trigger a click on the target instead.
                        else if ( $.contains( P.$root[0], target ) && keycode == 13 ) {
                            event.preventDefault()
                            target.click()
                        }
                    })
                }

                // Trigger the queued “open” events.
                return P.trigger( 'open' )
            }, //open


            /**
             * Close the picker
             */
            close: function( giveFocus ) {

                // If we need to give focus, do it before changing states.
                if ( giveFocus ) {
                    if ( SETTINGS.editable ) {
                        ELEMENT.focus()
                    }
                    else {
                        // ....ah yes! It would’ve been incomplete without a crazy workaround for IE :|
                        // The focus is triggered *after* the close has completed - causing it
                        // to open again. So unbind and rebind the event at the next tick.
                        P.$holder.off( 'focus.toOpen' ).focus()
                        setTimeout( function() {
                            P.$holder.on( 'focus.toOpen', handleFocusToOpenEvent )
                        }, 0 )
                    }
                }

                // Remove the “active” class.
                $ELEMENT.removeClass( CLASSES.active )
                aria( ELEMENT, 'expanded', false )

                // * A Firefox bug, when `html` has `overflow:hidden`, results in
                //   killing transitions :(. So remove the “opened” state on the next tick.
                //   Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=625289
                setTimeout( function() {

                    // Remove the “opened” and “focused” class from the picker root.
                    P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused )
                    aria( P.$root[0], 'hidden', true )

                }, 0 )

                // If it’s already closed, do nothing more.
                if ( !STATE.open ) return P

                // Set it as closed.
                STATE.open = false

                // Allow the page to scroll.
                if ( IS_DEFAULT_THEME ) {
                    $html.
                        css( 'overflow', '' ).
                        css( 'padding-right', '-=' + getScrollbarWidth() )
                }

                // Unbind the document events.
                $document.off( '.' + STATE.id )

                // Trigger the queued “close” events.
                return P.trigger( 'close' )
            }, //close


            /**
             * Clear the values
             */
            clear: function( options ) {
                return P.set( 'clear', null, options )
            }, //clear


            /**
             * Set something
             */
            set: function( thing, value, options ) {

                var thingItem, thingValue,
                    thingIsObject = $.isPlainObject( thing ),
                    thingObject = thingIsObject ? thing : {}

                // Make sure we have usable options.
                options = thingIsObject && $.isPlainObject( value ) ? value : options || {}

                if ( thing ) {

                    // If the thing isn’t an object, make it one.
                    if ( !thingIsObject ) {
                        thingObject[ thing ] = value
                    }

                    // Go through the things of items to set.
                    for ( thingItem in thingObject ) {

                        // Grab the value of the thing.
                        thingValue = thingObject[ thingItem ]

                        // First, if the item exists and there’s a value, set it.
                        if ( thingItem in P.component.item ) {
                            if ( thingValue === undefined ) thingValue = null
                            P.component.set( thingItem, thingValue, options )
                        }

                        // Then, check to update the element value and broadcast a change.
                        if ( thingItem == 'select' || thingItem == 'clear' ) {
                            $ELEMENT.
                                val( thingItem == 'clear' ? '' : P.get( thingItem, SETTINGS.format ) ).
                                trigger( 'change' )
                        }
                    }

                    // Render a new picker.
                    P.render()
                }

                // When the method isn’t muted, trigger queued “set” events and pass the `thingObject`.
                return options.muted ? P : P.trigger( 'set', thingObject )
            }, //set


            /**
             * Get something
             */
            get: function( thing, format ) {

                // Make sure there’s something to get.
                thing = thing || 'value'

                // If a picker state exists, return that.
                if ( STATE[ thing ] != null ) {
                    return STATE[ thing ]
                }

                // Return the submission value, if that.
                if ( thing == 'valueSubmit' ) {
                    if ( P._hidden ) {
                        return P._hidden.value
                    }
                    thing = 'value'
                }

                // Return the value, if that.
                if ( thing == 'value' ) {
                    return ELEMENT.value
                }

                // Check if a component item exists, return that.
                if ( thing in P.component.item ) {
                    if ( typeof format == 'string' ) {
                        var thingValue = P.component.get( thing )
                        return thingValue ?
                            PickerConstructor._.trigger(
                                P.component.formats.toString,
                                P.component,
                                [ format, thingValue ]
                            ) : ''
                    }
                    return P.component.get( thing )
                }
            }, //get



            /**
             * Bind events on the things.
             */
            on: function( thing, method, internal ) {

                var thingName, thingMethod,
                    thingIsObject = $.isPlainObject( thing ),
                    thingObject = thingIsObject ? thing : {}

                if ( thing ) {

                    // If the thing isn’t an object, make it one.
                    if ( !thingIsObject ) {
                        thingObject[ thing ] = method
                    }

                    // Go through the things to bind to.
                    for ( thingName in thingObject ) {

                        // Grab the method of the thing.
                        thingMethod = thingObject[ thingName ]

                        // If it was an internal binding, prefix it.
                        if ( internal ) {
                            thingName = '_' + thingName
                        }

                        // Make sure the thing methods collection exists.
                        STATE.methods[ thingName ] = STATE.methods[ thingName ] || []

                        // Add the method to the relative method collection.
                        STATE.methods[ thingName ].push( thingMethod )
                    }
                }

                return P
            }, //on



            /**
             * Unbind events on the things.
             */
            off: function() {
                var i, thingName,
                    names = arguments;
                for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) {
                    thingName = names[i]
                    if ( thingName in STATE.methods ) {
                        delete STATE.methods[thingName]
                    }
                }
                return P
            },


            /**
             * Fire off method events.
             */
            trigger: function( name, data ) {
                var _trigger = function( name ) {
                    var methodList = STATE.methods[ name ]
                    if ( methodList ) {
                        methodList.map( function( method ) {
                            PickerConstructor._.trigger( method, P, [ data ] )
                        })
                    }
                }
                _trigger( '_' + name )
                _trigger( name )
                return P
            } //trigger
        } //PickerInstance.prototype


    /**
     * Wrap the picker holder components together.
     */
    function createWrappedComponent() {

        // Create a picker wrapper holder
        return PickerConstructor._.node( 'div',

            // Create a picker wrapper node
            PickerConstructor._.node( 'div',

                // Create a picker frame
                PickerConstructor._.node( 'div',

                    // Create a picker box node
                    PickerConstructor._.node( 'div',

                        // Create the components nodes.
                        P.component.nodes( STATE.open ),

                        // The picker box class
                        CLASSES.box
                    ),

                    // Picker wrap class
                    CLASSES.wrap
                ),

                // Picker frame class
                CLASSES.frame
            ),

            // Picker holder class
            CLASSES.holder,

            'tabindex="-1"'
        ) //endreturn
    } //createWrappedComponent



    /**
     * Prepare the input element with all bindings.
     */
    function prepareElement() {

        $ELEMENT.

            // Store the picker data by component name.
            data(NAME, P).

            // Add the “input” class name.
            addClass(CLASSES.input).

            // If there’s a `data-value`, update the value of the element.
            val( $ELEMENT.data('value') ?
                P.get('select', SETTINGS.format) :
                ELEMENT.value
            )


        // Only bind keydown events if the element isn’t editable.
        if ( !SETTINGS.editable ) {

            $ELEMENT.

                // On focus/click, open the picker.
                on( 'focus.' + STATE.id + ' click.' + STATE.id, function(event) {
                    event.preventDefault()
                    P.open()
                }).

                // Handle keyboard event based on the picker being opened or not.
                on( 'keydown.' + STATE.id, handleKeydownEvent )
        }


        // Update the aria attributes.
        aria(ELEMENT, {
            haspopup: true,
            expanded: false,
            readonly: false,
            owns: ELEMENT.id + '_root'
        })
    }


    /**
     * Prepare the root picker element with all bindings.
     */
    function prepareElementRoot() {
        aria( P.$root[0], 'hidden', true )
    }


     /**
      * Prepare the holder picker element with all bindings.
      */
    function prepareElementHolder() {

        P.$holder.

            on({

                // For iOS8.
                keydown: handleKeydownEvent,

                'focus.toOpen': handleFocusToOpenEvent,

                blur: function() {
                    // Remove the “target” class.
                    $ELEMENT.removeClass( CLASSES.target )
                },

                // When something within the holder is focused, stop from bubbling
                // to the doc and remove the “focused” state from the root.
                focusin: function( event ) {
                    P.$root.removeClass( CLASSES.focused )
                    event.stopPropagation()
                },

                // When something within the holder is clicked, stop it
                // from bubbling to the doc.
                'mousedown click': function( event ) {

                    var target = event.target

                    // Make sure the target isn’t the root holder so it can bubble up.
                    if ( target != P.$holder[0] ) {

                        event.stopPropagation()

                        // * For mousedown events, cancel the default action in order to
                        //   prevent cases where focus is shifted onto external elements
                        //   when using things like jQuery mobile or MagnificPopup (ref: #249 & #120).
                        //   Also, for Firefox, don’t prevent action on the `option` element.
                        if ( event.type == 'mousedown' && !$( target ).is( 'input, select, textarea, button, option' )) {

                            event.preventDefault()

                            // Re-focus onto the holder so that users can click away
                            // from elements focused within the picker.
                            P.$holder[0].focus()
                        }
                    }
                }

            }).

            // If there’s a click on an actionable element, carry out the actions.
            on( 'click', '[data-pick], [data-nav], [data-clear], [data-close]', function() {

                var $target = $( this ),
                    targetData = $target.data(),
                    targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ),

                    // * For IE, non-focusable elements can be active elements as well
                    //   (http://stackoverflow.com/a/2684561).
                    activeElement = getActiveElement()
                    activeElement = activeElement && ( activeElement.type || activeElement.href )

                // If it’s disabled or nothing inside is actively focused, re-focus the element.
                if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) {
                    P.$holder[0].focus()
                }

                // If something is superficially changed, update the `highlight` based on the `nav`.
                if ( !targetDisabled && targetData.nav ) {
                    P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } )
                }

                // If something is picked, set `select` then close with focus.
                else if ( !targetDisabled && 'pick' in targetData ) {
                    P.set( 'select', targetData.pick )
                    if ( SETTINGS.closeOnSelect ) {
                        P.close( true )
                    }
                }

                // If a “clear” button is pressed, empty the values and close with focus.
                else if ( targetData.clear ) {
                    P.clear()
                    if ( SETTINGS.closeOnClear ) {
                        P.close( true )
                    }
                }

                else if ( targetData.close ) {
                    P.close( true )
                }

            }) //P.$holder

    }


     /**
      * Prepare the hidden input element along with all bindings.
      */
    function prepareElementHidden() {

        var name

        if ( SETTINGS.hiddenName === true ) {
            name = ELEMENT.name
            ELEMENT.name = ''
        }
        else {
            name = [
                typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '',
                typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit'
            ]
            name = name[0] + ELEMENT.name + name[1]
        }

        P._hidden = $(
            '<input ' +
            'type=hidden ' +

            // Create the name using the original input’s with a prefix and suffix.
            'name="' + name + '"' +

            // If the element has a value, set the hidden value as well.
            (
                $ELEMENT.data('value') || ELEMENT.value ?
                    ' value="' + P.get('select', SETTINGS.formatSubmit) + '"' :
                    ''
            ) +
            '>'
        )[0]

        $ELEMENT.

            // If the value changes, update the hidden input with the correct format.
            on('change.' + STATE.id, function() {
                P._hidden.value = ELEMENT.value ?
                    P.get('select', SETTINGS.formatSubmit) :
                    ''
            })
    }


    // Wait for transitions to end before focusing the holder. Otherwise, while
    // using the `container` option, the view jumps to the container.
    function focusPickerOnceOpened() {

        if (IS_DEFAULT_THEME && supportsTransitions) {
            P.$holder.find('.' + CLASSES.frame).one('transitionend', function() {
                P.$holder[0].focus()
            })
        }
        else {
            P.$holder[0].focus()
        }
    }


    function handleFocusToOpenEvent(event) {

        // Stop the event from propagating to the doc.
        event.stopPropagation()

        // Add the “target” class.
        $ELEMENT.addClass( CLASSES.target )

        // Add the “focused” class to the root.
        P.$root.addClass( CLASSES.focused )

        // And then finally open the picker.
        P.open()
    }


    // For iOS8.
    function handleKeydownEvent( event ) {

        var keycode = event.keyCode,

            // Check if one of the delete keys was pressed.
            isKeycodeDelete = /^(8|46)$/.test(keycode)

        // For some reason IE clears the input value on “escape”.
        if ( keycode == 27 ) {
            P.close( true )
            return false
        }

        // Check if `space` or `delete` was pressed or the picker is closed with a key movement.
        if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) {

            // Prevent it from moving the page and bubbling to doc.
            event.preventDefault()
            event.stopPropagation()

            // If `delete` was pressed, clear the values and close the picker.
            // Otherwise open the picker.
            if ( isKeycodeDelete ) { P.clear().close() }
            else { P.open() }
        }
    }


    // Return a new picker instance.
    return new PickerInstance()
} //PickerConstructor



/**
 * The default classes and prefix to use for the HTML classes.
 */
PickerConstructor.klasses = function( prefix ) {
    prefix = prefix || 'picker'
    return {

        picker: prefix,
        opened: prefix + '--opened',
        focused: prefix + '--focused',

        input: prefix + '__input',
        active: prefix + '__input--active',
        target: prefix + '__input--target',

        holder: prefix + '__holder',

        frame: prefix + '__frame',
        wrap: prefix + '__wrap',

        box: prefix + '__box'
    }
} //PickerConstructor.klasses



/**
 * Check if the default theme is being used.
 */
function isUsingDefaultTheme( element ) {

    var theme,
        prop = 'position'

    // For IE.
    if ( element.currentStyle ) {
        theme = element.currentStyle[prop]
    }

    // For normal browsers.
    else if ( window.getComputedStyle ) {
        theme = getComputedStyle( element )[prop]
    }

    return theme == 'fixed'
}



/**
 * Get the width of the browser’s scrollbar.
 * Taken from: https://github.com/VodkaBears/Remodal/blob/master/src/jquery.remodal.js
 */
function getScrollbarWidth() {

    if ( $html.height() <= $window.height() ) {
        return 0
    }

    var $outer = $( '<div style="visibility:hidden;width:100px" />' ).
        appendTo( 'body' )

    // Get the width without scrollbars.
    var widthWithoutScroll = $outer[0].offsetWidth

    // Force adding scrollbars.
    $outer.css( 'overflow', 'scroll' )

    // Add the inner div.
    var $inner = $( '<div style="width:100%" />' ).appendTo( $outer )

    // Get the width with scrollbars.
    var widthWithScroll = $inner[0].offsetWidth

    // Remove the divs.
    $outer.remove()

    // Return the difference between the widths.
    return widthWithoutScroll - widthWithScroll
}



/**
 * PickerConstructor helper methods.
 */
PickerConstructor._ = {

    /**
     * Create a group of nodes. Expects:
     * `
        {
            min:    {Integer},
            max:    {Integer},
            i:      {Integer},
            node:   {String},
            item:   {Function}
        }
     * `
     */
    group: function( groupObject ) {

        var
            // Scope for the looped object
            loopObjectScope,

            // Create the nodes list
            nodesList = '',

            // The counter starts from the `min`
            counter = PickerConstructor._.trigger( groupObject.min, groupObject )


        // Loop from the `min` to `max`, incrementing by `i`
        for ( ; counter <= PickerConstructor._.trigger( groupObject.max, groupObject, [ counter ] ); counter += groupObject.i ) {

            // Trigger the `item` function within scope of the object
            loopObjectScope = PickerConstructor._.trigger( groupObject.item, groupObject, [ counter ] )

            // Splice the subgroup and create nodes out of the sub nodes
            nodesList += PickerConstructor._.node(
                groupObject.node,
                loopObjectScope[ 0 ],   // the node
                loopObjectScope[ 1 ],   // the classes
                loopObjectScope[ 2 ]    // the attributes
            )
        }

        // Return the list of nodes
        return nodesList
    }, //group


    /**
     * Create a dom node string
     */
    node: function( wrapper, item, klass, attribute ) {

        // If the item is false-y, just return an empty string
        if ( !item ) return ''

        // If the item is an array, do a join
        item = $.isArray( item ) ? item.join( '' ) : item

        // Check for the class
        klass = klass ? ' class="' + klass + '"' : ''

        // Check for any attributes
        attribute = attribute ? ' ' + attribute : ''

        // Return the wrapped item
        return '<' + wrapper + klass + attribute + '>' + item + '</' + wrapper + '>'
    }, //node


    /**
     * Lead numbers below 10 with a zero.
     */
    lead: function( number ) {
        return ( number < 10 ? '0': '' ) + number
    },


    /**
     * Trigger a function otherwise return the value.
     */
    trigger: function( callback, scope, args ) {
        return typeof callback == 'function' ? callback.apply( scope, args || [] ) : callback
    },


    /**
     * If the second character is a digit, length is 2 otherwise 1.
     */
    digits: function( string ) {
        return ( /\d/ ).test( string[ 1 ] ) ? 2 : 1
    },


    /**
     * Tell if something is a date object.
     */
    isDate: function( value ) {
        return {}.toString.call( value ).indexOf( 'Date' ) > -1 && this.isInteger( value.getDate() )
    },


    /**
     * Tell if something is an integer.
     */
    isInteger: function( value ) {
        return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0
    },


    /**
     * Create ARIA attribute strings.
     */
    ariaAttr: ariaAttr
} //PickerConstructor._



/**
 * Extend the picker with a component and defaults.
 */
PickerConstructor.extend = function( name, Component ) {

    // Extend jQuery.
    $.fn[ name ] = function( options, action ) {

        // Grab the component data.
        var componentData = this.data( name )

        // If the picker is requested, return the data object.
        if ( options == 'picker' ) {
            return componentData
        }

        // If the component data exists and `options` is a string, carry out the action.
        if ( componentData && typeof options == 'string' ) {
            return PickerConstructor._.trigger( componentData[ options ], componentData, [ action ] )
        }

        // Otherwise go through each matched element and if the component
        // doesn’t exist, create a new picker using `this` element
        // and merging the defaults and options with a deep copy.
        return this.each( function() {
            var $this = $( this )
            if ( !$this.data( name ) ) {
                new PickerConstructor( this, name, Component, options )
            }
        })
    }

    // Set the defaults.
    $.fn[ name ].defaults = Component.defaults
} //PickerConstructor.extend



function aria(element, attribute, value) {
    if ( $.isPlainObject(attribute) ) {
        for ( var key in attribute ) {
            ariaSet(element, key, attribute[key])
        }
    }
    else {
        ariaSet(element, attribute, value)
    }
}
function ariaSet(element, attribute, value) {
    element.setAttribute(
        (attribute == 'role' ? '' : 'aria-') + attribute,
        value
    )
}
function ariaAttr(attribute, data) {
    if ( !$.isPlainObject(attribute) ) {
        attribute = { attribute: data }
    }
    data = ''
    for ( var key in attribute ) {
        var attr = (key == 'role' ? '' : 'aria-') + key,
            attrVal = attribute[key]
        data += attrVal == null ? '' : attr + '="' + attribute[key] + '"'
    }
    return data
}

// IE8 bug throws an error for activeElements within iframes.
function getActiveElement() {
    try {
        return document.activeElement
    } catch ( err ) { }
}



// Expose the picker constructor.
return PickerConstructor


}));



;
/*!
 * Date picker for pickadate.js v3.5.6
 * http://amsul.github.io/pickadate.js/date.htm
 */

(function ( factory ) {

    // AMD.
    if ( typeof define == 'function' && define.amd )
        define( ['picker', 'jquery'], factory )

    // Node.js/browserify.
    else if ( typeof exports == 'object' )
        module.exports = factory( require('./picker.js'), require('jquery') )

    // Browser globals.
    else factory( Picker, jQuery )

}(function( Picker, $ ) {


/**
 * Globals and constants
 */
var DAYS_IN_WEEK = 7,
    WEEKS_IN_CALENDAR = 6,
    _ = Picker._



/**
 * The date picker constructor
 */
function DatePicker( picker, settings ) {

    var calendar = this,
        element = picker.$node[ 0 ],
        elementValue = element.value,
        elementDataValue = picker.$node.data( 'value' ),
        valueString = elementDataValue || elementValue,
        formatString = elementDataValue ? settings.formatSubmit : settings.format,
        isRTL = function() {

            return element.currentStyle ?

                // For IE.
                element.currentStyle.direction == 'rtl' :

                // For normal browsers.
                getComputedStyle( picker.$root[0] ).direction == 'rtl'
        }

    calendar.settings = settings
    calendar.$node = picker.$node

    // The queue of methods that will be used to build item objects.
    calendar.queue = {
        min: 'measure create',
        max: 'measure create',
        now: 'now create',
        select: 'parse create validate',
        highlight: 'parse navigate create validate',
        view: 'parse create validate viewset',
        disable: 'deactivate',
        enable: 'activate'
    }

    // The component's item object.
    calendar.item = {}

    calendar.item.clear = null
    calendar.item.disable = ( settings.disable || [] ).slice( 0 )
    calendar.item.enable = -(function( collectionDisabled ) {
        return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1
    })( calendar.item.disable )

    calendar.
        set( 'min', settings.min ).
        set( 'max', settings.max ).
        set( 'now' )

    // When there’s a value, set the `select`, which in turn
    // also sets the `highlight` and `view`.
    if ( valueString ) {
        calendar.set( 'select', valueString, {
            format: formatString,
            defaultValue: true
        })
    }

    // If there’s no value, default to highlighting “today”.
    else {
        calendar.
            set( 'select', null ).
            set( 'highlight', calendar.item.now )
    }


    // The keycode to movement mapping.
    calendar.key = {
        40: 7, // Down
        38: -7, // Up
        39: function() { return isRTL() ? -1 : 1 }, // Right
        37: function() { return isRTL() ? 1 : -1 }, // Left
        go: function( timeChange ) {
            var highlightedObject = calendar.item.highlight,
                targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange )
            calendar.set(
                'highlight',
                targetDate,
                { interval: timeChange }
            )
            this.render()
        }
    }


    // Bind some picker events.
    picker.
        on( 'render', function() {
            picker.$root.find( '.' + settings.klass.selectMonth ).on( 'change', function() {
                var value = this.value
                if ( value ) {
                    picker.set( 'highlight', [ picker.get( 'view' ).year, value, picker.get( 'highlight' ).date ] )
                    picker.$root.find( '.' + settings.klass.selectMonth ).trigger( 'focus' )
                }
            })
            picker.$root.find( '.' + settings.klass.selectYear ).on( 'change', function() {
                var value = this.value
                if ( value ) {
                    picker.set( 'highlight', [ value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] )
                    picker.$root.find( '.' + settings.klass.selectYear ).trigger( 'focus' )
                }
            })
        }, 1 ).
        on( 'open', function() {
            var includeToday = ''
            if ( calendar.disabled( calendar.get('now') ) ) {
                includeToday = ':not(.' + settings.klass.buttonToday + ')'
            }
            picker.$root.find( 'button' + includeToday + ', select' ).attr( 'disabled', false )
        }, 1 ).
        on( 'close', function() {
            picker.$root.find( 'button, select' ).attr( 'disabled', true )
        }, 1 )

} //DatePicker


/**
 * Set a datepicker item object.
 */
DatePicker.prototype.set = function( type, value, options ) {

    var calendar = this,
        calendarItem = calendar.item

    // If the value is `null` just set it immediately.
    if ( value === null ) {
        if ( type == 'clear' ) type = 'select'
        calendarItem[ type ] = value
        return calendar
    }

    // Otherwise go through the queue of methods, and invoke the functions.
    // Update this as the time unit, and set the final value as this item.
    // * In the case of `enable`, keep the queue but set `disable` instead.
    //   And in the case of `flip`, keep the queue but set `enable` instead.
    calendarItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) {
        value = calendar[ method ]( type, value, options )
        return value
    }).pop()

    // Check if we need to cascade through more updates.
    if ( type == 'select' ) {
        calendar.set( 'highlight', calendarItem.select, options )
    }
    else if ( type == 'highlight' ) {
        calendar.set( 'view', calendarItem.highlight, options )
    }
    else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) {
        if ( calendarItem.select && calendar.disabled( calendarItem.select ) ) {
            calendar.set( 'select', calendarItem.select, options )
        }
        if ( calendarItem.highlight && calendar.disabled( calendarItem.highlight ) ) {
            calendar.set( 'highlight', calendarItem.highlight, options )
        }
    }

    return calendar
} //DatePicker.prototype.set


/**
 * Get a datepicker item object.
 */
DatePicker.prototype.get = function( type ) {
    return this.item[ type ]
} //DatePicker.prototype.get


/**
 * Create a picker date object.
 */
DatePicker.prototype.create = function( type, value, options ) {

    var isInfiniteValue,
        calendar = this

    // If there’s no value, use the type as the value.
    value = value === undefined ? type : value


    // If it’s infinity, update the value.
    if ( value == -Infinity || value == Infinity ) {
        isInfiniteValue = value
    }

    // If it’s an object, use the native date object.
    else if ( $.isPlainObject( value ) && _.isInteger( value.pick ) ) {
        value = value.obj
    }

    // If it’s an array, convert it into a date and make sure
    // that it’s a valid date – otherwise default to today.
    else if ( $.isArray( value ) ) {
        value = new Date( value[ 0 ], value[ 1 ], value[ 2 ] )
        value = _.isDate( value ) ? value : calendar.create().obj
    }

    // If it’s a number or date object, make a normalized date.
    else if ( _.isInteger( value ) || _.isDate( value ) ) {
        value = calendar.normalize( new Date( value ), options )
    }

    // If it’s a literal true or any other case, set it to now.
    else /*if ( value === true )*/ {
        value = calendar.now( type, value, options )
    }

    // Return the compiled object.
    return {
        year: isInfiniteValue || value.getFullYear(),
        month: isInfiniteValue || value.getMonth(),
        date: isInfiniteValue || value.getDate(),
        day: isInfiniteValue || value.getDay(),
        obj: isInfiniteValue || value,
        pick: isInfiniteValue || value.getTime()
    }
} //DatePicker.prototype.create


/**
 * Create a range limit object using an array, date object,
 * literal “true”, or integer relative to another time.
 */
DatePicker.prototype.createRange = function( from, to ) {

    var calendar = this,
        createDate = function( date ) {
            if ( date === true || $.isArray( date ) || _.isDate( date ) ) {
                return calendar.create( date )
            }
            return date
        }

    // Create objects if possible.
    if ( !_.isInteger( from ) ) {
        from = createDate( from )
    }
    if ( !_.isInteger( to ) ) {
        to = createDate( to )
    }

    // Create relative dates.
    if ( _.isInteger( from ) && $.isPlainObject( to ) ) {
        from = [ to.year, to.month, to.date + from ];
    }
    else if ( _.isInteger( to ) && $.isPlainObject( from ) ) {
        to = [ from.year, from.month, from.date + to ];
    }

    return {
        from: createDate( from ),
        to: createDate( to )
    }
} //DatePicker.prototype.createRange


/**
 * Check if a date unit falls within a date range object.
 */
DatePicker.prototype.withinRange = function( range, dateUnit ) {
    range = this.createRange(range.from, range.to)
    return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick
}


/**
 * Check if two date range objects overlap.
 */
DatePicker.prototype.overlapRanges = function( one, two ) {

    var calendar = this

    // Convert the ranges into comparable dates.
    one = calendar.createRange( one.from, one.to )
    two = calendar.createRange( two.from, two.to )

    return calendar.withinRange( one, two.from ) || calendar.withinRange( one, two.to ) ||
        calendar.withinRange( two, one.from ) || calendar.withinRange( two, one.to )
}


/**
 * Get the date today.
 */
DatePicker.prototype.now = function( type, value, options ) {
    value = new Date()
    if ( options && options.rel ) {
        value.setDate( value.getDate() + options.rel )
    }
    return this.normalize( value, options )
}


/**
 * Navigate to next/prev month.
 */
DatePicker.prototype.navigate = function( type, value, options ) {

    var targetDateObject,
        targetYear,
        targetMonth,
        targetDate,
        isTargetArray = $.isArray( value ),
        isTargetObject = $.isPlainObject( value ),
        viewsetObject = this.item.view/*,
        safety = 100*/


    if ( isTargetArray || isTargetObject ) {

        if ( isTargetObject ) {
            targetYear = value.year
            targetMonth = value.month
            targetDate = value.date
        }
        else {
            targetYear = +value[0]
            targetMonth = +value[1]
            targetDate = +value[2]
        }

        // If we’re navigating months but the view is in a different
        // month, navigate to the view’s year and month.
        if ( options && options.nav && viewsetObject && viewsetObject.month !== targetMonth ) {
            targetYear = viewsetObject.year
            targetMonth = viewsetObject.month
        }

        // Figure out the expected target year and month.
        targetDateObject = new Date( targetYear, targetMonth + ( options && options.nav ? options.nav : 0 ), 1 )
        targetYear = targetDateObject.getFullYear()
        targetMonth = targetDateObject.getMonth()

        // If the month we’re going to doesn’t have enough days,
        // keep decreasing the date until we reach the month’s last date.
        while ( /*safety &&*/ new Date( targetYear, targetMonth, targetDate ).getMonth() !== targetMonth ) {
            targetDate -= 1
            /*safety -= 1
            if ( !safety ) {
                throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.'
            }*/
        }

        value = [ targetYear, targetMonth, targetDate ]
    }

    return value
} //DatePicker.prototype.navigate


/**
 * Normalize a date by setting the hours to midnight.
 */
DatePicker.prototype.normalize = function( value/*, options*/ ) {
    value.setHours( 0, 0, 0, 0 )
    return value
}


/**
 * Measure the range of dates.
 */
DatePicker.prototype.measure = function( type, value/*, options*/ ) {

    var calendar = this

    // If it’s anything false-y, remove the limits.
    if ( !value ) {
        value = type == 'min' ? -Infinity : Infinity
    }

    // If it’s a string, parse it.
    else if ( typeof value == 'string' ) {
        value = calendar.parse( type, value )
    }

    // If it's an integer, get a date relative to today.
    else if ( _.isInteger( value ) ) {
        value = calendar.now( type, value, { rel: value } )
    }

    return value
} ///DatePicker.prototype.measure


/**
 * Create a viewset object based on navigation.
 */
DatePicker.prototype.viewset = function( type, dateObject/*, options*/ ) {
    return this.create([ dateObject.year, dateObject.month, 1 ])
}


/**
 * Validate a date as enabled and shift if needed.
 */
DatePicker.prototype.validate = function( type, dateObject, options ) {

    var calendar = this,

        // Keep a reference to the original date.
        originalDateObject = dateObject,

        // Make sure we have an interval.
        interval = options && options.interval ? options.interval : 1,

        // Check if the calendar enabled dates are inverted.
        isFlippedBase = calendar.item.enable === -1,

        // Check if we have any enabled dates after/before now.
        hasEnabledBeforeTarget, hasEnabledAfterTarget,

        // The min & max limits.
        minLimitObject = calendar.item.min,
        maxLimitObject = calendar.item.max,

        // Check if we’ve reached the limit during shifting.
        reachedMin, reachedMax,

        // Check if the calendar is inverted and at least one weekday is enabled.
        hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter( function( value ) {

            // If there’s a date, check where it is relative to the target.
            if ( $.isArray( value ) ) {
                var dateTime = calendar.create( value ).pick
                if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true
                else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true
            }

            // Return only integers for enabled weekdays.
            return _.isInteger( value )
        }).length/*,

        safety = 100*/



    // Cases to validate for:
    // [1] Not inverted and date disabled.
    // [2] Inverted and some dates enabled.
    // [3] Not inverted and out of range.
    //
    // Cases to **not** validate for:
    // • Navigating months.
    // • Not inverted and date enabled.
    // • Inverted and all dates disabled.
    // • ..and anything else.
    if ( !options || (!options.nav && !options.defaultValue) ) if (
        /* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) ||
        /* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) ||
        /* 3 */ ( !isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick) )
    ) {


        // When inverted, flip the direction if there aren’t any enabled weekdays
        // and there are no enabled dates in the direction of the interval.
        if ( isFlippedBase && !hasEnabledWeekdays && ( ( !hasEnabledAfterTarget && interval > 0 ) || ( !hasEnabledBeforeTarget && interval < 0 ) ) ) {
            interval *= -1
        }


        // Keep looping until we reach an enabled date.
        while ( /*safety &&*/ calendar.disabled( dateObject ) ) {

            /*safety -= 1
            if ( !safety ) {
                throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.'
            }*/


            // If we’ve looped into the next/prev month with a large interval, return to the original date and flatten the interval.
            if ( Math.abs( interval ) > 1 && ( dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month ) ) {
                dateObject = originalDateObject
                interval = interval > 0 ? 1 : -1
            }


            // If we’ve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit.
            if ( dateObject.pick <= minLimitObject.pick ) {
                reachedMin = true
                interval = 1
                dateObject = calendar.create([
                    minLimitObject.year,
                    minLimitObject.month,
                    minLimitObject.date + (dateObject.pick === minLimitObject.pick ? 0 : -1)
                ])
            }
            else if ( dateObject.pick >= maxLimitObject.pick ) {
                reachedMax = true
                interval = -1
                dateObject = calendar.create([
                    maxLimitObject.year,
                    maxLimitObject.month,
                    maxLimitObject.date + (dateObject.pick === maxLimitObject.pick ? 0 : 1)
                ])
            }


            // If we’ve reached both limits, just break out of the loop.
            if ( reachedMin && reachedMax ) {
                break
            }


            // Finally, create the shifted date using the interval and keep looping.
            dateObject = calendar.create([ dateObject.year, dateObject.month, dateObject.date + interval ])
        }

    } //endif


    // Return the date object settled on.
    return dateObject
} //DatePicker.prototype.validate


/**
 * Check if a date is disabled.
 */
DatePicker.prototype.disabled = function( dateToVerify ) {

    var
        calendar = this,

        // Filter through the disabled dates to check if this is one.
        isDisabledMatch = calendar.item.disable.filter( function( dateToDisable ) {

            // If the date is a number, match the weekday with 0index and `firstDay` check.
            if ( _.isInteger( dateToDisable ) ) {
                return dateToVerify.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7
            }

            // If it’s an array or a native JS date, create and match the exact date.
            if ( $.isArray( dateToDisable ) || _.isDate( dateToDisable ) ) {
                return dateToVerify.pick === calendar.create( dateToDisable ).pick
            }

            // If it’s an object, match a date within the “from” and “to” range.
            if ( $.isPlainObject( dateToDisable ) ) {
                return calendar.withinRange( dateToDisable, dateToVerify )
            }
        })

    // If this date matches a disabled date, confirm it’s not inverted.
    isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( dateToDisable ) {
        return $.isArray( dateToDisable ) && dateToDisable[3] == 'inverted' ||
            $.isPlainObject( dateToDisable ) && dateToDisable.inverted
    }).length

    // Check the calendar “enabled” flag and respectively flip the
    // disabled state. Then also check if it’s beyond the min/max limits.
    return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch ||
        dateToVerify.pick < calendar.item.min.pick ||
        dateToVerify.pick > calendar.item.max.pick

} //DatePicker.prototype.disabled


/**
 * Parse a string into a usable type.
 */
DatePicker.prototype.parse = function( type, value, options ) {

    var calendar = this,
        parsingObject = {}

    // If it’s already parsed, we’re good.
    if ( !value || typeof value != 'string' ) {
        return value
    }

    // We need a `.format` to parse the value with.
    if ( !( options && options.format ) ) {
        options = options || {}
        options.format = calendar.settings.format
    }

    // Convert the format into an array and then map through it.
    calendar.formats.toArray( options.format ).map( function( label ) {

        var
            // Grab the formatting label.
            formattingLabel = calendar.formats[ label ],

            // The format length is from the formatting label function or the
            // label length without the escaping exclamation (!) mark.
            formatLength = formattingLabel ? _.trigger( formattingLabel, calendar, [ value, parsingObject ] ) : label.replace( /^!/, '' ).length

        // If there's a format label, split the value up to the format length.
        // Then add it to the parsing object with appropriate label.
        if ( formattingLabel ) {
            parsingObject[ label ] = value.substr( 0, formatLength )
        }

        // Update the value as the substring from format length to end.
        value = value.substr( formatLength )
    })

    // Compensate for month 0index.
    return [
        parsingObject.yyyy || parsingObject.yy,
        +( parsingObject.mm || parsingObject.m ) - 1,
        parsingObject.dd || parsingObject.d
    ]
} //DatePicker.prototype.parse


/**
 * Various formats to display the object in.
 */
DatePicker.prototype.formats = (function() {

    // Return the length of the first word in a collection.
    function getWordLengthFromCollection( string, collection, dateObject ) {

        // Grab the first word from the string.
        // Regex pattern from http://stackoverflow.com/q/150033
        var word = string.match( /[^\x00-\x7F]+|\w+/ )[ 0 ]

        // If there's no month index, add it to the date object
        if ( !dateObject.mm && !dateObject.m ) {
            dateObject.m = collection.indexOf( word ) + 1
        }

        // Return the length of the word.
        return word.length
    }

    // Get the length of the first word in a string.
    function getFirstWordLength( string ) {
        return string.match( /\w+/ )[ 0 ].length
    }

    return {

        d: function( string, dateObject ) {

            // If there's string, then get the digits length.
            // Otherwise return the selected date.
            return string ? _.digits( string ) : dateObject.date
        },
        dd: function( string, dateObject ) {

            // If there's a string, then the length is always 2.
            // Otherwise return the selected date with a leading zero.
            return string ? 2 : _.lead( dateObject.date )
        },
        ddd: function( string, dateObject ) {

            // If there's a string, then get the length of the first word.
            // Otherwise return the short selected weekday.
            return string ? getFirstWordLength( string ) : this.settings.weekdaysShort[ dateObject.day ]
        },
        dddd: function( string, dateObject ) {

            // If there's a string, then get the length of the first word.
            // Otherwise return the full selected weekday.
            return string ? getFirstWordLength( string ) : this.settings.weekdaysFull[ dateObject.day ]
        },
        m: function( string, dateObject ) {

            // If there's a string, then get the length of the digits
            // Otherwise return the selected month with 0index compensation.
            return string ? _.digits( string ) : dateObject.month + 1
        },
        mm: function( string, dateObject ) {

            // If there's a string, then the length is always 2.
            // Otherwise return the selected month with 0index and leading zero.
            return string ? 2 : _.lead( dateObject.month + 1 )
        },
        mmm: function( string, dateObject ) {

            var collection = this.settings.monthsShort

            // If there's a string, get length of the relevant month from the short
            // months collection. Otherwise return the selected month from that collection.
            return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ]
        },
        mmmm: function( string, dateObject ) {

            var collection = this.settings.monthsFull

            // If there's a string, get length of the relevant month from the full
            // months collection. Otherwise return the selected month from that collection.
            return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ]
        },
        yy: function( string, dateObject ) {

            // If there's a string, then the length is always 2.
            // Otherwise return the selected year by slicing out the first 2 digits.
            return string ? 2 : ( '' + dateObject.year ).slice( 2 )
        },
        yyyy: function( string, dateObject ) {

            // If there's a string, then the length is always 4.
            // Otherwise return the selected year.
            return string ? 4 : dateObject.year
        },

        // Create an array by splitting the formatting string passed.
        toArray: function( formatString ) { return formatString.split( /(d{1,4}|m{1,4}|y{4}|yy|!.)/g ) },

        // Format an object into a string using the formatting options.
        toString: function ( formatString, itemObject ) {
            var calendar = this
            return calendar.formats.toArray( formatString ).map( function( label ) {
                return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' )
            }).join( '' )
        }
    }
})() //DatePicker.prototype.formats




/**
 * Check if two date units are the exact.
 */
DatePicker.prototype.isDateExact = function( one, two ) {

    var calendar = this

    // When we’re working with weekdays, do a direct comparison.
    if (
        ( _.isInteger( one ) && _.isInteger( two ) ) ||
        ( typeof one == 'boolean' && typeof two == 'boolean' )
     ) {
        return one === two
    }

    // When we’re working with date representations, compare the “pick” value.
    if (
        ( _.isDate( one ) || $.isArray( one ) ) &&
        ( _.isDate( two ) || $.isArray( two ) )
    ) {
        return calendar.create( one ).pick === calendar.create( two ).pick
    }

    // When we’re working with range objects, compare the “from” and “to”.
    if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
        return calendar.isDateExact( one.from, two.from ) && calendar.isDateExact( one.to, two.to )
    }

    return false
}


/**
 * Check if two date units overlap.
 */
DatePicker.prototype.isDateOverlap = function( one, two ) {

    var calendar = this,
        firstDay = calendar.settings.firstDay ? 1 : 0

    // When we’re working with a weekday index, compare the days.
    if ( _.isInteger( one ) && ( _.isDate( two ) || $.isArray( two ) ) ) {
        one = one % 7 + firstDay
        return one === calendar.create( two ).day + 1
    }
    if ( _.isInteger( two ) && ( _.isDate( one ) || $.isArray( one ) ) ) {
        two = two % 7 + firstDay
        return two === calendar.create( one ).day + 1
    }

    // When we’re working with range objects, check if the ranges overlap.
    if ( $.isPlainObject( one ) && $.isPlainObject( two ) ) {
        return calendar.overlapRanges( one, two )
    }

    return false
}


/**
 * Flip the “enabled” state.
 */
DatePicker.prototype.flipEnable = function(val) {
    var itemObject = this.item
    itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1)
}


/**
 * Mark a collection of dates as “disabled”.
 */
DatePicker.prototype.deactivate = function( type, datesToDisable ) {

    var calendar = this,
        disabledItems = calendar.item.disable.slice(0)


    // If we’re flipping, that’s all we need to do.
    if ( datesToDisable == 'flip' ) {
        calendar.flipEnable()
    }

    else if ( datesToDisable === false ) {
        calendar.flipEnable(1)
        disabledItems = []
    }

    else if ( datesToDisable === true ) {
        calendar.flipEnable(-1)
        disabledItems = []
    }

    // Otherwise go through the dates to disable.
    else {

        datesToDisable.map(function( unitToDisable ) {

            var matchFound

            // When we have disabled items, check for matches.
            // If something is matched, immediately break out.
            for ( var index = 0; index < disabledItems.length; index += 1 ) {
                if ( calendar.isDateExact( unitToDisable, disabledItems[index] ) ) {
                    matchFound = true
                    break
                }
            }

            // If nothing was found, add the validated unit to the collection.
            if ( !matchFound ) {
                if (
                    _.isInteger( unitToDisable ) ||
                    _.isDate( unitToDisable ) ||
                    $.isArray( unitToDisable ) ||
                    ( $.isPlainObject( unitToDisable ) && unitToDisable.from && unitToDisable.to )
                ) {
                    disabledItems.push( unitToDisable )
                }
            }
        })
    }

    // Return the updated collection.
    return disabledItems
} //DatePicker.prototype.deactivate


/**
 * Mark a collection of dates as “enabled”.
 */
DatePicker.prototype.activate = function( type, datesToEnable ) {

    var calendar = this,
        disabledItems = calendar.item.disable,
        disabledItemsCount = disabledItems.length

    // If we’re flipping, that’s all we need to do.
    if ( datesToEnable == 'flip' ) {
        calendar.flipEnable()
    }

    else if ( datesToEnable === true ) {
        calendar.flipEnable(1)
        disabledItems = []
    }

    else if ( datesToEnable === false ) {
        calendar.flipEnable(-1)
        disabledItems = []
    }

    // Otherwise go through the disabled dates.
    else {

        datesToEnable.map(function( unitToEnable ) {

            var matchFound,
                disabledUnit,
                index,
                isExactRange

            // Go through the disabled items and try to find a match.
            for ( index = 0; index < disabledItemsCount; index += 1 ) {

                disabledUnit = disabledItems[index]

                // When an exact match is found, remove it from the collection.
                if ( calendar.isDateExact( disabledUnit, unitToEnable ) ) {
                    matchFound = disabledItems[index] = null
                    isExactRange = true
                    break
                }

                // When an overlapped match is found, add the “inverted” state to it.
                else if ( calendar.isDateOverlap( disabledUnit, unitToEnable ) ) {
                    if ( $.isPlainObject( unitToEnable ) ) {
                        unitToEnable.inverted = true
                        matchFound = unitToEnable
                    }
                    else if ( $.isArray( unitToEnable ) ) {
                        matchFound = unitToEnable
                        if ( !matchFound[3] ) matchFound.push( 'inverted' )
                    }
                    else if ( _.isDate( unitToEnable ) ) {
                        matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ]
                    }
                    break
                }
            }

            // If a match was found, remove a previous duplicate entry.
            if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
                if ( calendar.isDateExact( disabledItems[index], unitToEnable ) ) {
                    disabledItems[index] = null
                    break
                }
            }

            // In the event that we’re dealing with an exact range of dates,
            // make sure there are no “inverted” dates because of it.
            if ( isExactRange ) for ( index = 0; index < disabledItemsCount; index += 1 ) {
                if ( calendar.isDateOverlap( disabledItems[index], unitToEnable ) ) {
                    disabledItems[index] = null
                    break
                }
            }

            // If something is still matched, add it into the collection.
            if ( matchFound ) {
                disabledItems.push( matchFound )
            }
        })
    }

    // Return the updated collection.
    return disabledItems.filter(function( val ) { return val != null })
} //DatePicker.prototype.activate


/**
 * Create a string for the nodes in the picker.
 */
DatePicker.prototype.nodes = function( isOpen ) {

    var
        calendar = this,
        settings = calendar.settings,
        calendarItem = calendar.item,
        nowObject = calendarItem.now,
        selectedObject = calendarItem.select,
        highlightedObject = calendarItem.highlight,
        viewsetObject = calendarItem.view,
        disabledCollection = calendarItem.disable,
        minLimitObject = calendarItem.min,
        maxLimitObject = calendarItem.max,


        // Create the calendar table head using a copy of weekday labels collection.
        // * We do a copy so we don't mutate the original array.
        tableHead = (function( collection, fullCollection ) {

            // If the first day should be Monday, move Sunday to the end.
            if ( settings.firstDay ) {
                collection.push( collection.shift() )
                fullCollection.push( fullCollection.shift() )
            }

            // Create and return the table head group.
            return _.node(
                'thead',
                _.node(
                    'tr',
                    _.group({
                        min: 0,
                        max: DAYS_IN_WEEK - 1,
                        i: 1,
                        node: 'th',
                        item: function( counter ) {
                            return [
                                collection[ counter ],
                                settings.klass.weekdays,
                                'scope=col title="' + fullCollection[ counter ] + '"'
                            ]
                        }
                    })
                )
            ) //endreturn
        })( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort ).slice( 0 ), settings.weekdaysFull.slice( 0 ) ), //tableHead


        // Create the nav for next/prev month.
        createMonthNav = function( next ) {

            // Otherwise, return the created month tag.
            return _.node(
                'div',
                ' ',
                settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + (

                    // If the focused month is outside the range, disabled the button.
                    ( next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month ) ||
                    ( !next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month ) ?
                    ' ' + settings.klass.navDisabled : ''
                ),
                'data-nav=' + ( next || -1 ) + ' ' +
                _.ariaAttr({
                    role: 'button',
                    controls: calendar.$node[0].id + '_table'
                }) + ' ' +
                'title="' + (next ? settings.labelMonthNext : settings.labelMonthPrev ) + '"'
            ) //endreturn
        }, //createMonthNav


        // Create the month label.
        createMonthLabel = function() {

            var monthsCollection = settings.showMonthsShort ? settings.monthsShort : settings.monthsFull

            // If there are months to select, add a dropdown menu.
            if ( settings.selectMonths ) {

                return _.node( 'select',
                    _.group({
                        min: 0,
                        max: 11,
                        i: 1,
                        node: 'option',
                        item: function( loopedMonth ) {

                            return [

                                // The looped month and no classes.
                                monthsCollection[ loopedMonth ], 0,

                                // Set the value and selected index.
                                'value=' + loopedMonth +
                                ( viewsetObject.month == loopedMonth ? ' selected' : '' ) +
                                (
                                    (
                                        ( viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month ) ||
                                        ( viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month )
                                    ) ?
                                    ' disabled' : ''
                                )
                            ]
                        }
                    }),
                    settings.klass.selectMonth,
                    ( isOpen ? '' : 'disabled' ) + ' ' +
                    _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' +
                    'title="' + settings.labelMonthSelect + '"'
                )
            }

            // If there's a need for a month selector
            return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month )
        }, //createMonthLabel


        // Create the year label.
        createYearLabel = function() {

            var focusedYear = viewsetObject.year,

            // If years selector is set to a literal "true", set it to 5. Otherwise
            // divide in half to get half before and half after focused year.
            numberYears = settings.selectYears === true ? 5 : ~~( settings.selectYears / 2 )

            // If there are years to select, add a dropdown menu.
            if ( numberYears ) {

                var
                    minYear = minLimitObject.year,
                    maxYear = maxLimitObject.year,
                    lowestYear = focusedYear - numberYears,
                    highestYear = focusedYear + numberYears

                // If the min year is greater than the lowest year, increase the highest year
                // by the difference and set the lowest year to the min year.
                if ( minYear > lowestYear ) {
                    highestYear += minYear - lowestYear
                    lowestYear = minYear
                }

                // If the max year is less than the highest year, decrease the lowest year
                // by the lower of the two: available and needed years. Then set the
                // highest year to the max year.
                if ( maxYear < highestYear ) {

                    var availableYears = lowestYear - minYear,
                        neededYears = highestYear - maxYear

                    lowestYear -= availableYears > neededYears ? neededYears : availableYears
                    highestYear = maxYear
                }

                return _.node( 'select',
                    _.group({
                        min: lowestYear,
                        max: highestYear,
                        i: 1,
                        node: 'option',
                        item: function( loopedYear ) {
                            return [

                                // The looped year and no classes.
                                loopedYear, 0,

                                // Set the value and selected index.
                                'value=' + loopedYear + ( focusedYear == loopedYear ? ' selected' : '' )
                            ]
                        }
                    }),
                    settings.klass.selectYear,
                    ( isOpen ? '' : 'disabled' ) + ' ' + _.ariaAttr({ controls: calendar.$node[0].id + '_table' }) + ' ' +
                    'title="' + settings.labelYearSelect + '"'
                )
            }

            // Otherwise just return the year focused
            return _.node( 'div', focusedYear, settings.klass.year )
        } //createYearLabel


    // Create and return the entire calendar.
    return _.node(
        'div',
        ( settings.selectYears ? createYearLabel() + createMonthLabel() : createMonthLabel() + createYearLabel() ) +
        createMonthNav() + createMonthNav( 1 ),
        settings.klass.header
    ) + _.node(
        'table',
        tableHead +
        _.node(
            'tbody',
            _.group({
                min: 0,
                max: WEEKS_IN_CALENDAR - 1,
                i: 1,
                node: 'tr',
                item: function( rowCounter ) {

                    // If Monday is the first day and the month starts on Sunday, shift the date back a week.
                    var shiftDateBy = settings.firstDay && calendar.create([ viewsetObject.year, viewsetObject.month, 1 ]).day === 0 ? -7 : 0

                    return [
                        _.group({
                            min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index
                            max: function() {
                                return this.min + DAYS_IN_WEEK - 1
                            },
                            i: 1,
                            node: 'td',
                            item: function( targetDate ) {

                                // Convert the time date from a relative date to a target date.
                                targetDate = calendar.create([ viewsetObject.year, viewsetObject.month, targetDate + ( settings.firstDay ? 1 : 0 ) ])

                                var isSelected = selectedObject && selectedObject.pick == targetDate.pick,
                                    isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick,
                                    isDisabled = disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick,
                                    formattedDate = _.trigger( calendar.formats.toString, calendar, [ settings.format, targetDate ] )

                                return [
                                    _.node(
                                        'div',
                                        targetDate.date,
                                        (function( klasses ) {

                                            // Add the `infocus` or `outfocus` classes based on month in view.
                                            klasses.push( viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus )

                                            // Add the `today` class if needed.
                                            if ( nowObject.pick == targetDate.pick ) {
                                                klasses.push( settings.klass.now )
                                            }

                                            // Add the `selected` class if something's selected and the time matches.
                                            if ( isSelected ) {
                                                klasses.push( settings.klass.selected )
                                            }

                                            // Add the `highlighted` class if something's highlighted and the time matches.
                                            if ( isHighlighted ) {
                                                klasses.push( settings.klass.highlighted )
                                            }

                                            // Add the `disabled` class if something's disabled and the object matches.
                                            if ( isDisabled ) {
                                                klasses.push( settings.klass.disabled )
                                            }

                                            return klasses.join( ' ' )
                                        })([ settings.klass.day ]),
                                        'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({
                                            role: 'gridcell',
                                            label: formattedDate,
                                            selected: isSelected && calendar.$node.val() === formattedDate ? true : null,
                                            activedescendant: isHighlighted ? true : null,
                                            disabled: isDisabled ? true : null
                                        })
                                    ),
                                    '',
                                    _.ariaAttr({ role: 'presentation' })
                                ] //endreturn
                            }
                        })
                    ] //endreturn
                }
            })
        ),
        settings.klass.table,
        'id="' + calendar.$node[0].id + '_table' + '" ' + _.ariaAttr({
            role: 'grid',
            controls: calendar.$node[0].id,
            readonly: true
        })
    ) +

    // * For Firefox forms to submit, make sure to set the buttons’ `type` attributes as “button”.
    _.node(
        'div',
        _.node( 'button', settings.today, settings.klass.buttonToday,
            'type=button data-pick=' + nowObject.pick +
            ( isOpen && !calendar.disabled(nowObject) ? '' : ' disabled' ) + ' ' +
            _.ariaAttr({ controls: calendar.$node[0].id }) ) +
        _.node( 'button', settings.clear, settings.klass.buttonClear,
            'type=button data-clear=1' +
            ( isOpen ? '' : ' disabled' ) + ' ' +
            _.ariaAttr({ controls: calendar.$node[0].id }) ) +
        _.node('button', settings.close, settings.klass.buttonClose,
            'type=button data-close=true ' +
            ( isOpen ? '' : ' disabled' ) + ' ' +
            _.ariaAttr({ controls: calendar.$node[0].id }) ),
        settings.klass.footer
    ) //endreturn
} //DatePicker.prototype.nodes




/**
 * The date picker defaults.
 */
DatePicker.defaults = (function( prefix ) {

    return {

        // The title label to use for the month nav buttons
        labelMonthNext: 'Next month',
        labelMonthPrev: 'Previous month',

        // The title label to use for the dropdown selectors
        labelMonthSelect: 'Select a month',
        labelYearSelect: 'Select a year',

        // Months and weekdays
        monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
        monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
        weekdaysFull: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ],
        weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],

        // Today and clear
        today: 'Today',
        clear: 'Clear',
        close: 'Close',

        // Picker close behavior
        closeOnSelect: true,
        closeOnClear: true,

        // The format to show on the `input` element
        format: 'd mmmm, yyyy',

        // Classes
        klass: {

            table: prefix + 'table',

            header: prefix + 'header',

            navPrev: prefix + 'nav--prev',
            navNext: prefix + 'nav--next',
            navDisabled: prefix + 'nav--disabled',

            month: prefix + 'month',
            year: prefix + 'year',

            selectMonth: prefix + 'select--month',
            selectYear: prefix + 'select--year',

            weekdays: prefix + 'weekday',

            day: prefix + 'day',
            disabled: prefix + 'day--disabled',
            selected: prefix + 'day--selected',
            highlighted: prefix + 'day--highlighted',
            now: prefix + 'day--today',
            infocus: prefix + 'day--infocus',
            outfocus: prefix + 'day--outfocus',

            footer: prefix + 'footer',

            buttonClear: prefix + 'button--clear',
            buttonToday: prefix + 'button--today',
            buttonClose: prefix + 'button--close'
        }
    }
})( Picker.klasses().picker + '__' )





/**
 * Extend the picker to add the date picker.
 */
Picker.extend( 'pickadate', DatePicker )


}));



;
/*=============================================================================
	Author:			Eric M. Barnard - @ericmbarnard								
	License:		MIT (http://opensource.org/licenses/mit-license.php)		
																				
	Description:	Validation Library for KnockoutJS							
	Version:		2.0.3											
===============================================================================
*/

!function(a){"function"==typeof require&&"object"==typeof exports&&"object"==typeof module?a(require("knockout"),exports):"function"==typeof define&&define.amd?define(["knockout","exports"],a):a(ko,ko.validation={})}(function(a,b){function c(a){var b="max"===a;return function(c,d){if(f.utils.isEmptyVal(c))return!0;var e,g;void 0===d.typeAttr?(g="text",e=d):(g=d.typeAttr,e=d.value),isNaN(e)||e instanceof Date||(g="number");var h,i,j;switch(g.toLowerCase()){case"week":if(h=/^(\d{4})-W(\d{2})$/,i=c.match(h),null===i)throw new Error("Invalid value for "+a+" attribute for week input.  Should look like '2000-W33' http://www.w3.org/TR/html-markup/input.week.html#input.week.attrs.min");return j=e.match(h),j?b?i[1]<j[1]||i[1]===j[1]&&i[2]<=j[2]:i[1]>j[1]||i[1]===j[1]&&i[2]>=j[2]:!1;case"month":if(h=/^(\d{4})-(\d{2})$/,i=c.match(h),null===i)throw new Error("Invalid value for "+a+" attribute for month input.  Should look like '2000-03' http://www.w3.org/TR/html-markup/input.month.html#input.month.attrs.min");return j=e.match(h),j?b?i[1]<j[1]||i[1]===j[1]&&i[2]<=j[2]:i[1]>j[1]||i[1]===j[1]&&i[2]>=j[2]:!1;case"number":case"range":return b?!isNaN(c)&&parseFloat(c)<=parseFloat(e):!isNaN(c)&&parseFloat(c)>=parseFloat(e);default:return b?e>=c:c>=e}}}function d(a,b,c){return b.validator(a(),void 0===c.params?!0:h(c.params))?!0:(a.setError(f.formatMessage(c.message||b.message,h(c.params),a)),!1)}function e(a,b,c){a.isValidating(!0);var d=function(d){var e=!1,g="";return a.__valid__()?(d.message?(e=d.isValid,g=d.message):e=d,e||(a.error(f.formatMessage(g||c.message||b.message,h(c.params),a)),a.__valid__(e)),void a.isValidating(!1)):void a.isValidating(!1)};f.utils.async(function(){b.validator(a(),void 0===c.params?!0:h(c.params),d)})}if("undefined"==typeof a)throw new Error("Knockout is required, please ensure it is loaded before loading this validation plug-in");a.validation=b;var f=a.validation,g=a.utils,h=g.unwrapObservable,i=g.arrayForEach,j=g.extend,k={registerExtenders:!0,messagesOnModified:!0,errorsAsTitle:!0,errorsAsTitleOnModified:!1,messageTemplate:null,insertMessages:!0,parseInputAttributes:!1,writeInputAttributes:!1,decorateInputElement:!1,decorateElementOnModified:!0,errorClass:null,errorElementClass:"validationElement",errorMessageClass:"validationMessage",allowHtmlMessages:!1,grouping:{deep:!1,observable:!0,live:!1},validate:{}},l=j({},k);l.html5Attributes=["required","pattern","min","max","step"],l.html5InputTypes=["email","number","date"],l.reset=function(){j(l,k)},f.configuration=l,f.utils=function(){var a=(new Date).getTime(),b={},c="__ko_validation__";return{isArray:function(a){return a.isArray||"[object Array]"===Object.prototype.toString.call(a)},isObject:function(a){return null!==a&&"object"==typeof a},isNumber:function(a){return!isNaN(a)},isObservableArray:function(a){return!!a&&"function"==typeof a.remove&&"function"==typeof a.removeAll&&"function"==typeof a.destroy&&"function"==typeof a.destroyAll&&"function"==typeof a.indexOf&&"function"==typeof a.replace},values:function(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(a[c]);return b},getValue:function(a){return"function"==typeof a?a():a},hasAttribute:function(a,b){return null!==a.getAttribute(b)},getAttribute:function(a,b){return a.getAttribute(b)},setAttribute:function(a,b,c){return a.setAttribute(b,c)},isValidatable:function(a){return!!(a&&a.rules&&a.isValid&&a.isModified)},insertAfter:function(a,b){a.parentNode.insertBefore(b,a.nextSibling)},newId:function(){return a+=1},getConfigOptions:function(a){var b=f.utils.contextFor(a);return b||f.configuration},setDomData:function(a,d){var e=a[c];e||(a[c]=e=f.utils.newId()),b[e]=d},getDomData:function(a){var d=a[c];return d?b[d]:void 0},contextFor:function(a){switch(a.nodeType){case 1:case 8:var b=f.utils.getDomData(a);if(b)return b;if(a.parentNode)return f.utils.contextFor(a.parentNode)}return void 0},isEmptyVal:function(a){return void 0===a?!0:null===a?!0:""===a?!0:void 0},getOriginalElementTitle:function(a){var b=f.utils.getAttribute(a,"data-orig-title"),c=a.title,d=f.utils.hasAttribute(a,"data-orig-title");return d?b:c},async:function(a){window.setImmediate?window.setImmediate(a):window.setTimeout(a,0)},forEach:function(a,b){if(f.utils.isArray(a))return i(a,b);for(var c in a)a.hasOwnProperty(c)&&b(a[c],c)}}}();var m=function(){function b(a){i(a.subscriptions,function(a){a.dispose()}),a.subscriptions=[]}function c(a){a.options.deep&&(i(a.flagged,function(a){delete a.__kv_traversed}),a.flagged.length=0),a.options.live||b(a)}function d(a,d){d.validatables=[],b(d),e(a,d),c(d)}function e(b,c,d){var f=[],g=b.peek?b.peek():b;b.__kv_traversed!==!0&&(c.options.deep&&(b.__kv_traversed=!0,c.flagged.push(b)),d=void 0!==d?d:c.options.deep?1:-1,a.isObservable(b)&&(b.errors||n.isValidatable(b)||b.extend({validatable:!0}),c.validatables.push(b),c.options.live&&n.isObservableArray(b)&&c.subscriptions.push(b.subscribe(function(){c.graphMonitor.valueHasMutated()}))),g&&!g._destroy&&(n.isArray(g)?f=g:n.isObject(g)&&(f=n.values(g))),0!==d&&n.forEach(f,function(b){!b||b.nodeType||a.isComputed(b)&&!b.rules||e(b,c,d+1)}))}function k(a){var b=[];return i(a,function(a){n.isValidatable(a)&&!a.isValid()&&b.push(a.error.peek())}),b}var l=0,m=f.configuration,n=f.utils;return{init:function(a,b){l>0&&!b||(a=a||{},a.errorElementClass=a.errorElementClass||a.errorClass||m.errorElementClass,a.errorMessageClass=a.errorMessageClass||a.errorClass||m.errorMessageClass,j(m,a),m.registerExtenders&&f.registerExtenders(),l=1)},reset:f.configuration.reset,group:function(b,c){c=j(j({},m.grouping),c);var e={options:c,graphMonitor:a.observable(),flagged:[],subscriptions:[],validatables:[]},f=null;return f=c.observable?a.computed(function(){return e.graphMonitor(),d(b,e),k(e.validatables)}):function(){return d(b,e),k(e.validatables)},f.showAllMessages=function(a){void 0===a&&(a=!0),f.forEach(function(b){n.isValidatable(b)&&b.isModified(a)})},f.isAnyMessageShown=function(){var a;return a=!!f.find(function(a){return n.isValidatable(a)&&!a.isValid()&&a.isModified()})},f.filter=function(a){return a=a||function(){return!0},f(),g.arrayFilter(e.validatables,a)},f.find=function(a){return a=a||function(){return!0},f(),g.arrayFirst(e.validatables,a)},f.forEach=function(a){a=a||function(){},f(),i(e.validatables,a)},f.map=function(a){return a=a||function(a){return a},f(),g.arrayMap(e.validatables,a)},f._updateState=function(a){if(!n.isObject(a))throw new Error("An object is required.");return b=a,c.observable?void e.graphMonitor.valueHasMutated():(d(a,e),k(e.validatables))},f},formatMessage:function(a,b,c){if(n.isObject(b)&&b.typeAttr&&(b=b.value),"function"==typeof a)return a(b,c);var d=h(b);return null==d&&(d=[]),n.isArray(d)||(d=[d]),a.replace(/{(\d+)}/gi,function(a,b){return"undefined"!=typeof d[b]?d[b]:a})},addRule:function(a,b){a.extend({validatable:!0});var c=!!g.arrayFirst(a.rules(),function(a){return a.rule&&a.rule===b.rule});return c||a.rules.push(b),a},addAnonymousRule:function(a,b){void 0===b.message&&(b.message="Error"),b.onlyIf&&(b.condition=b.onlyIf),f.addRule(a,b)},addExtender:function(b){a.extenders[b]=function(a,c){return c&&(c.message||c.onlyIf)?f.addRule(a,{rule:b,message:c.message,params:n.isEmptyVal(c.params)?!0:c.params,condition:c.onlyIf}):f.addRule(a,{rule:b,params:c})}},registerExtenders:function(){if(m.registerExtenders)for(var b in f.rules)f.rules.hasOwnProperty(b)&&(a.extenders[b]||f.addExtender(b))},insertValidationMessage:function(a){var b=document.createElement("SPAN");return b.className=n.getConfigOptions(a).errorMessageClass,n.insertAfter(a,b),b},parseInputValidationAttributes:function(a,b){i(f.configuration.html5Attributes,function(c){if(n.hasAttribute(a,c)){var d=a.getAttribute(c)||!0;if("min"===c||"max"===c){var e=a.getAttribute("type");"undefined"!=typeof e&&e||(e="text"),d={typeAttr:e,value:d}}f.addRule(b(),{rule:c,params:d})}});var c=a.getAttribute("type");i(f.configuration.html5InputTypes,function(a){a===c&&f.addRule(b(),{rule:"date"===a?"dateISO":a,params:!0})})},writeInputValidationAttributes:function(b,c){var d=c();if(d&&d.rules){var e=d.rules();i(f.configuration.html5Attributes,function(c){var d=g.arrayFirst(e,function(a){return a.rule&&a.rule.toLowerCase()===c.toLowerCase()});d&&a.computed({read:function(){var e=a.unwrap(d.params);"pattern"===d.rule&&e instanceof RegExp&&(e=e.source),b.setAttribute(c,e)},disposeWhenNodeIsRemoved:b})}),e=null}},makeBindingHandlerValidatable:function(b){var c=a.bindingHandlers[b].init;a.bindingHandlers[b].init=function(b,d,e,f,g){return c(b,d,e,f,g),a.bindingHandlers.validationCore.init(b,d,e,f,g)}},setRules:function(b,c){var d=function(b,c){if(b&&c)for(var e in c)if(c.hasOwnProperty(e)){var g=c[e];if(b[e]){var i=b[e],j=h(i),k={},l={};for(var m in g)g.hasOwnProperty(m)&&(f.rules[m]?k[m]=g[m]:l[m]=g[m]);if(a.isObservable(i)&&i.extend(k),j&&n.isArray(j))for(var o=0;o<j.length;o++)d(j[o],l);else d(j,l)}}};d(b,c)}}}();j(a.validation,m),f.rules={},f.rules.required={validator:function(a,b){var c;return void 0===a||null===a?!b:(c=a,"string"==typeof a&&(c=String.prototype.trim?a.trim():a.replace(/^\s+|\s+$/g,"")),b?(c+"").length>0:!0)},message:"This field is required."},f.rules.min={validator:c("min"),message:"Please enter a value greater than or equal to {0}."},f.rules.max={validator:c("max"),message:"Please enter a value less than or equal to {0}."},f.rules.minLength={validator:function(a,b){if(f.utils.isEmptyVal(a))return!0;var c=f.utils.isNumber(a)?""+a:a;return c.length>=b},message:"Please enter at least {0} characters."},f.rules.maxLength={validator:function(a,b){if(f.utils.isEmptyVal(a))return!0;var c=f.utils.isNumber(a)?""+a:a;return c.length<=b},message:"Please enter no more than {0} characters."},f.rules.pattern={validator:function(a,b){return f.utils.isEmptyVal(a)||null!==a.toString().match(b)},message:"Please check this value."},f.rules.step={validator:function(a,b){if(f.utils.isEmptyVal(a)||"any"===b)return!0;var c=100*a%(100*b);return Math.abs(c)<1e-5||Math.abs(1-c)<1e-5},message:"The value must increment by {0}."},f.rules.email={validator:function(a,b){return b?f.utils.isEmptyVal(a)||b&&/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(a):!0},message:"Please enter a proper email address."},f.rules.date={validator:function(a,b){return b?f.utils.isEmptyVal(a)||b&&!/Invalid|NaN/.test(new Date(a)):!0},message:"Please enter a proper date."},f.rules.dateISO={validator:function(a,b){return b?f.utils.isEmptyVal(a)||b&&/^\d{4}[-/](?:0?[1-9]|1[012])[-/](?:0?[1-9]|[12][0-9]|3[01])$/.test(a):!0},message:"Please enter a proper date."},f.rules.number={validator:function(a,b){return b?f.utils.isEmptyVal(a)||b&&/^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a):!0},message:"Please enter a number."},f.rules.digit={validator:function(a,b){return b?f.utils.isEmptyVal(a)||b&&/^\d+$/.test(a):!0},message:"Please enter a digit."},f.rules.phoneUS={validator:function(a,b){return b?f.utils.isEmptyVal(a)?!0:"string"!=typeof a?!1:(a=a.replace(/\s+/g,""),b&&a.length>9&&a.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/)):!0},message:"Please specify a valid phone number."},f.rules.equal={validator:function(a,b){var c=b;return a===f.utils.getValue(c)},message:"Values must equal."},f.rules.notEqual={validator:function(a,b){var c=b;return a!==f.utils.getValue(c)},message:"Please choose another value."},f.rules.unique={validator:function(a,b){var c=f.utils.getValue(b.collection),d=f.utils.getValue(b.externalValue),e=0;return a&&c?(g.arrayFilter(c,function(c){a===(b.valueAccessor?b.valueAccessor(c):c)&&e++}),(d?1:2)>e):!0},message:"Please make sure the value is unique."},function(){f.registerExtenders()}(),a.bindingHandlers.validationCore=function(){return{init:function(b,c){var d=f.utils.getConfigOptions(b),e=c();if(d.parseInputAttributes&&f.utils.async(function(){f.parseInputValidationAttributes(b,c)}),d.insertMessages&&f.utils.isValidatable(e)){var g=f.insertValidationMessage(b);d.messageTemplate?a.renderTemplate(d.messageTemplate,{field:e},null,g,"replaceNode"):a.applyBindingsToNode(g,{validationMessage:e})}d.writeInputAttributes&&f.utils.isValidatable(e)&&f.writeInputValidationAttributes(b,c),d.decorateInputElement&&f.utils.isValidatable(e)&&a.applyBindingsToNode(b,{validationElement:e})}}}(),f.makeBindingHandlerValidatable("value"),f.makeBindingHandlerValidatable("checked"),a.bindingHandlers.textInput&&f.makeBindingHandlerValidatable("textInput"),f.makeBindingHandlerValidatable("selectedOptions"),a.bindingHandlers.validationMessage={update:function(b,c){var d=c(),e=f.utils.getConfigOptions(b),i=(h(d),!1),j=!1;if(null===d||"undefined"==typeof d)throw new Error("Cannot bind validationMessage to undefined value. data-bind expression: "+b.getAttribute("data-bind"));i=d.isModified&&d.isModified(),j=d.isValid&&d.isValid();var k=null;(!e.messagesOnModified||i)&&(k=j?null:d.error);var l=!e.messagesOnModified||i?!j:!1,m="none"!==b.style.display;e.allowHtmlMessages?g.setHtml(b,k):a.bindingHandlers.text.update(b,function(){return k}),m&&!l?b.style.display="none":!m&&l&&(b.style.display="")}},a.bindingHandlers.validationElement={update:function(b,c,d){var e=c(),g=f.utils.getConfigOptions(b),i=(h(e),!1),j=!1;if(null===e||"undefined"==typeof e)throw new Error("Cannot bind validationElement to undefined value. data-bind expression: "+b.getAttribute("data-bind"));i=e.isModified&&e.isModified(),j=e.isValid&&e.isValid();var k=function(){var a={},b=!g.decorateElementOnModified||i?!j:!1;return a[g.errorElementClass]=b,a};a.bindingHandlers.css.update(b,k,d),g.errorsAsTitle&&a.bindingHandlers.attr.update(b,function(){var a=!g.errorsAsTitleOnModified||i,c=f.utils.getOriginalElementTitle(b);return a&&!j?{title:e.error,"data-orig-title":c}:!a||j?{title:c,"data-orig-title":null}:void 0})}},a.bindingHandlers.validationOptions=function(){return{init:function(a,b){var c=h(b());if(c){var d=j({},f.configuration);j(d,c),f.utils.setDomData(a,d)}}}}(),a.extenders.validation=function(a,b){return i(f.utils.isArray(b)?b:[b],function(b){f.addAnonymousRule(a,b)}),a},a.extenders.validatable=function(b,c){if(f.utils.isObject(c)||(c={enable:c}),"enable"in c||(c.enable=!0),c.enable&&!f.utils.isValidatable(b)){var d=f.configuration.validate||{},e={throttleEvaluation:c.throttle||d.throttle};b.error=a.observable(null),b.rules=a.observableArray(),b.isValidating=a.observable(!1),b.__valid__=a.observable(!0),b.isModified=a.observable(!1),b.isValid=a.computed(b.__valid__),b.setError=function(a){var c=b.error.peek(),d=b.__valid__.peek();b.error(a),b.__valid__(!1),c===a||d||b.isValid.notifySubscribers()},b.clearError=function(){return b.error(null),b.__valid__(!0),b};var g=b.subscribe(function(){b.isModified(!0)}),h=a.computed(j({read:function(){b(),b.rules();return f.validateObservable(b),!0}},e));j(h,e),b._disposeValidation=function(){b.isValid.dispose(),b.rules.removeAll(),g.dispose(),h.dispose(),delete b.rules,delete b.error,delete b.isValid,delete b.isValidating,delete b.__valid__,delete b.isModified,delete b.setError,delete b.clearError,delete b._disposeValidation}}else c.enable===!1&&b._disposeValidation&&b._disposeValidation();return b},f.validateObservable=function(a){for(var b,c,g=0,h=a.rules(),i=h.length;i>g;g++)if(c=h[g],!c.condition||c.condition())if(b=c.rule?f.rules[c.rule]:c,b.async||c.async)e(a,b,c);else if(!d(a,b,c))return!1;return a.clearError(),!0};var n,o={};f.defineLocale=function(a,b){return a&&b?(o[a.toLowerCase()]=b,b):null},f.locale=function(a){if(a){if(a=a.toLowerCase(),!o.hasOwnProperty(a))throw new Error("Localization "+a+" has not been loaded.");f.localize(o[a]),n=a}return n},f.localize=function(a){var b=f.rules;for(var c in a)b.hasOwnProperty(c)&&(b[c].message=a[c])},function(){var a={},b=f.rules;for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c].message);f.defineLocale("en-us",a)}(),n="en-us",a.applyBindingsWithValidation=function(b,c,d){var e,g=document.body;c&&c.nodeType?(g=c,e=d):e=c,f.init(),e&&(e=j(j({},f.configuration),e),f.utils.setDomData(g,e)),a.applyBindings(b,g)};var p=a.applyBindings;a.applyBindings=function(a,b){f.init(),p(a,b)},a.validatedObservable=function(b,c){if(!c&&!f.utils.isObject(b))return a.observable(b).extend({validatable:!0});var d=a.observable(b);return d.errors=f.group(f.utils.isObject(b)?b:{},c),d.isValid=a.observable(0===d.errors().length),a.isObservable(d.errors)?d.errors.subscribe(function(a){d.isValid(0===a.length)}):a.computed(d.errors).subscribe(function(a){d.isValid(0===a.length)}),d.subscribe(function(a){f.utils.isObject(a)||(a={}),d.errors._updateState(a),d.isValid(0===d.errors().length)}),d}});
//# sourceMappingURL=knockout.validation.min.js.map;
/*
 * knockout-file-bindings
 * Copyright 2014 Muhammad Safraz Razik
 * All Rights Reserved.
 * Use, reproduction, distribution, and modification of this code is subject to the terms and
 * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
 *
 * Author: Muhammad Safraz Razik
 * Project: https://github.com/adrotec/knockout-file-bindings
 */
(function (factory) {
    // Module systems magic dance.

    if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
        // CommonJS or Node: hard-coded dependency on "knockout"
        factory(require("knockout"), require("jquery"));
    } else if (typeof define === "function" && define["amd"]) {
        // AMD anonymous module with hard-coded dependency on "knockout"
        define(["knockout", "jquery"], factory);
    } else {
        // <script> tag: use the global `ko` object, attaching a `mapping` property
        factory(ko, jQuery);
    }
}(function (ko, $) {

    var fileBindings = {
        customFileInputSystemOptions: {
            wrapperClass: 'custom-file-input-wrapper',
            fileNameClass: 'custom-file-input-file-name',
            buttonGroupClass: 'custom-file-input-button-group',
            buttonClass: 'custom-file-input-button',
            clearButtonClass: 'custom-file-input-clear-button',
            buttonTextClass: 'custom-file-input-button-text',
        },
        defaultOptions: {
            wrapperClass: 'input-group',
            fileNameClass: 'disabled form-control',
            noFileText: 'No file chosen',
            buttonGroupClass: 'input-group-btn',
            buttonClass: 'btn btn-primary',
            clearButtonClass: 'btn btn-default',
            buttonText: 'Choose File',
            changeButtonText: 'Change',
            clearButtonText: 'Clear',
            fileName: true,
            clearButton: true,
            onClear: function(fileData, options) {
                if (typeof fileData.clear === 'function') {
                    fileData.clear();
                }
            }
        },
    }

    var windowURL = window.URL || window.webkitURL;

    ko.bindingHandlers.fileInput = {
        init: function(element, valueAccessor) {
            element.onchange = function() {
                var fileData = ko.utils.unwrapObservable(valueAccessor()) || {};
                if (fileData.dataUrl) {
                    fileData.dataURL = fileData.dataUrl;
                }
                if (fileData.objectUrl) {
                    fileData.objectURL = fileData.objectUrl;
                }
                fileData.file = fileData.file || ko.observable();

                if (this.files) {
                    var file = this.files[0];
                    if (file) {
                        fileData.file(file);
                    }
                }
                if (!fileData.clear) {
                    fileData.clear = function() {
                        $.each(['file', 'objectURL', 'base64String', 'binaryString', 'text', 'dataURL', 'arrayBuffer'], function(i, property) {
                            if (fileData[property] && ko.isObservable(fileData[property])) {
                                if (property == 'objectURL') {
                                    windowURL.revokeObjectURL(fileData.objectURL());
                                }
                                fileData[property](null);
                            }
                        });
                        element.value = '';
                    }
                }
                if (ko.isObservable(valueAccessor())) {
                    valueAccessor()(fileData);
                }
            };
            element.onchange();
        },
        update: function(element, valueAccessor, allBindingsAccessor) {

            var fileData = ko.utils.unwrapObservable(valueAccessor());

            var file = ko.isObservable(fileData.file) && fileData.file();

            if (fileData.objectURL && ko.isObservable(fileData.objectURL)) {
                var newUrl = file && windowURL.createObjectURL(file);
                if (newUrl) {
                    var oldUrl = fileData.objectURL();
                    if (oldUrl) {
                        windowURL.revokeObjectURL(oldUrl);
                    }
                    fileData.objectURL(newUrl);
                }
            }


            if (fileData.base64String && ko.isObservable(fileData.base64String)) {
                if (fileData.dataURL && ko.isObservable(fileData.dataURL)) {
                    // will be handled
                }
                else {
                    fileData.dataURL = ko.observable(); // hack
                }
            }

            // var properties = ['binaryString', 'text', 'dataURL', 'arrayBuffer'], property;
            // for(var i = 0; i < properties.length; i++){
            //     property = properties[i];
            ['binaryString', 'text', 'dataURL', 'arrayBuffer'].forEach(function(property){
                var method = 'readAs' + (property.substr(0, 1).toUpperCase() + property.substr(1));
                if (property != 'dataURL' && !(fileData[property] && ko.isObservable(fileData[property]))) {
                    return true;
                }
                if (!file) {
                    return true;
                }
                var reader = new FileReader();
                reader.onload = function(e) {
                    if (fileData[property]) {
                        fileData[property](e.target.result);
                    }
                    if (method == 'readAsDataURL' && fileData.base64String && ko.isObservable(fileData.base64String)) {
                        var resultParts = e.target.result.split(",");
                        if (resultParts.length === 2) {
                            fileData.base64String(resultParts[1]);
                        }
                    }
                };

                reader[method](file);
            });
        }
    };

    ko.bindingHandlers.fileDrag = {
        update: function(element, valueAccessor, allBindingsAccessor) {
            var fileData = ko.utils.unwrapObservable(valueAccessor()) || {};

            if (!$(element).data("fileDragInjected")) {
                element.classList.add('filedrag');
                element.ondragover = element.ondragleave = element.ondrop = function(e) {
                    e.stopPropagation();
                    e.preventDefault();
                    if(e.type == 'dragover'){
                        element.classList.add('hover');
                    }
                    else {
                        element.classList.remove('hover');
                    }
                    if (e.type == 'drop' && e.dataTransfer) {
                        var files = e.dataTransfer.files;
                        var file = files[0];
                        if (file) {
                            fileData.file(file);
                            if (ko.isObservable(valueAccessor())) {
                                valueAccessor()(fileData);
                            }
                        }
                    }
                };

                $(element).data("fileDragInjected", true);
            }
        }
    };

    ko.bindingHandlers.customFileInput = {
        init: function(element, valueAccessor, allBindingsAccessor) {
            if (ko.utils.unwrapObservable(valueAccessor()) === false) {
                return;
            }
            //*
            var sysOpts = fileBindings.customFileInputSystemOptions;
            var defOpts = fileBindings.defaultOptions;

            var $element = $(element);
            var $wrapper = $('<span>').addClass(sysOpts.wrapperClass).addClass(defOpts.wrapperClass);
            var $buttonGroup = $('<span>').addClass(sysOpts.buttonGroupClass).addClass(defOpts.buttonGroupClass);
            $buttonGroup.append($('<span>').addClass(sysOpts.buttonClass));
            $element.wrap($wrapper).wrap($buttonGroup);
            var $buttonGroup = $element.parent('.' + sysOpts.buttonClass).parent();
            $buttonGroup.before($('<input>').attr('type', 'text').attr('disabled', 'disabled').addClass(sysOpts.fileNameClass));
            $element.before($('<span>').addClass(sysOpts.buttonTextClass));

        },
        update: function(element, valueAccessor, allBindingsAccessor) {
            var options = ko.utils.unwrapObservable(valueAccessor());
            if (options === false) {
                return;
            }
            options = options || {};
            if (options && typeof options !== 'object') {
                options = {};
            }

            var sysOpts = fileBindings.customFileInputSystemOptions;
            var defOpts = fileBindings.defaultOptions;

            options = $.extend(defOpts, options);

            var allBindings = allBindingsAccessor();
            if (!allBindings.fileInput) {
                return;
            }
            var fileData = ko.utils.unwrapObservable(allBindings.fileInput) || {};

            var file = ko.utils.unwrapObservable(fileData.file);

            var $button = $(element).parent();
            var $buttonGroup = $button.parent();

            var $wrapper = $buttonGroup.parent();
            $button.addClass(ko.utils.unwrapObservable(options.buttonClass));
            $button.find('.' + sysOpts.buttonTextClass)
                    .html(ko.utils.unwrapObservable(file ? options.changeButtonText : options.buttonText));
            var $fileName = $wrapper.find('.' + sysOpts.fileNameClass);
            $fileName.addClass(ko.utils.unwrapObservable(options.fileNameClass));

            if (file && file.name) {
                $fileName.val(file.name);
            }
            else {
                $fileName.val(ko.utils.unwrapObservable(options.noFileText));
            }

            var $clearButton = $buttonGroup.find('.' + sysOpts.clearButtonClass);
            if (!$clearButton.length) {
                $clearButton = $('<span>').addClass(sysOpts.clearButtonClass);
                $clearButton.on('click', function(e) {
                    options.onClear(fileData, options);
                });
                $buttonGroup.append($clearButton);
            }
            $clearButton.html(ko.utils.unwrapObservable(options.clearButtonText));
            $clearButton.addClass(ko.utils.unwrapObservable(options.clearButtonClass));


            if (file && options.clearButton && file.name) {
//                $clearButton.show();
            }
            else {
                $clearButton.remove();
            }
        }
    };

    ko.fileBindings = fileBindings;
    return fileBindings;
}));
;
/**
 * Owl Carousel v2.2.0
 * Copyright 2013-2016 David Deutsch
 * Licensed under MIT (https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE)
 */
!function(a,b,c,d){function e(b,c){this.settings=null,this.options=a.extend({},e.Defaults,c),this.$element=a(b),this._handlers={},this._plugins={},this._supress={},this._current=null,this._speed=null,this._coordinates=[],this._breakpoint=null,this._width=null,this._items=[],this._clones=[],this._mergers=[],this._widths=[],this._invalidated={},this._pipe=[],this._drag={time:null,target:null,pointer:null,stage:{start:null,current:null},direction:null},this._states={current:{},tags:{initializing:["busy"],animating:["busy"],dragging:["interacting"]}},a.each(["onResize","onThrottledResize"],a.proxy(function(b,c){this._handlers[c]=a.proxy(this[c],this)},this)),a.each(e.Plugins,a.proxy(function(a,b){this._plugins[a.charAt(0).toLowerCase()+a.slice(1)]=new b(this)},this)),a.each(e.Workers,a.proxy(function(b,c){this._pipe.push({filter:c.filter,run:a.proxy(c.run,this)})},this)),this.setup(),this.initialize()}e.Defaults={items:3,loop:!1,center:!1,rewind:!1,mouseDrag:!0,touchDrag:!0,pullDrag:!0,freeDrag:!1,margin:0,stagePadding:0,merge:!1,mergeFit:!0,autoWidth:!1,startPosition:0,rtl:!1,smartSpeed:250,fluidSpeed:!1,dragEndSpeed:!1,responsive:{},responsiveRefreshRate:200,responsiveBaseElement:b,fallbackEasing:"swing",info:!1,nestedItemSelector:!1,itemElement:"div",stageElement:"div",refreshClass:"owl-refresh",loadedClass:"owl-loaded",loadingClass:"owl-loading",rtlClass:"owl-rtl",responsiveClass:"owl-responsive",dragClass:"owl-drag",itemClass:"owl-item",stageClass:"owl-stage",stageOuterClass:"owl-stage-outer",grabClass:"owl-grab"},e.Width={Default:"default",Inner:"inner",Outer:"outer"},e.Type={Event:"event",State:"state"},e.Plugins={},e.Workers=[{filter:["width","settings"],run:function(){this._width=this.$element.width()}},{filter:["width","items","settings"],run:function(a){a.current=this._items&&this._items[this.relative(this._current)]}},{filter:["items","settings"],run:function(){this.$stage.children(".cloned").remove()}},{filter:["width","items","settings"],run:function(a){var b=this.settings.margin||"",c=!this.settings.autoWidth,d=this.settings.rtl,e={width:"auto","margin-left":d?b:"","margin-right":d?"":b};!c&&this.$stage.children().css(e),a.css=e}},{filter:["width","items","settings"],run:function(a){var b=(this.width()/this.settings.items).toFixed(3)-this.settings.margin,c=null,d=this._items.length,e=!this.settings.autoWidth,f=[];for(a.items={merge:!1,width:b};d--;)c=this._mergers[d],c=this.settings.mergeFit&&Math.min(c,this.settings.items)||c,a.items.merge=c>1||a.items.merge,f[d]=e?b*c:this._items[d].width();this._widths=f}},{filter:["items","settings"],run:function(){var b=[],c=this._items,d=this.settings,e=Math.max(2*d.items,4),f=2*Math.ceil(c.length/2),g=d.loop&&c.length?d.rewind?e:Math.max(e,f):0,h="",i="";for(g/=2;g--;)b.push(this.normalize(b.length/2,!0)),h+=c[b[b.length-1]][0].outerHTML,b.push(this.normalize(c.length-1-(b.length-1)/2,!0)),i=c[b[b.length-1]][0].outerHTML+i;this._clones=b,a(h).addClass("cloned").appendTo(this.$stage),a(i).addClass("cloned").prependTo(this.$stage)}},{filter:["width","items","settings"],run:function(){for(var a=this.settings.rtl?1:-1,b=this._clones.length+this._items.length,c=-1,d=0,e=0,f=[];++c<b;)d=f[c-1]||0,e=this._widths[this.relative(c)]+this.settings.margin,f.push(d+e*a);this._coordinates=f}},{filter:["width","items","settings"],run:function(){var a=this.settings.stagePadding,b=this._coordinates,c={width:Math.ceil(Math.abs(b[b.length-1]))+2*a,"padding-left":a||"","padding-right":a||""};this.$stage.css(c)}},{filter:["width","items","settings"],run:function(a){var b=this._coordinates.length,c=!this.settings.autoWidth,d=this.$stage.children();if(c&&a.items.merge)for(;b--;)a.css.width=this._widths[this.relative(b)],d.eq(b).css(a.css);else c&&(a.css.width=a.items.width,d.css(a.css))}},{filter:["items"],run:function(){this._coordinates.length<1&&this.$stage.removeAttr("style")}},{filter:["width","items","settings"],run:function(a){a.current=a.current?this.$stage.children().index(a.current):0,a.current=Math.max(this.minimum(),Math.min(this.maximum(),a.current)),this.reset(a.current)}},{filter:["position"],run:function(){this.animate(this.coordinates(this._current))}},{filter:["width","position","items","settings"],run:function(){var a,b,c,d,e=this.settings.rtl?1:-1,f=2*this.settings.stagePadding,g=this.coordinates(this.current())+f,h=g+this.width()*e,i=[];for(c=0,d=this._coordinates.length;d>c;c++)a=this._coordinates[c-1]||0,b=Math.abs(this._coordinates[c])+f*e,(this.op(a,"<=",g)&&this.op(a,">",h)||this.op(b,"<",g)&&this.op(b,">",h))&&i.push(c);this.$stage.children(".active").removeClass("active"),this.$stage.children(":eq("+i.join("), :eq(")+")").addClass("active"),this.settings.center&&(this.$stage.children(".center").removeClass("center"),this.$stage.children().eq(this.current()).addClass("center"))}}],e.prototype.initialize=function(){if(this.enter("initializing"),this.trigger("initialize"),this.$element.toggleClass(this.settings.rtlClass,this.settings.rtl),this.settings.autoWidth&&!this.is("pre-loading")){var b,c,e;b=this.$element.find("img"),c=this.settings.nestedItemSelector?"."+this.settings.nestedItemSelector:d,e=this.$element.children(c).width(),b.length&&0>=e&&this.preloadAutoWidthImages(b)}this.$element.addClass(this.options.loadingClass),this.$stage=a("<"+this.settings.stageElement+' class="'+this.settings.stageClass+'"/>').wrap('<div class="'+this.settings.stageOuterClass+'"/>'),this.$element.append(this.$stage.parent()),this.replace(this.$element.children().not(this.$stage.parent())),this.$element.is(":visible")?this.refresh():this.invalidate("width"),this.$element.removeClass(this.options.loadingClass).addClass(this.options.loadedClass),this.registerEventHandlers(),this.leave("initializing"),this.trigger("initialized")},e.prototype.setup=function(){var b=this.viewport(),c=this.options.responsive,d=-1,e=null;c?(a.each(c,function(a){b>=a&&a>d&&(d=Number(a))}),e=a.extend({},this.options,c[d]),"function"==typeof e.stagePadding&&(e.stagePadding=e.stagePadding()),delete e.responsive,e.responsiveClass&&this.$element.attr("class",this.$element.attr("class").replace(new RegExp("("+this.options.responsiveClass+"-)\\S+\\s","g"),"$1"+d))):e=a.extend({},this.options),this.trigger("change",{property:{name:"settings",value:e}}),this._breakpoint=d,this.settings=e,this.invalidate("settings"),this.trigger("changed",{property:{name:"settings",value:this.settings}})},e.prototype.optionsLogic=function(){this.settings.autoWidth&&(this.settings.stagePadding=!1,this.settings.merge=!1)},e.prototype.prepare=function(b){var c=this.trigger("prepare",{content:b});return c.data||(c.data=a("<"+this.settings.itemElement+"/>").addClass(this.options.itemClass).append(b)),this.trigger("prepared",{content:c.data}),c.data},e.prototype.update=function(){for(var b=0,c=this._pipe.length,d=a.proxy(function(a){return this[a]},this._invalidated),e={};c>b;)(this._invalidated.all||a.grep(this._pipe[b].filter,d).length>0)&&this._pipe[b].run(e),b++;this._invalidated={},!this.is("valid")&&this.enter("valid")},e.prototype.width=function(a){switch(a=a||e.Width.Default){case e.Width.Inner:case e.Width.Outer:return this._width;default:return this._width-2*this.settings.stagePadding+this.settings.margin}},e.prototype.refresh=function(){this.enter("refreshing"),this.trigger("refresh"),this.setup(),this.optionsLogic(),this.$element.addClass(this.options.refreshClass),this.update(),this.$element.removeClass(this.options.refreshClass),this.leave("refreshing"),this.trigger("refreshed")},e.prototype.onThrottledResize=function(){b.clearTimeout(this.resizeTimer),this.resizeTimer=b.setTimeout(this._handlers.onResize,this.settings.responsiveRefreshRate)},e.prototype.onResize=function(){return this._items.length?this._width===this.$element.width()?!1:this.$element.is(":visible")?(this.enter("resizing"),this.trigger("resize").isDefaultPrevented()?(this.leave("resizing"),!1):(this.invalidate("width"),this.refresh(),this.leave("resizing"),void this.trigger("resized"))):!1:!1},e.prototype.registerEventHandlers=function(){a.support.transition&&this.$stage.on(a.support.transition.end+".owl.core",a.proxy(this.onTransitionEnd,this)),this.settings.responsive!==!1&&this.on(b,"resize",this._handlers.onThrottledResize),this.settings.mouseDrag&&(this.$element.addClass(this.options.dragClass),this.$stage.on("mousedown.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("dragstart.owl.core selectstart.owl.core",function(){return!1})),this.settings.touchDrag&&(this.$stage.on("touchstart.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("touchcancel.owl.core",a.proxy(this.onDragEnd,this)))},e.prototype.onDragStart=function(b){var d=null;3!==b.which&&(a.support.transform?(d=this.$stage.css("transform").replace(/.*\(|\)| /g,"").split(","),d={x:d[16===d.length?12:4],y:d[16===d.length?13:5]}):(d=this.$stage.position(),d={x:this.settings.rtl?d.left+this.$stage.width()-this.width()+this.settings.margin:d.left,y:d.top}),this.is("animating")&&(a.support.transform?this.animate(d.x):this.$stage.stop(),this.invalidate("position")),this.$element.toggleClass(this.options.grabClass,"mousedown"===b.type),this.speed(0),this._drag.time=(new Date).getTime(),this._drag.target=a(b.target),this._drag.stage.start=d,this._drag.stage.current=d,this._drag.pointer=this.pointer(b),a(c).on("mouseup.owl.core touchend.owl.core",a.proxy(this.onDragEnd,this)),a(c).one("mousemove.owl.core touchmove.owl.core",a.proxy(function(b){var d=this.difference(this._drag.pointer,this.pointer(b));a(c).on("mousemove.owl.core touchmove.owl.core",a.proxy(this.onDragMove,this)),Math.abs(d.x)<Math.abs(d.y)&&this.is("valid")||(b.preventDefault(),this.enter("dragging"),this.trigger("drag"))},this)))},e.prototype.onDragMove=function(a){var b=null,c=null,d=null,e=this.difference(this._drag.pointer,this.pointer(a)),f=this.difference(this._drag.stage.start,e);this.is("dragging")&&(a.preventDefault(),this.settings.loop?(b=this.coordinates(this.minimum()),c=this.coordinates(this.maximum()+1)-b,f.x=((f.x-b)%c+c)%c+b):(b=this.settings.rtl?this.coordinates(this.maximum()):this.coordinates(this.minimum()),c=this.settings.rtl?this.coordinates(this.minimum()):this.coordinates(this.maximum()),d=this.settings.pullDrag?-1*e.x/5:0,f.x=Math.max(Math.min(f.x,b+d),c+d)),this._drag.stage.current=f,this.animate(f.x))},e.prototype.onDragEnd=function(b){var d=this.difference(this._drag.pointer,this.pointer(b)),e=this._drag.stage.current,f=d.x>0^this.settings.rtl?"left":"right";a(c).off(".owl.core"),this.$element.removeClass(this.options.grabClass),(0!==d.x&&this.is("dragging")||!this.is("valid"))&&(this.speed(this.settings.dragEndSpeed||this.settings.smartSpeed),this.current(this.closest(e.x,0!==d.x?f:this._drag.direction)),this.invalidate("position"),this.update(),this._drag.direction=f,(Math.abs(d.x)>3||(new Date).getTime()-this._drag.time>300)&&this._drag.target.one("click.owl.core",function(){return!1})),this.is("dragging")&&(this.leave("dragging"),this.trigger("dragged"))},e.prototype.closest=function(b,c){var d=-1,e=30,f=this.width(),g=this.coordinates();return this.settings.freeDrag||a.each(g,a.proxy(function(a,h){return"left"===c&&b>h-e&&h+e>b?d=a:"right"===c&&b>h-f-e&&h-f+e>b?d=a+1:this.op(b,"<",h)&&this.op(b,">",g[a+1]||h-f)&&(d="left"===c?a+1:a),-1===d},this)),this.settings.loop||(this.op(b,">",g[this.minimum()])?d=b=this.minimum():this.op(b,"<",g[this.maximum()])&&(d=b=this.maximum())),d},e.prototype.animate=function(b){var c=this.speed()>0;this.is("animating")&&this.onTransitionEnd(),c&&(this.enter("animating"),this.trigger("translate")),a.support.transform3d&&a.support.transition?this.$stage.css({transform:"translate3d("+b+"px,0px,0px)",transition:this.speed()/1e3+"s"}):c?this.$stage.animate({left:b+"px"},this.speed(),this.settings.fallbackEasing,a.proxy(this.onTransitionEnd,this)):this.$stage.css({left:b+"px"})},e.prototype.is=function(a){return this._states.current[a]&&this._states.current[a]>0},e.prototype.current=function(a){if(a===d)return this._current;if(0===this._items.length)return d;if(a=this.normalize(a),this._current!==a){var b=this.trigger("change",{property:{name:"position",value:a}});b.data!==d&&(a=this.normalize(b.data)),this._current=a,this.invalidate("position"),this.trigger("changed",{property:{name:"position",value:this._current}})}return this._current},e.prototype.invalidate=function(b){return"string"===a.type(b)&&(this._invalidated[b]=!0,this.is("valid")&&this.leave("valid")),a.map(this._invalidated,function(a,b){return b})},e.prototype.reset=function(a){a=this.normalize(a),a!==d&&(this._speed=0,this._current=a,this.suppress(["translate","translated"]),this.animate(this.coordinates(a)),this.release(["translate","translated"]))},e.prototype.normalize=function(a,b){var c=this._items.length,e=b?0:this._clones.length;return!this.isNumeric(a)||1>c?a=d:(0>a||a>=c+e)&&(a=((a-e/2)%c+c)%c+e/2),a},e.prototype.relative=function(a){return a-=this._clones.length/2,this.normalize(a,!0)},e.prototype.maximum=function(a){var b,c,d,e=this.settings,f=this._coordinates.length;if(e.loop)f=this._clones.length/2+this._items.length-1;else if(e.autoWidth||e.merge){for(b=this._items.length,c=this._items[--b].width(),d=this.$element.width();b--&&(c+=this._items[b].width()+this.settings.margin,!(c>d)););f=b+1}else f=e.center?this._items.length-1:this._items.length-e.items;return a&&(f-=this._clones.length/2),Math.max(f,0)},e.prototype.minimum=function(a){return a?0:this._clones.length/2},e.prototype.items=function(a){return a===d?this._items.slice():(a=this.normalize(a,!0),this._items[a])},e.prototype.mergers=function(a){return a===d?this._mergers.slice():(a=this.normalize(a,!0),this._mergers[a])},e.prototype.clones=function(b){var c=this._clones.length/2,e=c+this._items.length,f=function(a){return a%2===0?e+a/2:c-(a+1)/2};return b===d?a.map(this._clones,function(a,b){return f(b)}):a.map(this._clones,function(a,c){return a===b?f(c):null})},e.prototype.speed=function(a){return a!==d&&(this._speed=a),this._speed},e.prototype.coordinates=function(b){var c,e=1,f=b-1;return b===d?a.map(this._coordinates,a.proxy(function(a,b){return this.coordinates(b)},this)):(this.settings.center?(this.settings.rtl&&(e=-1,f=b+1),c=this._coordinates[b],c+=(this.width()-c+(this._coordinates[f]||0))/2*e):c=this._coordinates[f]||0,c=Math.ceil(c))},e.prototype.duration=function(a,b,c){return 0===c?0:Math.min(Math.max(Math.abs(b-a),1),6)*Math.abs(c||this.settings.smartSpeed)},e.prototype.to=function(a,b){var c=this.current(),d=null,e=a-this.relative(c),f=(e>0)-(0>e),g=this._items.length,h=this.minimum(),i=this.maximum();this.settings.loop?(!this.settings.rewind&&Math.abs(e)>g/2&&(e+=-1*f*g),a=c+e,d=((a-h)%g+g)%g+h,d!==a&&i>=d-e&&d-e>0&&(c=d-e,a=d,this.reset(c))):this.settings.rewind?(i+=1,a=(a%i+i)%i):a=Math.max(h,Math.min(i,a)),this.speed(this.duration(c,a,b)),this.current(a),this.$element.is(":visible")&&this.update()},e.prototype.next=function(a){a=a||!1,this.to(this.relative(this.current())+1,a)},e.prototype.prev=function(a){a=a||!1,this.to(this.relative(this.current())-1,a)},e.prototype.onTransitionEnd=function(a){return a!==d&&(a.stopPropagation(),(a.target||a.srcElement||a.originalTarget)!==this.$stage.get(0))?!1:(this.leave("animating"),void this.trigger("translated"))},e.prototype.viewport=function(){var d;if(this.options.responsiveBaseElement!==b)d=a(this.options.responsiveBaseElement).width();else if(b.innerWidth)d=b.innerWidth;else{if(!c.documentElement||!c.documentElement.clientWidth)throw"Can not detect viewport width.";d=c.documentElement.clientWidth}return d},e.prototype.replace=function(b){this.$stage.empty(),this._items=[],b&&(b=b instanceof jQuery?b:a(b)),this.settings.nestedItemSelector&&(b=b.find("."+this.settings.nestedItemSelector)),b.filter(function(){return 1===this.nodeType}).each(a.proxy(function(a,b){b=this.prepare(b),this.$stage.append(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)},this)),this.reset(this.isNumeric(this.settings.startPosition)?this.settings.startPosition:0),this.invalidate("items")},e.prototype.add=function(b,c){var e=this.relative(this._current);c=c===d?this._items.length:this.normalize(c,!0),b=b instanceof jQuery?b:a(b),this.trigger("add",{content:b,position:c}),b=this.prepare(b),0===this._items.length||c===this._items.length?(0===this._items.length&&this.$stage.append(b),0!==this._items.length&&this._items[c-1].after(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)):(this._items[c].before(b),this._items.splice(c,0,b),this._mergers.splice(c,0,1*b.find("[data-merge]").addBack("[data-merge]").attr("data-merge")||1)),this._items[e]&&this.reset(this._items[e].index()),this.invalidate("items"),this.trigger("added",{content:b,position:c})},e.prototype.remove=function(a){a=this.normalize(a,!0),a!==d&&(this.trigger("remove",{content:this._items[a],position:a}),this._items[a].remove(),this._items.splice(a,1),this._mergers.splice(a,1),this.invalidate("items"),this.trigger("removed",{content:null,position:a}))},e.prototype.preloadAutoWidthImages=function(b){b.each(a.proxy(function(b,c){this.enter("pre-loading"),c=a(c),a(new Image).one("load",a.proxy(function(a){c.attr("src",a.target.src),c.css("opacity",1),this.leave("pre-loading"),!this.is("pre-loading")&&!this.is("initializing")&&this.refresh()},this)).attr("src",c.attr("src")||c.attr("data-src")||c.attr("data-src-retina"))},this))},e.prototype.destroy=function(){this.$element.off(".owl.core"),this.$stage.off(".owl.core"),a(c).off(".owl.core"),this.settings.responsive!==!1&&(b.clearTimeout(this.resizeTimer),this.off(b,"resize",this._handlers.onThrottledResize));for(var d in this._plugins)this._plugins[d].destroy();this.$stage.children(".cloned").remove(),this.$stage.unwrap(),this.$stage.children().contents().unwrap(),this.$stage.children().unwrap(),this.$element.removeClass(this.options.refreshClass).removeClass(this.options.loadingClass).removeClass(this.options.loadedClass).removeClass(this.options.rtlClass).removeClass(this.options.dragClass).removeClass(this.options.grabClass).attr("class",this.$element.attr("class").replace(new RegExp(this.options.responsiveClass+"-\\S+\\s","g"),"")).removeData("owl.carousel")},e.prototype.op=function(a,b,c){var d=this.settings.rtl;switch(b){case"<":return d?a>c:c>a;case">":return d?c>a:a>c;case">=":return d?c>=a:a>=c;case"<=":return d?a>=c:c>=a}},e.prototype.on=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},e.prototype.off=function(a,b,c,d){a.removeEventListener?a.removeEventListener(b,c,d):a.detachEvent&&a.detachEvent("on"+b,c)},e.prototype.trigger=function(b,c,d,f,g){var h={item:{count:this._items.length,index:this.current()}},i=a.camelCase(a.grep(["on",b,d],function(a){return a}).join("-").toLowerCase()),j=a.Event([b,"owl",d||"carousel"].join(".").toLowerCase(),a.extend({relatedTarget:this},h,c));return this._supress[b]||(a.each(this._plugins,function(a,b){b.onTrigger&&b.onTrigger(j)}),this.register({type:e.Type.Event,name:b}),this.$element.trigger(j),this.settings&&"function"==typeof this.settings[i]&&this.settings[i].call(this,j)),j},e.prototype.enter=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]===d&&(this._states.current[b]=0),this._states.current[b]++},this))},e.prototype.leave=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]--},this))},e.prototype.register=function(b){if(b.type===e.Type.Event){if(a.event.special[b.name]||(a.event.special[b.name]={}),!a.event.special[b.name].owl){var c=a.event.special[b.name]._default;a.event.special[b.name]._default=function(a){return!c||!c.apply||a.namespace&&-1!==a.namespace.indexOf("owl")?a.namespace&&a.namespace.indexOf("owl")>-1:c.apply(this,arguments)},a.event.special[b.name].owl=!0}}else b.type===e.Type.State&&(this._states.tags[b.name]?this._states.tags[b.name]=this._states.tags[b.name].concat(b.tags):this._states.tags[b.name]=b.tags,this._states.tags[b.name]=a.grep(this._states.tags[b.name],a.proxy(function(c,d){return a.inArray(c,this._states.tags[b.name])===d},this)))},e.prototype.suppress=function(b){a.each(b,a.proxy(function(a,b){this._supress[b]=!0},this))},e.prototype.release=function(b){a.each(b,a.proxy(function(a,b){delete this._supress[b]},this))},e.prototype.pointer=function(a){var c={x:null,y:null};return a=a.originalEvent||a||b.event,a=a.touches&&a.touches.length?a.touches[0]:a.changedTouches&&a.changedTouches.length?a.changedTouches[0]:a,a.pageX?(c.x=a.pageX,c.y=a.pageY):(c.x=a.clientX,c.y=a.clientY),c},e.prototype.isNumeric=function(a){return!isNaN(parseFloat(a))},e.prototype.difference=function(a,b){return{x:a.x-b.x,y:a.y-b.y}},a.fn.owlCarousel=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),f=d.data("owl.carousel");f||(f=new e(this,"object"==typeof b&&b),d.data("owl.carousel",f),a.each(["next","prev","to","destroy","refresh","replace","add","remove"],function(b,c){f.register({type:e.Type.Event,name:c}),f.$element.on(c+".owl.carousel.core",a.proxy(function(a){a.namespace&&a.relatedTarget!==this&&(this.suppress([c]),f[c].apply(this,[].slice.call(arguments,1)),this.release([c]))},f))})),"string"==typeof b&&"_"!==b.charAt(0)&&f[b].apply(f,c)})},a.fn.owlCarousel.Constructor=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._interval=null,this._visible=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoRefresh&&this.watch()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoRefresh:!0,autoRefreshInterval:500},e.prototype.watch=function(){this._interval||(this._visible=this._core.$element.is(":visible"),this._interval=b.setInterval(a.proxy(this.refresh,this),this._core.settings.autoRefreshInterval))},e.prototype.refresh=function(){this._core.$element.is(":visible")!==this._visible&&(this._visible=!this._visible,this._core.$element.toggleClass("owl-hidden",!this._visible),this._visible&&this._core.invalidate("width")&&this._core.refresh())},e.prototype.destroy=function(){var a,c;b.clearInterval(this._interval);for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoRefresh=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._loaded=[],this._handlers={"initialized.owl.carousel change.owl.carousel resized.owl.carousel":a.proxy(function(b){if(b.namespace&&this._core.settings&&this._core.settings.lazyLoad&&(b.property&&"position"==b.property.name||"initialized"==b.type))for(var c=this._core.settings,e=c.center&&Math.ceil(c.items/2)||c.items,f=c.center&&-1*e||0,g=(b.property&&b.property.value!==d?b.property.value:this._core.current())+f,h=this._core.clones().length,i=a.proxy(function(a,b){this.load(b)},this);f++<e;)this.load(h/2+this._core.relative(g)),h&&a.each(this._core.clones(this._core.relative(g)),i),g++},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={lazyLoad:!1},e.prototype.load=function(c){var d=this._core.$stage.children().eq(c),e=d&&d.find(".owl-lazy");!e||a.inArray(d.get(0),this._loaded)>-1||(e.each(a.proxy(function(c,d){var e,f=a(d),g=b.devicePixelRatio>1&&f.attr("data-src-retina")||f.attr("data-src");this._core.trigger("load",{element:f,url:g},"lazy"),f.is("img")?f.one("load.owl.lazy",a.proxy(function(){f.css("opacity",1),this._core.trigger("loaded",{element:f,url:g},"lazy")},this)).attr("src",g):(e=new Image,e.onload=a.proxy(function(){f.css({"background-image":"url("+g+")",opacity:"1"}),this._core.trigger("loaded",{element:f,url:g},"lazy")},this),e.src=g)},this)),this._loaded.push(d.get(0)))},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this._core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Lazy=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._handlers={"initialized.owl.carousel refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&this.update()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&"position"==a.property.name&&this.update()},this),"loaded.owl.lazy":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&a.element.closest("."+this._core.settings.itemClass).index()===this._core.current()&&this.update()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoHeight:!1,autoHeightClass:"owl-height"},e.prototype.update=function(){var b=this._core._current,c=b+this._core.settings.items,d=this._core.$stage.children().toArray().slice(b,c),e=[],f=0;a.each(d,function(b,c){e.push(a(c).height())}),f=Math.max.apply(null,e),this._core.$stage.parent().height(f).addClass(this._core.settings.autoHeightClass)},e.prototype.destroy=function(){var a,b;for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoHeight=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._videos={},this._playing=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.register({type:"state",name:"playing",tags:["interacting"]})},this),"resize.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.video&&this.isInFullScreen()&&a.preventDefault()},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.is("resizing")&&this._core.$stage.find(".cloned .owl-video-frame").remove()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"===a.property.name&&this._playing&&this.stop()},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find(".owl-video");c.length&&(c.css("display","none"),this.fetch(c,a(b.content)))}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers),this._core.$element.on("click.owl.video",".owl-video-play-icon",a.proxy(function(a){this.play(a)},this))};e.Defaults={video:!1,videoHeight:!1,videoWidth:!1},e.prototype.fetch=function(a,b){var c=function(){return a.attr("data-vimeo-id")?"vimeo":a.attr("data-vzaar-id")?"vzaar":"youtube"}(),d=a.attr("data-vimeo-id")||a.attr("data-youtube-id")||a.attr("data-vzaar-id"),e=a.attr("data-width")||this._core.settings.videoWidth,f=a.attr("data-height")||this._core.settings.videoHeight,g=a.attr("href");if(!g)throw new Error("Missing video URL.");if(d=g.match(/(http:|https:|)\/\/(player.|www.|app.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com)|vzaar\.com)\/(video\/|videos\/|embed\/|channels\/.+\/|groups\/.+\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/),d[3].indexOf("youtu")>-1)c="youtube";else if(d[3].indexOf("vimeo")>-1)c="vimeo";else{if(!(d[3].indexOf("vzaar")>-1))throw new Error("Video URL not supported.");c="vzaar"}d=d[6],this._videos[g]={type:c,id:d,width:e,height:f},b.attr("data-video",g),this.thumbnail(a,this._videos[g])},e.prototype.thumbnail=function(b,c){var d,e,f,g=c.width&&c.height?'style="width:'+c.width+"px;height:"+c.height+'px;"':"",h=b.find("img"),i="src",j="",k=this._core.settings,l=function(a){e='<div class="owl-video-play-icon"></div>',d=k.lazyLoad?'<div class="owl-video-tn '+j+'" '+i+'="'+a+'"></div>':'<div class="owl-video-tn" style="opacity:1;background-image:url('+a+')"></div>',b.after(d),b.after(e)};return b.wrap('<div class="owl-video-wrapper"'+g+"></div>"),this._core.settings.lazyLoad&&(i="data-src",j="owl-lazy"),h.length?(l(h.attr(i)),h.remove(),!1):void("youtube"===c.type?(f="//img.youtube.com/vi/"+c.id+"/hqdefault.jpg",l(f)):"vimeo"===c.type?a.ajax({type:"GET",url:"//vimeo.com/api/v2/video/"+c.id+".json",jsonp:"callback",dataType:"jsonp",success:function(a){f=a[0].thumbnail_large,l(f)}}):"vzaar"===c.type&&a.ajax({type:"GET",url:"//vzaar.com/api/videos/"+c.id+".json",jsonp:"callback",dataType:"jsonp",success:function(a){f=a.framegrab_url,l(f)}}))},e.prototype.stop=function(){this._core.trigger("stop",null,"video"),this._playing.find(".owl-video-frame").remove(),this._playing.removeClass("owl-video-playing"),this._playing=null,this._core.leave("playing"),this._core.trigger("stopped",null,"video")},e.prototype.play=function(b){var c,d=a(b.target),e=d.closest("."+this._core.settings.itemClass),f=this._videos[e.attr("data-video")],g=f.width||"100%",h=f.height||this._core.$stage.height();this._playing||(this._core.enter("playing"),this._core.trigger("play",null,"video"),e=this._core.items(this._core.relative(e.index())),this._core.reset(e.index()),"youtube"===f.type?c='<iframe width="'+g+'" height="'+h+'" src="//www.youtube.com/embed/'+f.id+"?autoplay=1&v="+f.id+'" frameborder="0" allowfullscreen></iframe>':"vimeo"===f.type?c='<iframe src="//player.vimeo.com/video/'+f.id+'?autoplay=1" width="'+g+'" height="'+h+'" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>':"vzaar"===f.type&&(c='<iframe frameborder="0"height="'+h+'"width="'+g+'" allowfullscreen mozallowfullscreen webkitAllowFullScreen src="//view.vzaar.com/'+f.id+'/player?autoplay=true"></iframe>'),a('<div class="owl-video-frame">'+c+"</div>").insertAfter(e.find(".owl-video")),this._playing=e.addClass("owl-video-playing"))},e.prototype.isInFullScreen=function(){var b=c.fullscreenElement||c.mozFullScreenElement||c.webkitFullscreenElement;return b&&a(b).parent().hasClass("owl-video-frame")},e.prototype.destroy=function(){var a,b;this._core.$element.off("click.owl.video");for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Video=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this.core=b,this.core.options=a.extend({},e.Defaults,this.core.options),this.swapping=!0,this.previous=d,this.next=d,this.handlers={"change.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&(this.previous=this.core.current(),this.next=a.property.value)},this),"drag.owl.carousel dragged.owl.carousel translated.owl.carousel":a.proxy(function(a){a.namespace&&(this.swapping="translated"==a.type)},this),"translate.owl.carousel":a.proxy(function(a){a.namespace&&this.swapping&&(this.core.options.animateOut||this.core.options.animateIn)&&this.swap()},this)},this.core.$element.on(this.handlers)};e.Defaults={animateOut:!1,animateIn:!1},e.prototype.swap=function(){if(1===this.core.settings.items&&a.support.animation&&a.support.transition){this.core.speed(0);var b,c=a.proxy(this.clear,this),d=this.core.$stage.children().eq(this.previous),e=this.core.$stage.children().eq(this.next),f=this.core.settings.animateIn,g=this.core.settings.animateOut;this.core.current()!==this.previous&&(g&&(b=this.core.coordinates(this.previous)-this.core.coordinates(this.next),d.one(a.support.animation.end,c).css({left:b+"px"}).addClass("animated owl-animated-out").addClass(g)),f&&e.one(a.support.animation.end,c).addClass("animated owl-animated-in").addClass(f))}},e.prototype.clear=function(b){a(b.target).css({left:""}).removeClass("animated owl-animated-out owl-animated-in").removeClass(this.core.settings.animateIn).removeClass(this.core.settings.animateOut),this.core.onTransitionEnd()},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this.core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null);
},a.fn.owlCarousel.Constructor.Plugins.Animate=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._timeout=null,this._paused=!1,this._handlers={"changed.owl.carousel":a.proxy(function(a){a.namespace&&"settings"===a.property.name?this._core.settings.autoplay?this.play():this.stop():a.namespace&&"position"===a.property.name&&this._core.settings.autoplay&&this._setAutoPlayInterval()},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoplay&&this.play()},this),"play.owl.autoplay":a.proxy(function(a,b,c){a.namespace&&this.play(b,c)},this),"stop.owl.autoplay":a.proxy(function(a){a.namespace&&this.stop()},this),"mouseover.owl.autoplay":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.pause()},this),"mouseleave.owl.autoplay":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.play()},this),"touchstart.owl.core":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.pause()},this),"touchend.owl.core":a.proxy(function(){this._core.settings.autoplayHoverPause&&this.play()},this)},this._core.$element.on(this._handlers),this._core.options=a.extend({},e.Defaults,this._core.options)};e.Defaults={autoplay:!1,autoplayTimeout:5e3,autoplayHoverPause:!1,autoplaySpeed:!1},e.prototype.play=function(a,b){this._paused=!1,this._core.is("rotating")||(this._core.enter("rotating"),this._setAutoPlayInterval())},e.prototype._getNextTimeout=function(d,e){return this._timeout&&b.clearTimeout(this._timeout),b.setTimeout(a.proxy(function(){this._paused||this._core.is("busy")||this._core.is("interacting")||c.hidden||this._core.next(e||this._core.settings.autoplaySpeed)},this),d||this._core.settings.autoplayTimeout)},e.prototype._setAutoPlayInterval=function(){this._timeout=this._getNextTimeout()},e.prototype.stop=function(){this._core.is("rotating")&&(b.clearTimeout(this._timeout),this._core.leave("rotating"))},e.prototype.pause=function(){this._core.is("rotating")&&(this._paused=!0)},e.prototype.destroy=function(){var a,b;this.stop();for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.autoplay=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(b){this._core=b,this._initialized=!1,this._pages=[],this._controls={},this._templates=[],this.$element=this._core.$element,this._overrides={next:this._core.next,prev:this._core.prev,to:this._core.to},this._handlers={"prepared.owl.carousel":a.proxy(function(b){b.namespace&&this._core.settings.dotsData&&this._templates.push('<div class="'+this._core.settings.dotClass+'">'+a(b.content).find("[data-dot]").addBack("[data-dot]").attr("data-dot")+"</div>")},this),"added.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,0,this._templates.pop())},this),"remove.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,1)},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&this.draw()},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&!this._initialized&&(this._core.trigger("initialize",null,"navigation"),this.initialize(),this.update(),this.draw(),this._initialized=!0,this._core.trigger("initialized",null,"navigation"))},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._initialized&&(this._core.trigger("refresh",null,"navigation"),this.update(),this.draw(),this._core.trigger("refreshed",null,"navigation"))},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers)};e.Defaults={nav:!1,navText:["prev","next"],navSpeed:!1,navElement:"div",navContainer:!1,navContainerClass:"owl-nav",navClass:["owl-prev","owl-next"],slideBy:1,dotClass:"owl-dot",dotsClass:"owl-dots",dots:!0,dotsEach:!1,dotsData:!1,dotsSpeed:!1,dotsContainer:!1},e.prototype.initialize=function(){var b,c=this._core.settings;this._controls.$relative=(c.navContainer?a(c.navContainer):a("<div>").addClass(c.navContainerClass).appendTo(this.$element)).addClass("disabled"),this._controls.$previous=a("<"+c.navElement+">").addClass(c.navClass[0]).html(c.navText[0]).prependTo(this._controls.$relative).on("click",a.proxy(function(a){this.prev(c.navSpeed)},this)),this._controls.$next=a("<"+c.navElement+">").addClass(c.navClass[1]).html(c.navText[1]).appendTo(this._controls.$relative).on("click",a.proxy(function(a){this.next(c.navSpeed)},this)),c.dotsData||(this._templates=[a("<div>").addClass(c.dotClass).append(a("<span>")).prop("outerHTML")]),this._controls.$absolute=(c.dotsContainer?a(c.dotsContainer):a("<div>").addClass(c.dotsClass).appendTo(this.$element)).addClass("disabled"),this._controls.$absolute.on("click","div",a.proxy(function(b){var d=a(b.target).parent().is(this._controls.$absolute)?a(b.target).index():a(b.target).parent().index();b.preventDefault(),this.to(d,c.dotsSpeed)},this));for(b in this._overrides)this._core[b]=a.proxy(this[b],this)},e.prototype.destroy=function(){var a,b,c,d;for(a in this._handlers)this.$element.off(a,this._handlers[a]);for(b in this._controls)this._controls[b].remove();for(d in this.overides)this._core[d]=this._overrides[d];for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},e.prototype.update=function(){var a,b,c,d=this._core.clones().length/2,e=d+this._core.items().length,f=this._core.maximum(!0),g=this._core.settings,h=g.center||g.autoWidth||g.dotsData?1:g.dotsEach||g.items;if("page"!==g.slideBy&&(g.slideBy=Math.min(g.slideBy,g.items)),g.dots||"page"==g.slideBy)for(this._pages=[],a=d,b=0,c=0;e>a;a++){if(b>=h||0===b){if(this._pages.push({start:Math.min(f,a-d),end:a-d+h-1}),Math.min(f,a-d)===f)break;b=0,++c}b+=this._core.mergers(this._core.relative(a))}},e.prototype.draw=function(){var b,c=this._core.settings,d=this._core.items().length<=c.items,e=this._core.relative(this._core.current()),f=c.loop||c.rewind;this._controls.$relative.toggleClass("disabled",!c.nav||d),c.nav&&(this._controls.$previous.toggleClass("disabled",!f&&e<=this._core.minimum(!0)),this._controls.$next.toggleClass("disabled",!f&&e>=this._core.maximum(!0))),this._controls.$absolute.toggleClass("disabled",!c.dots||d),c.dots&&(b=this._pages.length-this._controls.$absolute.children().length,c.dotsData&&0!==b?this._controls.$absolute.html(this._templates.join("")):b>0?this._controls.$absolute.append(new Array(b+1).join(this._templates[0])):0>b&&this._controls.$absolute.children().slice(b).remove(),this._controls.$absolute.find(".active").removeClass("active"),this._controls.$absolute.children().eq(a.inArray(this.current(),this._pages)).addClass("active"))},e.prototype.onTrigger=function(b){var c=this._core.settings;b.page={index:a.inArray(this.current(),this._pages),count:this._pages.length,size:c&&(c.center||c.autoWidth||c.dotsData?1:c.dotsEach||c.items)}},e.prototype.current=function(){var b=this._core.relative(this._core.current());return a.grep(this._pages,a.proxy(function(a,c){return a.start<=b&&a.end>=b},this)).pop()},e.prototype.getPosition=function(b){var c,d,e=this._core.settings;return"page"==e.slideBy?(c=a.inArray(this.current(),this._pages),d=this._pages.length,b?++c:--c,c=this._pages[(c%d+d)%d].start):(c=this._core.relative(this._core.current()),d=this._core.items().length,b?c+=e.slideBy:c-=e.slideBy),c},e.prototype.next=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!0),b)},e.prototype.prev=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!1),b)},e.prototype.to=function(b,c,d){var e;!d&&this._pages.length?(e=this._pages.length,a.proxy(this._overrides.to,this._core)(this._pages[(b%e+e)%e].start,c)):a.proxy(this._overrides.to,this._core)(b,c)},a.fn.owlCarousel.Constructor.Plugins.Navigation=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(c){this._core=c,this._hashes={},this.$element=this._core.$element,this._handlers={"initialized.owl.carousel":a.proxy(function(c){c.namespace&&"URLHash"===this._core.settings.startPosition&&a(b).trigger("hashchange.owl.navigation")},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find("[data-hash]").addBack("[data-hash]").attr("data-hash");if(!c)return;this._hashes[c]=b.content}},this),"changed.owl.carousel":a.proxy(function(c){if(c.namespace&&"position"===c.property.name){var d=this._core.items(this._core.relative(this._core.current())),e=a.map(this._hashes,function(a,b){return a===d?b:null}).join();if(!e||b.location.hash.slice(1)===e)return;b.location.hash=e}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers),a(b).on("hashchange.owl.navigation",a.proxy(function(a){var c=b.location.hash.substring(1),e=this._core.$stage.children(),f=this._hashes[c]&&e.index(this._hashes[c]);f!==d&&f!==this._core.current()&&this._core.to(this._core.relative(f),!1,!0)},this))};e.Defaults={URLhashListener:!1},e.prototype.destroy=function(){var c,d;a(b).off("hashchange.owl.navigation");for(c in this._handlers)this._core.$element.off(c,this._handlers[c]);for(d in Object.getOwnPropertyNames(this))"function"!=typeof this[d]&&(this[d]=null)},a.fn.owlCarousel.Constructor.Plugins.Hash=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){function e(b,c){var e=!1,f=b.charAt(0).toUpperCase()+b.slice(1);return a.each((b+" "+h.join(f+" ")+f).split(" "),function(a,b){return g[b]!==d?(e=c?b:!0,!1):void 0}),e}function f(a){return e(a,!0)}var g=a("<support>").get(0).style,h="Webkit Moz O ms".split(" "),i={transition:{end:{WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"}},animation:{end:{WebkitAnimation:"webkitAnimationEnd",MozAnimation:"animationend",OAnimation:"oAnimationEnd",animation:"animationend"}}},j={csstransforms:function(){return!!e("transform")},csstransforms3d:function(){return!!e("perspective")},csstransitions:function(){return!!e("transition")},cssanimations:function(){return!!e("animation")}};j.csstransitions()&&(a.support.transition=new String(f("transition")),a.support.transition.end=i.transition.end[a.support.transition]),j.cssanimations()&&(a.support.animation=new String(f("animation")),a.support.animation.end=i.animation.end[a.support.animation]),j.csstransforms()&&(a.support.transform=new String(f("transform")),a.support.transform3d=j.csstransforms3d())}(window.Zepto||window.jQuery,window,document);;
"use strict";

var templateLoader = new (function () {
    var self = this;
    var cache = {};

    self.getConfig = ko.components.defaultLoader.getConfig;
    self.loadComponent = ko.components.defaultLoader.loadComponent;
    self.loadViewModel = ko.components.defaultLoader.loadViewModel;

    self.loadTemplate = function (name, templateConfig, callback) {
        if (templateConfig.load == null) {
            callback(null);
        } else {
            var existing = cache[name];
            if (existing) {
                callback(existing);
            }
            else {
                var culture = $("head meta[http-equiv='content-language']").attr("content");
                $.ajax({
                    type: 'GET',
                    url: templateConfig.load,
                    contentType: "application/x-www-form-urlencoded",
                    dataType: 'html',
                    data: { culture: culture },
                    timeout: 10000
                }).done(function (template) {
                    var nodes = ko.utils.parseHtmlFragment(template.trim());

                    cache[name] = nodes;

                    callback(nodes);
                }).fail(function () {
                    callback(null);
                });
            }
        }
    };
});

ko.components.loaders.unshift(templateLoader);;
//Simple pub/sub with knockout
var postbox = (function () {
    var pb = new ko.subscribable();
    return {
        subscribe: function (topic, handler) {
            pb.subscribe(handler, null, topic);

            return this; //for chaining
        },
        publish: function (topic, message) {
            pb.notifySubscribers(message, topic);

            return this; //for chaining
        }
    };
}());;
(function ($) {
    //Currency - Formats as 0,000,000.00
    ko.bindingHandlers.currency = {
        update: function(element, valueAccessor, allBindings) {
            var el = $(element);
            var method;

            // Gives us the real value if it is a computed observable or not
            var valueUnwrapped = ko.unwrap(valueAccessor());

            if (el.is(':input')) {
                method = 'val';
            } else {
                method = 'text';
            }

            var toCurrency = function(num) {
                //return (num.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,'));//add commas
                return (num.toFixed(2));
            };

            return el[method](toCurrency(valueUnwrapped));
        }
    }

    //Minimal Currency - Formats as 0000.00 OR 0000 if the value does not have a decimal
    ko.bindingHandlers.minimalCurrency = {
        update: function (element, valueAccessor, allBindings) {
            var el = $(element);
            var method;

            // Gives us the real value if it is a computed observable or not
            var valueUnwrapped = ko.unwrap(valueAccessor());

            if (el.is(':input')) {
                method = 'val';
            } else {
                method = 'text';
            }

            var toCurrency = function (num) {
                if (num % 1 === 0) {
                    return num;
                }
                return (num.toFixed(2));
            };

            return el[method](toCurrency(valueUnwrapped));
        }
    }
})(jQuery);;
//site namespace
var GK = GK || {};

GK.NotificationService = (function (window, transationService) {

    var loadingMessageInterval = 0;

    var showMessage = function (messageType, message) {
        var alertBox = $('#alertBanner.alert-box');
        alertBox.attr('class', "alert-box")
            .addClass(messageType)
            .find('.wrapper div')
            .html(message);

        $('html, body').animate({
            scrollTop: alertBox.offset().top
        }, 750);
    }

    var error = function (message) {
        showMessage('error', message);
    };

    var success = function (message) {
        showMessage('success', message);
    };

    var info = function (message) {
        showMessage('info', message);
    };

    var geo = function (messageMarkup) {
        var alertBox = $('#geoBanner.alert-box');
        alertBox.attr('class', "alert-box")
            .addClass("location")
            .find('.wrapper div')
            .html("<p>" + messageMarkup + "</p>");

        //add current pageid to locale switcher link in banner- this expects the content to contain a link with "geolocation-link" class
        var pageid = $('html').attr("page-id");
        var lnk = $('#geoBanner.alert-box .geolocation-link');
        lnk.attr('href', lnk.attr('href') + pageid);
    }

    var loading = function (isLoading) {
        stopLoadingMessageCycle();
        if (isLoading) {
            var loadingMessages = transationService.getLoadingMessages();
            if (loadingMessages && loadingMessages.length > 0) {
                startLoadingMessageCycle(loadingMessages);
            }
            $('body').addClass('ajax-loading');
            $('.loading-overlay .icon').addClass('loading');
        } else {
            $('body').removeClass('ajax-loading');
            $('.loading-overlay .icon').removeClass('loading');

        }

    };

    var startLoadingMessageCycle = function (msgList) {
        var length = msgList.length;
        var index = -1;
        var delay = 5000;//5 seconds
        var fade = 400;// 

        stopLoadingMessageCycle();

        var messageContainer = $('.loading-overlay .loading-messages');
        for (var i = 0; i < length; i++) {
            messageContainer.append('<div style="display:none;">' + msgList[i] + '</div>');
        }

        //function for loop to handle toggling next message
        var nextMessage = function () {
            var oldIndex = index;
            index++;
            if (index >= length) {
                index = 0;
            }
            var childMessages = $('.loading-overlay .loading-messages').children();
            childMessages.eq(oldIndex).fadeOut(fade, function () {
                childMessages.eq(index).fadeIn(fade);
            });
            
        }
        
        nextMessage();
        return loadingMessageInterval = window.setInterval(nextMessage, delay);
    };

    var stopLoadingMessageCycle = function () {
        if (loadingMessageInterval) {
            //stop loop and clear messages
            var messageContainer = $('.loading-overlay .loading-messages');
            window.clearInterval(loadingMessageInterval);
            messageContainer.children().hide();
            messageContainer.empty();
            loadingMessageInterval = null;
        }
    }

    var isMessageDismissed = function (key, changeVal) {
        //if a value is passed in then change - otherwise just return the value
        if (changeVal === true || changeVal === false) {
            //save changes across site for session
            $.cookie(key, changeVal, { path: '/' });
        }
        var cookieVal = $.cookie(key);

        //false if cookie is not set
        if (!cookieVal) {
            return false;
        }
        return cookieVal;
    };

    //return public functions
    return {
        notifyError: error,
        notifySuccess: success,
        notifyInfo: info,
        notifyLoading: loading,
        notifyGeo: geo,
        isMessageDismissed: isMessageDismissed
    };

})(window, GK.TranslationService);;
//site namespace
var GK = GK || {};

GK.ErrorService = (function (notificationService, translationService, $, window) {

    var getUrl = function (action) {
        return '/umbraco/api/logapi/' + action;
    }

    var getModelStateErrors = function (jqXHR) {
        var errors = [];
        if (jqXHR
            && jqXHR.status === 400
            && jqXHR.responseJSON
            && jqXHR.responseJSON.ModelState) {

            for (var propertyName in jqXHR.responseJSON.ModelState) {
                if ($.isArray(jqXHR.responseJSON.ModelState[propertyName])) {
                    errors.push(jqXHR.responseJSON.ModelState[propertyName][0]);
                }
            }
        }
        return errors;
    };

    var handleError = function (jqXHR, textStatus, errorThrown, sourceUrl) {

        if (typeof console !== 'undefined' && console && console.dir) { console.dir({ jqXHR: jqXHR, textStatus: textStatus, errorThrown: errorThrown }); }

        if (jqXHR.status === 400
            && jqXHR.responseJSON
            && jqXHR.responseJSON.ModelState) {

            var errors = getModelStateErrors(jqXHR);

            if (errors.length > 0) {
                var message;
                if (errors.length === 1) {
                    message = errors[0];
                }
                else {
                    var $list = $('<ul></ul>');
                    for (var i = 0; i < errors.length; i++) {
                        $list.append('<li>' + errors[i] + '</li>');
                    }
                    message = $list.html();
                }

                notificationService.notifyError(message);
                return;
            }
        }

        notificationService.notifyError(translationService.getTranslation('An unexpected error has occurred.'));
        logError("handleError", errorThrown, { responseText: jqXHR, textStatus: textStatus, sourceUrl: sourceUrl });
    };

    var logError = function (message, error, data) {

        try {
            //convert data to string
            if (data && typeof data !== 'string') {
                data = JSON.stringify(data);
            }

            //fire and forget
            $.post(getUrl("error"),
            {
                message: message,
                error: error,
                data: data
            });
        } catch (ex) { }
    }

    var init = function () {
        //catch global errors
        window.onerror = function (msg, url, num) {
            //logError("window.onerror", msg, url + ';' + num);
            return true;
        }
    }

    init();

    //return public functions
    return {
        getModelStateErrors: getModelStateErrors,
        handleError: handleError,
        logError: logError
    };

})(GK.NotificationService, GK.TranslationService, jQuery, window);;
//site namespace
var GK = GK || {};

GK.LocaleService = (function ($, errorService, notificationService, translationService) {

    var states = {}
    var getStatesForCountry = function(countryCode, culture, successCallback, errorCallback) {
        if (!states[countryCode]) {
            states[countryCode] = {}
        }

        if (states[countryCode][culture]) {
            successCallback(states[countryCode][culture]);
            return;
        }
        var url = '/umbraco/api/localeapi/states';
        $.getJSON(url,
            {
                countryCode: countryCode,
                cultureCode: culture
            })
            .done(function(data) {
                if (typeof successCallback === "function") {
                    states[countryCode][culture] = data;
                    successCallback(data);
                }
            })
            .fail(function(jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                } else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            });
    };

    var getCurrentCulture = function() {
        return $("head meta[http-equiv='content-language']").attr("content");
    };

    var getCurrentCountry = function() {
        return getCurrentCulture().substring(3, 5).toUpperCase();
    }

    var isCultureCheckDisabled = function () {
        var key = "DismissCultureCheck";
        return notificationService.isMessageDismissed(key);
    };

    var checkUseCulture = function (userCulture) {
        if (!isCultureCheckDisabled() && userCulture.toUpperCase() !== getCurrentCulture().toUpperCase()) {
            var fnDisplayMessage = function (data) {
                var msg = data.Message;
                notificationService.notifyGeo(msg);
            };
            translationService.getGeolocationMessage(userCulture, fnDisplayMessage);
            
        }

    };

    return {
        getStatesForCountry: getStatesForCountry,
        getCurrentCulture: getCurrentCulture,
        checkUseCulture: checkUseCulture,
        getCurrentCountry: getCurrentCountry
    }
})(jQuery, GK.ErrorService, GK.NotificationService, GK.TranslationService);;
//site namespace
var GK = GK || {};


//-----------------
// Tag Manager Service Specific
//-----------------
GK.TagManagerService = (function ($, dataLayer, currencyCode) {

    var getCheckoutTypeDimension = function (isGuest) {
        var type = 'regular';
        if (isGuest || false) {
            type = 'guest';
        }

        return {
            'checkout-type': type
        };
    }

    var viewCart = function (cartSummary) {
        var products = [];
        $.each(cartSummary.Items, function (i, v) {
            var product = {
                item_id: v.OfferingId + '',
                item_name: v.Name,
                quantity: v.Quantity,
                modalityName: v.ModalityName,
                price: v.UnitDiscountedPrice,
                originalPrice: v.UnitPrice,
                discount: v.DiscountAmount || 0,
                gkId: v.ProductCode
            };

            if (v.Vendor) {
                product.item_brand = v.Vendor;
            }

            if (v.PromoCode && v.PromoCode.length > 0) {
                product.coupon = v.PromoCode;
            }

            products.push(product);
        });

        dataLayer.push({
            'event': 'view_cart',
            'ecommerce': {
                'currency': cartSummary.CurrencyCode,
                'value': cartSummary.OrderTotal,
                'items': products
            }
        });
    };

    var addToCart = function (offeringId, courseName, gkId, brand, price, quantity) {
        const addedItem = {
            'item_id': offeringId + '',
            'item_name': courseName,
            'price': price,
            'quantity': quantity,
            'gkId': gkId
        };

        if (brand) {
            addedItem.item_brand = brand
        }

        dataLayer.push({
            'event': 'add_to_cart',
            'ecommerce': {
                'currency': currencyCode,
                'value': price,
                'items': [ addedItem ]
            }
        });
    };

    var removeFromCart = function (offeringId, courseName, gkId, brand, value, unitPrice, quantity) {
        const removedItem = {
            'item_id': offeringId + '',
            'item_name': courseName,
            'price': unitPrice,
            'quantity': quantity,
            'gkId': gkId
        };

        if (brand) {
            removedItem.item_brand = brand
        }

        dataLayer.push({
            'event': 'remove_from_cart',
            'ecommerce': {
                'currency': currencyCode,
                'value': value,
                'items': [removedItem]
            }
        });
    };
    var purchase = function (transId, cartItems, total, totalDiscountAmount, isGuest, callback) {

        callback = callback || function () { };
        var productsList = [];
        var promos = [];
        $.each(cartItems, function (i, v) {
            var product = {
                'item_id': v.OfferingId + '',
                'item_name': v.Name,
                'quantity': v.Quantity,
                'price': v.UnitDiscountedPrice,
                'originalPrice': v.UnitPrice,
                'discount': v.DiscountAmount || 0,
                'gkId': v.ProductCode
            };

            if (v.Vendor) {
                product.item_brand = v.Vendor
            }

            if (v.PromoCode
                && v.PromoCode.length > 0) {
                if ($.inArray(v.PromoCode, promos) === -1) {
                    promos.push(v.PromoCode);
                }

                product.promo = v.PromoCode;
                product.coupon = v.PromoCode;
            }

            productsList.push(product);
        });

        var promises = [];
        var purchaseDeferred = $.Deferred();
        promises.push(purchaseDeferred);

        dataLayer.push({
            'event': 'purchase',
            'ecommerce': {
                'transaction_id': transId,
                'currency': currencyCode,
                'value': total,
                'couponDiscount': totalDiscountAmount || 0,
                'items': productsList
            },
            'eventCallback': function () {
                purchaseDeferred.resolve();
            }
        });

        $.each(productsList,
            function (i, product) {
                var deferred = $.Deferred();
                promises.push(deferred);

                dataLayer.push({
                    'event': 'productPurchase',
                    'product': product,
                    'eventCallback': function () {
                        deferred.resolve();
                    }
                });
            });

        if (promos.length > 0) {
            var promoDeferred = $.Deferred();
            promises.push(promoDeferred);

            var promoEvent = {
                'promoCode': promos,
                'event': 'promo',
                'eventCallback': function () {
                    promoDeferred.resolve();
                }
            };
            $.extend(promoEvent, checkoutTypeDimension);
            dataLayer.push(promoEvent);
        }

        $.when.apply(this, promises)
            .always(function () {
                callback();
            });
    };

    var checkoutStep = function (stepNumber, cartItems, isGuest) {
        //Step: 1 = Student Registration, 2 = Payment, 3 = Complete
        var products = [];
        var cartValue = 0;
        $.each(cartItems, function (i, v) {
            var product = {
                item_id: v.OfferingId + '',
                item_name: v.Name,
                quantity: v.Quantity,
                modalityName: v.ModalityName,
                price: v.UnitDiscountedPrice,
                originalPrice: v.UnitPrice,
                discount: v.DiscountAmount || 0,
                gkId: v.ProductCode
            };

            if (v.Vendor) {
                product.item_brand = v.Vendor;
            }

            if (v.PromoCode && v.PromoCode.length > 0) {
                product.coupon = v.PromoCode;
            }

            cartValue += product.price * product.quantity;

            products.push(product);
        });

        const eventData = {
            'ecommerce': {
                'currency': currencyCode,
                'value': cartValue,
                'items': products
            }
        }
        if (stepNumber === 1) {
            eventData.event = 'begin_checkout';
            dataLayer.push(eventData);
        } else if (stepNumber === 2) {
            eventData.event = 'add_payment_info';
            dataLayer.push(eventData);
        } // There is no event in GA4 for the final step so do nothing
    };

    var viewItemList = function (listId, listName, courses) {
        var products = [];
        $.each(courses, function (i, v) {
            var product = {
                index: i,
                item_id: v.Id + '',
                item_name: v.PageTitle,
                price: v.StartingPrice?.Amount,
                item_list_id: listId,
                item_list_name: listName,
            };

            products.push(product);
        });

        dataLayer.push({
            'event': 'view_item_list',
            'ecommerce': {
                'item_list_id': listId,
                'item_list_name': listName,
                'items': products
            }
        });
    }

    // When clicking on a course from the catalog page (compare to viewItem)
    var selectItem = function (listId, listTitle, course) {
        var courseItem = {
            item_id: course.Id + '',
            item_name: course.PageTitle,
            price: course.StartingPrice?.Amount,
            item_list_id: listId,
            item_list_name: listTitle,
            quantity: 1
        };

        dataLayer.push({
            'event': 'select_item',
            'ecommerce': {
                item_list_id: listId,
                item_list_name: listTitle,
                'items': [courseItem]
            }
        });
    }

    // When a course page loads (compared to select item)
    var viewItem = function (offerings) {
        if (offerings.length === 0) {
            // No offerings so we don't have any information to tag
            return; 
        }

        // GK does not need information about the different offerings (times) for a course, just the course information itself
        // So we just take the first offering as the exemplar item
        const exemplarItem = offerings[0]; 

        var item = {
            index: 0,
            item_id: exemplarItem.Id + '',
            item_name: exemplarItem.CourseName,
            price: exemplarItem.Price?.Amount,
            quantity: exemplarItem.quantity(),
            item_list_id: exemplarItem.ProductCode,
            item_list_name: exemplarItem.CourseName,
            gkId: exemplarItem.ProductCode
        };

        if (exemplarItem.Vendor) {
            item.item_brand = exemplarItem.Vendor
        }

        dataLayer.push({
            'event': 'view_item',
            'ecommerce': {
                'currency': exemplarItem.Price?.CurrencyCode,
                'value': exemplarItem.Price?.Amount,
                'items': [item]
            }
        });
    }

    var freeCourseRegister = function (cartItems, totalDiscountAmount, isGuest, callback) {

        //transaction ids do not exist yet so create a fake one
        var freeTransId = generatedUUID();

        callback = callback || function () { };
        var productsList = [];
        var promos = [];
        $.each(cartItems, function (i, v) {
            var product = {
                'id': v.OfferingId,
                'name': v.Name,
                'quantity': v.Quantity,
                'price': v.UnitDiscountedPrice,
                'originalPrice': v.UnitPrice,
                'productDiscount': v.DiscountAmount || 0,
                'gkId': v.ProductCode,
                'brand': v.Vendor
            };
            if (v.PromoCode
                && v.PromoCode.length > 0) {
                if ($.inArray(v.PromoCode, promos) === -1) {
                    promos.push(v.PromoCode);
                }

                product.promo = v.PromoCode;
                product.coupon = v.PromoCode;
            }

            productsList.push(product);
        });

        var promises = [];
        var purchaseDeferred = $.Deferred();
        promises.push(purchaseDeferred);

        var checkoutTypeDimension = getCheckoutTypeDimension(isGuest);
        var purchaseDimension = {
            'actionField': {
                'id': freeTransId,
                'revenue': 0,
                'couponDiscount': totalDiscountAmount || 0
            },
            products: productsList
        };
        $.extend(purchaseDimension, checkoutTypeDimension);

        dataLayer.push({
            'event': 'free-course-register',
            'ecommerce': {
                'currencyCode': currencyCode,
                'purchase': purchaseDimension
            },
            'eventCallback': function () {
                purchaseDeferred.resolve();
            }
        });

        $.each(productsList,
            function (i, product) {
                var deferred = $.Deferred();
                promises.push(deferred);

                dataLayer.push({
                    'event': 'productPurchase',
                    'product': product,
                    'eventCallback': function () {
                        deferred.resolve();
                    }
                });
            });

        if (promos.length > 0) {
            var promoDeferred = $.Deferred();
            promises.push(promoDeferred);

            var promoEvent = {
                'promoCode': promos,
                'event': 'promo',
                'eventCallback': function () {
                    promoDeferred.resolve();
                }
            };
            $.extend(promoEvent, checkoutTypeDimension);

            dataLayer.push(promoEvent);
        }

        $.when.apply(this, promises)
            .always(function () {
                callback();
            });
    };

    var roadblockShown = function () {
        dataLayer.push({
            'event': 'MCpopupshown',
            'pop-upType': 'subscription'
        });
    };

    var setCheckoutType = function (isGuest) {
        dataLayer.push(getCheckoutTypeDimension(isGuest));
    };

    var generatedUUID = function () {
        var uuid = 'fcxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
        return uuid;
    }

    //return public functions
    return {
        viewCart: viewCart,
        addToCart: addToCart,
        removeFromCart: removeFromCart,
        purchase: purchase,
        checkoutStep: checkoutStep,
        viewItemList: viewItemList,
        selectItem: selectItem,
        viewItem: viewItem,
        freeCourseRegister: freeCourseRegister,
        roadblockShown: roadblockShown,
        setCheckoutType: setCheckoutType,
        generatedUUID: generatedUUID
    };

})(jQuery, dataLayer, currencyCode);;
//site namespace
var GK = GK || {};

GK.UserService = (function ($, postbox, errorService) {

    var getUrl = function(action) {
        return '/umbraco/api/accountapi/' + action;
    }

    var checkIfDisplayNameAvailable = function (userId, newDisplayName, successCallback, errorCallback) {

        var url = getUrl("checkDisplayName");
        var requestData = { UserId: userId, newDisplayName: newDisplayName };

        $.ajax({
                type: "POST",
                data: JSON.stringify(requestData),
                url: url,
                contentType: "application/json"
            })
            .done(function(data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
            })
            .fail(function(jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                } else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            });
    };

    var getUserPromise = undefined;
    function getCurrentUser() {
        if (getUserPromise) {
            return getUserPromise;
        }
        var url = getUrl("getCurrentUser");
        getUserPromise = $.getJSON(url)
            .fail(function(jqXHR, textStatus, errorThrown) {
                errorService.handleError(jqXHR, textStatus, errorThrown, url);
            });

        return getUserPromise;
    };

    var login = function (userName, password, successCallback, errorCallback) {

        var url = getUrl("login");
        var requestData = { userName: userName, password: password };

        $.ajax({
            type: "POST",
            data: JSON.stringify(requestData),
            url: url,
            contentType: "application/json"
        })
            .done(function (data) {
                postbox.publish("login", data);
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                } else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            });
    };

    var getDefaultAddress = function (successCallback, errorCallback) {

        var url = getUrl("getDefaultAddress");
        $.ajax({
            type: "GET",
            url: url,
        })
            .done(function (data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                } else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            });
    };

    return {
        checkIfDisplayNameAvailable: checkIfDisplayNameAvailable,
        getCurrentUser: getCurrentUser,
        login: login,
        getDefaultAddress: getDefaultAddress
    };
})(jQuery, postbox, GK.ErrorService);;
//site namespace
var GK = GK || {};

//-----------------
// Cart Specific 
//-----------------
GK.CartService = (function ($, errorService, localeService, postbox, notificationService, translationService, tagManagerService) {

    //Cached cart summary
    //Subscribe to 'cartUpdated' to get updates
    var currentCartSummary = null;

    var getUrl = function (action) {
        return '/umbraco/api/cartapi/' + action;
    }

    function init() {
        //preload
        updateCartSummary();

        $("#view-cart-trigger").on("click", function () {
            tagManagerService.viewCart(cartSummary());
        });
    }

    function cartSummary() {
        return currentCartSummary;
    }

    function updateCartSummary(culture, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("get");
        culture = culture || localeService.getCurrentCulture();

        $.ajax(url,
            {
                type: "GET",
                data: { culture: culture }
            })
            .done(function (data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
                handleInactiveItems(data);
                currentCartSummary = data;
            }).fail(function (jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                } else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            }).always(function () {
                postbox.publish("cartUpdated");
                if (typeof alwaysCallback === "function") {
                    alwaysCallback();
                }
            });
    }

    var handleInactiveItems = function(cartData) {
        if (cartData && cartData.HasInactiveItems) {
            notifyInactiveItemsRemovedFromCart();
        }
    };

    function notifyInactiveItemsRemovedFromCart() {
        notificationService.notifyInfo(translationService.getTranslation("Some items have been removed from your cart because they are no longer available"));
    }

    function updateQuantity(offeringId, quantity, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("updatequantity");
        $.post(url,
        {
            offeringId: offeringId,
            quantity: quantity,
            cultureCode: localeService.getCurrentCulture()
        }).done(function (data) {
            if (typeof successCallback === "function") {
                successCallback(data);
            }
        }).fail(function (jqXHR, textStatus, errorThrown) {
            if (typeof errorCallback === "function") {
                errorCallback(jqXHR, textStatus, errorThrown, url);
            } else {
                errorService.handleError(jqXHR, textStatus, errorThrown, url);
            }
        }).always(function () {
            updateCartSummary();
            if (typeof alwaysCallback === "function") {
                alwaysCallback();
            }
        });
    };

    function removeCourse(offeringId, successCallback, errorCallback) {
        var url = getUrl("remove");
        $.post(url,
        {
            offeringId: offeringId,
            cultureCode: localeService.getCurrentCulture()
        }).done(function (data) {
            if (typeof successCallback === "function") {
                successCallback(data);
            }
        }).fail(function (jqXHR, textStatus, errorThrown) {
            if (typeof errorCallback === "function") {
                errorCallback(jqXHR, textStatus, errorThrown, url);
            } else {
                errorService.handleError(jqXHR, textStatus, errorThrown, url);
            }
        }).always(function () {
            updateCartSummary();
            if (typeof alwaysCallback === "function") {
                alwaysCallback();
            }
        });
    };

    function registerStudents(studentMap, purchaser, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("registerStudents");
        var data = {
            attendance: studentMap,  
            cultureCode: localeService.getCurrentCulture()
        };
        if (purchaser) {
            data.purchasingStudent = purchaser;
        }

        $.ajax({
                type: "POST",
                url: url,
                data: data,
                dataType: 'json',
                beforeSend: function (request) {
                    request.setRequestHeader("cultureCode", localeService.getCurrentCulture());
                }
            }).done(function (data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
            }).fail(function (jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                } else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            }).always(function() {
                updateCartSummary();
                if (typeof alwaysCallback === "function") {
                    alwaysCallback();
                }
            });
    }

    function removeStudent(offeringId, studentId, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("removeStudent");
        $.post(url,
        {
            offeringId: offeringId,
            studentId: studentId,
            cultureCode: localeService.getCurrentCulture()
        }).done(function (data) {
            if (typeof successCallback === "function") {
                successCallback(data);
            }
        }).fail(function (jqXHR, textStatus, errorThrown) {
            if (typeof errorCallback === "function") {
                errorCallback(jqXHR, textStatus, errorThrown, url);
            } else {
                errorService.handleError(jqXHR, textStatus, errorThrown, url);
            }
        }).always(function () {
            updateCartSummary();
            if (typeof alwaysCallback === "function") {
                alwaysCallback();
            }
        });
    };

    function addToCart(offeringId, quantity, promoCode, successCallback, errorCallback, completeCallback, skipCartExpand) {
        if (skipCartExpand === undefined) { skipCartExpand = false; }
        if (offeringId && quantity) {
            var url = getUrl("add");
            var reqObj = {
                offeringId: offeringId,
                quantity: quantity,
                cultureCode: localeService.getCurrentCulture()
            };
            if (promoCode) {
                reqObj.promoCode = promoCode;
            }
            $.post(url, reqObj)
                .done(function (data) {
                    updateCartSummary();
                    if (!skipCartExpand) {
                        postbox.publish("cartItemAdded");
                    }
                    if (typeof successCallback === "function") {
                        successCallback(data);
                    }

                    handleInactiveItems(data);
                }).fail(function(jqXHR, textStatus, errorThrown) {
                    if (typeof errorCallback === "function") {
                        errorCallback(jqXHR, textStatus, errorThrown, url);
                    }
                    else {
                        errorService.handleError(jqXHR, textStatus, errorThrown, url);
                    }
                }).always(function() {
                    if (typeof completeCallback === "function") {
                        completeCallback();
                    }
                });

        }
    }

    function addOn(offeringId, quantity, successCallback, errorCallback, completeCallback) {
        if (offeringId && quantity) {
            var url = getUrl("addOn");
            var reqObj = {
                offeringId: offeringId,
                quantity: quantity,
                cultureCode: localeService.getCurrentCulture()
            };
            $.post(url, reqObj)
                .done(function (data) {
                    if (typeof successCallback === "function") {
                        successCallback(data);
                    }
                }).fail(function (jqXHR, textStatus, errorThrown) {
                    if (typeof errorCallback === "function") {
                        errorCallback(jqXHR, textStatus, errorThrown, url);
                    }
                    else {
                        errorService.handleError(jqXHR, textStatus, errorThrown, url);
                    }
                }).always(function () {
                    updateCartSummary();
                    if (typeof completeCallback === "function") {
                        completeCallback();
                    }
                });

        }
    }

    function applyPromoCode(promoCode, successCallback, errorCallback, completeCallback) {
        if (promoCode) {
            var url = getUrl("applyPromoCode");
            $.post(url,
                {
                    promoCode: promoCode,
                    cultureCode: localeService.getCurrentCulture()
                })
                .done(function (data) {
                    if (typeof successCallback === "function") {
                        successCallback(data);
                    }

                }).fail(function (jqXHR, textStatus, errorThrown) {
                    if (typeof errorCallback === "function") {
                        errorCallback(jqXHR, textStatus, errorThrown, url);
                    }
                    else {
                        errorService.handleError(jqXHR, textStatus, errorThrown, url);
                    }
                }).always(function () {
                    if (typeof completeCallback === "function") {
                        completeCallback();
                    }
                });

        }
    }

    function clearPromoCode(successCallback, errorCallback, completeCallback) {
        var url = getUrl("clearPromoCode");
        $.post(url,
            {
                cultureCode: localeService.getCurrentCulture()
            })
            .done(function (data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }

            }).fail(function (jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                }
                else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            }).always(function () {
                if (typeof completeCallback === "function") {
                    completeCallback();
                }
            });

        
    }

    //init
    init();

    //return public functions
    return {
        updateQuantity: updateQuantity,
        removeCourse: removeCourse,
        registerStudents: registerStudents,
        removeStudent: removeStudent,
        cartSummary: cartSummary,
        addToCart: addToCart,
        notifyInactiveItemsRemovedFromCart: notifyInactiveItemsRemovedFromCart,
        applyPromoCode: applyPromoCode,
        clearPromoCode: clearPromoCode,
        addOn: addOn
    };

})(jQuery, GK.ErrorService, GK.LocaleService, postbox, GK.NotificationService, GK.TranslationService, GK.TagManagerService);;
//site namespace
var GK = GK || {};

//-----------------
// Quote Specific 
//-----------------
GK.QuoteService = (function ($, errorService, localeService, notificationService, translationService) {

    var getUrl = function (action) {
        return '/umbraco/surface/QuoteSurface/' + action;
    }

    function updatePricing(model, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("UpdatePricing");
        $.post(url, model)
            .done(function (data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
            }).fail(function (jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                } else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            }).always(function () {
                if (typeof alwaysCallback === "function") {
                    alwaysCallback();
                }
            });
    };

    //return public functions
    return {
        updatePricing: updatePricing
    };

})(jQuery, GK.ErrorService, GK.LocaleService, GK.NotificationService, GK.TranslationService);;
//site namespace
var GK = GK || {};

//-----------------
// Payment Specific 
//-----------------
GK.PaymentService = (function ($, errorService, localeService) {

    var getUrl = function (action) {
        return '/umbraco/api/paymentapi/' + action;
    }

    function submitVendorVoucherPayment(vendorId, vendorCode, transactionId, gRecaptchaResponse, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("vendorvoucher");
        $.post(url,
        {
            transactionId: transactionId,
            vendorId: vendorId,
            vendorCode: vendorCode,
            cultureCode: localeService.getCurrentCulture(),
            recaptchaToken: gRecaptchaResponse
        }).done(function (data) {
            if (typeof successCallback === "function") {
                successCallback(data);
            }
        }).fail(function (jqXHR, textStatus, errorThrown) {
            if (typeof errorCallback === "function") {
                errorCallback(jqXHR, textStatus, errorThrown, url);
            } else {
                errorService.handleError(jqXHR, textStatus, errorThrown, url);
            }
        }).always(function () {
            if (typeof alwaysCallback === "function") {
                alwaysCallback();
            }
        });
    }

    function submitPrePaidPayment(companyName, departmentName, transactionId, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("PrepaidCardPayment");
        $.post(url,
        {
            transactionId: transactionId,
            companyName: companyName,
            departmentName: departmentName,
            cultureCode: localeService.getCurrentCulture()
        }).done(function (data) {
            if (typeof successCallback === "function") {
                successCallback(data);
            }
        }).fail(function (jqXHR, textStatus, errorThrown) {
            if (typeof errorCallback === "function") {
                errorCallback(jqXHR, textStatus, errorThrown, url);
            } else {
                errorService.handleError(jqXHR, textStatus, errorThrown, url);
            }
        }).always(function () {
            if (typeof alwaysCallback === "function") {
                alwaysCallback();
            }
        });
    }

    function submitPOPayment(poNumber, file, transactionId, gRecaptchaResponse, successCallback, errorCallback, alwaysCallback) {

        //this adds support to IE9 - IE9 cannot upload files
        var url = getUrl("PurchaseOrder");
        var ajaxRequest;
        if (typeof FormData !== 'undefined' && FormData) {
            var data = new FormData();
            data.append('PurchaseOrderNumber', poNumber);
            data.append('TransactionId', transactionId);
            data.append('CultureCode', localeService.getCurrentCulture());
            data.append('RecaptchaToken', gRecaptchaResponse)
            if (file) {
                data.append('File', file);
            }
            ajaxRequest = {
                url: url,
                data: data,
                type: 'POST',
                cache: false,
                processData: false, //-- So jQuery won't convert the files arrays into strings which the server can't pick up.
                contentType: false // Set content type to false as jQuery will tell the server its a query string request
            };
        } else {
            ajaxRequest = {
                url: url,
                data: {
                    'PurchaseOrderNumber': poNumber,
                    'TransactionId': transactionId,
                    'CultureCode': localeService.getCurrentCulture()
                },
                type: 'POST',
                cache: false
            };
        }

        $.ajax(ajaxRequest).done(function (data) {
            if (typeof successCallback === "function") {
                successCallback(data);
            }
        }).fail(function (jqXHR, textStatus, errorThrown) {
            if (typeof errorCallback === "function") {
                errorCallback(jqXHR, textStatus, errorThrown, url);
            } else {
                errorService.handleError(jqXHR, textStatus, errorThrown, url);
            }
        }).always(function () {
            if (typeof alwaysCallback === "function") {
                alwaysCallback();
            }
        });
    }

    //return public functions
    return {
        submitVendorVoucherPayment: submitVendorVoucherPayment,
        submitPrePaidPayment: submitPrePaidPayment,
        submitPOPayment: submitPOPayment
    };

})(jQuery, GK.ErrorService, GK.LocaleService);;
//site namespace
var GK = GK || {};

//-----------------
// Offering Specific 
//-----------------
GK.OfferingService = (function ($, errorService) {

    var getUrl = function (action) {
        return '/umbraco/api/schedulebyapi/' + action;
    }
    
    function getScheduledOfferings(filter, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("getofferings");
        $.get(url, filter)
            .done(function(data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
            }).fail(function(jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                }
                else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            }).always(function() {
                if (typeof alwaysCallback === "function") {
                    alwaysCallback();
                }
            });
    };

    function getAddOns(selectedOfferingIds, cultureCode, successCallback, errorCallback, alwaysCallback) {
        var url = "/umbraco/surface/CourseSurface/AddOns/";
        var serializedObj = $.param({ cultureCode: cultureCode, selectedOfferingIds: selectedOfferingIds }, true); 
        $.get(url, serializedObj)
            .done(function (data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
            }).fail(function (jqXHR, textStatus, errorThrown) {
                if (typeof errorCallback === "function") {
                    errorCallback(jqXHR, textStatus, errorThrown, url);
                }
                else {
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                }
            }).always(function () {
                if (typeof alwaysCallback === "function") {
                    alwaysCallback();
                }
            });
    };

    //return public functions
    return {
        getScheduledOfferings: getScheduledOfferings,
        getAddOns: getAddOns
    };

})(jQuery, GK.ErrorService, GK.LocaleService, postbox, GK.NotificationService, GK.TranslationService);;
//site namespace
var GK = GK || {};

GK.ReferralService = (function ($, localeService) {

    var ensureExtoleExists = function () {
        /* Start Extole */
        (function (c, e, k, l, a) { c[e] = c[e] || {}; for (c[e].q = c[e].q || []; a < l.length;) k(l[a++], c[e]) })(window, "extole", function (c, e) { e[c] = e[c] || function () { e.q.push([c, arguments]) } }, ["createZone"], 0);
        /* End Extole */
    };

    var getZoneName = function(rootName) {
        var culture = localeService.getCurrentCulture();
        if ((culture || '').toLowerCase() === 'en-ca') {
            return rootName + "_ca";
        }
        return rootName;
    }

    var getId = function (usId, caId) {
        var culture = localeService.getCurrentCulture();
        if ((culture || '').toLowerCase() === 'en-ca') {
            return caId;
        }
        return usId;
    }

    var createZoneCallback = function (error, zone) {
        if (console) { console.log({ zone: zone, ele: zone.getElement(), name: zone.getName(), error: error }); }
        if (error) {
            //--Ensure element is hidden if something went wrong
            $(zone.getElement()).hide();
        }
        else {
            //--Ensure element is shown if everything went well
            $(zone.getElement()).show();
        }
    };

    var initGeneralCTATagZone = function(user, tag, zoneElementId) {
        ensureExtoleExists();
        var data = undefined;
        if (user && user.Authenticated) {
            data = {
                "first_name": user.FirstName,
                "last_name": user.LastName,
                "email": user.EmailAddress,
                "partner_user_id": user.Id
            };
        }

        extole.createZone({
            name: tag,
            element_id: zoneElementId,
            data: data
        }, createZoneCallback);
    };

    var trackRegistration = function (user) {
        ensureExtoleExists();
        
        extole.createZone({
            name: getZoneName('registration'),
            data: {
                "first_name": user.FirstName,
                "last_name": user.LastName,
                "email": user.EmailAddress,
                "partner_user_id": user.Id
            }
        });
    };

    var showOrderCompletionReferral = function (user, orderNumber, orderAmount, promoCode) {

        ensureExtoleExists();
        var confirmationElementId = getId('extole_zone_global_confirmation', 'extole-zone-confirmation_ca');
        extole.createZone({
            name: getZoneName('confirmation'),
            element_id: confirmationElementId,
            data: {
                "first_name": user.FirstName,
                "last_name": user.LastName,
                "email": user.EmailAddress,
                "partner_user_id": user.Id,
            }
        }, createZoneCallback);
        extole.createZone({
            name: getZoneName('conversion_confirmation_page'),
            data: {
                "first_name": user.FirstName,
                "last_name": user.LastName,
                "email": user.EmailAddress,
                "partner_user_id": user.Id,
                "partner_conversion_id": orderNumber,
                "cart_value": orderAmount
            }
        });
        extole.createZone({
            name: getZoneName('conversion'),
            data: {
                "email": user.EmailAddress,
                "coupon_code": promoCode,
                "first_name": user.FirstName,
                "last_name": user.LastName,
                "partner_user_id": user.Id,
                "partner_conversion_id": orderNumber,
                "cart_value": orderAmount

            }

        });
    };

    var createFooterReferralCTA = function (user) {
        var elementId = getId('extole_zone_global_footer', 'extole-zone-global_footer_ca');
        initGeneralCTATagZone(user, getZoneName('global_footer'), elementId);
    };

    var createProductReferralCTA = function (user) {
        var elementId = getId('extole_zone_product', 'extole-zone-product_ca');
        initGeneralCTATagZone(user, getZoneName('product'), elementId);
    };

    var createHeaderReferralCTA = function (user) {
        var elementId = getId('extole-zone-global_header', 'extole-zone-global_header_ca');
        initGeneralCTATagZone(user, getZoneName('global_header'), elementId);
    };

    return {
        trackRegistration: trackRegistration,
        showOrderCompletionReferral: showOrderCompletionReferral,
        createHeaderReferralCTA: createHeaderReferralCTA,
        createFooterReferralCTA: createFooterReferralCTA,
        createProductReferralCTA: createProductReferralCTA,
        initGeneralCTATagZone: initGeneralCTATagZone,
    };

})(jQuery, GK.LocaleService);;
//site namespace
var GK = GK || {};


//-----------------
// 3rd party scripts provided by TSA to manage recent couses
//-----------------
GK.RecentCoursesService = (function () {
    var cookieName = 'tsaRecentCourses';

    var getCookieArray = function () {
        $.cookie.json = true;
        var recentCourses = $.cookie(cookieName);
        $.cookie.json = false;
        return recentCourses;
    }

    var addCurrentCourse = function () {
        // This script should fire on individual course pages.
        // It gets the course name from the h1 & the URL of the page & stores them in a cookie for recall later on the home page in the new "recently viewed courses" module.
        // When a new course is added, it's added to the front of the recently viewed list in the cookie.
        // The list contains a maximum of 3 courses.

        // Look for existing recentCourses cookie & grab the value if it exists
        var recentCourses = getCookieArray();

        // Define current course & get it ready for plugging it into the recent courses array
        var coursePath = document.location.pathname; // Get course page path
        var courseName = document.querySelector('h1').innerHTML; // Get course name
        var newCourse = [coursePath, courseName]; // Bundle course path & name into an array

        // If a recent course array already exists for user, add the new course to the list
        if (typeof recentCourses !== 'undefined') { // If there were already courses stored in the tsaRecentCourses cookie ...
            if (recentCourses.toString().indexOf(newCourse.toString()) == -1) { // If the new course to add isn't already in the recent courses array ...
                if (recentCourses.length > 2) { // If there are already 3 courses stored ...
                    recentCourses.pop(); // Remove the oldest (last) course in the array
                }
                recentCourses.unshift(newCourse); // Add the new course to the front of the array
            }
        }
        else {
            // If a recent course array doesn't already exist for user, set it up & include the new course
            recentCourses = [newCourse];
        }

        // Push updated values to cookie
        var expires = new Date();
        expires.setTime(expires.getTime() + (60 * 24 * 60 * 60 * 1000));//60 days
        $.cookie.json = true;
        $.cookie(cookieName, recentCourses, { expires: expires, path: '/' });
        $.cookie.json = false;

        addToLocalStorage(newCourse);

    }

    var getLocalStorageArray = function () {
        var recentCourses = JSON.parse(localStorage.getItem('recentCourses'));
        if (!recentCourses) {
            recentCourses = [];
        }
        return recentCourses;
    }

    //extending script so we can store more items
    var addToLocalStorage = function (newCourse) {
        if (localStorage) {
            var recentCourses = getLocalStorageArray();
            var found = false;
            for (var i = 0; i < recentCourses.length; i++) { //make sure it doesnt already exist
                found = recentCourses[i].path == newCourse[0];
                if (found) {
                    recentCourses[i].viewed = new Date();
                    break;
                }
            }
            if (!found) {
                if (recentCourses.length >= 50) { // If there are already 50 courses stored ...
                    recentCourses.pop(); // Remove the oldest (last) course in the array
                }
                recentCourses.unshift({ path: newCourse[0], name: newCourse[1], viewed: new Date() }); // Add the new course to the front of the array
            }
            localStorage.setItem('recentCourses', JSON.stringify(recentCourses));
        }
    }

    var getRecentCourses = function () {
        // This script gets a list of recently viewed courses from a cookie & displays those courses as a list of links on the home page.
        // This script inserts the list after the h1 for demo purposes. The insertion destination should be updated for the new design.

        // Get recentCourses from cookie
        var recentCourses = getCookieArray();

        // Insert link list if cookie exists
        if (typeof recentCourses !== 'undefined') { // If there is a tsaRecentCourses cookie ...
            var recentCourseObjects = [];
            for (var i = 0; i < recentCourses.length; i++) { // For each of the recently viewed courses ...
                recentCourseObjects.push({ path: recentCourses[i][0], name: recentCourses[i][1] });
            }
            return recentCourseObjects;
        }

        return null;
    }

    var getRecentCoursesExtend = function () {
        if (!localStorage) return getRecentCourses();
        var recentCourses = getLocalStorageArray();
        if (!recentCourses || !recentCourses.length) return getRecentCourses();
        return recentCourses;
    }

    var removeRecentCourse = function (itemPath) {
        //remove from local storage
        var recentCoursesLocal = getLocalStorageArray();
        for (var i = 0; i < recentCoursesLocal.length; i++) { //make sure it doesnt already exist
            if (recentCoursesLocal[i].path == itemPath) {
                recentCoursesLocal.splice(i, 1)
                localStorage.setItem('recentCourses', JSON.stringify(recentCoursesLocal));
                break;
            };
        }
        
        //remove from cookie
        var recentCoursesCookie = getCookieArray();
        for (var i = 0; i < recentCoursesCookie.length; i++) { //make sure it doesnt already exist
            if (recentCoursesCookie[i][0] == itemPath) {
                recentCoursesCookie.splice(i, 1);
                // Push updated values to cookie
                var expires = new Date();
                expires.setTime(expires.getTime() + (60 * 24 * 60 * 60 * 1000));//60 days
                $.cookie.json = true;
                $.cookie(cookieName, recentCoursesCookie, { expires: expires, path: '/' });
                $.cookie.json = false;
                break;
            };
        }
    }

    //return public functions
    return {
        addCurrentCourse: addCurrentCourse,
        getRecentCourses: getRecentCourses,
        getRecentCoursesExtend: getRecentCoursesExtend,
        removeRecentCourse: removeRecentCourse
    };
})();;
//site namespace
var GK = GK || {};

//-----------------
// Search Specific 
//-----------------
GK.SearchService = (function ($, errorService, localeService) {

    var currentRequest;
    var suggestionCache = [];

    var getUrl = function (action) {
        return '/'+ location.pathname.split('/')[1] + '/' + action;//move it under a locale so we can cache. isnt under the search directory to avoid CF rules
    }

    function suggestions(term, pageType, successCallback, errorCallback, alwaysCallback) {
        var url = getUrl("searchsuggestions");

        if (currentRequest) {
            try { currentRequest.abort(); } catch (e) { }//only allow a single request at a time
        }

        if (suggestionCache[term + pageType] && typeof successCallback === "function") {
            successCallback(suggestionCache[term + pageType]);
            return;
        }

        currentRequest = $.getJSON(url,
            {
                searchTerms: term,
                culture: localeService.getCurrentCulture(),
                pageType: pageType
            })
            .done(function (data) {
                if (typeof successCallback === "function") {
                    successCallback(data);
                }
                suggestionCache[term + pageType] = data;
            })
            .fail(function (jqXHR, textStatus, errorThrown) {
                //disabled because it throws an error when aborted
                //if (typeof errorCallback === "function") {
                //    errorCallback(jqXHR, textStatus, errorThrown, url);
                //} else {
                //    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                //}
            }).always(function () {
                if (typeof alwaysCallback === "function") {
                    alwaysCallback();
                }
            });
    };


    //return public functions
    return {
        suggestions: suggestions
    };

})(jQuery, GK.ErrorService, GK.LocaleService);;
//site namespace
var GK = GK || {};

//set fallback for translation service in case its missing
//GK.TranslationService should get set before init on basepage template and it generated dynamically
if (!GK.TranslationService) { GK.TranslationService = (function() { return { getTranslation: function(englishText) { return englishText; } } })(); };

/* =======================================================================================================
    Functions
======================================================================================================= */
GK.Global = (function($, translationService, notificationService, userService, referralService) {
    function initRegistrationReferralTracking() {
        var cookieName = "registered";
        var justRegistered = $.cookie(cookieName);

        if (justRegistered) {
            userService.getCurrentUser().done(function(user) {
                if (user && user.Authenticated) {
                    referralService.trackRegistration(user);
                }

                //remove cookie so that they are registered once
                $.removeCookie(cookieName, { path: '/' });
            });
        }
    };

    function initHeaderAndFooterReferralCTA() {
        userService.getCurrentUser().done(function (user) {
            referralService.createHeaderReferralCTA(user);
            referralService.createFooterReferralCTA(user);
        });
    };

    function initReferralTags() {
        initRegistrationReferralTracking();
        initHeaderAndFooterReferralCTA();
    };


    /* Set up mobile variable for quick access
    ------------------------------------------------------------------------ */
    enquire.register("screen and (max-width:768px)", {
        setup: function() {
            GK.isMobileDevice = false;
        },
        match: function() {
            GK.isMobileDevice = true;
        },
        unmatch: function() {
            GK.isMobileDevice = false;
        }
    });

    /* Function utilityToggle / Sets classes for toggling utility dropdowns
    ------------------------------------------------------------------------ */
    function utilityToggle(e) {
        var $section = $('.ut-section');
        var $validSectionLink = $('div[class^="ut-"] > a');

        $validSectionLink.on('click', function(event) {
            if ($(this).attr('href') == '#') {
                event.preventDefault();
            }
            var $parent = $(this).parent();
            $('body').removeClass('focus');
            if ($parent.hasClass('open')) {
                $parent.removeClass('open');
            } else {
                $section.removeClass('open');
                $parent.addClass('open');
                if (($parent.attr('id') == "CartMenuContainer" || $parent.attr('id') == "StickyCartMenuContainer") && !$parent.hasClass('disable-cart-menu')) {
                    $('body').addClass('focus');
                    setCartHeight();
                }
            }
        });

        //Close Utility on outside clicks without stopping event propagation
        $(document).on('click', function(event) {
            if ((!$(event.target).closest('.ut-section > a').length) && (!$(event.target).closest('.ut-section').length)) {
                $('body').removeClass('focus');
                $('.ut-section').removeClass('open');
            }
        });

        //other close utility butons
        $('.utility-bar, .navigation--sticky').on('click', 'a.close-utility', function(event) {
            $('body').removeClass('focus');
            $('.ut-section').removeClass('open');
        });
    }

    /* Function setCartHeight / Sets height for Cart Dropdown when a lot of items are in cart
    ------------------------------------------------------------------------ */
    function setCartHeight() {
        var initialHeight = $('.ut-options.cart-sum').outerHeight() + 50; //plus some
        var pageHeight = $('body').outerHeight();

        //page is smaller or difference by 100 px
        if (initialHeight > pageHeight || Math.abs(pageHeight - initialHeight) < 100) {
            if ($('#CartMenuContainer').hasClass('open') || $('#StickyCartMenuContainer').hasClass('open')) {

                $('.ut-options.cart-sum .cart-summary').addClass('overflow');

                var footerHeight = $('.ut-cart .cart-summary .checkout-footer').outerHeight() + $('.ut-cart .cart-summary .checkout-help').outerHeight();
                var containerHeight = $('.ut-cart .cart-summary').outerHeight();

                var courseListHeight = containerHeight - footerHeight - 100;
                $('.ut-cart .cart-items').css({
                    'max-height': courseListHeight,
                    'overflow-y': 'scroll'
                });
            }
        }
    }

    function openCartMenu() {

        //open menu, fix height and scroll to top
        $('body').addClass('focus');
        var menuContainer = $('#CartMenuContainer');
        menuContainer.addClass('open');

        setCartHeight();

        $('html, body').animate({
            scrollTop: menuContainer.offset().top,
        }, 1000);
    }



    /* Function navToggle / Sets classes for toggling mobile navigation
    ------------------------------------------------------------------------ */
    function navToggle(e) {
        var $body = $('body');
        var $toggle = $('header .menu-toggle');
        var $close = $('header .menu-close');

        $toggle.on('click', function() {

            if ($body.hasClass('nav-open')) {
                $body.removeClass('nav-open').addClass('nav-closed');
            } else if ($body.hasClass('nav-closed')) {
                $body.removeClass('nav-closed').addClass('nav-open');
            } else {
                $body.removeClass('nav-closed').removeClass('nav-open').addClass('nav-open');
            }
        });

        $close.on('click', function() {
            $body.removeClass('nav-open').addClass('nav-closed');
        });

        //toggle sub menus
        var $subToggle = $('.parent > a');
        $subToggle.on('click', function(ev) {
            if ($body.hasClass('nav-open')) {
                ev.preventDefault();
                var $parentLI = $(this).parents('li');
                var $subMenu = $parentLI.find('ul');

                if ($parentLI.hasClass('open')) {
                    $parentLI.removeClass('open');
                    $subMenu.slideUp();
                } else {
                    $parentLI.addClass('open');
                    $subMenu.slideDown();
                }
            }
        });
    }

    /* Function sticky nav
    ------------------------------------------------------------------------ */
    function stickyNav(e) {
        $('.parent--sticky').on('click', function() {
            $(this).toggleClass('open');
        });

        //Close Search on outside clicks without stopping event propagation
        $(document).on('click', function(event) {
            if ($('.parent--sticky').hasClass('open')) {
                if (!$(event.target).closest('.parent--sticky').length) {
                    $('.parent--sticky').removeClass('open');
                }
            }
        });

        $('#content-container, #courseOverviewContainer').waypoint(function () {
            $('#header-container--sticky').toggleClass('fixed');
        });
        $('#content-container, #courseOverviewNewContainer').waypoint(function () {
            $('#header-container--sticky').toggleClass('fixed');
        });
    }


    /* Function sideNavToggle / Sets classes for toggling sidebar navigation
    ------------------------------------------------------------------------ */
    function sideNavToggle(e) {
        var $sideNav = $('.side-nav');
        var $toggle = $('.side-nav > h3');

        $toggle.on('click', function() {
            $sideNav.toggleClass('open');
        })
    }


    /* Function searchToggle / Sets classes for toggling desktop Search
    ------------------------------------------------------------------------ */
    function searchToggle() {
        var $searchToggle = $('.navigation .search > a');

        $searchToggle.on('click', function(e) {
            e.preventDefault();
            var thisSearchToggle = $(this);
            var $search = thisSearchToggle.closest('.navigation .search');
            var $searchInput = $search.find('input.search-field');
            if ($search.hasClass('open')) {
                $search.removeClass('open');
            } else {
                $search.removeClass('open').addClass('open');
                window.setTimeout(function () { $searchInput.focus(); }, 1);
            }
        });

        //Close Search on outside clicks without stopping event propagation
        $(document).on('click', function(event) {
            if ($('.search').hasClass('open')) {
                if (!$(event.target).closest('.search').length && event.target.getAttribute("class") != 'autocomplete-remove') {
                    $('.search').removeClass('open');
                }
            }
        });
    }

    /* Function filterToggle / Sets classes for toggling mobile catalog filter
      ------------------------------------------------------------------------ */
    function filterToggle(e) {
        var $body = $('body');
        var $toggle = $('.mobile-filter-btn');
        var $close = $('.filter-bar .menu-close');

        $toggle.on('click', function() {

            if ($body.hasClass('filter-open')) {
                $body.removeClass('filter-open').addClass('filter-closed');
            }
            else if ($body.hasClass('filter-closed')) {
                $body.removeClass('filter-closed').addClass('filter-open');
            }
            else {
                $body.removeClass('filter-closed').removeClass('filter-open').addClass('filter-open');
            }
        });

        $close.on('click', function() {
            $body.removeClass('filter-open').addClass('filter-closed');
        });
    }

    /* Builds a list of active filters
    --- Specify the controls that contain filter options and the action that happens when you click a tag
    ------------------------------------------------------------------------ */
    function displayResultFilterTags(filterControlIds, clickFn) {
        var action = function() {
            var filterHolder = $('#filterTags');

            //find selected options for each control
            $.each(filterControlIds, function(i, ctrl) {
                var control = $(ctrl);
                //checkboxes
                var selectedOptions = control.find('.filter-topic-option.checked');
                $.each(selectedOptions, function(index, opt) {
                    var option = $(opt);
                    var text = option.find('strong').text();
                    if (text != null && text != '') {
                        //some options have (n) in text that need removed
                        if (text.lastIndexOf(')') == text.length - 1 && text.lastIndexOf('(') > 0) {
                            text = text.substring(0, text.lastIndexOf('('));
                        }

                        var tag = $('<a class="tag"><span>' + text + '</span> <i class="icon icon-x"></i></a>');

                        tag.on('click', function() {
                            option.toggleClass('checked');
                            clickFn(this, option);
                        });

                        filterHolder.append(tag);
                    }
                });

                //selects
                selectedOptions = $(ctrl).find('select');
                $.each(selectedOptions, function(index, opt) {
                    var option = $(opt);
                    var selected = option.find('option:selected');
                    var text = selected.text();
                    if (text != null && text != '' && selected.val() != '') {
                        var tag = $('<a class="tag"><span>' + text + '</span> <i class="icon icon-x"></i></a>');

                        tag.on('click', function() {
                            if (option[0].selectize) {
                                option[0].selectize.clear();
                            } else {
                                selected.prop("selected", false);
                                clickFn(this, selected);
                            }
                        });

                        filterHolder.append(tag);
                    }
                });
            });

            //hide control if no filters
            if (filterHolder.children().length === 0) {
                $('#filterTagsTitle').hide();
            } else {
                $('#filterTagsTitle').show();
            }
        }
        window.setTimeout(action, 1); //needs to happen after selectize so make sure this loads last
    }

    /* Alert Messages
    -------------------------------------------------------------------------*/
    function alertBox() {
        $('.alert-box').on('click', '.close', function() {
            // Remove all classes except 'alert-box' to hide
            $(this).closest('.alert-box').attr('class', "alert-box");
        });

        $('.alert-box').on('click', '.dismiss-notice', function() {
            // Remove all classes except 'alert-box' to hide
            $(this).closest('.alert-box').attr('class', "alert-box");
            notificationService.isMessageDismissed($(this).data("notice"), true);
        });
    }



    /* Read More / View More Global Toggles
    -------------------------------------------------------------------------*/
    function readMoreToggle() {
        var toggleWrapper = $('.toggle-wrapper');
        var viewMore = translationService.getTranslation('View More');

        toggleWrapper.each(function() {
            var $this = $(this);
            if ($this.data('readMoreToggle.initialized')) {
                //Toggle is already initialized, so move on
                return;
            }

            //If content is taller than 100px
            if ($this.height() > 250) {

                $this.data('readMoreToggle.initialized', true);

                var $readmore = $('<p class="read-more"><a href="#"><em>' + viewMore + '</em> <span class="icon icon-arrow-down"></span></a></p>');
                $this.addClass('activated').after($readmore);

                $readmore.on('click.readMoreToggle', 'a', function(e) {
                    e.preventDefault();
                    var current = $(this).parent('.read-more').siblings('.toggle-wrapper');
                    var link = $(this).find('em');

                    current.toggleClass('open');
                    if (current.hasClass('open')) {
                        link.text(translationService.getTranslation('View Less'));
                    } else {
                        link.text(viewMore);

                        if (GK.isMobileDevice === true) {
                            $('html, body').animate({
                                scrollTop: current.offset().top
                            }, 1000);
                        }
                    }
                });
            }
        });
    }

    function scrollToError() {
        var errors = $(".error:visible");
        if (errors.length) {
            $('html, body').animate({
                scrollTop: errors.first().offset().top
            }, 1000);
        }
    }

    /* get parameter from querystring
    -------------------------------------------------------------------------*/
    function getUrlParameter(sParam) {
        var sPageURL = decodeURIComponent(window.location.search.substring(1));
        var sURLVariables = sPageURL.split('&');
        var sParameterName;

        for (var i = 0; i < sURLVariables.length; i++) {
            sParameterName = sURLVariables[i].split('=');

            if (sParameterName[0] === sParam) {
                return sParameterName[1] === undefined ? true : sParameterName[1];
            }
        }
    };


    /* includes the wistia script only if any popover links exist
    -------------------------------------------------------------------------*/
    function wistiaInclude() {
        if ($("a[class^='wistia-popover']").length > 0) {
            //<!-- Call Wistia video lightbox script -->
            var first = document.getElementsByTagName('script')[0];
            var js = document.createElement('script');
            js.src = "//fast.wistia.com/assets/external/popover-v1.js";
            js.charset = "ISO-8859-1";
            first.parentNode.insertBefore(js, first);
        }
    }

    /* Toggle Long Filter Lists
    -------------------------------------------------------------------------*/
    function toggleLongFilterLists() {
        var canToggle = $('.toggle-list > ul');
        canToggle.each(function() {
            //If list is longer than 5, hide 6+ and add toggle link
            if ($(this).children('li').length > 5) {
                var viewMore = translationService.getTranslation('View More');
                $(this).after('<a class="show-more" href="#">' + viewMore + ' <span class="icon icon-arrow-down"></span></a>');
                $(this).children('li').each(function(i) {
                    if (i > 5) {
                        $(this).hide();
                    }
                    i++;
                });
            }
        });

        $('body').on('click', '.toggle-list > .show-more', function(e) {
            e.preventDefault();
            var link = $(this);
            var list = $(this).siblings('ul');
            var listitems = $(this).siblings('ul').children('li');

            if (list.hasClass('long')) {
                listitems.each(function(i) {
                    if (i > 5) {
                        $(this).hide();
                    }
                    i++;
                });
                list.removeClass('long');
                var viewMore = translationService.getTranslation('View More');
                link.html(viewMore + ' <span class="icon icon-arrow-down"></span>');
            } else {
                list.addClass('long');
                var viewLess = translationService.getTranslation('View Less');
                link.html(viewLess + ' <span class="icon icon-arrow-down"></span>');
                listitems.each(function() {
                    $(this).show();
                });
            }
        });
    }

    /* Toggle open/close sory by drop downs
    -------------------------------------------------------------------------*/
    function sortByToggle() {
        $('.sort-by div.drop').click(function() {
            $(this).parent('.sort-by').toggleClass('active');
        });

        //Close 'dropdown' when clicked elsewhere or on another 'dropdown'
        $(document).on('click.sortByToggle', function(e) {
            var $target = $(e.target);
            var $sortBy = undefined;
            if ($target.is('.sort-by')) {
                $sortBy = $target;
            }
            else {
                var $sortBys = $target.parents('.sort-by');
                if ($sortBys.length > 0) {
                    $sortBy = $sortBys[0];
                }
            }

            if ($sortBy) {
                $('.sort-by').not($sortBy).removeClass('active');
            }
            else {
                $('.sort-by').removeClass('active');
            }
        });
    }

    /* Enable enhanced tooltips
    -------------------------------------------------------------------------*/
    function tooltips() {

        // Complex Tooltips that use HTML
        $('.qtooltip').each(function() {
            if ($(this).data('hasTooltip')) return;
            $(this).data('hasTooltip', true);

            $(this).qtip({
                content: {
                    text: $(this).next('.tooltip') // Use the "div" element next to this for the content
                },
                style: {
                    classes: 'qtip-gk'
                },
                position: {
                    viewport: $(window)
                },
                show: {
                    solo: true
                },
                hide: {
                    fixed: true
                }
            }).on('click', function() {
                $(this).qtip("hide");
            });
        });

        // Simple Tooltips based off Title attribute
        $('[title!=""]:not(.icon-search)').each(function() {
            if ($(this).data('hasTooltip')) return;
            $(this).data('hasTooltip', true);

            $(this).qtip({
                style: {
                    classes: 'qtip-tipsy'
                },
                position: {
                    viewport: $(window)
                },
                show: {
                    solo: true
                },
                events: {
                    show: function(event, api) {
                        //Don't show if any element has 'noqtip' class
                        if ($(api.target).hasClass('noqtip')) {
                            try { event.preventDefault(); } catch (e) {}
                        }
                    }
                }
            }).on('click', function() {
                $(this).qtip("hide");
            });
        });
    };

    /* Function replaceSVGs / update svg img to inline svg
    ------------------------------------------------------------------------ */
    function replaceSVGs() {

        /*  Replace all SVG images with inline SVG */
        $('img[data-svgsrc]').each(function() {
            var $img = $(this);
            var imgID = $img.attr('id');
            var imgClass = $img.attr('class');
            var imgURL = $img.attr('data-svgsrc');

            if (!imgURL || imgURL.length == 0) {
                return;
            }

            $.get(imgURL,
                function(data) {
                    // Get the SVG tag, ignore the rest
                    var $svg = $(data).find('svg');
                    // Add replaced image's ID to the new SVG
                    if (typeof imgID !== 'undefined') {
                        $svg = $svg.attr('id', imgID);
                    }
                    // Add replaced image's classes to the new SVG
                    if (typeof imgClass !== 'undefined') {
                        $svg = $svg.attr('class', imgClass + ' replaced-svg');
                    }
                    // Remove any invalid XML tags as per http://validator.w3.org
                    $svg = $svg.removeAttr('xmlns:a');
                    // Replace image with new SVG
                    $img.replaceWith($svg);
                });
        });

    }

    /**
     * Copy string to clipboard
     */
    function copyStringToClipboard(str) {
        var el = document.createElement('textarea');
        el.value = str;
        el.setAttribute('readonly', '');
        el.style = { position: 'absolute', left: '-9999px' };
        document.body.appendChild(el);
        el.select();
        document.execCommand('copy');
        document.body.removeChild(el);
    }

    /* Function replaceSVGs / update svg img to inline svg
    ------------------------------------------------------------------------ */
    function initResponsiveTableWrapper() {
        var $responsiveTableTarget = $('.editor table'),
            $hasResponsiveTableClass = $('.editor table tr:first-child td:first-child .responsive-table');

        if ($hasResponsiveTableClass.length > 0) {
            $responsiveTableTarget.wrap('<div class="responsive-table-wrapper"></div>');
        }
    }

    function loadingOverlayEvents() {
        $("form.show-loading-overlay").submit(function() {
            notificationService.notifyLoading(true);
        });
    }


    /* Owl Carousel
    ------------------------------------------------------------------------ */
    function initOwlCarousel() {

        var $carouselOffers = $('#owl-carousel-offers');
        var nextText = translationService.getTranslation("Next Slide");
        var previousText = translationService.getTranslation("Previous Slide");

        if ($carouselOffers.length > 0) {
            var multi = $carouselOffers.children().length > 1;
            $carouselOffers.owlCarousel({
                loop: multi,
                nav: multi,
                items: 1,
                navText: multi ? ['<svg class="carousel-arrow-left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 75"><defs><style>.cls-1{fill:none;stroke:#4DA4D9;stroke-linecap:round;stroke-miterlimit:10;stroke-width:3px;}</style></defs><title>slider-arrow-left</title><path class="cls-1" d="M38,73,2.5,37.5,38,2"/></svg>', '<svg class="carousel-arrow-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 75"><defs><style>.cls-1{fill:none;stroke:#4DA4D9;stroke-linecap:round;stroke-miterlimit:10;stroke-width:3px;}</style></defs><title>slider-arrow-right</title><path class="cls-1" d="M2,2,37.5,37.5,2,73"/></svg>'] : ['','']
            });
        }
    }


    /* Lazy Images
    ------------------------------------------------------------------------ */
    function lazyImages() {

        var lazyloadImages;    

        if ("IntersectionObserver" in window) {
            lazyloadImages = document.querySelectorAll("img[data-src]");
            var imageObserver = new IntersectionObserver(function(entries, observer) {
                entries.forEach(function(entry) {
                    if (entry.isIntersecting) {
                        var image = entry.target;
                        image.src = image.dataset.src;
                        image.removeAttribute("data-src");
                        imageObserver.unobserve(image);
                    }
                });
            });
      
            lazyloadImages.forEach(function(image) {
                imageObserver.observe(image);
            });
        } else {  
            var lazyloadThrottleTimeout;
            lazyloadImages = document.querySelectorAll("img[data-src]");
            
            function lazyload () {
            if(lazyloadThrottleTimeout) {
                clearTimeout(lazyloadThrottleTimeout);
            }    
        
            lazyloadThrottleTimeout = setTimeout(function() {
                var scrollTop = window.pageYOffset;
                
                lazyloadImages.forEach(function(img) {
                    if(img.offsetTop < (window.innerHeight + scrollTop)) {
                        img.src = img.dataset.src;
                        img.removeAttribute("data-src");
                    }
                });

                if(lazyloadImages.length == 0) { 
                    document.removeEventListener("scroll", lazyload);
                    window.removeEventListener("resize", lazyload);
                    window.removeEventListener("orientationChange", lazyload);
                }
            }, 20);
            }
        
            document.addEventListener("scroll", lazyload);
            window.addEventListener("resize", lazyload);
            window.addEventListener("orientationChange", lazyload);
        }
    }

    /* =======================================================================================================
        Document Ready Scripts
    ======================================================================================================= */

    function init() {
        replaceSVGs();
        utilityToggle();
        navToggle();
        sideNavToggle();
        searchToggle();
        filterToggle();
        alertBox();
        readMoreToggle();
        wistiaInclude();
        toggleLongFilterLists();
        sortByToggle();
        tooltips();
        initResponsiveTableWrapper();
        loadingOverlayEvents();
        initOwlCarousel();
        scrollToSection();
        initReferralTags();
        lazyImages();

        //Changes Select elements to HTML for styling
        if (!window.isLteIE) {
            enquire.register("screen and (min-width:768px)", {
                match: function() {
                    $('select').not('.nav-search-select').selectize({
                        onInitialize: function() {
                            var s = this;
                            //preserve all attributes
                            this.revertSettings.$children.each(function() {
                                $.extend(s.options[this.value], $(this).data());
                            });
                        }
                    });
                }
            });
        }

        enquire.register("screen and (max-width:1023px)", {
            match: function() {
                $('.trigger').addClass('noqtip');
            },
            unmatch: function() {
                $('.trigger').removeClass('noqtip');            },
            destroy: function() {}
        });


        //Disabled events when disabled
        $('body').on('click', '.btn.disabled', function(e) {
            e.preventDefault();
        });

        //Global equalheights set based on class 
        $('.equalheight > *').matchHeight({
            byRow: true,
            property: 'height',
            target: null,
            remove: false
        });


        //-----------------
        // HP Specific 
        //-----------------
        $('.training-verticals article').matchHeight();
        $('.training-verticals article .editor p').matchHeight();

        //-----------------
        // Catalog Landing Specific 
        //-----------------
        function breakList(numOfLists, list, className) {

            var listLength = list.find("li").size();
            var numInColumn = Math.ceil(listLength / numOfLists);
            for (var i = 0; i < numOfLists; i++) {
                var listItems = list.find("li").slice(0, numInColumn);
                if (listItems.length > 0) {
                    var newList = $('<ul class="' + className + '"/>').append(listItems);
                    list.before(newList);
                }
            }

            //Remove original empty list
            list.remove();
        }

        breakList(4, $(".cat-vendors"), 'cat-vendors');
        breakList(3, $(".cat-topics"), 'cat-topics');

        $('ul.cat-vendors li').matchHeight({
            byRow: false,
            property: 'height',
            target: null,
            remove: false
        });
        $('ul.cat-topics li').matchHeight({
            byRow: false,
            property: 'height',
            target: null,
            remove: false
        });

        function scrollToOptions() {
            $id = $('h2#' + $(this).attr('data'));
            $("html, body").animate({ scrollTop: $id.offset().top }, 600);
            return false;
        }

        enquire.register("screen and (min-width:1024px)", {

            match: function() {
                $('.options > li').on('click', scrollToOptions);
                if ($('body').hasClass('nav-open')) {
                    $('body').removeClass('nav-open');
                }
                stickyNav();
            },
            unmatch: function() {
                $('.options > li').off('click', scrollToOptions);
                stickyNav();
            },
            setup: function() {},
            deferSetup: true,
            destroy: function() {}
        });


        //-----------------
        // Jump To Section 
        //-----------------
        function scrollToSection() {
            var $scrollToSectionLink = $('a.jump-link'),
                $jumpMenuLink = $('#jump-menu a');

            if ($scrollToSectionLink.length > 0) {
                $scrollToSectionLink.click(function(event) {
                    /* Act on the event */
                    event.preventDefault();
                    if ($(this).parents('#jump-menu').length) {
                        $jumpMenuLink.removeClass('active');
                    }

                    if ($(this).parents('#jump-menu').length) {
                        $(this).addClass('active');
                    }
                    var $target = $(this).attr('data-target');
                    $("html, body").animate({ scrollTop: $('#' + $target).offset().top }, 600);
                });
            }
        }

        //-----------------
        // Delivery Format Page Specific
        //-----------------
        function deliveryFormatTabs() {
            $('.formats.v2 > li').on('click', function(e) {
                e.preventDefault();
                $('.formats.v2 > li').removeClass('active');
                $(this).toggleClass('active');
                var tabContentId = "#" + $(this).find('a').attr('data-tabId');

                $('.df-section').removeClass('active');
                $(tabContentId).addClass('active').fadeIn();

                if (history && history.replaceState) {
                    var tabUrl = $(this).find('a').attr('href');
                    history.replaceState(null, null, tabUrl);
                }

                $('html, body').animate({
                    scrollTop: $(this).offset().top - 30
                }, 1000);
            });
        }
        deliveryFormatTabs();


        //-----------------
        // Course Detail Specific
        //-----------------
        $('ul.formats > li').matchHeight({
            byRow: false,
            property: 'height',
            target: null,
            remove: false
        });
        $('ul.formats > li h2').matchHeight({
            byRow: false,
            property: 'height',
            target: null,
            remove: false
        });
        $('ul.formats > li .format-info').matchHeight({
            byRow: false,
            property: 'height',
            target: null,
            remove: false
        });
        $('ul.formats > li .btn').matchHeight({
            byRow: false,
            property: 'height',
            target: null,
            remove: false
        });

        //-----------------
        // Checkout (all) Specific
        //-----------------
        $('.checkout-steps ol > li strong').matchHeight({
            byRow: true,
            property: 'height',
            target: null,
            remove: false
        });

        //-----------------
        // Checkout Payment Specific for iframe laoding
        //-----------------
        var iframe = $('.payment-cc iframe');
        iframe.on('load', function() {
            $('.payment-cc').addClass('loaded');
        });

        //-----------------
        // Vendor Landing Specific
        //-----------------
        $('.featured-course-list li > a').matchHeight({
            byRow: true,
            property: 'height',
            target: null,
            remove: false
        });
        $('.stats li .stat-wrap').matchHeight({
            byRow: false,
            property: 'height',
            target: null,
            remove: false
        });

        //-----------------
        // Awards Page Specific
        //-----------------
        $('.awards > .award > .award-img').matchHeight({
            byRow: true,
            property: 'height',
            target: null,
            remove: false
        });

        //-----------------
        // Profile Specific 
        //-----------------
        $('.user-photo > a').on('click', function() {
            $('.pic-upload').trigger('click');
        });

        $('.course-select__tabs-item').on('click', function() {
            $('.course-select__tabs-item').removeClass('active');
            $(this).addClass('active');
        });

        //-----------------
        // auto download links - disables auto download if the user clicks the link
        //-----------------
        $('a.auto-download').each(function() {
            var link = $(this);
            link.on('click', function() {
                link.attr('downloaded', 1);
            });
            window.setTimeout(function() {
                if (link.attr('downloaded') != 1) {
                    link.attr('downloaded', 1);
                    var h = link.attr('href');
                    if (h) {
                        window.location = h;
                    }
                }
            }, 1000);
        });


        //-----------------
        // Example query from Enquire. (http://wicky.nillia.ms/enquire.js)
        //-----------------
        enquire.register("screen and (min-width:900px)", {

            match: function() {
                $('#jump-menu').removeClass('jump--dropdown');
            },

            unmatch: function() {
                $('#jump-menu').addClass('jump--dropdown');
                $('#jump-menu .sort-by').removeClass('active');
            }

        }); // end enquire.register

        //-----------------
        // Course Overview Page Specific
        //-----------------

        $merchandiseContent = $('.p-course-overview #merchandising-content');
        $merchandiseContentNew = $('.p-course-overview-2 #merchandising-content');
        $pcourseOverviewNew = $('.p-course-overview-2');
        $partnerLogoContent = $('#partner-logo');
        $sideColTop = $('#side-col--top');
        $sideColBottom = $('#side-col--bottom');
        $courseOverviewDownloads = $('#downloads');
        $courseDelivery = $('#course-delivery');
        $courseSelectFilters = $('#course-select-filters');
        $courseOverviewCard = $('#course-overview-card');
        $courseInfo = $('#course-info');
        $ratingsNumbersCredits = $('#ratings-numbers-credits');
        $userSelection = $('#user-selection');
        $overviewBadges = $('#overview-badges');
        $courseOverviewVideo =  $('#course-overview-video');

        // for new course overview template
        function desktopOverViewNew() {
            if ($pcourseOverviewNew.length > 0) {
                $courseOverviewCard.appendTo($sideColTop);
                $ratingsNumbersCredits.insertBefore($userSelection);
                $courseOverviewVideo.prependTo($courseOverviewCard);
            }

            if ($merchandiseContentNew.length > 0) {
                $partnerLogoContent.appendTo($sideColTop);
                $merchandiseContentNew.appendTo($sideColTop);
                $courseOverviewDownloads.insertAfter($partnerLogoContent);
            }

            if (!$userSelection.length) {
               $ratingsNumbersCredits.appendTo($courseOverviewCard);
            }
        }

        // for original course overview
        function desktopCourseOverview() {
            if ($merchandiseContent.length > 0) {
                $partnerLogoContent.appendTo($sideColBottom);
                $merchandiseContent.prependTo($sideColBottom);
                $courseOverviewDownloads.insertBefore($courseDelivery);
            }
        }

        // for new course overview template
        function mobileCourseOverviewNew() {
            if ($pcourseOverviewNew.length > 0) {
                $courseOverviewCard.insertBefore($courseInfo);
                $ratingsNumbersCredits.insertBefore($overviewBadges);
                $courseOverviewVideo.insertAfter($courseOverviewCard);
            }

            if ($merchandiseContentNew.length > 0) {
                $merchandiseContentNew.appendTo('.course-overview .main-col');
                $partnerLogoContent.insertBefore($merchandiseContentNew);
                $courseOverviewDownloads.insertAfter($partnerLogoContent);
                $courseDelivery.insertAfter($courseOverviewDownloads);
            }
        }

        function mobileCourseOverview() {
            if ($merchandiseContent.length > 0) {
                $merchandiseContent.appendTo('.course-overview .main-col');
                $partnerLogoContent.prependTo($merchandiseContent);
                $courseDelivery.insertBefore($courseOverviewDownloads);
            }
        }

        enquire.register("screen and (min-width:1023px)", {

            match: function() {
                desktopCourseOverview();
                desktopOverViewNew();
            },

            unmatch: function() {
                mobileCourseOverview();
                mobileCourseOverviewNew();
            }

        }); // end enquire.register

    }

    $(document).ready(function() { init(); });
    return {
        initializeReadMoreToggles: readMoreToggle,
        openCartMenu: openCartMenu,
        displayResultFilterTags: displayResultFilterTags,
        scrollToError: scrollToError,
        tooltips: tooltips,
        getUrlParameter: getUrlParameter,
        copyStringToClipboard: copyStringToClipboard
    };

})(jQuery, GK.TranslationService, GK.NotificationService, GK.UserService, GK.ReferralService);;
(function ($) {
    //event to unload ko objects
    //usage: "unload: removedEvent.bind($data)"
    ko.bindingHandlers.unload = {
        init: function (element, valueAccessor) {
            var eventName = 'ko_unload_event';
            if (!$.event.special[eventName]) {
                $.event.special[eventName] = {
                    remove: function (o) {
                        o.data.onUnload();
                    }
                };
            }
            $(element).on(eventName, { onUnload: valueAccessor() }, $.noop);
        }
    };

    //event to unload dom elements
    //usage: "unloadElement: removedEvent"
    ko.bindingHandlers.unloadElement = {
        init: function (element, valueAccessor) {
            var eventName = 'ko_unloadElement_event';
            if (!$.event.special[eventName]) {
                $.event.special[eventName] = {
                    remove: function (o) {
                        o.data.onUnload();
                    }
                };
            }
            $(element).on(eventName, { onUnload: valueAccessor().bind(element) }, $.noop);
        }
    };
})(jQuery);;
(function ($) {
    ko.bindingHandlers.tooltip = {
        init: function (element, valueAccessor) {
            var el = $(element);
            
            // Gives us the real value if it is a computed observable or not
            var valueUnwrapped = ko.unwrap(valueAccessor());
            if (!el.data('qtip-enabled')) {
                el.data('qtip-enabled', true);
                el.attr('title',valueUnwrapped);
                el.qtip({
                    overwrite: false,
                    content: {
                        text: valueUnwrapped
                    },
                    style: {
                        classes: 'qtip-tipsy'
                    },
                    position: {
                        viewport: $(window)
                    },
                    show: {
                        solo: true
                    },
                    events: {
                        show: function(event, api) {
                            //Don't show if any element has 'noqtip' class
                            if ($(api.target).hasClass('noqtip')) {
                                try {
                                    event.preventDefault();
                                } catch (e) {
                                }
                            }
                        }
                    }
                }).on('click', function() {
                    el.qtip("hide");
                });
            }

            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                el.qtip("destroy", true);
            });
        }
    }
})(jQuery);;
(function (translationService) {
    ko.validation.init({
        errorClass: 'error',
        decorateInputElement: true,
        insertMessages: false,
        errorsAsTitle: false
    });

    ko.validation.rules['checked'] = {
        validator: function (value) {
            if (!value) {
                return false;
            }
            return true;
        }
    };

    ko.validation.rules['requiredIfNotEmpty'] = {
        validator: function (value, collection) {
            if (!collection) {
                return true;
            }

            if ((ko.isObservable(collection) && collection().length > 0)
                || (!ko.isObservable(collection) && collection.length > 0)) {
                if (!value) {
                    return false;
                }
            }

            return true;
        }
    };

    ko.validation.rules['requiredIf'] = {
        validator: function(value, condition) {
            var required = condition && ((ko.isObservable(condition) && condition()) || !ko.isObservable(condition));
            if (!required) {
                return true;
            }

            if (value === undefined || value === null) {
                return false;
            }

            var testVal = value;
            if (typeof (value) === 'string') {
                if (String.prototype.trim) {
                    testVal = value.trim();
                }
                else {
                    testVal = value.replace(/^\s+|\s+$/g, '');
                }
            }
            
            return ((testVal + '').length > 0);
        }
    };

    ko.validation.rules['fileSize'] = {
        validator: function(value, max) {
            if (!value
                || !ko.isObservable(value.file)
                || !value.file()) {
                return true;
            }

            return value.file().size <= max;
        }
    };

    ko.validation.rules['mimeType'] = {
        validator: function (value, type) {
            if (!value
                || !ko.isObservable(value.file)
                || !value.file()) {
                return true;
            }

            return value.file().type.indexOf(type) !== -1;
        }
    };

    ko.validation.rules['fileExtension'] = {
        validator: function(value, extensions) {
            if (!value
                || !ko.isObservable(value.file)
                || !value.file()) {
                return true;
            }

            var extension = value.file().name.substr(value.file().name.lastIndexOf('.') + 1);
            var match = ko.utils.arrayFirst(extensions, function(item) {
                return extension && item.toLowerCase() == extension.toLowerCase();
            });

            return match;
        }
    };

    ko.validation.rules['minValue'] = {
        validator: function (value, minVal) {
            return value >= minVal;
        }
    };

    // This assumes the normal 'email' rule is run before this rule
    ko.validation.rules['emailBlacklist'] = {
        validator: function (value) {
            const domain = value.split('@')[1];
            const isBlacklisted = blacklistedDomains.includes(domain);
            return !isBlacklisted;
        }
    };

    ko.validation.registerExtenders();

    ko.bindingHandlers.validateFor = {
        update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var value = valueAccessor();
            var isValid = value.isValid && value.isValid();
            var isModified = value.isModified && value.isModified();

            if (!isValid && isModified) {
                $(element).addClass(ko.validation.configuration.errorClass);
            }
            else {
                $(element).removeClass(ko.validation.configuration.errorClass);
            }
        }
    };

    ko.bindingHandlers.validateSelectizeFor = {
        update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var value = valueAccessor();
            var isValid = value.isValid && value.isValid();
            var isModified = value.isModified && value.isModified();

            if (!isValid && isModified) {
                $(element).find('+ .selectize-control').addClass(ko.validation.configuration.errorClass);
            }
            else {
                $(element).find('+ .selectize-control').removeClass(ko.validation.configuration.errorClass);
            }
        }
    };

    var blacklistedDomains = ['0-mail.com', '007addict.com', '020.co.uk', '027168.com', '0815.ru', '0815.su', '0clickemail.com', '0sg.net', '0wnd.net', '0wnd.org', '1033edge.com', '10mail.org', '10minutemail.co.za', '10minutemail.com', '11mail.com', '123-m.com', '123.com', '123box.net', '123india.com', '123mail.cl', '123mail.org', '123qwe.co.uk', '126.com', '126.net', '138mail.com', '139.com', '150mail.com', '150ml.com', '15meg4free.com', '163.com', '16mail.com', '188.com', '189.cn', '1auto.com', '1ce.us', '1chuan.com', '1colony.com', '1coolplace.com', '1email.eu', '1freeemail.com', '1fsdfdsfsdf.tk', '1funplace.com', '1internetdrive.com', '1mail.ml', '1mail.net', '1me.net', '1mum.com', '1musicrow.com', '1netdrive.com', '1nsyncfan.com', '1pad.de', '1under.com', '1webave.com', '1webhighway.com', '1zhuan.com', '2-mail.com', '20email.eu', '20mail.in', '20mail.it', '20minutemail.com', '212.com', '21cn.com', '247emails.com', '24horas.com', '2911.net', '2980.com', '2bmail.co.uk', '2coolforyou.net', '2d2i.com', '2die4.com', '2fdgdfgdfgdf.tk', '2hotforyou.net', '2mydns.com', '2net.us', '2prong.com', '2trom.com', '3000.it', '30minutemail.com', '30minutesmail.com', '3126.com', '321media.com', '33mail.com', '360.ru', '37.com', '3ammagazine.com', '3dmail.com', '3email.com', '3g.ua', '3mail.ga', '3trtretgfrfe.tk', '3xl.net', '444.net', '4email.com', '4email.net', '4gfdsgfdgfd.tk', '4mg.com', '4newyork.com', '4warding.com', '4warding.net', '4warding.org', '4x4fan.com', '4x4man.com', '50mail.com', '5fm.za.com', '5ghgfhfghfgh.tk', '5iron.com', '5star.com', '60minutemail.com', '6hjgjhgkilkj.tk', '6ip.us', '6mail.cf', '6paq.com', '702mail.co.za', '74.ru', '7mail.ga', '7mail.ml', '7tags.com', '88.am', '8848.net', '888.nu', '8mail.ga', '8mail.ml', '97rock.com', '99experts.com', '9ox.net', 'a-bc.net', 'a-player.org', 'a2z4u.net', 'a45.in', 'aaamail.zzn.com', 'aahlife.com', 'aamail.net', 'aapt.net.au', 'aaronkwok.net', 'abbeyroadlondon.co.uk', 'abcflash.net', 'abdulnour.com', 'aberystwyth.com', 'abolition-now.com', 'about.com', 'absolutevitality.com', 'abusemail.de', 'abv.bg', 'abwesend.de', 'abyssmail.com', 'ac20mail.in', 'academycougars.com', 'acceso.or.cr', 'access4less.net', 'accessgcc.com', 'accountant.com', 'acdcfan.com', 'acdczone.com', 'ace-of-base.com', 'acmecity.com', 'acmemail.net', 'acninc.net', 'acrobatmail.com', 'activatormail.com', 'activist.com', 'adam.com.au', 'add3000.pp.ua', 'addcom.de', 'address.com', 'adelphia.net', 'adexec.com', 'adfarrow.com', 'adinet.com.uy', 'adios.net', 'admin.in.th', 'administrativos.com', 'adoption.com', 'ados.fr', 'adrenalinefreak.com', 'adres.nl', 'advalvas.be', 'advantimo.com', 'aeiou.pt', 'aemail4u.com', 'aeneasmail.com', 'afreeinternet.com', 'africa-11.com', 'africamail.com', 'africamel.net', 'africanpartnersonline.com', 'afrobacon.com', 'ag.us.to', 'agedmail.com', 'agelessemail.com', 'agoodmail.com', 'ahaa.dk', 'ahk.jp', 'aichi.com', 'aim.com', 'aircraftmail.com', 'airforce.net', 'airforceemail.com', 'airpost.net', 'aiutamici.com', 'ajacied.com', 'ajaxapp.net', 'ak47.hu', 'aknet.kg', 'akphantom.com', 'albawaba.com', 'alecsmail.com', 'alex4all.com', 'alexandria.cc', 'algeria.com', 'algeriamail.com', 'alhilal.net', 'alibaba.com', 'alice.it', 'aliceadsl.fr', 'aliceinchainsmail.com', 'alivance.com', 'alive.cz', 'aliyun.com', 'allergist.com', 'allmail.net', 'alloymail.com', 'allracing.com', 'allsaintsfan.com', 'alltel.net', 'alpenjodel.de', 'alphafrau.de', 'alskens.dk', 'altavista.com', 'altavista.net', 'altavista.se', 'alternativagratis.com', 'alumni.com', 'alumnidirector.com', 'alvilag.hu', 'ama-trade.de', 'amail.com', 'amazonses.com', 'amele.com', 'america.hm', 'ameritech.net', 'amilegit.com', 'amiri.net', 'amiriindustries.com', 'amnetsal.com', 'amorki.pl', 'amrer.net', 'amuro.net', 'amuromail.com', 'ananzi.co.za', 'ancestry.com', 'andreabocellimail.com', 'andylau.net', 'anfmail.com', 'angelfan.com', 'angelfire.com', 'angelic.com', 'animail.net', 'animal.net', 'animalhouse.com', 'animalwoman.net', 'anjungcafe.com', 'anniefans.com', 'annsmail.com', 'ano-mail.net', 'anonmails.de', 'anonymbox.com', 'anonymous.to', 'anote.com', 'another.com', 'anotherdomaincyka.tk', 'anotherwin95.com', 'anti-ignorance.net', 'anti-social.com', 'antichef.com', 'antichef.net', 'antiqueemail.com', 'antireg.ru', 'antisocial.com', 'antispam.de', 'antispam24.de', 'antispammail.de', 'antongijsen.com', 'antwerpen.com', 'anymoment.com', 'anytimenow.com', 'aol.co.uk', 'aol.com', 'aol.de', 'aol.fr', 'aol.it', 'aol.jp', 'aon.at', 'apexmail.com', 'apmail.com', 'apollo.lv', 'aport.ru', 'aport2000.ru', 'apple.sib.ru', 'appraiser.net', 'approvers.net', 'aquaticmail.net', 'arabia.com', 'arabtop.net', 'arcademaster.com', 'archaeologist.com', 'archerymail.com', 'arcor.de', 'arcotronics.bg', 'arcticmail.com', 'argentina.com', 'arhaelogist.com', 'aristotle.org', 'army.net', 'armyspy.com', 'arnet.com.ar', 'art-en-ligne.pro', 'artistemail.com', 'artlover.com', 'artlover.com.au', 'artman-conception.com', 'as-if.com', 'asdasd.nl', 'asean-mail', 'asean-mail.com', 'asheville.com', 'asia-links.com', 'asia-mail.com', 'asia.com', 'asiafind.com', 'asianavenue.com', 'asiancityweb.com', 'asiansonly.net', 'asianwired.net', 'asiapoint.net', 'askaclub.ru', 'ass.pp.ua', 'assala.com', 'assamesemail.com', 'astroboymail.com', 'astrolover.com', 'astrosfan.com', 'astrosfan.net', 'asurfer.com', 'atheist.com', 'athenachu.net', 'atina.cl', 'atl.lv', 'atlas.cz', 'atlaswebmail.com', 'atlink.com', 'atmc.net', 'ato.check.com', 'atozasia.com', 'atrus.ru', 'att.net', 'attglobal.net', 'attymail.com', 'au.ru', 'auctioneer.net', 'aufeminin.com', 'aus-city.com', 'ausi.com', 'aussiemail.com.au', 'austin.rr.com', 'australia.edu', 'australiamail.com', 'austrosearch.net', 'autoescuelanerja.com', 'autograf.pl', 'automail.ru', 'automotiveauthority.com', 'autorambler.ru', 'aver.com', 'avh.hu', 'avia-tonic.fr', 'avtoritet.ru', 'awayonvacation.com', 'awholelotofamechi.com', 'awsom.net', 'axoskate.com', 'ayna.com', 'azazazatashkent.tk', 'azimiweb.com', 'azmeil.tk', 'bachelorboy.com', 'bachelorgal.com', 'backfliper.com', 'backpackers.com', 'backstreet-boys.com', 'backstreetboysclub.com', 'backtothefuturefans.com', 'backwards.com', 'badtzmail.com', 'bagherpour.com', 'bahrainmail.com', 'bakpaka.com', 'bakpaka.net', 'baldmama.de', 'baldpapa.de', 'ballerstatus.net', 'ballyfinance.com', 'balochistan.org', 'baluch.com', 'bangkok.com', 'bangkok2000.com', 'bannertown.net', 'baptistmail.com', 'baptized.com', 'barcelona.com', 'bareed.ws', 'barid.com', 'barlick.net', 'bartender.net', 'baseball-email.com', 'baseballmail.com', 'basketballmail.com', 'batuta.net', 'baudoinconsulting.com', 'baxomale.ht.cx', 'bboy.com', 'bboy.zzn.com', 'bcvibes.com', 'beddly.com', 'beeebank.com', 'beefmilk.com', 'beenhad.com', 'beep.ru', 'beer.com', 'beerandremotes.com', 'beethoven.com', 'beirut.com', 'belice.com', 'belizehome.com', 'belizemail.net', 'belizeweb.com', 'bell.net', 'bellair.net', 'bellsouth.net', 'berkscounty.com', 'berlin.com', 'berlin.de', 'berlinexpo.de', 'bestmail.us', 'betriebsdirektor.de', 'bettergolf.net', 'bharatmail.com', 'big1.us', 'big5mail.com', 'bigassweb.com', 'bigblue.net.au', 'bigboab.com', 'bigfoot.com', 'bigfoot.de', 'bigger.com', 'biggerbadder.com', 'bigmailbox.com', 'bigmir.net', 'bigpond.au', 'bigpond.com', 'bigpond.com.au', 'bigpond.net', 'bigpond.net.au', 'bigramp.com', 'bigstring.com', 'bikemechanics.com', 'bikeracer.com', 'bikeracers.net', 'bikerider.com', 'billsfan.com', 'billsfan.net', 'bimamail.com', 'bimla.net', 'bin-wieder-da.de', 'binkmail.com', 'bio-muesli.info', 'bio-muesli.net', 'biologyfan.com', 'birdfanatic.com', 'birdlover.com', 'birdowner.net', 'bisons.com', 'bitmail.com', 'bitpage.net', 'bizhosting.com', 'bk.ru', 'bkkmail.com', 'bla-bla.com', 'blackburnfans.com', 'blackburnmail.com', 'blackplanet.com', 'blader.com', 'bladesmail.net', 'blazemail.com', 'bleib-bei-mir.de', 'blink182.net', 'blockfilter.com', 'blogmyway.org', 'blondandeasy.com', 'bluebottle.com', 'bluehyppo.com', 'bluemail.ch', 'bluemail.dk', 'bluesfan.com', 'bluewin.ch', 'blueyonder.co.uk', 'blumail.org', 'blushmail.com', 'blutig.me', 'bmlsports.net', 'boardermail.com', 'boarderzone.com', 'boatracers.com', 'bobmail.info', 'bodhi.lawlita.com', 'bofthew.com', 'bol.com.br', 'bolando.com', 'bollywoodz.com', 'bolt.com', 'boltonfans.com', 'bombdiggity.com', 'bonbon.net', 'boom.com', 'bootmail.com', 'bootybay.de', 'bornagain.com', 'bornnaked.com', 'bossofthemoss.com', 'bostonoffice.com', 'boun.cr', 'bounce.net', 'bounces.amazon.com', 'bouncr.com', 'box.az', 'box.ua', 'boxbg.com', 'boxemail.com', 'boxformail.in', 'boxfrog.com', 'boximail.com', 'boyzoneclub.com', 'bradfordfans.com', 'brasilia.net', 'bratan.ru', 'brazilmail.com', 'brazilmail.com.br', 'breadtimes.press', 'breakthru.com', 'breathe.com', 'brefmail.com', 'brennendesreich.de', 'bresnan.net', 'brestonline.com', 'brew-master.com', 'brew-meister.com', 'brfree.com.br', 'briefemail.com', 'bright.net', 'britneyclub.com', 'brittonsign.com', 'broadcast.net', 'broadwaybuff.com', 'broadwaylove.com', 'brokeandhappy.com', 'brokenvalve.com', 'brujula.net', 'brunetka.ru', 'brusseler.com', 'bsdmail.com', 'bsnow.net', 'bspamfree.org', 'bt.com', 'btcc.org', 'btcmail.pw', 'btconnect.co.uk', 'btconnect.com', 'btinternet.com', 'btopenworld.co.uk', 'buerotiger.de', 'buffymail.com', 'bugmenot.com', 'bulgaria.com', 'bullsfan.com', 'bullsgame.com', 'bumerang.ro', 'bumpymail.com', 'bumrap.com', 'bund.us', 'bunita.net', 'bunko.com', 'burnthespam.info', 'burntmail.com', 'burstmail.info', 'buryfans.com', 'bushemail.com', 'business-man.com', 'businessman.net', 'businessweekmail.com', 'bust.com', 'busta-rhymes.com', 'busymail.com', 'busymail.com.com', 'busymail.comhomeart.com', 'butch-femme.net', 'butovo.net', 'buyersusa.com', 'buymoreplays.com', 'buzy.com', 'bvimailbox.com', 'byke.com', 'byom.de', 'byteme.com', 'c2.hu', 'c2i.net', 'c3.hu', 'c4.com', 'c51vsgq.com', 'cabacabana.com', 'cable.comcast.com', 'cableone.net', 'caere.it', 'cairomail.com', 'calcuttaads.com', 'calendar-server.bounces.google.com', 'calidifontain.be', 'californiamail.com', 'callnetuk.com', 'callsign.net', 'caltanet.it', 'camidge.com', 'canada-11.com', 'canada.com', 'canadianmail.com', 'canoemail.com', 'cantv.net', 'canwetalk.com', 'caramail.com', 'card.zp.ua', 'care2.com', 'careceo.com', 'careerbuildermail.com', 'carioca.net', 'cartelera.org', 'cartestraina.ro', 'casablancaresort.com', 'casema.nl', 'cash4u.com', 'cashette.com', 'casino.com', 'casualdx.com', 'cataloniamail.com', 'cataz.com', 'catcha.com', 'catchamail.com', 'catemail.com', 'catholic.org', 'catlover.com', 'catsrule.garfield.com', 'ccnmail.com', 'cd2.com', 'cek.pm', 'celineclub.com', 'celtic.com', 'center-mail.de', 'centermail.at', 'centermail.com', 'centermail.de', 'centermail.info', 'centermail.net', 'centoper.it', 'centralpets.com', 'centrum.cz', 'centrum.sk', 'centurylink.net', 'centurytel.net', 'certifiedmail.com', 'cfl.rr.com', 'cgac.es', 'cghost.s-a-d.de', 'chacuo.net', 'chaiyo.com', 'chaiyomail.com', 'chalkmail.net', 'chammy.info', 'chance2mail.com', 'chandrasekar.net', 'channelonetv.com', 'charityemail.com', 'charmedmail.com', 'charter.com', 'charter.net', 'chat.ru', 'chatlane.ru', 'chattown.com', 'chauhanweb.com', 'cheatmail.de', 'chechnya.conf.work', 'check.com', 'check.com12', 'check1check.com', 'cheeb.com', 'cheerful.com', 'chef.net', 'chefmail.com', 'chek.com', 'chello.nl', 'chemist.com', 'chequemail.com', 'cheshiremail.com', 'cheyenneweb.com', 'chez.com', 'chickmail.com', 'chil-e.com', 'childrens.md', 'childsavetrust.org', 'china.com', 'china.net.vg', 'chinalook.com', 'chinamail.com', 'chinesecool.com', 'chirk.com', 'chocaholic.com.au', 'chocofan.com', 'chogmail.com', 'choicemail1.com', 'chong-mail.com', 'chong-mail.net', 'christianmail.net', 'chronicspender.com', 'churchusa.com', 'cia-agent.com', 'cia.hu', 'ciaoweb.it', 'cicciociccio.com', 'cincinow.net', 'cirquefans.com', 'citeweb.net', 'citiz.net', 'citlink.net', 'city-of-bath.org', 'city-of-birmingham.com', 'city-of-brighton.org', 'city-of-cambridge.com', 'city-of-coventry.com', 'city-of-edinburgh.com', 'city-of-lichfield.com', 'city-of-lincoln.com', 'city-of-liverpool.com', 'city-of-manchester.com', 'city-of-nottingham.com', 'city-of-oxford.com', 'city-of-swansea.com', 'city-of-westminster.com', 'city-of-westminster.net', 'city-of-york.net', 'city2city.com', 'citynetusa.com', 'cityofcardiff.net', 'cityoflondon.org', 'ciudad.com.ar', 'ckaazaza.tk', 'claramail.com', 'classicalfan.com', 'classicmail.co.za', 'clear.net.nz', 'clearwire.net', 'clerk.com', 'clickforadate.com', 'cliffhanger.com', 'clixser.com', 'close2you.ne', 'close2you.net', 'clrmail.com', 'club-internet.fr', 'club4x4.net', 'clubalfa.com', 'clubbers.net', 'clubducati.com', 'clubhonda.net', 'clubmember.org', 'clubnetnoir.com', 'clubvdo.net', 'cluemail.com', 'cmail.net', 'cmail.org', 'cmail.ru', 'cmpmail.com', 'cmpnetmail.com', 'cnegal.com', 'cnnsimail.com', 'cntv.cn', 'codec.ro', 'codec.ro.ro', 'codec.roemail.ro', 'coder.hu', 'coid.biz', 'coldemail.info', 'coldmail.com', 'collectiblesuperstore.com', 'collector.org', 'collegebeat.com', 'collegeclub.com', 'collegemail.com', 'colleges.com', 'columbus.rr.com', 'columbusrr.com', 'columnist.com', 'comast.com', 'comast.net', 'comcast.com', 'comcast.net', 'comic.com', 'communityconnect.com', 'complxmind.com', 'comporium.net', 'comprendemail.com', 'compuserve.com', 'computer-expert.net', 'computer-freak.com', 'computer4u.com', 'computerconfused.com', 'computermail.net', 'computernaked.com', 'conexcol.com', 'cong.ru', 'conk.com', 'connect4free.net', 'connectbox.com', 'conok.com', 'consultant.com', 'consumerriot.com', 'contractor.net', 'contrasto.cu.cc', 'cookiemonster.com', 'cool.br', 'cool.fr.nf', 'coole-files.de', 'coolgoose.ca', 'coolgoose.com', 'coolkiwi.com', 'coollist.com', 'coolmail.com', 'coolmail.net', 'coolrio.com', 'coolsend.com', 'coolsite.net', 'cooooool.com', 'cooperation.net', 'cooperationtogo.net', 'copacabana.com', 'copper.net', 'copticmail.com', 'cornells.com', 'cornerpub.com', 'corporatedirtbag.com', 'correo.terra.com.gt', 'corrsfan.com', 'cortinet.com', 'cosmo.com', 'cotas.net', 'counsellor.com', 'countrylover.com', 'courriel.fr.nf', 'courrieltemporaire.com', 'cox.com', 'cox.net', 'coxinet.net', 'cpaonline.net', 'cracker.hu', 'craftemail.com', 'crapmail.org', 'crazedanddazed.com', 'crazy.ru', 'crazymailing.com', 'crazysexycool.com', 'crewstart.com', 'cristianemail.com', 'critterpost.com', 'croeso.com', 'crosshairs.com', 'crosswinds.net', 'crunkmail.com', 'crwmail.com', 'cry4helponline.com', 'cryingmail.com', 'cs.com', 'csinibaba.hu', 'cubiclink.com', 'cuemail.com', 'cumbriamail.com', 'curio-city.com', 'curryworld.de', 'curtsmail.com', 'cust.in', 'cute-girl.com', 'cuteandcuddly.com', 'cutekittens.com', 'cutey.com', 'cuvox.de', 'cww.de', 'cyber-africa.net', 'cyber-innovation.club', 'cyber-matrix.com', 'cyber-phone.eu', 'cyber-wizard.com', 'cyber4all.com', 'cyberbabies.com', 'cybercafemaui.com', 'cybercity-online.net', 'cyberdude.com', 'cyberforeplay.net', 'cybergal.com', 'cybergrrl.com', 'cyberinbox.com', 'cyberleports.com', 'cybermail.net', 'cybernet.it', 'cyberservices.com', 'cyberspace-asia.com', 'cybertrains.org', 'cyclefanz.com', 'cymail.net', 'cynetcity.com', 'd3p.dk', 'dabsol.net', 'dacoolest.com', 'dadacasa.com', 'daha.com', 'dailypioneer.com', 'dallas.theboys.com', 'dallasmail.com', 'dandikmail.com', 'dangerous-minds.com', 'dansegulvet.com', 'dasdasdascyka.tk', 'data54.com', 'date.by', 'daum.net', 'davegracey.com', 'dawnsonmail.com', 'dawsonmail.com', 'dayrep.com', 'dazedandconfused.com', 'dbzmail.com', 'dcemail.com', 'dcsi.net', 'ddns.org', 'deadaddress.com', 'deadlymob.org', 'deadspam.com', 'deafemail.net', 'deagot.com', 'deal-maker.com', 'dearriba.com', 'death-star.com', 'deepseafisherman.net', 'deforestationsucks.com', 'degoo.com', 'dejanews.com', 'delikkt.de', 'deliveryman.com', 'deneg.net', 'depechemode.com', 'deseretmail.com', 'desertmail.com', 'desertonline.com', 'desertsaintsmail.com', 'desilota.com', 'deskmail.com', 'deskpilot.com', 'despam.it', 'despammed.com', 'destin.com', 'detik.com', 'deutschland-net.com', 'devnullmail.com', 'devotedcouples.com', 'dezigner.ru', 'dfgh.net', 'dfwatson.com', 'dglnet.com.br', 'dgoh.org', 'di-ve.com', 'diamondemail.com', 'didamail.com', 'die-besten-bilder.de', 'die-genossen.de', 'die-optimisten.de', 'die-optimisten.net', 'die.life', 'diehardmail.com', 'diemailbox.de', 'digibel.be', 'digital-filestore.de', 'digitalforeplay.net', 'digitalsanctuary.com', 'digosnet.com', 'dingbone.com', 'diplomats.com', 'directbox.com', 'director-general.com', 'diri.com', 'dirtracer.com', 'dirtracers.com', 'discard.email', 'discard.ga', 'discard.gq', 'discardmail.com', 'discardmail.de', 'disciples.com', 'discofan.com', 'discovery.com', 'discoverymail.com', 'discoverymail.net', 'disign-concept.eu', 'disign-revelation.com', 'disinfo.net', 'dispomail.eu', 'disposable.com', 'disposableaddress.com', 'disposableemailaddresses.com', 'disposableinbox.com', 'dispose.it', 'dispostable.com', 'divismail.ru', 'divorcedandhappy.com', 'dm.w3internet.co.uk', 'dmailman.com', 'dmitrovka.net', 'dmitry.ru', 'dnainternet.net', 'dnsmadeeasy.com', 'doar.net', 'doclist.bounces.google.com', 'docmail.cz', 'docs.google.com', 'doctor.com', 'dodgeit.com', 'dodgit.com', 'dodgit.org', 'dodo.com.au', 'dodsi.com', 'dog.com', 'dogit.com', 'doglover.com', 'dogmail.co.uk', 'dogsnob.net', 'doityourself.com', 'domforfb1.tk', 'domforfb2.tk', 'domforfb3.tk', 'domforfb4.tk', 'domforfb5.tk', 'domforfb6.tk', 'domforfb7.tk', 'domforfb8.tk', 'domozmail.com', 'doneasy.com', 'donegal.net', 'donemail.ru', 'donjuan.com', 'dontgotmail.com', 'dontmesswithtexas.com', 'dontreg.com', 'dontsendmespam.de', 'doramail.com', 'dostmail.com', 'dotcom.fr', 'dotmsg.com', 'dotnow.com', 'dott.it', 'download-privat.de', 'dplanet.ch', 'dr.com', 'dragoncon.net', 'dragracer.com', 'drdrb.net', 'drivehq.com', 'dropmail.me', 'dropzone.com', 'drotposta.hu', 'dubaimail.com', 'dublin.com', 'dublin.ie', 'dump-email.info', 'dumpandjunk.com', 'dumpmail.com', 'dumpmail.de', 'dumpyemail.com', 'dunlopdriver.com', 'dunloprider.com', 'duno.com', 'duskmail.com', 'dustdevil.com', 'dutchmail.com', 'dvd-fan.net', 'dwp.net', 'dygo.com', 'dynamitemail.com', 'dyndns.org', 'e-apollo.lv', 'e-hkma.com', 'e-mail.com', 'e-mail.com.tr', 'e-mail.dk', 'e-mail.org', 'e-mail.ru', 'e-mail.ua', 'e-mailanywhere.com', 'e-mails.ru', 'e-tapaal.com', 'e-webtec.com', 'e4ward.com', 'earthalliance.com', 'earthcam.net', 'earthdome.com', 'earthling.net', 'earthlink.net', 'earthonline.net', 'eastcoast.co.za', 'eastlink.ca', 'eastmail.com', 'eastrolog.com', 'easy.com', 'easy.to', 'easypeasy.com', 'easypost.com', 'easytrashmail.com', 'eatmydirt.com', 'ebprofits.net', 'ec.rr.com', 'ecardmail.com', 'ecbsolutions.net', 'echina.com', 'ecolo-online.fr', 'ecompare.com', 'edmail.com', 'ednatx.com', 'edtnmail.com', 'educacao.te.pt', 'educastmail.com', 'eelmail.com', 'ehmail.com', 'einmalmail.de', 'einrot.com', 'einrot.de', 'eintagsmail.de', 'eircom.net', 'ekidz.com.au', 'elisanet.fi', 'elitemail.org', 'elsitio.com', 'eltimon.com', 'elvis.com', 'elvisfan.com', 'email-fake.gq', 'email-london.co.uk', 'email-value.com', 'email.biz', 'email.cbes.net', 'email.com', 'email.cz', 'email.ee', 'email.it', 'email.nu', 'email.org', 'email.ro', 'email.ru', 'email.si', 'email.su', 'email.ua', 'email.women.com', 'email2me.com', 'email2me.net', 'email4u.info', 'email60.com', 'emailacc.com', 'emailaccount.com', 'emailaddresses.com', 'emailage.ga', 'emailage.gq', 'emailasso.net', 'emailchoice.com', 'emailcorner.net', 'emailem.com', 'emailengine.net', 'emailengine.org', 'emailer.hubspot.com', 'emailforyou.net', 'emailgaul.com', 'emailgo.de', 'emailgroups.net', 'emailias.com', 'emailinfive.com', 'emailit.com', 'emaillime.com', 'emailmiser.com', 'emailoregon.com', 'emailpinoy.com', 'emailplanet.com', 'emailplus.org', 'emailproxsy.com', 'emails.ga', 'emails.incisivemedia.com', 'emails.ru', 'emailsensei.com', 'emailservice.com', 'emailsydney.com', 'emailtemporanea.com', 'emailtemporanea.net', 'emailtemporar.ro', 'emailtemporario.com.br', 'emailthe.net', 'emailtmp.com', 'emailto.de', 'emailuser.net', 'emailwarden.com', 'emailx.at.hm', 'emailx.net', 'emailxfer.com', 'emailz.ga', 'emailz.gq', 'emale.ru', 'ematic.com', 'embarqmail.com', 'emeil.in', 'emeil.ir', 'emil.com', 'eml.cc', 'eml.pp.ua', 'empereur.com', 'emptymail.com', 'emumail.com', 'emz.net', 'end-war.com', 'enel.net', 'enelpunto.net', 'engineer.com', 'england.com', 'england.edu', 'englandmail.com', 'epage.ru', 'epatra.com', 'ephemail.net', 'epiqmail.com', 'epix.net', 'epomail.com', 'epost.de', 'eposta.hu', 'eprompter.com', 'eqqu.com', 'eramail.co.za', 'eresmas.com', 'eriga.lv', 'ero-tube.org', 'eshche.net', 'esmailweb.net', 'estranet.it', 'ethos.st', 'etoast.com', 'etrademail.com', 'etranquil.com', 'etranquil.net', 'eudoramail.com', 'europamel.net', 'europe.com', 'europemail.com', 'euroseek.com', 'eurosport.com', 'evafan.com', 'evertonfans.com', 'every1.net', 'everyday.com.kh', 'everymail.net', 'everyone.net', 'everytg.ml', 'evopo.com', 'examnotes.net', 'excite.co.jp', 'excite.co.uk', 'excite.com', 'excite.it', 'execs.com', 'execs2k.com', 'executivemail.co.za', 'exemail.com.au', 'exg6.exghost.com', 'explodemail.com', 'express.net.ua', 'expressasia.com', 'extenda.net', 'extended.com', 'extremail.ru', 'eyepaste.com', 'eyou.com', 'ezagenda.com', 'ezcybersearch.com', 'ezmail.egine.com', 'ezmail.ru', 'ezrs.com', 'f-m.fm', 'f1fans.net', 'facebook-email.ga', 'facebook.com', 'facebookmail.com', 'facebookmail.gq', 'fadrasha.net', 'fadrasha.org', 'fahr-zur-hoelle.org', 'fake-email.pp.ua', 'fake-mail.cf', 'fake-mail.ga', 'fake-mail.ml', 'fakeinbox.com', 'fakeinformation.com', 'fakemailz.com', 'falseaddress.com', 'fan.com', 'fan.theboys.com', 'fannclub.com', 'fansonlymail.com', 'fansworldwide.de', 'fantasticmail.com', 'fantasymail.de', 'farang.net', 'farifluset.mailexpire.com', 'faroweb.com', 'fast-email.com', 'fast-mail.fr', 'fast-mail.org', 'fastacura.com', 'fastchevy.com', 'fastchrysler.com', 'fastem.com', 'fastemail.us', 'fastemailer.com', 'fastemailextractor.net', 'fastermail.com', 'fastest.cc', 'fastimap.com', 'fastkawasaki.com', 'fastmail.ca', 'fastmail.cn', 'fastmail.co.uk', 'fastmail.com', 'fastmail.com.au', 'fastmail.es', 'fastmail.fm', 'fastmail.gr', 'fastmail.im', 'fastmail.in', 'fastmail.jp', 'fastmail.mx', 'fastmail.net', 'fastmail.nl', 'fastmail.se', 'fastmail.to', 'fastmail.tw', 'fastmail.us', 'fastmailbox.net', 'fastmazda.com', 'fastmessaging.com', 'fastmitsubishi.com', 'fastnissan.com', 'fastservice.com', 'fastsubaru.com', 'fastsuzuki.com', 'fasttoyota.com', 'fastyamaha.com', 'fatcock.net', 'fatflap.com', 'fathersrightsne.org', 'fatyachts.com', 'fax.ru', 'fbi-agent.com', 'fbi.hu', 'fdfdsfds.com', 'fea.st', 'federalcontractors.com', 'feinripptraeger.de', 'felicity.com', 'felicitymail.com', 'female.ru', 'femenino.com', 'fepg.net', 'fetchmail.co.uk', 'fetchmail.com', 'fettabernett.de', 'feyenoorder.com', 'ffanet.com', 'fiberia.com', 'fibertel.com.ar', 'ficken.de', 'fificorp.com', 'fificorp.net', 'fightallspam.com', 'filipinolinks.com', 'filzmail.com', 'financefan.net', 'financemail.net', 'financier.com', 'findfo.com', 'findhere.com', 'findmail.com', 'findmemail.com', 'finebody.com', 'fineemail.com', 'finfin.com', 'finklfan.com', 'fire-brigade.com', 'fireman.net', 'fishburne.org', 'fishfuse.com', 'fivemail.de', 'fixmail.tk', 'fizmail.com', 'flashbox.5july.org', 'flashemail.com', 'flashmail.com', 'flashmail.net', 'fleckens.hu', 'flipcode.com', 'floridaemail.net', 'flytecrew.com', 'fmail.co.uk', 'fmailbox.com', 'fmgirl.com', 'fmguy.com', 'fnbmail.co.za', 'fnmail.com', 'folkfan.com', 'foodmail.com', 'footard.com', 'football.theboys.com', 'footballmail.com', 'foothills.net', 'for-president.com', 'force9.co.uk', 'forfree.at', 'forgetmail.com', 'fornow.eu', 'forpresident.com', 'fortuncity.com', 'fortunecity.com', 'forum.dk', 'fossefans.com', 'foxmail.com', 'fr33mail.info', 'francefans.com', 'francemel.fr', 'frapmail.com', 'free-email.ga', 'free-online.net', 'free-org.com', 'free.com.pe', 'free.fr', 'freeaccess.nl', 'freeaccount.com', 'freeandsingle.com', 'freebox.com', 'freedom.usa.com', 'freedomlover.com', 'freefanmail.com', 'freegates.be', 'freeghana.com', 'freelance-france.eu', 'freeler.nl', 'freemail.bozz.com', 'freemail.c3.hu', 'freemail.com.au', 'freemail.com.pk', 'freemail.de', 'freemail.et', 'freemail.gr', 'freemail.hu', 'freemail.it', 'freemail.lt', 'freemail.ms', 'freemail.nl', 'freemail.org.mk', 'freemail.ru', 'freemails.ga', 'freemeil.gq', 'freenet.de', 'freenet.kg', 'freeola.com', 'freeola.net', 'freeproblem.com', 'freesbee.fr', 'freeserve.co.uk', 'freeservers.com', 'freestamp.com', 'freestart.hu', 'freesurf.fr', 'freesurf.nl', 'freeuk.com', 'freeuk.net', 'freeukisp.co.uk', 'freeweb.org', 'freewebemail.com', 'freeyellow.com', 'freezone.co.uk', 'fresnomail.com', 'freudenkinder.de', 'freundin.ru', 'friction.net', 'friendlydevices.com', 'friendlymail.co.uk', 'friends-cafe.com', 'friendsfan.com', 'from-africa.com', 'from-america.com', 'from-argentina.com', 'from-asia.com', 'from-australia.com', 'from-belgium.com', 'from-brazil.com', 'from-canada.com', 'from-china.net', 'from-england.com', 'from-europe.com', 'from-france.net', 'from-germany.net', 'from-holland.com', 'from-israel.com', 'from-italy.net', 'from-japan.net', 'from-korea.com', 'from-mexico.com', 'from-outerspace.com', 'from-russia.com', 'from-spain.net', 'fromalabama.com', 'fromalaska.com', 'fromarizona.com', 'fromarkansas.com', 'fromcalifornia.com', 'fromcolorado.com', 'fromconnecticut.com', 'fromdelaware.com', 'fromflorida.net', 'fromgeorgia.com', 'fromhawaii.net', 'fromidaho.com', 'fromillinois.com', 'fromindiana.com', 'frominter.net', 'fromiowa.com', 'fromjupiter.com', 'fromkansas.com', 'fromkentucky.com', 'fromlouisiana.com', 'frommaine.net', 'frommaryland.com', 'frommassachusetts.com', 'frommiami.com', 'frommichigan.com', 'fromminnesota.com', 'frommississippi.com', 'frommissouri.com', 'frommontana.com', 'fromnebraska.com', 'fromnevada.com', 'fromnewhampshire.com', 'fromnewjersey.com', 'fromnewmexico.com', 'fromnewyork.net', 'fromnorthcarolina.com', 'fromnorthdakota.com', 'fromohio.com', 'fromoklahoma.com', 'fromoregon.net', 'frompennsylvania.com', 'fromrhodeisland.com', 'fromru.com', 'fromru.ru', 'fromsouthcarolina.com', 'fromsouthdakota.com', 'fromtennessee.com', 'fromtexas.com', 'fromthestates.com', 'fromutah.com', 'fromvermont.com', 'fromvirginia.com', 'fromwashington.com', 'fromwashingtondc.com', 'fromwestvirginia.com', 'fromwisconsin.com', 'fromwyoming.com', 'front.ru', 'frontier.com', 'frontiernet.net', 'frostbyte.uk.net', 'fsmail.net', 'ftc-i.net', 'ftml.net', 'fuckingduh.com', 'fudgerub.com', 'fullmail.com', 'funiran.com', 'funkfan.com', 'funky4.com', 'fuorissimo.com', 'furnitureprovider.com', 'fuse.net', 'fusemail.com', 'fut.es', 'fux0ringduh.com', 'fwnb.com', 'fxsmails.com', 'fyii.de', 'galamb.net', 'galaxy5.com', 'galaxyhit.com', 'gamebox.com', 'gamebox.net', 'gamegeek.com', 'games.com', 'gamespotmail.com', 'gamil.com', 'gamil.com.au', 'gamno.config.work', 'garbage.com', 'gardener.com', 'garliclife.com', 'gatwickemail.com', 'gawab.com', 'gay.com', 'gaybrighton.co.uk', 'gaza.net', 'gazeta.pl', 'gazibooks.com', 'gci.net', 'gdi.net', 'gee-wiz.com', 'geecities.com', 'geek.com', 'geek.hu', 'geeklife.com', 'gehensiemirnichtaufdensack.de', 'gelitik.in', 'gencmail.com', 'general-hospital.com', 'gentlemansclub.de', 'genxemail.com', 'geocities.com', 'geography.net', 'geologist.com', 'geopia.com', 'germanymail.com', 'get.pp.ua', 'get1mail.com', 'get2mail.fr', 'getairmail.cf', 'getairmail.com', 'getairmail.ga', 'getairmail.gq', 'getmails.eu', 'getonemail.com', 'getonemail.net', 'gfxartist.ru', 'gh2000.com', 'ghanamail.com', 'ghostmail.com', 'ghosttexter.de', 'giantmail.de', 'giantsfan.com', 'giga4u.de', 'gigileung.org', 'girl4god.com', 'girlsundertheinfluence.com', 'gishpuppy.com', 'givepeaceachance.com', 'glay.org', 'glendale.net', 'globalfree.it', 'globalpagan.com', 'globalsite.com.br', 'globetrotter.net', 'globo.com', 'globomail.com', 'gmail.co.za', 'gmail.com', 'gmail.com.au', 'gmail.com.br', 'gmail.ru', 'gmial.com', 'gmx.at', 'gmx.ch', 'gmx.co.uk', 'gmx.com', 'gmx.de', 'gmx.fr', 'gmx.li', 'gmx.net', 'gmx.us', 'gnwmail.com', 'go.com', 'go.ro', 'go.ru', 'go2.com.py', 'go2net.com', 'go4.it', 'gobrainstorm.net', 'gocollege.com', 'gocubs.com', 'godmail.dk', 'goemailgo.com', 'gofree.co.uk', 'gol.com', 'goldenmail.ru', 'goldmail.ru', 'goldtoolbox.com', 'golfemail.com', 'golfilla.info', 'golfmail.be', 'gonavy.net', 'gonuts4free.com', 'goodnewsmail.com', 'goodstick.com', 'google.com', 'googlegroups.com', 'googlemail.com', 'goosemoose.com', 'goplay.com', 'gorillaswithdirtyarmpits.com', 'gorontalo.net', 'gospelfan.com', 'gothere.uk.com', 'gotmail.com', 'gotmail.net', 'gotmail.org', 'gotomy.com', 'gotti.otherinbox.com', 'govolsfan.com', 'gportal.hu', 'grabmail.com', 'graduate.org', 'graffiti.net', 'gramszu.net', 'grandmamail.com', 'grandmasmail.com', 'graphic-designer.com', 'grapplers.com', 'gratisweb.com', 'great-host.in', 'greenmail.net', 'greensloth.com', 'groupmail.com', 'grr.la', 'grungecafe.com', 'gsrv.co.uk', 'gtemail.net', 'gtmc.net', 'gua.net', 'guerillamail.biz', 'guerillamail.com', 'guerrillamail.biz', 'guerrillamail.com', 'guerrillamail.de', 'guerrillamail.info', 'guerrillamail.net', 'guerrillamail.org', 'guerrillamailblock.com', 'guessmail.com', 'guju.net', 'gurlmail.com', 'gustr.com', 'guy.com', 'guy2.com', 'guyanafriends.com', 'gwhsgeckos.com', 'gyorsposta.com', 'gyorsposta.hu', 'h-mail.us', 'hab-verschlafen.de', 'hablas.com', 'habmalnefrage.de', 'hacccc.com', 'hackermail.com', 'hackermail.net', 'hailmail.net', 'hairdresser.com', 'hairdresser.net', 'haltospam.com', 'hamptonroads.com', 'handbag.com', 'handleit.com', 'hang-ten.com', 'hangglidemail.com', 'hanmail.net', 'happemail.com', 'happycounsel.com', 'happypuppy.com', 'harakirimail.com', 'haramamba.ru', 'hardcorefreak.com', 'hardyoungbabes.com', 'hartbot.de', 'hat-geld.de', 'hatespam.org', 'hawaii.rr.com', 'hawaiiantel.net', 'headbone.com', 'healthemail.net', 'heartthrob.com', 'heavynoize.net', 'heerschap.com', 'heesun.net', 'hehe.com', 'hello.hu', 'hello.net.au', 'hello.to', 'hellokitty.com', 'helter-skelter.com', 'hempseed.com', 'herediano.com', 'heremail.com', 'herono1.com', 'herp.in', 'herr-der-mails.de', 'hetnet.nl', 'hewgen.ru', 'hey.to', 'hhdevel.com', 'hideakifan.com', 'hidemail.de', 'hidzz.com', 'highmilton.com', 'highquality.com', 'highveldmail.co.za', 'hilarious.com', 'hinduhome.com', 'hingis.org', 'hiphopfan.com', 'hispavista.com', 'hitmail.com', 'hitmanrecords.com', 'hitthe.net', 'hkg.net', 'hkstarphoto.com', 'hmamail.com', 'hochsitze.com', 'hockeymail.com', 'hollywoodkids.com', 'home-email.com', 'home.de', 'home.nl', 'home.no.net', 'home.ro', 'home.se', 'homeart.com', 'homelocator.com', 'homemail.com', 'homenetmail.com', 'homeonthethrone.com', 'homestead.com', 'homeworkcentral.com', 'honduras.com', 'hongkong.com', 'hookup.net', 'hoopsmail.com', 'hopemail.biz', 'horrormail.com', 'host-it.com.sg', 'hot-mail.gq', 'hot-shop.com', 'hot-shot.com', 'hot.ee', 'hotbot.com', 'hotbox.ru', 'hotbrev.com', 'hotcoolmail.com', 'hotepmail.com', 'hotfire.net', 'hotletter.com', 'hotlinemail.com', 'hotmail.be', 'hotmail.ca', 'hotmail.ch', 'hotmail.co', 'hotmail.co.il', 'hotmail.co.jp', 'hotmail.co.nz', 'hotmail.co.uk', 'hotmail.co.za', 'hotmail.com', 'hotmail.com.ar', 'hotmail.com.au', 'hotmail.com.br', 'hotmail.com.mx', 'hotmail.com.tr', 'hotmail.de', 'hotmail.es', 'hotmail.fi', 'hotmail.fr', 'hotmail.it', 'hotmail.kg', 'hotmail.kz', 'hotmail.my', 'hotmail.nl', 'hotmail.ro', 'hotmail.roor', 'hotmail.ru', 'hotpop.com', 'hotpop3.com', 'hotvoice.com', 'housefan.com', 'housefancom', 'housemail.com', 'hsuchi.net', 'html.tou.com', 'hu2.ru', 'hughes.net', 'hulapla.de', 'humanoid.net', 'humanux.com', 'humn.ws.gy', 'humour.com', 'hunsa.com', 'hurting.com', 'hush.com', 'hushmail.com', 'hypernautica.com', 'i-connect.com', 'i-france.com', 'i-love-cats.com', 'i-mail.com.au', 'i-mailbox.net', 'i-p.com', 'i.am', 'i.am.to', 'i.amhey.to', 'i.ua', 'i12.com', 'i2828.com', 'i2pmail.org', 'iam4msu.com', 'iamawoman.com', 'iamfinallyonline.com', 'iamwaiting.com', 'iamwasted.com', 'iamyours.com', 'icestorm.com', 'ich-bin-verrueckt-nach-dir.de', 'ich-will-net.de', 'icloud.com', 'icmsconsultants.com', 'icq.com', 'icqmail.com', 'icrazy.com', 'icu.md', 'id-base.com', 'id.ru', 'ididitmyway.com', 'idigjesus.com', 'idirect.com', 'ieatspam.eu', 'ieatspam.info', 'ieh-mail.de', 'iespana.es', 'ifoward.com', 'ig.com.br', 'ignazio.it', 'ignmail.com', 'ihateclowns.com', 'ihateyoualot.info', 'iheartspam.org', 'iinet.net.au', 'ijustdontcare.com', 'ikbenspamvrij.nl', 'ilkposta.com', 'ilovechocolate.com', 'ilovegiraffes.net', 'ilovejesus.com', 'ilovelionking.com', 'ilovepokemonmail.com', 'ilovethemovies.com', 'ilovetocollect.net', 'ilse.nl', 'imaginemail.com', 'imail.org', 'imail.ru', 'imailbox.com', 'imails.info', 'imap-mail.com', 'imap.cc', 'imapmail.org', 'imel.org', 'imgof.com', 'imgv.de', 'immo-gerance.info', 'imneverwrong.com', 'imposter.co.uk', 'imstations.com', 'imstressed.com', 'imtoosexy.com', 'in-box.net', 'in2jesus.com', 'iname.com', 'inbax.tk', 'inbound.plus', 'inbox.com', 'inbox.lv', 'inbox.net', 'inbox.ru', 'inbox.si', 'inboxalias.com', 'inboxclean.com', 'inboxclean.org', 'incamail.com', 'includingarabia.com', 'incredimail.com', 'indeedemail.com', 'index.ua', 'indexa.fr', 'india.com', 'indiatimes.com', 'indo-mail.com', 'indocities.com', 'indomail.com', 'indosat.net.id', 'indus.ru', 'indyracers.com', 'inerted.com', 'inet.com', 'inet.net.au', 'info-media.de', 'info-radio.ml', 'info.com', 'info66.com', 'infoapex.com', 'infocom.zp.ua', 'infohq.com', 'infomail.es', 'infomart.or.jp', 'informaticos.com', 'infospacemail.com', 'infovia.com.ar', 'inicia.es', 'inmail.sk', 'inmail24.com', 'inmano.com', 'inmynetwork.tk', 'innocent.com', 'inonesearch.com', 'inorbit.com', 'inoutbox.com', 'insidebaltimore.net', 'insight.rr.com', 'inspectorjavert.com', 'instant-mail.de', 'instantemailaddress.com', 'instantmail.fr', 'instruction.com', 'instructor.net', 'insurer.com', 'interburp.com', 'interfree.it', 'interia.pl', 'interlap.com.ar', 'intermail.co.il', 'internet-club.com', 'internet-e-mail.com', 'internet-mail.org', 'internet-police.com', 'internetbiz.com', 'internetdrive.com', 'internetegypt.com', 'internetemails.net', 'internetmailing.net', 'internode.on.net', 'invalid.com', 'investormail.com', 'inwind.it', 'iobox.com', 'iobox.fi', 'iol.it', 'iol.pt', 'iowaemail.com', 'ip3.com', 'ip4.pp.ua', 'ip6.li', 'ip6.pp.ua', 'ipdeer.com', 'ipex.ru', 'ipoo.org', 'iportalexpress.com', 'iprimus.com.au', 'iqemail.com', 'irangate.net', 'iraqmail.com', 'ireland.com', 'irelandmail.com', 'irish2me.com', 'irj.hu', 'iroid.com', 'iscooler.com', 'isellcars.com', 'iservejesus.com', 'islamonline.net', 'islandemail.net', 'isleuthmail.com', 'ismart.net', 'isonfire.com', 'isp9.net', 'israelmail.com', 'ist-allein.info', 'ist-einmalig.de', 'ist-ganz-allein.de', 'ist-willig.de', 'italymail.com', 'itelefonica.com.br', 'itloox.com', 'itmom.com', 'ivebeenframed.com', 'ivillage.com', 'iwan-fals.com', 'iwi.net', 'iwmail.com', 'iwon.com', 'izadpanah.com', 'jabble.com', 'jahoopa.com', 'jakuza.hu', 'japan.com', 'jaydemail.com', 'jazzandjava.com', 'jazzfan.com', 'jazzgame.com', 'je-recycle.info', 'jeanvaljean.com', 'jerusalemmail.com', 'jesusanswers.com', 'jet-renovation.fr', 'jetable.com', 'jetable.de', 'jetable.fr.nf', 'jetable.net', 'jetable.org', 'jetable.pp.ua', 'jetemail.net', 'jewishmail.com', 'jfkislanders.com', 'jingjo.net', 'jippii.fi', 'jmail.co.za', 'jnxjn.com', 'job4u.com', 'jobbikszimpatizans.hu', 'joelonsoftware.com', 'joinme.com', 'jojomail.com', 'jokes.com', 'jordanmail.com', 'journalist.com', 'jourrapide.com', 'jovem.te.pt', 'joymail.com', 'jpopmail.com', 'jsrsolutions.com', 'jubiimail.dk', 'jump.com', 'jumpy.it', 'juniormail.com', 'junk1e.com', 'junkmail.com', 'junkmail.gq', 'juno.com', 'justemail.net', 'justicemail.com', 'justmail.de', 'justmailz.com', 'justmarriedmail.com', 'jwspamspy ', 'k.ro', 'kaazoo.com', 'kabissa.org', 'kaduku.net', 'kaffeeschluerfer.com', 'kaffeeschluerfer.de', 'kaixo.com', 'kalpoint.com', 'kansascity.com', 'kapoorweb.com', 'karachian.com', 'karachioye.com', 'karbasi.com', 'kasmail.com', 'kaspop.com', 'katamail.com', 'kayafmmail.co.za', 'kbjrmail.com', 'kcks.com', 'kebi.com', 'keftamail.com', 'keg-party.com', 'keinpardon.de', 'keko.com.ar', 'kellychen.com', 'keptprivate.com', 'keromail.com', 'kewpee.com', 'keyemail.com', 'kgb.hu', 'khosropour.com', 'kichimail.com', 'kickassmail.com', 'killamail.com', 'killergreenmail.com', 'killermail.com', 'killmail.com', 'killmail.net', 'kimo.com', 'kimsdisk.com', 'kinglibrary.net', 'kinki-kids.com', 'kismail.ru', 'kissfans.com', 'kitemail.com', 'kittymail.com', 'kitznet.at', 'kiwibox.com', 'kiwitown.com', 'klassmaster.com', 'klassmaster.net', 'klzlk.com', 'km.ru', 'kmail.com.au', 'knol-power.nl', 'koko.com', 'kolumbus.fi', 'kommespaeter.de', 'konkovo.net', 'konsul.ru', 'konx.com', 'korea.com', 'koreamail.com', 'kosino.net', 'koszmail.pl', 'kozmail.com', 'kpnmail.nl', 'kreditor.ru', 'krim.ws', 'krongthip.com', 'krovatka.net', 'krunis.com', 'ksanmail.com', 'ksee24mail.com', 'kube93mail.com', 'kukamail.com', 'kulturbetrieb.info', 'kumarweb.com', 'kurzepost.de', 'kuwait-mail.com', 'kuzminki.net', 'kyokodate.com', 'kyokofukada.net', 'l33r.eu', 'la.com', 'labetteraverouge.at', 'lackmail.ru', 'ladyfire.com', 'ladymail.cz', 'lagerlouts.com', 'lags.us', 'lahoreoye.com', 'lakmail.com', 'lamer.hu', 'land.ru', 'langoo.com', 'lankamail.com', 'laoeq.com', 'laposte.net', 'lass-es-geschehen.de', 'last-chance.pro', 'lastmail.co', 'latemodels.com', 'latinmail.com', 'latino.com', 'lavabit.com', 'lavache.com', 'law.com', 'lawlita.com', 'lawyer.com', 'lazyinbox.com', 'learn2compute.net', 'lebanonatlas.com', 'leeching.net', 'leehom.net', 'lefortovo.net', 'legalactions.com', 'legalrc.loan', 'legislator.com', 'legistrator.com', 'lenta.ru', 'leonlai.net', 'letsgomets.net', 'letterbox.com', 'letterboxes.org', 'letthemeatspam.com', 'levele.com', 'levele.hu', 'lex.bg', 'lexis-nexis-mail.com', 'lhsdv.com', 'lianozovo.net', 'libero.it', 'liberomail.com', 'lick101.com', 'liebt-dich.info', 'lifebyfood.com', 'link2mail.net', 'linkmaster.com', 'linktrader.com', 'linuxfreemail.com', 'linuxmail.org', 'lionsfan.com.au', 'liontrucks.com', 'liquidinformation.net', 'lissamail.com', 'list.ru', 'listomail.com', 'litedrop.com', 'literaturelover.com', 'littleapple.com', 'littleblueroom.com', 'live.at', 'live.be', 'live.ca', 'live.cl', 'live.cn', 'live.co.uk', 'live.co.za', 'live.com', 'live.com.ar', 'live.com.au', 'live.com.mx', 'live.com.my', 'live.com.pt', 'live.com.sg', 'live.de', 'live.dk', 'live.fr', 'live.hk', 'live.ie', 'live.in', 'live.it', 'live.jp', 'live.nl', 'live.no', 'live.ru', 'live.se', 'liveradio.tk', 'liverpoolfans.com', 'ljiljan.com', 'llandudno.com', 'llangollen.com', 'lmxmail.sk', 'lobbyist.com', 'localbar.com', 'localgenius.com', 'locos.com', 'login-email.ga', 'loh.pp.ua', 'lol.ovpn.to', 'lolfreak.net', 'lolito.tk', 'lolnetwork.net', 'london.com', 'loobie.com', 'looksmart.co.uk', 'looksmart.com', 'looksmart.com.au', 'lookugly.com', 'lopezclub.com', 'lortemail.dk', 'louiskoo.com', 'lov.ru', 'love.com', 'love.cz', 'loveable.com', 'lovecat.com', 'lovefall.ml', 'lovefootball.com', 'loveforlostcats.com', 'lovelygirl.net', 'lovemail.com', 'lover-boy.com', 'lovergirl.com', 'lovesea.gq', 'lovethebroncos.com', 'lovethecowboys.com', 'lovetocook.net', 'lovetohike.com', 'loveyouforever.de', 'lovingjesus.com', 'lowandslow.com', 'lr7.us', 'lr78.com', 'lroid.com', 'lubovnik.ru', 'lukop.dk', 'luso.pt', 'luukku.com', 'luv2.us', 'luvrhino.com', 'lvie.com.sg', 'lvwebmail.com', 'lycos.co.uk', 'lycos.com', 'lycos.es', 'lycos.it', 'lycos.ne.jp', 'lycos.ru', 'lycosemail.com', 'lycosmail.com', 'm-a-i-l.com', 'm-hmail.com', 'm21.cc', 'm4.org', 'm4ilweb.info', 'mac.com', 'macbox.com', 'macbox.ru', 'macfreak.com', 'machinecandy.com', 'macmail.com', 'mad.scientist.com', 'madcrazy.com', 'madcreations.com', 'madonnafan.com', 'madrid.com', 'maennerversteherin.com', 'maennerversteherin.de', 'maffia.hu', 'magicmail.co.za', 'mahmoodweb.com', 'mail-awu.de', 'mail-box.cz', 'mail-center.com', 'mail-central.com', 'mail-easy.fr', 'mail-filter.com', 'mail-me.com', 'mail-page.com', 'mail-temporaire.fr', 'mail-tester.com', 'mail.austria.com', 'mail.az', 'mail.be', 'mail.bg', 'mail.bulgaria.com', 'mail.by', 'mail.byte.it', 'mail.co.za', 'mail.com', 'mail.com.tr', 'mail.ee', 'mail.entrepeneurmag.com', 'mail.freetown.com', 'mail.gr', 'mail.hitthebeach.com', 'mail.htl22.at', 'mail.kmsp.com', 'mail.md', 'mail.mezimages.net', 'mail.misterpinball.de', 'mail.nu', 'mail.org.uk', 'mail.pf', 'mail.pharmacy.com', 'mail.pt', 'mail.r-o-o-t.com', 'mail.ru', 'mail.salu.net', 'mail.sisna.com', 'mail.spaceports.com', 'mail.svenz.eu', 'mail.theboys.com', 'mail.usa.com', 'mail.vasarhely.hu', 'mail.vu', 'mail.wtf', 'mail.zp.ua', 'mail114.net', 'mail15.com', 'mail1a.de', 'mail1st.com', 'mail2007.com', 'mail21.cc', 'mail2aaron.com', 'mail2abby.com', 'mail2abc.com', 'mail2actor.com', 'mail2admiral.com', 'mail2adorable.com', 'mail2adoration.com', 'mail2adore.com', 'mail2adventure.com', 'mail2aeolus.com', 'mail2aether.com', 'mail2affection.com', 'mail2afghanistan.com', 'mail2africa.com', 'mail2agent.com', 'mail2aha.com', 'mail2ahoy.com', 'mail2aim.com', 'mail2air.com', 'mail2airbag.com', 'mail2airforce.com', 'mail2airport.com', 'mail2alabama.com', 'mail2alan.com', 'mail2alaska.com', 'mail2albania.com', 'mail2alcoholic.com', 'mail2alec.com', 'mail2alexa.com', 'mail2algeria.com', 'mail2alicia.com', 'mail2alien.com', 'mail2allan.com', 'mail2allen.com', 'mail2allison.com', 'mail2alpha.com', 'mail2alyssa.com', 'mail2amanda.com', 'mail2amazing.com', 'mail2amber.com', 'mail2america.com', 'mail2american.com', 'mail2andorra.com', 'mail2andrea.com', 'mail2andy.com', 'mail2anesthesiologist.com', 'mail2angela.com', 'mail2angola.com', 'mail2ann.com', 'mail2anna.com', 'mail2anne.com', 'mail2anthony.com', 'mail2anything.com', 'mail2aphrodite.com', 'mail2apollo.com', 'mail2april.com', 'mail2aquarius.com', 'mail2arabia.com', 'mail2arabic.com', 'mail2architect.com', 'mail2ares.com', 'mail2argentina.com', 'mail2aries.com', 'mail2arizona.com', 'mail2arkansas.com', 'mail2armenia.com', 'mail2army.com', 'mail2arnold.com', 'mail2art.com', 'mail2artemus.com', 'mail2arthur.com', 'mail2artist.com', 'mail2ashley.com', 'mail2ask.com', 'mail2astronomer.com', 'mail2athena.com', 'mail2athlete.com', 'mail2atlas.com', 'mail2atom.com', 'mail2attitude.com', 'mail2auction.com', 'mail2aunt.com', 'mail2australia.com', 'mail2austria.com', 'mail2azerbaijan.com', 'mail2baby.com', 'mail2bahamas.com', 'mail2bahrain.com', 'mail2ballerina.com', 'mail2ballplayer.com', 'mail2band.com', 'mail2bangladesh.com', 'mail2bank.com', 'mail2banker.com', 'mail2bankrupt.com', 'mail2baptist.com', 'mail2bar.com', 'mail2barbados.com', 'mail2barbara.com', 'mail2barter.com', 'mail2basketball.com', 'mail2batter.com', 'mail2beach.com', 'mail2beast.com', 'mail2beatles.com', 'mail2beauty.com', 'mail2becky.com', 'mail2beijing.com', 'mail2belgium.com', 'mail2belize.com', 'mail2ben.com', 'mail2bernard.com', 'mail2beth.com', 'mail2betty.com', 'mail2beverly.com', 'mail2beyond.com', 'mail2biker.com', 'mail2bill.com', 'mail2billionaire.com', 'mail2billy.com', 'mail2bio.com', 'mail2biologist.com', 'mail2black.com', 'mail2blackbelt.com', 'mail2blake.com', 'mail2blind.com', 'mail2blonde.com', 'mail2blues.com', 'mail2bob.com', 'mail2bobby.com', 'mail2bolivia.com', 'mail2bombay.com', 'mail2bonn.com', 'mail2bookmark.com', 'mail2boreas.com', 'mail2bosnia.com', 'mail2boston.com', 'mail2botswana.com', 'mail2bradley.com', 'mail2brazil.com', 'mail2breakfast.com', 'mail2brian.com', 'mail2bride.com', 'mail2brittany.com', 'mail2broker.com', 'mail2brook.com', 'mail2bruce.com', 'mail2brunei.com', 'mail2brunette.com', 'mail2brussels.com', 'mail2bryan.com', 'mail2bug.com', 'mail2bulgaria.com', 'mail2business.com', 'mail2buy.com', 'mail2ca.com', 'mail2california.com', 'mail2calvin.com', 'mail2cambodia.com', 'mail2cameroon.com', 'mail2canada.com', 'mail2cancer.com', 'mail2capeverde.com', 'mail2capricorn.com', 'mail2cardinal.com', 'mail2cardiologist.com', 'mail2care.com', 'mail2caroline.com', 'mail2carolyn.com', 'mail2casey.com', 'mail2cat.com', 'mail2caterer.com', 'mail2cathy.com', 'mail2catlover.com', 'mail2catwalk.com', 'mail2cell.com', 'mail2chad.com', 'mail2champaign.com', 'mail2charles.com', 'mail2chef.com', 'mail2chemist.com', 'mail2cherry.com', 'mail2chicago.com', 'mail2chile.com', 'mail2china.com', 'mail2chinese.com', 'mail2chocolate.com', 'mail2christian.com', 'mail2christie.com', 'mail2christmas.com', 'mail2christy.com', 'mail2chuck.com', 'mail2cindy.com', 'mail2clark.com', 'mail2classifieds.com', 'mail2claude.com', 'mail2cliff.com', 'mail2clinic.com', 'mail2clint.com', 'mail2close.com', 'mail2club.com', 'mail2coach.com', 'mail2coastguard.com', 'mail2colin.com', 'mail2college.com', 'mail2colombia.com', 'mail2color.com', 'mail2colorado.com', 'mail2columbia.com', 'mail2comedian.com', 'mail2composer.com', 'mail2computer.com', 'mail2computers.com', 'mail2concert.com', 'mail2congo.com', 'mail2connect.com', 'mail2connecticut.com', 'mail2consultant.com', 'mail2convict.com', 'mail2cook.com', 'mail2cool.com', 'mail2cory.com', 'mail2costarica.com', 'mail2country.com', 'mail2courtney.com', 'mail2cowboy.com', 'mail2cowgirl.com', 'mail2craig.com', 'mail2crave.com', 'mail2crazy.com', 'mail2create.com', 'mail2croatia.com', 'mail2cry.com', 'mail2crystal.com', 'mail2cuba.com', 'mail2culture.com', 'mail2curt.com', 'mail2customs.com', 'mail2cute.com', 'mail2cutey.com', 'mail2cynthia.com', 'mail2cyprus.com', 'mail2czechrepublic.com', 'mail2dad.com', 'mail2dale.com', 'mail2dallas.com', 'mail2dan.com', 'mail2dana.com', 'mail2dance.com', 'mail2dancer.com', 'mail2danielle.com', 'mail2danny.com', 'mail2darlene.com', 'mail2darling.com', 'mail2darren.com', 'mail2daughter.com', 'mail2dave.com', 'mail2dawn.com', 'mail2dc.com', 'mail2dealer.com', 'mail2deanna.com', 'mail2dearest.com', 'mail2debbie.com', 'mail2debby.com', 'mail2deer.com', 'mail2delaware.com', 'mail2delicious.com', 'mail2demeter.com', 'mail2democrat.com', 'mail2denise.com', 'mail2denmark.com', 'mail2dennis.com', 'mail2dentist.com', 'mail2derek.com', 'mail2desert.com', 'mail2devoted.com', 'mail2devotion.com', 'mail2diamond.com', 'mail2diana.com', 'mail2diane.com', 'mail2diehard.com', 'mail2dilemma.com', 'mail2dillon.com', 'mail2dinner.com', 'mail2dinosaur.com', 'mail2dionysos.com', 'mail2diplomat.com', 'mail2director.com', 'mail2dirk.com', 'mail2disco.com', 'mail2dive.com', 'mail2diver.com', 'mail2divorced.com', 'mail2djibouti.com', 'mail2doctor.com', 'mail2doglover.com', 'mail2dominic.com', 'mail2dominica.com', 'mail2dominicanrepublic.com', 'mail2don.com', 'mail2donald.com', 'mail2donna.com', 'mail2doris.com', 'mail2dorothy.com', 'mail2doug.com', 'mail2dough.com', 'mail2douglas.com', 'mail2dow.com', 'mail2downtown.com', 'mail2dream.com', 'mail2dreamer.com', 'mail2dude.com', 'mail2dustin.com', 'mail2dyke.com', 'mail2dylan.com', 'mail2earl.com', 'mail2earth.com', 'mail2eastend.com', 'mail2eat.com', 'mail2economist.com', 'mail2ecuador.com', 'mail2eddie.com', 'mail2edgar.com', 'mail2edwin.com', 'mail2egypt.com', 'mail2electron.com', 'mail2eli.com', 'mail2elizabeth.com', 'mail2ellen.com', 'mail2elliot.com', 'mail2elsalvador.com', 'mail2elvis.com', 'mail2emergency.com', 'mail2emily.com', 'mail2engineer.com', 'mail2english.com', 'mail2environmentalist.com', 'mail2eos.com', 'mail2eric.com', 'mail2erica.com', 'mail2erin.com', 'mail2erinyes.com', 'mail2eris.com', 'mail2eritrea.com', 'mail2ernie.com', 'mail2eros.com', 'mail2estonia.com', 'mail2ethan.com', 'mail2ethiopia.com', 'mail2eu.com', 'mail2europe.com', 'mail2eurus.com', 'mail2eva.com', 'mail2evan.com', 'mail2evelyn.com', 'mail2everything.com', 'mail2exciting.com', 'mail2expert.com', 'mail2fairy.com', 'mail2faith.com', 'mail2fanatic.com', 'mail2fancy.com', 'mail2fantasy.com', 'mail2farm.com', 'mail2farmer.com', 'mail2fashion.com', 'mail2fat.com', 'mail2feeling.com', 'mail2female.com', 'mail2fever.com', 'mail2fighter.com', 'mail2fiji.com', 'mail2filmfestival.com', 'mail2films.com', 'mail2finance.com', 'mail2finland.com', 'mail2fireman.com', 'mail2firm.com', 'mail2fisherman.com', 'mail2flexible.com', 'mail2florence.com', 'mail2florida.com', 'mail2floyd.com', 'mail2fly.com', 'mail2fond.com', 'mail2fondness.com', 'mail2football.com', 'mail2footballfan.com', 'mail2found.com', 'mail2france.com', 'mail2frank.com', 'mail2frankfurt.com', 'mail2franklin.com', 'mail2fred.com', 'mail2freddie.com', 'mail2free.com', 'mail2freedom.com', 'mail2french.com', 'mail2freudian.com', 'mail2friendship.com', 'mail2from.com', 'mail2fun.com', 'mail2gabon.com', 'mail2gabriel.com', 'mail2gail.com', 'mail2galaxy.com', 'mail2gambia.com', 'mail2games.com', 'mail2gary.com', 'mail2gavin.com', 'mail2gemini.com', 'mail2gene.com', 'mail2genes.com', 'mail2geneva.com', 'mail2george.com', 'mail2georgia.com', 'mail2gerald.com', 'mail2german.com', 'mail2germany.com', 'mail2ghana.com', 'mail2gilbert.com', 'mail2gina.com', 'mail2girl.com', 'mail2glen.com', 'mail2gloria.com', 'mail2goddess.com', 'mail2gold.com', 'mail2golfclub.com', 'mail2golfer.com', 'mail2gordon.com', 'mail2government.com', 'mail2grab.com', 'mail2grace.com', 'mail2graham.com', 'mail2grandma.com', 'mail2grandpa.com', 'mail2grant.com', 'mail2greece.com', 'mail2green.com', 'mail2greg.com', 'mail2grenada.com', 'mail2gsm.com', 'mail2guard.com', 'mail2guatemala.com', 'mail2guy.com', 'mail2hades.com', 'mail2haiti.com', 'mail2hal.com', 'mail2handhelds.com', 'mail2hank.com', 'mail2hannah.com', 'mail2harold.com', 'mail2harry.com', 'mail2hawaii.com', 'mail2headhunter.com', 'mail2heal.com', 'mail2heather.com', 'mail2heaven.com', 'mail2hebe.com', 'mail2hecate.com', 'mail2heidi.com', 'mail2helen.com', 'mail2hell.com', 'mail2help.com', 'mail2helpdesk.com', 'mail2henry.com', 'mail2hephaestus.com', 'mail2hera.com', 'mail2hercules.com', 'mail2herman.com', 'mail2hermes.com', 'mail2hespera.com', 'mail2hestia.com', 'mail2highschool.com', 'mail2hindu.com', 'mail2hip.com', 'mail2hiphop.com', 'mail2holland.com', 'mail2holly.com', 'mail2hollywood.com', 'mail2homer.com', 'mail2honduras.com', 'mail2honey.com', 'mail2hongkong.com', 'mail2hope.com', 'mail2horse.com', 'mail2hot.com', 'mail2hotel.com', 'mail2houston.com', 'mail2howard.com', 'mail2hugh.com', 'mail2human.com', 'mail2hungary.com', 'mail2hungry.com', 'mail2hygeia.com', 'mail2hyperspace.com', 'mail2hypnos.com', 'mail2ian.com', 'mail2ice-cream.com', 'mail2iceland.com', 'mail2idaho.com', 'mail2idontknow.com', 'mail2illinois.com', 'mail2imam.com', 'mail2in.com', 'mail2india.com', 'mail2indian.com', 'mail2indiana.com', 'mail2indonesia.com', 'mail2infinity.com', 'mail2intense.com', 'mail2iowa.com', 'mail2iran.com', 'mail2iraq.com', 'mail2ireland.com', 'mail2irene.com', 'mail2iris.com', 'mail2irresistible.com', 'mail2irving.com', 'mail2irwin.com', 'mail2isaac.com', 'mail2israel.com', 'mail2italian.com', 'mail2italy.com', 'mail2jackie.com', 'mail2jacob.com', 'mail2jail.com', 'mail2jaime.com', 'mail2jake.com', 'mail2jamaica.com', 'mail2james.com', 'mail2jamie.com', 'mail2jan.com', 'mail2jane.com', 'mail2janet.com', 'mail2janice.com', 'mail2japan.com', 'mail2japanese.com', 'mail2jasmine.com', 'mail2jason.com', 'mail2java.com', 'mail2jay.com', 'mail2jazz.com', 'mail2jed.com', 'mail2jeffrey.com', 'mail2jennifer.com', 'mail2jenny.com', 'mail2jeremy.com', 'mail2jerry.com', 'mail2jessica.com', 'mail2jessie.com', 'mail2jesus.com', 'mail2jew.com', 'mail2jeweler.com', 'mail2jim.com', 'mail2jimmy.com', 'mail2joan.com', 'mail2joann.com', 'mail2joanna.com', 'mail2jody.com', 'mail2joe.com', 'mail2joel.com', 'mail2joey.com', 'mail2john.com', 'mail2join.com', 'mail2jon.com', 'mail2jonathan.com', 'mail2jones.com', 'mail2jordan.com', 'mail2joseph.com', 'mail2josh.com', 'mail2joy.com', 'mail2juan.com', 'mail2judge.com', 'mail2judy.com', 'mail2juggler.com', 'mail2julian.com', 'mail2julie.com', 'mail2jumbo.com', 'mail2junk.com', 'mail2justin.com', 'mail2justme.com', 'mail2k.ru', 'mail2kansas.com', 'mail2karate.com', 'mail2karen.com', 'mail2karl.com', 'mail2karma.com', 'mail2kathleen.com', 'mail2kathy.com', 'mail2katie.com', 'mail2kay.com', 'mail2kazakhstan.com', 'mail2keen.com', 'mail2keith.com', 'mail2kelly.com', 'mail2kelsey.com', 'mail2ken.com', 'mail2kendall.com', 'mail2kennedy.com', 'mail2kenneth.com', 'mail2kenny.com', 'mail2kentucky.com', 'mail2kenya.com', 'mail2kerry.com', 'mail2kevin.com', 'mail2kim.com', 'mail2kimberly.com', 'mail2king.com', 'mail2kirk.com', 'mail2kiss.com', 'mail2kosher.com', 'mail2kristin.com', 'mail2kurt.com', 'mail2kuwait.com', 'mail2kyle.com', 'mail2kyrgyzstan.com', 'mail2la.com', 'mail2lacrosse.com', 'mail2lance.com', 'mail2lao.com', 'mail2larry.com', 'mail2latvia.com', 'mail2laugh.com', 'mail2laura.com', 'mail2lauren.com', 'mail2laurie.com', 'mail2lawrence.com', 'mail2lawyer.com', 'mail2lebanon.com', 'mail2lee.com', 'mail2leo.com', 'mail2leon.com', 'mail2leonard.com', 'mail2leone.com', 'mail2leslie.com', 'mail2letter.com', 'mail2liberia.com', 'mail2libertarian.com', 'mail2libra.com', 'mail2libya.com', 'mail2liechtenstein.com', 'mail2life.com', 'mail2linda.com', 'mail2linux.com', 'mail2lionel.com', 'mail2lipstick.com', 'mail2liquid.com', 'mail2lisa.com', 'mail2lithuania.com', 'mail2litigator.com', 'mail2liz.com', 'mail2lloyd.com', 'mail2lois.com', 'mail2lola.com', 'mail2london.com', 'mail2looking.com', 'mail2lori.com', 'mail2lost.com', 'mail2lou.com', 'mail2louis.com', 'mail2louisiana.com', 'mail2lovable.com', 'mail2love.com', 'mail2lucky.com', 'mail2lucy.com', 'mail2lunch.com', 'mail2lust.com', 'mail2luxembourg.com', 'mail2luxury.com', 'mail2lyle.com', 'mail2lynn.com', 'mail2madagascar.com', 'mail2madison.com', 'mail2madrid.com', 'mail2maggie.com', 'mail2mail4.com', 'mail2maine.com', 'mail2malawi.com', 'mail2malaysia.com', 'mail2maldives.com', 'mail2mali.com', 'mail2malta.com', 'mail2mambo.com', 'mail2man.com', 'mail2mandy.com', 'mail2manhunter.com', 'mail2mankind.com', 'mail2many.com', 'mail2marc.com', 'mail2marcia.com', 'mail2margaret.com', 'mail2margie.com', 'mail2marhaba.com', 'mail2maria.com', 'mail2marilyn.com', 'mail2marines.com', 'mail2mark.com', 'mail2marriage.com', 'mail2married.com', 'mail2marries.com', 'mail2mars.com', 'mail2marsha.com', 'mail2marshallislands.com', 'mail2martha.com', 'mail2martin.com', 'mail2marty.com', 'mail2marvin.com', 'mail2mary.com', 'mail2maryland.com', 'mail2mason.com', 'mail2massachusetts.com', 'mail2matt.com', 'mail2matthew.com', 'mail2maurice.com', 'mail2mauritania.com', 'mail2mauritius.com', 'mail2max.com', 'mail2maxwell.com', 'mail2maybe.com', 'mail2mba.com', 'mail2me4u.com', 'mail2mechanic.com', 'mail2medieval.com', 'mail2megan.com', 'mail2mel.com', 'mail2melanie.com', 'mail2melissa.com', 'mail2melody.com', 'mail2member.com', 'mail2memphis.com', 'mail2methodist.com', 'mail2mexican.com', 'mail2mexico.com', 'mail2mgz.com', 'mail2miami.com', 'mail2michael.com', 'mail2michelle.com', 'mail2michigan.com', 'mail2mike.com', 'mail2milan.com', 'mail2milano.com', 'mail2mildred.com', 'mail2milkyway.com', 'mail2millennium.com', 'mail2millionaire.com', 'mail2milton.com', 'mail2mime.com', 'mail2mindreader.com', 'mail2mini.com', 'mail2minister.com', 'mail2minneapolis.com', 'mail2minnesota.com', 'mail2miracle.com', 'mail2missionary.com', 'mail2mississippi.com', 'mail2missouri.com', 'mail2mitch.com', 'mail2model.com', 'mail2moldova.commail2molly.com', 'mail2mom.com', 'mail2monaco.com', 'mail2money.com', 'mail2mongolia.com', 'mail2monica.com', 'mail2montana.com', 'mail2monty.com', 'mail2moon.com', 'mail2morocco.com', 'mail2morpheus.com', 'mail2mors.com', 'mail2moscow.com', 'mail2moslem.com', 'mail2mouseketeer.com', 'mail2movies.com', 'mail2mozambique.com', 'mail2mp3.com', 'mail2mrright.com', 'mail2msright.com', 'mail2museum.com', 'mail2music.com', 'mail2musician.com', 'mail2muslim.com', 'mail2my.com', 'mail2myboat.com', 'mail2mycar.com', 'mail2mycell.com', 'mail2mygsm.com', 'mail2mylaptop.com', 'mail2mymac.com', 'mail2mypager.com', 'mail2mypalm.com', 'mail2mypc.com', 'mail2myphone.com', 'mail2myplane.com', 'mail2namibia.com', 'mail2nancy.com', 'mail2nasdaq.com', 'mail2nathan.com', 'mail2nauru.com', 'mail2navy.com', 'mail2neal.com', 'mail2nebraska.com', 'mail2ned.com', 'mail2neil.com', 'mail2nelson.com', 'mail2nemesis.com', 'mail2nepal.com', 'mail2netherlands.com', 'mail2network.com', 'mail2nevada.com', 'mail2newhampshire.com', 'mail2newjersey.com', 'mail2newmexico.com', 'mail2newyork.com', 'mail2newzealand.com', 'mail2nicaragua.com', 'mail2nick.com', 'mail2nicole.com', 'mail2niger.com', 'mail2nigeria.com', 'mail2nike.com', 'mail2no.com', 'mail2noah.com', 'mail2noel.com', 'mail2noelle.com', 'mail2normal.com', 'mail2norman.com', 'mail2northamerica.com', 'mail2northcarolina.com', 'mail2northdakota.com', 'mail2northpole.com', 'mail2norway.com', 'mail2notus.com', 'mail2noway.com', 'mail2nowhere.com', 'mail2nuclear.com', 'mail2nun.com', 'mail2ny.com', 'mail2oasis.com', 'mail2oceanographer.com', 'mail2ohio.com', 'mail2ok.com', 'mail2oklahoma.com', 'mail2oliver.com', 'mail2oman.com', 'mail2one.com', 'mail2onfire.com', 'mail2online.com', 'mail2oops.com', 'mail2open.com', 'mail2ophthalmologist.com', 'mail2optometrist.com', 'mail2oregon.com', 'mail2oscars.com', 'mail2oslo.com', 'mail2painter.com', 'mail2pakistan.com', 'mail2palau.com', 'mail2pan.com', 'mail2panama.com', 'mail2paraguay.com', 'mail2paralegal.com', 'mail2paris.com', 'mail2park.com', 'mail2parker.com', 'mail2party.com', 'mail2passion.com', 'mail2pat.com', 'mail2patricia.com', 'mail2patrick.com', 'mail2patty.com', 'mail2paul.com', 'mail2paula.com', 'mail2pay.com', 'mail2peace.com', 'mail2pediatrician.com', 'mail2peggy.com', 'mail2pennsylvania.com', 'mail2perry.com', 'mail2persephone.com', 'mail2persian.com', 'mail2peru.com', 'mail2pete.com', 'mail2peter.com', 'mail2pharmacist.com', 'mail2phil.com', 'mail2philippines.com', 'mail2phoenix.com', 'mail2phonecall.com', 'mail2phyllis.com', 'mail2pickup.com', 'mail2pilot.com', 'mail2pisces.com', 'mail2planet.com', 'mail2platinum.com', 'mail2plato.com', 'mail2pluto.com', 'mail2pm.com', 'mail2podiatrist.com', 'mail2poet.com', 'mail2poland.com', 'mail2policeman.com', 'mail2policewoman.com', 'mail2politician.com', 'mail2pop.com', 'mail2pope.com', 'mail2popular.com', 'mail2portugal.com', 'mail2poseidon.com', 'mail2potatohead.com', 'mail2power.com', 'mail2presbyterian.com', 'mail2president.com', 'mail2priest.com', 'mail2prince.com', 'mail2princess.com', 'mail2producer.com', 'mail2professor.com', 'mail2protect.com', 'mail2psychiatrist.com', 'mail2psycho.com', 'mail2psychologist.com', 'mail2qatar.com', 'mail2queen.com', 'mail2rabbi.com', 'mail2race.com', 'mail2racer.com', 'mail2rachel.com', 'mail2rage.com', 'mail2rainmaker.com', 'mail2ralph.com', 'mail2randy.com', 'mail2rap.com', 'mail2rare.com', 'mail2rave.com', 'mail2ray.com', 'mail2raymond.com', 'mail2realtor.com', 'mail2rebecca.com', 'mail2recruiter.com', 'mail2recycle.com', 'mail2redhead.com', 'mail2reed.com', 'mail2reggie.com', 'mail2register.com', 'mail2rent.com', 'mail2republican.com', 'mail2resort.com', 'mail2rex.com', 'mail2rhodeisland.com', 'mail2rich.com', 'mail2richard.com', 'mail2ricky.com', 'mail2ride.com', 'mail2riley.com', 'mail2rita.com', 'mail2rob.com', 'mail2robert.com', 'mail2roberta.com', 'mail2robin.com', 'mail2rock.com', 'mail2rocker.com', 'mail2rod.com', 'mail2rodney.com', 'mail2romania.com', 'mail2rome.com', 'mail2ron.com', 'mail2ronald.com', 'mail2ronnie.com', 'mail2rose.com', 'mail2rosie.com', 'mail2roy.com', 'mail2rss.org', 'mail2rudy.com', 'mail2rugby.com', 'mail2runner.com', 'mail2russell.com', 'mail2russia.com', 'mail2russian.com', 'mail2rusty.com', 'mail2ruth.com', 'mail2rwanda.com', 'mail2ryan.com', 'mail2sa.com', 'mail2sabrina.com', 'mail2safe.com', 'mail2sagittarius.com', 'mail2sail.com', 'mail2sailor.com', 'mail2sal.com', 'mail2salaam.com', 'mail2sam.com', 'mail2samantha.com', 'mail2samoa.com', 'mail2samurai.com', 'mail2sandra.com', 'mail2sandy.com', 'mail2sanfrancisco.com', 'mail2sanmarino.com', 'mail2santa.com', 'mail2sara.com', 'mail2sarah.com', 'mail2sat.com', 'mail2saturn.com', 'mail2saudi.com', 'mail2saudiarabia.com', 'mail2save.com', 'mail2savings.com', 'mail2school.com', 'mail2scientist.com', 'mail2scorpio.com', 'mail2scott.com', 'mail2sean.com', 'mail2search.com', 'mail2seattle.com', 'mail2secretagent.com', 'mail2senate.com', 'mail2senegal.com', 'mail2sensual.com', 'mail2seth.com', 'mail2sevenseas.com', 'mail2sexy.com', 'mail2seychelles.com', 'mail2shane.com', 'mail2sharon.com', 'mail2shawn.com', 'mail2ship.com', 'mail2shirley.com', 'mail2shoot.com', 'mail2shuttle.com', 'mail2sierraleone.com', 'mail2simon.com', 'mail2singapore.com', 'mail2single.com', 'mail2site.com', 'mail2skater.com', 'mail2skier.com', 'mail2sky.com', 'mail2sleek.com', 'mail2slim.com', 'mail2slovakia.com', 'mail2slovenia.com', 'mail2smile.com', 'mail2smith.com', 'mail2smooth.com', 'mail2soccer.com', 'mail2soccerfan.com', 'mail2socialist.com', 'mail2soldier.com', 'mail2somalia.com', 'mail2son.com', 'mail2song.com', 'mail2sos.com', 'mail2sound.com', 'mail2southafrica.com', 'mail2southamerica.com', 'mail2southcarolina.com', 'mail2southdakota.com', 'mail2southkorea.com', 'mail2southpole.com', 'mail2spain.com', 'mail2spanish.com', 'mail2spare.com', 'mail2spectrum.com', 'mail2splash.com', 'mail2sponsor.com', 'mail2sports.com', 'mail2srilanka.com', 'mail2stacy.com', 'mail2stan.com', 'mail2stanley.com', 'mail2star.com', 'mail2state.com', 'mail2stephanie.com', 'mail2steve.com', 'mail2steven.com', 'mail2stewart.com', 'mail2stlouis.com', 'mail2stock.com', 'mail2stockholm.com', 'mail2stockmarket.com', 'mail2storage.com', 'mail2store.com', 'mail2strong.com', 'mail2student.com', 'mail2studio.com', 'mail2studio54.com', 'mail2stuntman.com', 'mail2subscribe.com', 'mail2sudan.com', 'mail2superstar.com', 'mail2surfer.com', 'mail2suriname.com', 'mail2susan.com', 'mail2suzie.com', 'mail2swaziland.com', 'mail2sweden.com', 'mail2sweetheart.com', 'mail2swim.com', 'mail2swimmer.com', 'mail2swiss.com', 'mail2switzerland.com', 'mail2sydney.com', 'mail2sylvia.com', 'mail2syria.com', 'mail2taboo.com', 'mail2taiwan.com', 'mail2tajikistan.com', 'mail2tammy.com', 'mail2tango.com', 'mail2tanya.com', 'mail2tanzania.com', 'mail2tara.com', 'mail2taurus.com', 'mail2taxi.com', 'mail2taxidermist.com', 'mail2taylor.com', 'mail2taz.com', 'mail2teacher.com', 'mail2technician.com', 'mail2ted.com', 'mail2telephone.com', 'mail2teletubbie.com', 'mail2tenderness.com', 'mail2tennessee.com', 'mail2tennis.com', 'mail2tennisfan.com', 'mail2terri.com', 'mail2terry.com', 'mail2test.com', 'mail2texas.com', 'mail2thailand.com', 'mail2therapy.com', 'mail2think.com', 'mail2tickets.com', 'mail2tiffany.com', 'mail2tim.com', 'mail2time.com', 'mail2timothy.com', 'mail2tina.com', 'mail2titanic.com', 'mail2toby.com', 'mail2todd.com', 'mail2togo.com', 'mail2tom.com', 'mail2tommy.com', 'mail2tonga.com', 'mail2tony.com', 'mail2touch.com', 'mail2tourist.com', 'mail2tracey.com', 'mail2tracy.com', 'mail2tramp.com', 'mail2travel.com', 'mail2traveler.com', 'mail2travis.com', 'mail2trekkie.com', 'mail2trex.com', 'mail2triallawyer.com', 'mail2trick.com', 'mail2trillionaire.com', 'mail2troy.com', 'mail2truck.com', 'mail2trump.com', 'mail2try.com', 'mail2tunisia.com', 'mail2turbo.com', 'mail2turkey.com', 'mail2turkmenistan.com', 'mail2tv.com', 'mail2tycoon.com', 'mail2tyler.com', 'mail2u4me.com', 'mail2uae.com', 'mail2uganda.com', 'mail2uk.com', 'mail2ukraine.com', 'mail2uncle.com', 'mail2unsubscribe.com', 'mail2uptown.com', 'mail2uruguay.com', 'mail2usa.com', 'mail2utah.com', 'mail2uzbekistan.com', 'mail2v.com', 'mail2vacation.com', 'mail2valentines.com', 'mail2valerie.com', 'mail2valley.com', 'mail2vamoose.com', 'mail2vanessa.com', 'mail2vanuatu.com', 'mail2venezuela.com', 'mail2venous.com', 'mail2venus.com', 'mail2vermont.com', 'mail2vickie.com', 'mail2victor.com', 'mail2victoria.com', 'mail2vienna.com', 'mail2vietnam.com', 'mail2vince.com', 'mail2virginia.com', 'mail2virgo.com', 'mail2visionary.com', 'mail2vodka.com', 'mail2volleyball.com', 'mail2waiter.com', 'mail2wallstreet.com', 'mail2wally.com', 'mail2walter.com', 'mail2warren.com', 'mail2washington.com', 'mail2wave.com', 'mail2way.com', 'mail2waycool.com', 'mail2wayne.com', 'mail2webmaster.com', 'mail2webtop.com', 'mail2webtv.com', 'mail2weird.com', 'mail2wendell.com', 'mail2wendy.com', 'mail2westend.com', 'mail2westvirginia.com', 'mail2whether.com', 'mail2whip.com', 'mail2white.com', 'mail2whitehouse.com', 'mail2whitney.com', 'mail2why.com', 'mail2wilbur.com', 'mail2wild.com', 'mail2willard.com', 'mail2willie.com', 'mail2wine.com', 'mail2winner.com', 'mail2wired.com', 'mail2wisconsin.com', 'mail2woman.com', 'mail2wonder.com', 'mail2world.com', 'mail2worship.com', 'mail2wow.com', 'mail2www.com', 'mail2wyoming.com', 'mail2xfiles.com', 'mail2xox.com', 'mail2yachtclub.com', 'mail2yahalla.com', 'mail2yemen.com', 'mail2yes.com', 'mail2yugoslavia.com', 'mail2zack.com', 'mail2zambia.com', 'mail2zenith.com', 'mail2zephir.com', 'mail2zeus.com', 'mail2zipper.com', 'mail2zoo.com', 'mail2zoologist.com', 'mail2zurich.com', 'mail3000.com', 'mail333.com', 'mail4trash.com', 'mail4u.info', 'mail8.com', 'mailandftp.com', 'mailandnews.com', 'mailas.com', 'mailasia.com', 'mailbidon.com', 'mailbiz.biz', 'mailblocks.com', 'mailbolt.com', 'mailbomb.net', 'mailboom.com', 'mailbox.as', 'mailbox.co.za', 'mailbox.gr', 'mailbox.hu', 'mailbox72.biz', 'mailbox80.biz', 'mailbr.com.br', 'mailbucket.org', 'mailc.net', 'mailcan.com', 'mailcat.biz', 'mailcatch.com', 'mailcc.com', 'mailchoose.co', 'mailcity.com', 'mailclub.fr', 'mailclub.net', 'mailde.de', 'mailde.info', 'maildrop.cc', 'maildrop.gq', 'maildx.com', 'mailed.ro', 'maileimer.de', 'mailexcite.com', 'mailexpire.com', 'mailfa.tk', 'mailfly.com', 'mailforce.net', 'mailforspam.com', 'mailfree.gq', 'mailfreeonline.com', 'mailfreeway.com', 'mailfs.com', 'mailftp.com', 'mailgate.gr', 'mailgate.ru', 'mailgenie.net', 'mailguard.me', 'mailhaven.com', 'mailhood.com', 'mailimate.com', 'mailin8r.com', 'mailinatar.com', 'mailinater.com', 'mailinator.com', 'mailinator.net', 'mailinator.org', 'mailinator.us', 'mailinator2.com', 'mailinblack.com', 'mailincubator.com', 'mailingaddress.org', 'mailingweb.com', 'mailisent.com', 'mailismagic.com', 'mailite.com', 'mailmate.com', 'mailme.dk', 'mailme.gq', 'mailme.ir', 'mailme.lv', 'mailme24.com', 'mailmetrash.com', 'mailmight.com', 'mailmij.nl', 'mailmoat.com', 'mailms.com', 'mailnator.com', 'mailnesia.com', 'mailnew.com', 'mailnull.com', 'mailops.com', 'mailorg.org', 'mailoye.com', 'mailpanda.com', 'mailpick.biz', 'mailpokemon.com', 'mailpost.zzn.com', 'mailpride.com', 'mailproxsy.com', 'mailpuppy.com', 'mailquack.com', 'mailrock.biz', 'mailroom.com', 'mailru.com', 'mailsac.com', 'mailscrap.com', 'mailseal.de', 'mailsent.net', 'mailserver.ru', 'mailservice.ms', 'mailshell.com', 'mailshuttle.com', 'mailsiphon.com', 'mailslapping.com', 'mailsnare.net', 'mailstart.com', 'mailstartplus.com', 'mailsurf.com', 'mailtag.com', 'mailtemp.info', 'mailto.de', 'mailtome.de', 'mailtothis.com', 'mailtrash.net', 'mailtv.net', 'mailtv.tv', 'mailueberfall.de', 'mailup.net', 'mailwire.com', 'mailworks.org', 'mailzi.ru', 'mailzilla.com', 'mailzilla.org', 'makemetheking.com', 'maktoob.com', 'malayalamtelevision.net', 'malayalapathram.com', 'male.ru', 'maltesemail.com', 'mamber.net', 'manager.de', 'manager.in.th', 'mancity.net', 'manlymail.net', 'mantrafreenet.com', 'mantramail.com', 'mantraonline.com', 'manutdfans.com', 'manybrain.com', 'marchmail.com', 'marfino.net', 'margarita.ru', 'mariah-carey.ml.org', 'mariahc.com', 'marijuana.com', 'marijuana.nl', 'marketing.lu', 'marketingfanatic.com', 'marketweighton.com', 'married-not.com', 'marriedandlovingit.com', 'marry.ru', 'marsattack.com', 'martindalemail.com', 'martinguerre.net', 'mash4077.com', 'masrawy.com', 'matmail.com', 'mauimail.com', 'mauritius.com', 'maximumedge.com', 'maxleft.com', 'maxmail.co.uk', 'mayaple.ru', 'mbox.com.au', 'mbx.cc', 'mchsi.com', 'mcrmail.com', 'me-mail.hu', 'me.com', 'meanpeoplesuck.com', 'meatismurder.net', 'medical.net.au', 'medmail.com', 'medscape.com', 'meetingmall.com', 'mega.zik.dj', 'megago.com', 'megamail.pt', 'megapoint.com', 'mehrani.com', 'mehtaweb.com', 'meine-dateien.info', 'meine-diashow.de', 'meine-fotos.info', 'meine-urlaubsfotos.de', 'meinspamschutz.de', 'mekhong.com', 'melodymail.com', 'meloo.com', 'meltmail.com', 'members.student.com', 'menja.net', 'merda.flu.cc', 'merda.igg.biz', 'merda.nut.cc', 'merda.usa.cc', 'merseymail.com', 'mesra.net', 'message.hu', 'message.myspace.com', 'messagebeamer.de', 'messages.to', 'messagez.com', 'metacrawler.com', 'metalfan.com', 'metaping.com', 'metta.lk', 'mexicomail.com', 'mezimages.net', 'mfsa.ru', 'miatadriver.com', 'mierdamail.com', 'miesto.sk', 'mighty.co.za', 'migmail.net', 'migmail.pl', 'migumail.com', 'miho-nakayama.com', 'mikrotamanet.com', 'millionaireintraining.com', 'millionairemail.com', 'milmail.com', 'milmail.com15', 'mindless.com', 'mindspring.com', 'minermail.com', 'mini-mail.com', 'minister.com', 'ministry-of-silly-walks.de', 'mintemail.com', 'misery.net', 'misterpinball.de', 'mit.tc', 'mittalweb.com', 'mixmail.com', 'mjfrogmail.com', 'ml1.net', 'mlanime.com', 'mlb.bounce.ed10.net', 'mm.st', 'mmail.com', 'mns.ru', 'mo3gov.net', 'moakt.com', 'mobico.ru', 'mobilbatam.com', 'mobileninja.co.uk', 'mochamail.com', 'modemnet.net', 'modernenglish.com', 'modomail.com', 'mohammed.com', 'mohmal.com', 'moldova.cc', 'moldova.com', 'moldovacc.com', 'mom-mail.com', 'momslife.com', 'moncourrier.fr.nf', 'monemail.com', 'monemail.fr.nf', 'money.net', 'mongol.net', 'monmail.fr.nf', 'monsieurcinema.com', 'montevideo.com.uy', 'monumentmail.com', 'moomia.com', 'moonman.com', 'moose-mail.com', 'mor19.uu.gl', 'mortaza.com', 'mosaicfx.com', 'moscowmail.com', 'mosk.ru', 'most-wanted.com', 'mostlysunny.com', 'motorcyclefan.net', 'motormania.com', 'movemail.com', 'movieemail.net', 'movieluver.com', 'mox.pp.ua', 'mozartmail.com', 'mozhno.net', 'mp3haze.com', 'mp4.it', 'mr-potatohead.com', 'mrpost.com', 'mrspender.com', 'mscold.com', 'msgbox.com', 'msn.cn', 'msn.com', 'msn.nl', 'msx.ru', 'mt2009.com', 'mt2014.com', 'mt2015.com', 'mt2016.com', 'mttestdriver.com', 'muehlacker.tk', 'multiplechoices', 'mundomail.net', 'munich.com', 'music.com', 'music.com19', 'music.maigate.ru', 'musician.com', 'musician.org', 'musicscene.org', 'muskelshirt.de', 'muslim.com', 'muslimemail.com', 'muslimsonline.com', 'mutantweb.com', 'mvrht.com', 'my.com', 'my10minutemail.com', 'mybox.it', 'mycabin.com', 'mycampus.com', 'mycard.net.ua', 'mycity.com', 'mycleaninbox.net', 'mycool.com', 'mydomain.com', 'mydotcomaddress.com', 'myfairpoint.net', 'myfamily.com', 'myfastmail.com', 'myfunnymail.com', 'mygo.com', 'myiris.com', 'myjazzmail.com', 'mymac.ru', 'mymacmail.com', 'mymail-in.net', 'mymail.ro', 'mynamedot.com', 'mynet.com', 'mynetaddress.com', 'mynetstore.de', 'myotw.net', 'myownemail.com', 'myownfriends.com', 'mypacks.net', 'mypad.com', 'mypartyclip.de', 'mypersonalemail.com', 'myphantomemail.com', 'myplace.com', 'myrambler.ru', 'myrealbox.com', 'myremarq.com', 'mysamp.de', 'myself.com', 'myspaceinc.net', 'myspamless.com', 'mystupidjob.com', 'mytemp.email', 'mytempemail.com', 'mytempmail.com', 'mythirdage.com', 'mytrashmail.com', 'myway.com', 'myworldmail.com', 'n2.com', 'n2baseball.com', 'n2business.com', 'n2mail.com', 'n2soccer.com', 'n2software.com', 'nabc.biz', 'nabuma.com', 'nafe.com', 'nagarealm.com', 'nagpal.net', 'nakedgreens.com', 'name.com', 'nameplanet.com', 'nanaseaikawa.com', 'nandomail.com', 'naplesnews.net', 'naseej.com', 'nate.com', 'nativestar.net', 'nativeweb.net', 'naui.net', 'naver.com', 'navigator.lv', 'navy.org', 'naz.com', 'nc.rr.com', 'nc.ru', 'nchoicemail.com', 'neeva.net', 'nekto.com', 'nekto.net', 'nekto.ru', 'nemra1.com', 'nenter.com', 'neo.rr.com', 'neomailbox.com', 'nepwk.com', 'nervhq.org', 'nervmich.net', 'nervtmich.net', 'net-c.be', 'net-c.ca', 'net-c.cat', 'net-c.com', 'net-c.es', 'net-c.fr', 'net-c.it', 'net-c.lu', 'net-c.nl', 'net-c.pl', 'net-pager.net', 'net-shopping.com', 'net.tf', 'net4b.pt', 'net4you.at', 'netaddres.ru', 'netaddress.ru', 'netbounce.com', 'netbroadcaster.com', 'netby.dk', 'netc.eu', 'netc.fr', 'netc.it', 'netc.lu', 'netc.pl', 'netcenter-vn.net', 'netcity.ru', 'netcmail.com', 'netcourrier.com', 'netexecutive.com', 'netexpressway.com', 'netfirms.com', 'netgenie.com', 'netian.com', 'netizen.com.ar', 'netkushi.com', 'netlane.com', 'netlimit.com', 'netmail.kg', 'netmails.com', 'netmails.net', 'netman.ru', 'netmanor.com', 'netmongol.com', 'netnet.com.sg', 'netnoir.net', 'netpiper.com', 'netposta.net', 'netradiomail.com', 'netralink.com', 'netscape.net', 'netscapeonline.co.uk', 'netspace.net.au', 'netspeedway.com', 'netsquare.com', 'netster.com', 'nettaxi.com', 'nettemail.com', 'netterchef.de', 'netti.fi', 'netvigator.com', 'netzero.com', 'netzero.net', 'netzidiot.de', 'netzoola.com', 'neue-dateien.de', 'neuf.fr', 'neuro.md', 'neustreet.com', 'neverbox.com', 'newap.ru', 'newarbat.net', 'newmail.com', 'newmail.net', 'newmail.ru', 'newsboysmail.com', 'newyork.com', 'newyorkcity.com', 'nextmail.ru', 'nexxmail.com', 'nfmail.com', 'ngs.ru', 'nhmail.com', 'nice-4u.com', 'nicebush.com', 'nicegal.com', 'nicholastse.net', 'nicolastse.com', 'niepodam.pl', 'nightimeuk.com', 'nightmail.com', 'nightmail.ru', 'nikopage.com', 'nikulino.net', 'nimail.com', 'nincsmail.hu', 'ninfan.com', 'nirvanafan.com', 'nm.ru', 'nmail.cf', 'nnh.com', 'nnov.ru', 'no-spam.ws', 'no4ma.ru', 'noavar.com', 'noblepioneer.com', 'nogmailspam.info', 'nomail.pw', 'nomail.xl.cx', 'nomail2me.com', 'nomorespamemails.com', 'nonpartisan.com', 'nonspam.eu', 'nonspammer.de', 'nonstopcinema.com', 'norika-fujiwara.com', 'norikomail.com', 'northgates.net', 'nospam.ze.tc', 'nospam4.us', 'nospamfor.us', 'nospammail.net', 'nospamthanks.info', 'notmailinator.com', 'notsharingmy.info', 'notyouagain.com', 'novogireevo.net', 'novokosino.net', 'nowhere.org', 'nowmymail.com', 'ntelos.net', 'ntlhelp.net', 'ntlworld.com', 'ntscan.com', 'null.net', 'nullbox.info', 'numep.ru', 'nur-fuer-spam.de', 'nurfuerspam.de', 'nus.edu.sg', 'nuvse.com', 'nwldx.com', 'nxt.ru', 'ny.com', 'nybce.com', 'nybella.com', 'nyc.com', 'nycmail.com', 'nz11.com', 'nzoomail.com', 'o-tay.com', 'o2.co.uk', 'o2.pl', 'oaklandas-fan.com', 'oath.com', 'objectmail.com', 'obobbo.com', 'oceanfree.net', 'ochakovo.net', 'odaymail.com', 'oddpost.com', 'odmail.com', 'odnorazovoe.ru', 'office-dateien.de', 'office-email.com', 'officedomain.com', 'offroadwarrior.com', 'oi.com.br', 'oicexchange.com', 'oikrach.com', 'ok.kz', 'ok.net', 'ok.ru', 'okbank.com', 'okhuman.com', 'okmad.com', 'okmagic.com', 'okname.net', 'okuk.com', 'oldbuthealthy.com', 'oldies1041.com', 'oldies104mail.com', 'ole.com', 'olemail.com', 'oligarh.ru', 'olympist.net', 'olypmall.ru', 'omaninfo.com', 'omen.ru', 'ondikoi.com', 'onebox.com', 'onenet.com.ar', 'oneoffemail.com', 'oneoffmail.com', 'onet.com.pl', 'onet.eu', 'onet.pl', 'onewaymail.com', 'oninet.pt', 'onlatedotcom.info', 'online.de', 'online.ie', 'online.ms', 'online.nl', 'online.ru', 'onlinecasinogamblings.com', 'onlinewiz.com', 'onmicrosoft.com', 'onmilwaukee.com', 'onobox.com', 'onvillage.com', 'oopi.org', 'op.pl', 'opayq.com', 'opendiary.com', 'openmailbox.org', 'operafan.com', 'operamail.com', 'opoczta.pl', 'optician.com', 'optonline.net', 'optusnet.com.au', 'orange.fr', 'orange.net', 'orbitel.bg', 'ordinaryamerican.net', 'orgmail.net', 'orthodontist.net', 'osite.com.br', 'oso.com', 'otakumail.com', 'otherinbox.com', 'our-computer.com', 'our-office.com', 'our.st', 'ourbrisbane.com', 'ourklips.com', 'ournet.md', 'outel.com', 'outgun.com', 'outlawspam.com', 'outlook.at', 'outlook.be', 'outlook.cl', 'outlook.co.id', 'outlook.co.il', 'outlook.co.nz', 'outlook.co.th', 'outlook.com', 'outlook.com.au', 'outlook.com.br', 'outlook.com.gr', 'outlook.com.pe', 'outlook.com.tr', 'outlook.com.vn', 'outlook.cz', 'outlook.de', 'outlook.dk', 'outlook.es', 'outlook.fr', 'outlook.hu', 'outlook.ie', 'outlook.in', 'outlook.it', 'outlook.jp', 'outlook.kr', 'outlook.lv', 'outlook.my', 'outlook.nl', 'outlook.ph', 'outlook.pt', 'outlook.sa', 'outlook.sg', 'outlook.sk', 'outloook.com', 'over-the-rainbow.com', 'ovi.com', 'ovpn.to', 'owlpic.com', 'ownmail.net', 'ozbytes.net.au', 'ozemail.com.au', 'ozz.ru', 'pacbell.net', 'pacific-ocean.com', 'pacific-re.com', 'pacificwest.com', 'packersfan.com', 'pagina.de', 'pagons.org', 'paidforsurf.com', 'pakistanmail.com', 'pakistanoye.com', 'palestinemail.com', 'pancakemail.com', 'pandawa.com', 'pandora.be', 'paradiseemail.com', 'paris.com', 'parkjiyoon.com', 'parrot.com', 'parsmail.com', 'partlycloudy.com', 'partybombe.de', 'partyheld.de', 'partynight.at', 'parvazi.com', 'passwordmail.com', 'pathfindermail.com', 'patmail.com', 'patra.net', 'pconnections.net', 'pcpostal.com', 'pcsrock.com', 'pcusers.otherinbox.com', 'peachworld.com', 'pechkin.ru', 'pediatrician.com', 'pekklemail.com', 'pemail.net', 'penpen.com', 'peoplepc.com', 'peopleweb.com', 'pepbot.com', 'perfectmail.com', 'perovo.net', 'perso.be', 'personal.ro', 'personales.com', 'petlover.com', 'petml.com', 'petr.ru', 'pettypool.com', 'pezeshkpour.com', 'pfui.ru', 'phayze.com', 'phone.net', 'photo-impact.eu', 'photographer.net', 'phpbb.uu.gl', 'phreaker.net', 'phus8kajuspa.cu.cc', 'physicist.net', 'pianomail.com', 'pickupman.com', 'picusnet.com', 'piercedallover.com', 'pigeonportal.com', 'pigmail.net', 'pigpig.net', 'pilotemail.com', 'pimagop.com', 'pinoymail.com', 'piracha.net', 'pisem.net', 'pjjkp.com', 'planet-mail.com', 'planet.nl', 'planetaccess.com', 'planetall.com', 'planetarymotion.net', 'planetdirect.com', 'planetearthinter.net', 'planetmail.com', 'planetmail.net', 'planetout.com', 'plasa.com', 'playersodds.com', 'playful.com', 'playstation.sony.com', 'plexolan.de', 'pluno.com', 'plus.com', 'plus.google.com', 'plusmail.com.br', 'pmail.net', 'pobox.com', 'pobox.hu', 'pobox.ru', 'pobox.sk', 'pochta.by', 'pochta.ru', 'pochta.ws', 'pochtamt.ru', 'poczta.fm', 'poczta.onet.pl', 'poetic.com', 'pokemail.net', 'pokemonpost.com', 'pokepost.com', 'polandmail.com', 'polbox.com', 'policeoffice.com', 'politician.com', 'politikerclub.de', 'polizisten-duzer.de', 'polyfaust.com', 'poofy.org', 'poohfan.com', 'pookmail.com', 'pool-sharks.com', 'poond.com', 'pop3.ru', 'popaccount.com', 'popmail.com', 'popsmail.com', 'popstar.com', 'populus.net', 'portableoffice.com', 'portugalmail.com', 'portugalmail.pt', 'portugalnet.com', 'positive-thinking.com', 'post.com', 'post.cz', 'post.sk', 'posta.net', 'posta.ro', 'posta.rosativa.ro.org', 'postaccesslite.com', 'postafiok.hu', 'postafree.com', 'postaweb.com', 'poste.it', 'postfach.cc', 'postinbox.com', 'postino.ch', 'postino.it', 'postmark.net', 'postmaster.co.uk', 'postmaster.twitter.com', 'postpro.net', 'pousa.com', 'powerdivas.com', 'powerfan.com', 'pp.inet.fi', 'praize.com', 'pray247.com', 'predprinimatel.ru', 'premium-mail.fr', 'premiumproducts.com', 'premiumservice.com', 'prepodavatel.ru', 'presidency.com', 'presnya.net', 'press.co.jp', 'prettierthanher.com', 'priest.com', 'primposta.com', 'primposta.hu', 'printesamargareta.ro', 'privacy.net', 'privatdemail.net', 'privy-mail.com', 'privymail.de', 'pro.hu', 'probemail.com', 'prodigy.net', 'prodigy.net.mx', 'professor.ru', 'progetplus.it', 'programist.ru', 'programmer.net', 'programozo.hu', 'proinbox.com', 'project2k.com', 'prokuratura.ru', 'prolaunch.com', 'promessage.com', 'prontomail.com', 'prontomail.compopulus.net', 'protestant.com', 'protonmail.com', 'proxymail.eu', 'prtnx.com', 'prydirect.info', 'psv-supporter.com', 'ptd.net', 'public-files.de', 'public.usa.com', 'publicist.com', 'pulp-fiction.com', 'punkass.com', 'puppy.com.my', 'purinmail.com', 'purpleturtle.com', 'put2.net', 'putthisinyourspamdatabase.com', 'pwrby.com', 'q.com', 'qatar.io', 'qatarmail.com', 'qdice.com', 'qip.ru', 'qmail.com', 'qprfans.com', 'qq.com', 'qrio.com', 'quackquack.com', 'quake.ru', 'quakemail.com', 'qualityservice.com', 'quantentunnel.de', 'qudsmail.com', 'quepasa.com', 'quickhosts.com', 'quickinbox.com', 'quickmail.nl', 'quickmail.ru', 'quicknet.nl', 'quickwebmail.com', 'quiklinks.com', 'quikmail.com', 'qv7.info', 'qwest.net', 'qwestoffice.net', 'r-o-o-t.com', 'r7.com', 'raakim.com', 'racedriver.com', 'racefanz.com', 'racingfan.com.au', 'racingmail.com', 'radicalz.com', 'radiku.ye.vc', 'radiologist.net', 'ragingbull.com', 'ralib.com', 'rambler.ru', 'ranmamail.com', 'rastogi.net', 'ratt-n-roll.com', 'rattle-snake.com', 'raubtierbaendiger.de', 'ravearena.com', 'ravefan.com', 'ravemail.co.za', 'ravemail.com', 'razormail.com', 'rccgmail.org', 'rcn.com', 'rcpt.at', 'realemail.net', 'realestatemail.net', 'reality-concept.club', 'reallyfast.biz', 'reallyfast.info', 'reallymymail.com', 'realradiomail.com', 'realtyagent.com', 'realtyalerts.ca', 'reborn.com', 'recode.me', 'reconmail.com', 'recursor.net', 'recycledmail.com', 'recycler.com', 'recyclermail.com', 'rediff.com', 'rediffmail.com', 'rediffmailpro.com', 'rednecks.com', 'redseven.de', 'redsfans.com', 'redwhitearmy.com', 'regbypass.com', 'reggaefan.com', 'reggafan.com', 'regiononline.com', 'registerednurses.com', 'regspaces.tk', 'reincarnate.com', 'relia.com', 'reliable-mail.com', 'religious.com', 'remail.ga', 'renren.com', 'repairman.com', 'reply.hu', 'reply.ticketmaster.com', 'represantive.com', 'representative.com', 'rescueteam.com', 'resgedvgfed.tk', 'resource.calendar.google.com', 'resumemail.com', 'retailfan.com', 'rexian.com', 'rezai.com', 'rhyta.com', 'richmondhill.com', 'rickymail.com', 'rin.ru', 'ring.by', 'riopreto.com.br', 'rklips.com', 'rmqkr.net', 'rn.com', 'ro.ru', 'roadrunner.com', 'roanokemail.com', 'rock.com', 'rocketmail.com', 'rocketship.com', 'rockfan.com', 'rodrun.com', 'rogers.com', 'rojname.com', 'rol.ro', 'rome.com', 'romymichele.com', 'roosh.com', 'rootprompt.org', 'rotfl.com', 'roughnet.com', 'royal.net', 'rpharmacist.com', 'rr.com', 'rrohio.com', 'rsub.com', 'rt.nl', 'rtrtr.com', 'ru.ru', 'rubyridge.com', 'runbox.com', 'rushpost.com', 'ruttolibero.com', 'rvshop.com', 'rxdoc.biz', 's-mail.com', 's0ny.net', 'sabreshockey.com', 'sacbeemail.com', 'saeuferleber.de', 'safarimail.com', 'safe-mail.net', 'safersignup.de', 'safetymail.info', 'safetypost.de', 'safrica.com', 'sagra.lu', 'sagra.lu.lu', 'sagra.lumarketing.lu', 'sags-per-mail.de', 'sailormoon.com', 'saint-mike.org', 'saintly.com', 'saintmail.net', 'sale-sale-sale.com', 'salehi.net', 'salesperson.net', 'samerica.com', 'samilan.net', 'samiznaetekogo.net', 'sammimail.com', 'sanchezsharks.com', 'sandelf.de', 'sanfranmail.com', 'sanook.com', 'sanriotown.com', 'santanmail.com', 'sapo.pt', 'sativa.ro.org', 'saturnfans.com', 'saturnperformance.com', 'saudia.com', 'savecougars.com', 'savelife.ml', 'saveowls.com', 'sayhi.net', 'saynotospams.com', 'sbcglbal.net', 'sbcglobal.com', 'sbcglobal.net', 'scandalmail.com', 'scanova.in', 'scanova.io', 'scarlet.nl', 'scfn.net', 'schafmail.de', 'schizo.com', 'schmusemail.de', 'schoolemail.com', 'schoolmail.com', 'schoolsucks.com', 'schreib-doch-mal-wieder.de', 'schrott-email.de', 'schweiz.org', 'sci.fi', 'science.com.au', 'scientist.com', 'scifianime.com', 'scotland.com', 'scotlandmail.com', 'scottishmail.co.uk', 'scottishtories.com', 'scottsboro.org', 'scrapbookscrapbook.com', 'scubadiving.com', 'seanet.com', 'search.ua', 'search417.com', 'searchwales.com', 'sebil.com', 'seckinmail.com', 'secret-police.com', 'secretarias.com', 'secretary.net', 'secretemail.de', 'secretservices.net', 'secure-mail.biz', 'secure-mail.cc', 'seductive.com', 'seekstoyboy.com', 'seguros.com.br', 'sekomaonline.com', 'selfdestructingmail.com', 'sellingspree.com', 'send.hu', 'sendmail.ru', 'sendme.cz', 'sendspamhere.com', 'senseless-entertainment.com', 'sent.as', 'sent.at', 'sent.com', 'sentrismail.com', 'serga.com.ar', 'servemymail.com', 'servermaps.net', 'services391.com', 'sesmail.com', 'sexmagnet.com', 'seznam.cz', 'sfr.fr', 'shahweb.net', 'shaniastuff.com', 'shared-files.de', 'sharedmailbox.org', 'sharewaredevelopers.com', 'sharklasers.com', 'sharmaweb.com', 'shaw.ca', 'she.com', 'shellov.net', 'shieldedmail.com', 'shieldemail.com', 'shiftmail.com', 'shinedyoureyes.com', 'shitaway.cf', 'shitaway.cu.cc', 'shitaway.ga', 'shitaway.gq', 'shitaway.ml', 'shitaway.tk', 'shitaway.usa.cc', 'shitmail.de', 'shitmail.me', 'shitmail.org', 'shitware.nl', 'shmeriously.com', 'shockinmytown.cu.cc', 'shootmail.com', 'shortmail.com', 'shortmail.net', 'shotgun.hu', 'showfans.com', 'showslow.de', 'shqiptar.eu', 'shuf.com', 'sialkotcity.com', 'sialkotian.com', 'sialkotoye.com', 'sibmail.com', 'sify.com', 'sigaret.net', 'silkroad.net', 'simbamail.fm', 'sina.cn', 'sina.com', 'sinamail.com', 'singapore.com', 'singles4jesus.com', 'singmail.com', 'singnet.com.sg', 'singpost.com', 'sinnlos-mail.de', 'sirindia.com', 'siteposter.net', 'skafan.com', 'skeefmail.com', 'skim.com', 'skizo.hu', 'skrx.tk', 'skunkbox.com', 'sky.com', 'skynet.be', 'slamdunkfan.com', 'slapsfromlastnight.com', 'slaskpost.se', 'slave-auctions.net', 'slickriffs.co.uk', 'slingshot.com', 'slippery.email', 'slipry.net', 'slo.net', 'slotter.com', 'sm.westchestergov.com', 'smap.4nmv.ru', 'smapxsmap.net', 'smashmail.de', 'smellfear.com', 'smellrear.com', 'smileyface.comsmithemail.net', 'sminkymail.com', 'smoothmail.com', 'sms.at', 'smtp.ru', 'snail-mail.net', 'snail-mail.ney', 'snakebite.com', 'snakemail.com', 'sndt.net', 'sneakemail.com', 'sneakmail.de', 'snet.net', 'sniper.hu', 'snkmail.com', 'snoopymail.com', 'snowboarding.com', 'snowdonia.net', 'so-simple.org', 'socamail.com', 'socceraccess.com', 'socceramerica.net', 'soccermail.com', 'soccermomz.com', 'social-mailer.tk', 'socialworker.net', 'sociologist.com', 'sofimail.com', 'sofort-mail.de', 'sofortmail.de', 'softhome.net', 'sogetthis.com', 'sogou.com', 'sohu.com', 'sokolniki.net', 'sol.dk', 'solar-impact.pro', 'solcon.nl', 'soldier.hu', 'solution4u.com', 'solvemail.info', 'songwriter.net', 'sonnenkinder.org', 'soodomail.com', 'soodonims.com', 'soon.com', 'soulfoodcookbook.com', 'soundofmusicfans.com', 'southparkmail.com', 'sovsem.net', 'sp.nl', 'space-bank.com', 'space-man.com', 'space-ship.com', 'space-travel.com', 'space.com', 'spaceart.com', 'spacebank.com', 'spacemart.com', 'spacetowns.com', 'spacewar.com', 'spainmail.com', 'spam.2012-2016.ru', 'spam4.me', 'spamail.de', 'spamarrest.com', 'spamavert.com', 'spambob.com', 'spambob.net', 'spambob.org', 'spambog.com', 'spambog.de', 'spambog.net', 'spambog.ru', 'spambooger.com', 'spambox.info', 'spambox.us', 'spamcannon.com', 'spamcannon.net', 'spamcero.com', 'spamcon.org', 'spamcorptastic.com', 'spamcowboy.com', 'spamcowboy.net', 'spamcowboy.org', 'spamday.com', 'spamdecoy.net', 'spameater.com', 'spameater.org', 'spamex.com', 'spamfree.eu', 'spamfree24.com', 'spamfree24.de', 'spamfree24.info', 'spamfree24.net', 'spamfree24.org', 'spamgoes.in', 'spamgourmet.com', 'spamgourmet.net', 'spamgourmet.org', 'spamherelots.com', 'spamhereplease.com', 'spamhole.com', 'spamify.com', 'spaminator.de', 'spamkill.info', 'spaml.com', 'spaml.de', 'spammotel.com', 'spamobox.com', 'spamoff.de', 'spamslicer.com', 'spamspot.com', 'spamstack.net', 'spamthis.co.uk', 'spamtroll.net', 'spankthedonkey.com', 'spartapiet.com', 'spazmail.com', 'speed.1s.fr', 'speedemail.net', 'speedpost.net', 'speedrules.com', 'speedrulz.com', 'speedy.com.ar', 'speedymail.org', 'sperke.net', 'spils.com', 'spinfinder.com', 'spiritseekers.com', 'spl.at', 'spoko.pl', 'spoofmail.de', 'sportemail.com', 'sportmail.ru', 'sportsmail.com', 'sporttruckdriver.com', 'spray.no', 'spray.se', 'spybox.de', 'spymac.com', 'sraka.xyz', 'srilankan.net', 'ssl-mail.com', 'st-davids.net', 'stade.fr', 'stalag13.com', 'standalone.net', 'starbuzz.com', 'stargateradio.com', 'starmail.com', 'starmail.org', 'starmedia.com', 'starplace.com', 'starspath.com', 'start.com.au', 'starting-point.com', 'startkeys.com', 'startrekmail.com', 'starwars-fans.com', 'stealthmail.com', 'stillchronic.com', 'stinkefinger.net', 'stipte.nl', 'stockracer.com', 'stockstorm.com', 'stoned.com', 'stones.com', 'stop-my-spam.pp.ua', 'stopdropandroll.com', 'storksite.com', 'streber24.de', 'streetwisemail.com', 'stribmail.com', 'strompost.com', 'strongguy.com', 'student.su', 'studentcenter.org', 'stuffmail.de', 'subnetwork.com', 'subram.com', 'sudanmail.net', 'sudolife.me', 'sudolife.net', 'sudomail.biz', 'sudomail.com', 'sudomail.net', 'sudoverse.com', 'sudoverse.net', 'sudoweb.net', 'sudoworld.com', 'sudoworld.net', 'sueddeutsche.de', 'suhabi.com', 'suisse.org', 'sukhumvit.net', 'sul.com.br', 'sunmail1.com', 'sunpoint.net', 'sunrise-sunset.com', 'sunsgame.com', 'sunumail.sn', 'suomi24.fi', 'super-auswahl.de', 'superdada.com', 'supereva.it', 'supergreatmail.com', 'supermail.ru', 'supermailer.jp', 'superman.ru', 'superposta.com', 'superrito.com', 'superstachel.de', 'surat.com', 'suremail.info', 'surf3.net', 'surfree.com', 'surfsupnet.net', 'surfy.net', 'surgical.net', 'surimail.com', 'survivormail.com', 'susi.ml', 'sviblovo.net', 'svk.jp', 'swbell.net', 'sweb.cz', 'swedenmail.com', 'sweetville.net', 'sweetxxx.de', 'swift-mail.com', 'swiftdesk.com', 'swingeasyhithard.com', 'swingfan.com', 'swipermail.zzn.com', 'swirve.com', 'swissinfo.org', 'swissmail.com', 'swissmail.net', 'switchboardmail.com', 'switzerland.org', 'sx172.com', 'sympatico.ca', 'syom.com', 'syriamail.com', 't-online.de', 't.psh.me', 't2mail.com', 'tafmail.com', 'takoe.com', 'takoe.net', 'takuyakimura.com', 'talk21.com', 'talkcity.com', 'talkinator.com', 'talktalk.co.uk', 'tamb.ru', 'tamil.com', 'tampabay.rr.com', 'tangmonkey.com', 'tankpolice.com', 'taotaotano.com', 'tatanova.com', 'tattooedallover.com', 'tattoofanatic.com', 'tbwt.com', 'tcc.on.ca', 'tds.net', 'teacher.com', 'teachermail.net', 'teachers.org', 'teamdiscovery.com', 'teamtulsa.net', 'tech-center.com', 'tech4peace.org', 'techemail.com', 'techie.com', 'technisamail.co.za', 'technologist.com', 'technologyandstocks.com', 'techpointer.com', 'techscout.com', 'techseek.com', 'techsniper.com', 'techspot.com', 'teenagedirtbag.com', 'teewars.org', 'tele2.nl', 'telebot.com', 'telebot.net', 'telefonica.net', 'teleline.es', 'telenet.be', 'telepac.pt', 'telerymd.com', 'teleserve.dynip.com', 'teletu.it', 'teleworm.com', 'teleworm.us', 'telfort.nl', 'telfortglasvezel.nl', 'telinco.net', 'telkom.net', 'telpage.net', 'telstra.com', 'telstra.com.au', 'temp-mail.com', 'temp-mail.de', 'temp-mail.org', 'temp-mail.ru', 'temp.headstrong.de', 'tempail.com', 'tempe-mail.com', 'tempemail.biz', 'tempemail.co.za', 'tempemail.com', 'tempemail.net', 'tempinbox.co.uk', 'tempinbox.com', 'tempmail.eu', 'tempmail.it', 'tempmail.us', 'tempmail2.com', 'tempmaildemo.com', 'tempmailer.com', 'tempmailer.de', 'tempomail.fr', 'temporarioemail.com.br', 'temporaryemail.net', 'temporaryemail.us', 'temporaryforwarding.com', 'temporaryinbox.com', 'temporarymailaddress.com', 'tempthe.net', 'tempymail.com', 'temtulsa.net', 'tenchiclub.com', 'tenderkiss.com', 'tennismail.com', 'terminverpennt.de', 'terra.cl', 'terra.com', 'terra.com.ar', 'terra.com.br', 'terra.com.pe', 'terra.es', 'test.com', 'test.de', 'tfanus.com.er', 'tfbnw.net', 'tfz.net', 'tgasa.ru', 'tgma.ru', 'tgngu.ru', 'tgu.ru', 'thai.com', 'thaimail.com', 'thaimail.net', 'thanksnospam.info', 'thankyou2010.com', 'thc.st', 'the-african.com', 'the-airforce.com', 'the-aliens.com', 'the-american.com', 'the-animal.com', 'the-army.com', 'the-astronaut.com', 'the-beauty.com', 'the-big-apple.com', 'the-biker.com', 'the-boss.com', 'the-brazilian.com', 'the-canadian.com', 'the-canuck.com', 'the-captain.com', 'the-chinese.com', 'the-country.com', 'the-cowboy.com', 'the-davis-home.com', 'the-dutchman.com', 'the-eagles.com', 'the-englishman.com', 'the-fastest.net', 'the-fool.com', 'the-frenchman.com', 'the-galaxy.net', 'the-genius.com', 'the-gentleman.com', 'the-german.com', 'the-gremlin.com', 'the-hooligan.com', 'the-italian.com', 'the-japanese.com', 'the-lair.com', 'the-madman.com', 'the-mailinglist.com', 'the-marine.com', 'the-master.com', 'the-mexican.com', 'the-ministry.com', 'the-monkey.com', 'the-newsletter.net', 'the-pentagon.com', 'the-police.com', 'the-prayer.com', 'the-professional.com', 'the-quickest.com', 'the-russian.com', 'the-seasiders.com', 'the-snake.com', 'the-spaceman.com', 'the-stock-market.com', 'the-student.net', 'the-whitehouse.net', 'the-wild-west.com', 'the18th.com', 'thecoolguy.com', 'thecriminals.com', 'thedoghousemail.com', 'thedorm.com', 'theend.hu', 'theglobe.com', 'thegolfcourse.com', 'thegooner.com', 'theheadoffice.com', 'theinternetemail.com', 'thelanddownunder.com', 'thelimestones.com', 'themail.com', 'themillionare.net', 'theoffice.net', 'theplate.com', 'thepokerface.com', 'thepostmaster.net', 'theraces.com', 'theracetrack.com', 'therapist.net', 'thereisnogod.com', 'thesimpsonsfans.com', 'thestreetfighter.com', 'theteebox.com', 'thewatercooler.com', 'thewebpros.co.uk', 'thewizzard.com', 'thewizzkid.com', 'thexyz.ca', 'thexyz.cn', 'thexyz.com', 'thexyz.es', 'thexyz.fr', 'thexyz.in', 'thexyz.mobi', 'thexyz.net', 'thexyz.org', 'thezhangs.net', 'thirdage.com', 'thisgirl.com', 'thisisnotmyrealemail.com', 'thismail.net', 'thoic.com', 'thraml.com', 'thrott.com', 'throwam.com', 'throwawayemailaddress.com', 'thundermail.com', 'tibetemail.com', 'tidni.com', 'tilien.com', 'timein.net', 'timormail.com', 'tin.it', 'tipsandadvice.com', 'tiran.ru', 'tiscali.at', 'tiscali.be', 'tiscali.co.uk', 'tiscali.it', 'tiscali.lu', 'tiscali.se', 'tittbit.in', 'tizi.com', 'tkcity.com', 'tlcfan.com', 'tmail.ws', 'tmailinator.com', 'tmicha.net', 'toast.com', 'toke.com', 'tokyo.com', 'tom.com', 'toolsource.com', 'toomail.biz', 'toothfairy.com', 'topchat.com', 'topgamers.co.uk', 'topletter.com', 'topmail-files.de', 'topmail.com.ar', 'topranklist.de', 'topsurf.com', 'topteam.bg', 'toquedequeda.com', 'torba.com', 'torchmail.com', 'torontomail.com', 'tortenboxer.de', 'totalmail.com', 'totalmail.de', 'totalmusic.net', 'totalsurf.com', 'toughguy.net', 'townisp.com', 'tpg.com.au', 'tradermail.info', 'trainspottingfan.com', 'trash-amil.com', 'trash-mail.at', 'trash-mail.com', 'trash-mail.de', 'trash-mail.ga', 'trash-mail.ml', 'trash2009.com', 'trash2010.com', 'trash2011.com', 'trashdevil.com', 'trashdevil.de', 'trashemail.de', 'trashmail.at', 'trashmail.com', 'trashmail.de', 'trashmail.me', 'trashmail.net', 'trashmail.org', 'trashmailer.com', 'trashymail.com', 'trashymail.net', 'travel.li', 'trayna.com', 'trbvm.com', 'trbvn.com', 'trevas.net', 'trialbytrivia.com', 'trialmail.de', 'trickmail.net', 'trillianpro.com', 'trimix.cn', 'tritium.net', 'trjam.net', 'trmailbox.com', 'tropicalstorm.com', 'truckeremail.net', 'truckers.com', 'truckerz.com', 'truckracer.com', 'truckracers.com', 'trust-me.com', 'truth247.com', 'truthmail.com', 'tsamail.co.za', 'ttml.co.in', 'tulipsmail.net', 'tunisiamail.com', 'turboprinz.de', 'turboprinzessin.de', 'turkey.com', 'turual.com', 'tushino.net', 'tut.by', 'tvcablenet.be', 'tverskie.net', 'tverskoe.net', 'tvnet.lv', 'tvstar.com', 'twc.com', 'twcny.com', 'twentylove.com', 'twinmail.de', 'twinstarsmail.com', 'tx.rr.com', 'tycoonmail.com', 'tyldd.com', 'typemail.com', 'tyt.by', 'u14269.ml', 'u2club.com', 'ua.fm', 'uae.ac', 'uaemail.com', 'ubbi.com', 'ubbi.com.br', 'uboot.com', 'uggsrock.com', 'uk2.net', 'uk2k.com', 'uk2net.com', 'uk7.net', 'uk8.net', 'ukbuilder.com', 'ukcool.com', 'ukdreamcast.com', 'ukmail.org', 'ukmax.com', 'ukr.net', 'ukrpost.net', 'ukrtop.com', 'uku.co.uk', 'ultapulta.com', 'ultimatelimos.com', 'ultrapostman.com', 'umail.net', 'ummah.org', 'umpire.com', 'unbounded.com', 'underwriters.com', 'unforgettable.com', 'uni.de', 'uni.de.de', 'uni.demailto.de', 'unican.es', 'unihome.com', 'universal.pt', 'uno.ee', 'uno.it', 'unofree.it', 'unomail.com', 'unterderbruecke.de', 'uogtritons.com', 'uol.com.ar', 'uol.com.br', 'uol.com.co', 'uol.com.mx', 'uol.com.ve', 'uole.com', 'uole.com.ve', 'uolmail.com', 'uomail.com', 'upc.nl', 'upcmail.nl', 'upf.org', 'upliftnow.com', 'uplipht.com', 'uraniomail.com', 'ureach.com', 'urgentmail.biz', 'uroid.com', 'us.af', 'usa.com', 'usa.net', 'usaaccess.net', 'usanetmail.com', 'used-product.fr', 'userbeam.com', 'usermail.com', 'username.e4ward.com', 'userzap.com', 'usma.net', 'usmc.net', 'uswestmail.net', 'uymail.com', 'uyuyuy.com', 'uzhe.net', 'v-sexi.com', 'v8email.com', 'vaasfc4.tk', 'vahoo.com', 'valemail.net', 'valudeal.net', 'vampirehunter.com', 'varbizmail.com', 'vcmail.com', 'velnet.co.uk', 'velnet.com', 'velocall.com', 'veloxmail.com.br', 'venompen.com', 'verizon.net', 'verizonmail.com', 'verlass-mich-nicht.de', 'versatel.nl', 'verticalheaven.com', 'veryfast.biz', 'veryrealemail.com', 'veryspeedy.net', 'vfemail.net', 'vickaentb.tk', 'videotron.ca', 'viditag.com', 'viewcastmedia.com', 'viewcastmedia.net', 'vinbazar.com', 'violinmakers.co.uk', 'vip.126.com', 'vip.21cn.com', 'vip.citiz.net', 'vip.gr', 'vip.onet.pl', 'vip.qq.com', 'vip.sina.com', 'vipmail.ru', 'viralplays.com', 'virgilio.it', 'virgin.net', 'virginbroadband.com.au', 'virginmedia.com', 'virtual-mail.com', 'virtualactive.com', 'virtualguam.com', 'virtualmail.com', 'visitmail.com', 'visitweb.com', 'visto.com', 'visualcities.com', 'vivavelocity.com', 'vivianhsu.net', 'viwanet.ru', 'vjmail.com', 'vjtimail.com', 'vkcode.ru', 'vlcity.ru', 'vlmail.com', 'vnet.citiz.net', 'vnn.vn', 'vnukovo.net', 'vodafone.nl', 'vodafonethuis.nl', 'voila.fr', 'volcanomail.com', 'vollbio.de', 'volloeko.de', 'vomoto.com', 'voo.be', 'vorsicht-bissig.de', 'vorsicht-scharf.de', 'vote-democrats.com', 'vote-hillary.com', 'vote-republicans.com', 'vote4gop.org', 'votenet.com', 'vovan.ru', 'vp.pl', 'vpn.st', 'vr9.com', 'vsimcard.com', 'vubby.com', 'vyhino.net', 'w3.to', 'wahoye.com', 'walala.org', 'wales2000.net', 'walkmail.net', 'walkmail.ru', 'walla.co.il', 'wam.co.za', 'wanaboo.com', 'wanadoo.co.uk', 'wanadoo.es', 'wanadoo.fr', 'wapda.com', 'war-im-urlaub.de', 'warmmail.com', 'warpmail.net', 'warrior.hu', 'wasteland.rfc822.org', 'watchmail.com', 'waumail.com', 'wazabi.club', 'wbdet.com', 'wearab.net', 'web-contact.info', 'web-emailbox.eu', 'web-ideal.fr', 'web-mail.com.ar', 'web-mail.pp.ua', 'web-police.com', 'web.de', 'webaddressbook.com', 'webadicta.org', 'webave.com', 'webbworks.com', 'webcammail.com', 'webcity.ca', 'webcontact-france.eu', 'webdream.com', 'webemail.me', 'webemaillist.com', 'webinbox.com', 'webindia123.com', 'webjump.com', 'webm4il.info', 'webmail.bellsouth.net', 'webmail.blue', 'webmail.co.yu', 'webmail.co.za', 'webmail.fish', 'webmail.hu', 'webmail.lawyer', 'webmail.ru', 'webmail.wiki', 'webmails.com', 'webmailv.com', 'webname.com', 'webprogramming.com', 'webskulker.com', 'webstation.com', 'websurfer.co.za', 'webtopmail.com', 'webtribe.net', 'webuser.in', 'wee.my', 'weedmail.com', 'weekmail.com', 'weekonline.com', 'wefjo.grn.cc', 'weg-werf-email.de', 'wegas.ru', 'wegwerf-emails.de', 'wegwerfadresse.de', 'wegwerfemail.com', 'wegwerfemail.de', 'wegwerfmail.de', 'wegwerfmail.info', 'wegwerfmail.net', 'wegwerfmail.org', 'wegwerpmailadres.nl', 'wehshee.com', 'weibsvolk.de', 'weibsvolk.org', 'weinenvorglueck.de', 'welsh-lady.com', 'wesleymail.com', 'westnet.com', 'westnet.com.au', 'wetrainbayarea.com', 'wfgdfhj.tk', 'wh4f.org', 'whale-mail.com', 'whartontx.com', 'whatiaas.com', 'whatpaas.com', 'wheelweb.com', 'whipmail.com', 'whoever.com', 'wholefitness.com', 'whoopymail.com', 'whtjddn.33mail.com', 'whyspam.me', 'wickedmail.com', 'wickmail.net', 'wideopenwest.com', 'wildmail.com', 'wilemail.com', 'will-hier-weg.de', 'willhackforfood.biz', 'willselfdestruct.com', 'windowslive.com', 'windrivers.net', 'windstream.com', 'windstream.net', 'winemaven.info', 'wingnutz.com', 'winmail.com.au', 'winning.com', 'winrz.com', 'wir-haben-nachwuchs.de', 'wir-sind-cool.org', 'wirsindcool.de', 'witty.com', 'wiz.cc', 'wkbwmail.com', 'wmail.cf', 'wo.com.cn', 'woh.rr.com', 'wolf-web.com', 'wolke7.net', 'wollan.info', 'wombles.com', 'women-at-work.org', 'women-only.net', 'wonder-net.com', 'wongfaye.com', 'wooow.it', 'work4teens.com', 'worker.com', 'workmail.co.za', 'workmail.com', 'worldbreak.com', 'worldemail.com', 'worldmailer.com', 'worldnet.att.net', 'wormseo.cn', 'wosaddict.com', 'wouldilie.com', 'wovz.cu.cc', 'wow.com', 'wowgirl.com', 'wowmail.com', 'wowway.com', 'wp.pl', 'wptamail.com', 'wrestlingpages.com', 'wrexham.net', 'writeme.com', 'writemeback.com', 'writeremail.com', 'wronghead.com', 'wrongmail.com', 'wtvhmail.com', 'wwdg.com', 'www.com', 'www.e4ward.com', 'www.mailinator.com', 'www2000.net', 'wwwnew.eu', 'wx88.net', 'wxs.net', 'wyrm.supernews.com', 'x-mail.net', 'x-networks.net', 'x.ip6.li', 'x5g.com', 'xagloo.com', 'xaker.ru', 'xd.ae', 'xemaps.com', 'xents.com', 'xing886.uu.gl', 'xmail.com', 'xmaily.com', 'xmastime.com', 'xmenfans.com', 'xms.nl', 'xmsg.com', 'xoom.com', 'xoommail.com', 'xoxox.cc', 'xoxy.net', 'xpectmore.com', 'xpressmail.zzn.com', 'xs4all.nl', 'xsecurity.org', 'xsmail.com', 'xtra.co.nz', 'xtram.com', 'xuno.com', 'xww.ro', 'xy9ce.tk', 'xyz.am', 'xyzfree.net', 'xzapmail.com', 'y7mail.com', 'ya.ru', 'yada-yada.com', 'yaho.com', 'yahoo.ae', 'yahoo.at', 'yahoo.be', 'yahoo.ca', 'yahoo.ch', 'yahoo.cn', 'yahoo.co', 'yahoo.co.id', 'yahoo.co.il', 'yahoo.co.in', 'yahoo.co.jp', 'yahoo.co.kr', 'yahoo.co.nz', 'yahoo.co.th', 'yahoo.co.uk', 'yahoo.co.za', 'yahoo.com', 'yahoo.com.ar', 'yahoo.com.au', 'yahoo.com.br', 'yahoo.com.cn', 'yahoo.com.co', 'yahoo.com.hk', 'yahoo.com.is', 'yahoo.com.mx', 'yahoo.com.my', 'yahoo.com.ph', 'yahoo.com.ru', 'yahoo.com.sg', 'yahoo.com.tr', 'yahoo.com.tw', 'yahoo.com.vn', 'yahoo.cz', 'yahoo.de', 'yahoo.dk', 'yahoo.es', 'yahoo.fi', 'yahoo.fr', 'yahoo.gr', 'yahoo.hu', 'yahoo.ie', 'yahoo.in', 'yahoo.it', 'yahoo.jp', 'yahoo.net', 'yahoo.nl', 'yahoo.no', 'yahoo.pl', 'yahoo.pt', 'yahoo.ro', 'yahoo.ru', 'yahoo.se', 'yahoofs.com', 'yahoomail.com', 'yalla.com', 'yalla.com.lb', 'yalook.com', 'yam.com', 'yandex.com', 'yandex.mail', 'yandex.pl', 'yandex.ru', 'yandex.ua', 'yapost.com', 'yapped.net', 'yawmail.com', 'yclub.com', 'yeah.net', 'yebox.com', 'yeehaa.com', 'yehaa.com', 'yehey.com', 'yemenmail.com', 'yep.it', 'yepmail.net', 'yert.ye.vc', 'yesbox.net', 'yesey.net', 'yeswebmaster.com', 'ygm.com', 'yifan.net', 'ymail.com', 'ynnmail.com', 'yogamaven.com', 'yogotemail.com', 'yomail.info', 'yopmail.com', 'yopmail.fr', 'yopmail.net', 'yopmail.org', 'yopmail.pp.ua', 'yopolis.com', 'yopweb.com', 'youareadork.com', 'youmailr.com', 'youpy.com', 'your-house.com', 'your-mail.com', 'yourdomain.com', 'yourinbox.com', 'yourlifesucks.cu.cc', 'yourlover.net', 'yournightmare.com', 'yours.com', 'yourssincerely.com', 'yourteacher.net', 'yourwap.com', 'youthfire.com', 'youthpost.com', 'youvegotmail.net', 'yuuhuu.net', 'yuurok.com', 'yyhmail.com', 'z1p.biz', 'z6.com', 'z9mail.com', 'za.com', 'zahadum.com', 'zaktouni.fr', 'zcities.com', 'zdnetmail.com', 'zdorovja.net', 'zeeks.com', 'zeepost.nl', 'zehnminuten.de', 'zehnminutenmail.de', 'zensearch.com', 'zensearch.net', 'zerocrime.org', 'zetmail.com', 'zhaowei.net', 'zhouemail.510520.org', 'ziggo.nl', 'zing.vn', 'zionweb.org', 'zip.net', 'zipido.com', 'ziplip.com', 'zipmail.com', 'zipmail.com.br', 'zipmax.com', 'zippymail.info', 'zmail.pt', 'zmail.ru', 'zoemail.com', 'zoemail.net', 'zoemail.org', 'zoho.com', 'zomg.info', 'zonai.com', 'zoneview.net', 'zonnet.nl', 'zooglemail.com', 'zoominternet.net', 'zubee.com', 'zuvio.com', 'zuzzurello.com', 'zvmail.com', 'zwallet.com', 'zweb.in', 'zxcv.com', 'zxcvbnm.com', 'zybermail.com', 'zydecofan.com', 'zzn.com', 'zzom.co.uk', 'zzz.com'];
})(GK.TranslationService);
(function() {

    ko.bindingHandlers.modal = {
        update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var showModal = ko.unwrap(valueAccessor().showModal);
            var showLoading = ko.unwrap(valueAccessor().showLoading);
            var bodyClass = ko.unwrap(valueAccessor().bodyClass);
            
            if (bodyClass === undefined || bodyClass === null || $.trim(bodyClass).length === 0) {
                bodyClass = 'show-modal';
            }

            var $body = $('body');
            var $modal = $(element);
            var $loader = $modal.find('~ .icon-refresh');

            if (showModal || showLoading) {
                $body.addClass(bodyClass);
            }

            if (showModal ) {
                $modal.addClass('open');
            }
            else {
                $modal.removeClass('open');
            }

            if (showLoading) {
                $loader.addClass('loading');
            }
            else {
                $loader.removeClass('loading');
                
            }

            if (!showModal && !showLoading) {
                $body.removeClass(bodyClass);
            }
        }
    };

})();;
(function(commonService) {

    ko.bindingHandlers.readMoreToggle = {
        init: function(element, valueAccessor) {
            commonService.initializeReadMoreToggles();
        }
    };

})(GK.Global);;
(function ($) {
    ko.bindingHandlers.inputMask = {
        init: function(element, valueAccessor, allBindingsAccessor) {
            var $element = $(element);
            var value = allBindingsAccessor().value;

            if (value && ko.isObservable(value)) {
                $element.on('focusout.inputMask change.inputMask', function() {
                    value($element.val());
                }).on('keypress.inputMask', function(e) {
                    if (e.keyCode == 13) {
                        $element.change();
                    }
                });
            }

            ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
                $element.unmask();
            });
        },

        update: function(element, valueAccessor) {
            var $element = $(element);

            // Gives us the real value if it is a computed observable or not
            var valueUnwrapped = ko.unwrap(valueAccessor());
            if (valueUnwrapped) {
                $element.mask(valueUnwrapped, {
                    translation: {
                        'C': { pattern: /[^\s]/ },
                        'c': { pattern: /[^\s]/, optional: true }
                    }
                });
            }
            else {
                $element.unmask();
            }
        }
    }
})(jQuery);;
(function ($, translationService) {
    ko.bindingHandlers.pickADate = {
        init: function (element, valueAccessor, allBindingsAccessor) {
            var $element = $(element);
            
            $element.pickadate({
                weekdaysShort: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
                showMonthsShort: false,
                labelMonthNext: translationService.getTranslation('Go to the next month'),
                labelMonthPrev: translationService.getTranslation('Go to the previous month'),
                labelMonthSelect: translationService.getTranslation('Pick a month from the dropdown'),
                labelYearSelect: translationService.getTranslation('Pick a year from the dropdown')
            });

            var picker = $element.pickadate('picker');

            //--'extend' value binding
            ko.applyBindingsToNode(element, { value: valueAccessor() });

            var minDate = ko.unwrap(allBindingsAccessor().pickADateMinDate);
            if (minDate && minDate.length > 0) {
                picker.set('min', minDate);
            }

            var maxDate = ko.unwrap(allBindingsAccessor().pickADateMaxDate);
            if (maxDate && maxDate.length > 0) {
                picker.set('max', minDate);
            }
        },

        update: function(element, valueAccessor, allBindingsAccessor) {
            var $element = $(element);
            var valueUnwrapped = ko.unwrap(valueAccessor());
            
            var picker = $element.pickadate('picker');

            if (valueUnwrapped && (valueUnwrapped instanceof Date || valueUnwrapped.length > 0)) {
                picker.set('select', valueUnwrapped);
            }
            else {
                picker.set('clear');
            }
        }
    };

    ko.bindingHandlers.pickADateMinDate = {
        init: function (element, valueAccessor, allBindingsAccessor) {
        },

        update: function (element, valueAccessor) {
            var $element = $(element);
            var valueUnwrapped = ko.unwrap(valueAccessor());
            
            if (!$element.pickadate) {
                //--Not a date picker, so do nothing
                return;
            }

            var picker = $element.pickadate('picker');
            if (valueUnwrapped && (valueUnwrapped instanceof Date || valueUnwrapped.length > 0)) {
                picker.set('min', valueUnwrapped);
            }
            else {
                picker.set('min', false);
            }
        }
    };

    ko.bindingHandlers.pickADateMaxDate = {
        init: function (element, valueAccessor, allBindingsAccessor) {
        },

        update: function (element, valueAccessor) {
            var $element = $(element);
            var valueUnwrapped = ko.unwrap(valueAccessor());

            if (!$element.pickadate) {
                //--Not a date picker, so do nothing
                return;
            }

            var picker = $element.pickadate('picker');
            if (valueUnwrapped && (valueUnwrapped instanceof Date || valueUnwrapped.length > 0)) {
                picker.set('max', valueUnwrapped);
            }
            else {
                picker.set('max', false);
            }
        }
    };
})(jQuery, GK.TranslationService);;
ko.bindingHandlers.enterkey = {
    init: function (element, valueAccessor, allBindings, viewModel) {
        var callback = valueAccessor();
        $(element).keypress(function (event) {
            var keyCode = (event.which ? event.which : event.keyCode);
            if (keyCode === 13) {
                callback.call(viewModel);
                return false;
            }
            return true;
        });
    }
};;
ko.bindingHandlers.changedEvent = {
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var allBindings = allBindingsAccessor();
        var unwrapped = ko.unwrap(allBindings.changedEvent.data());
        var fnEvent = allBindings.changedEvent.event;
        if (fnEvent) {
            fnEvent(element, allBindings.changedEvent.data());
        }
    }
};;
ko.components.register('paging', {
    viewModel: function (params) {
        var self = this;
        self.currentPage = params.currentPage;
        self.pageSize = params.pageSize;
        self.pagedItemsLength = params.pagedItemsLength;
        self.setPage = params.setPage;//optional

        //computed
        self.numberOfPages = ko.pureComputed(function () {
            return Math.ceil(self.pagedItemsLength() / self.pageSize());
        }, self);

        self.pages = ko.pureComputed(function () {
            var num = self.numberOfPages();
            if (!num || num == 0) {
                return [];
            }
            if (num < self.currentPage()) { self.setPage(1); }//fix having an empty page after filtering
            if (num <= 2) { return []; }//no pages needed until more then 2

            var arr = [];

            //start with current page (min page 2, max maxpages-1)
            var startPage = Math.min(Math.max(2, self.currentPage()), num - 1);
            arr.push(startPage);

            //add page before and after
            if (startPage - 1 > 1) arr.unshift(startPage - 1);
            if (startPage + 1 < num) arr.push(startPage + 1);

            //check if ... can be replaced with numbers
            if (arr[0] === 3) arr.unshift(2);
            if (arr[arr.length - 1] === num - 2) arr.push(num - 1);

            return arr;
        }, self);

        //actions - overridable from parent controller
        self.setPage = self.setPage || function (num) {
            self.currentPage(num);
            return false;
        };

    },
    template:
            '<div class="pagination" data-bind="visible: numberOfPages() > 1">\
                <ul>\
                    <li class="arrow" data-bind="visible: currentPage() > 1"><a href="javascript:;" data-bind="click: setPage.bind($data,(currentPage()-1))"><span class="visuallyhidden">&laquo;</span><span class="icon icon-arrow-left"></span></a></li>\
                    <li data-bind="css: { current: currentPage() == 1}"><a href="javascript:;" data-bind="text: \'1\', click: setPage.bind($data,(1))"></a></li>\
                    <li data-bind="visible: (pages().length > 0 && pages()[0] > 2)">&hellip;</li>\
                    <!-- ko foreach: pages -->\
                    <li data-bind="css: { current: $parent.currentPage() == $data}"><a href="javascript:;" data-bind="text: $data, click: $parent.setPage.bind($data, $data)"></a></li>\
                    <!-- /ko -->\
                    <li class="" data-bind="visible: (pages().length > 0 && (numberOfPages()-pages()[pages().length-1] > 1))">&hellip;</li>\
                    <li data-bind="css: { current: currentPage() == numberOfPages()}, visible: numberOfPages() > 1"><a href="javascript:;" data-bind="text: numberOfPages(), click: setPage.bind($data,numberOfPages())"></a></li>\
                    <li class="arrow" data-bind="visible: currentPage() < numberOfPages()"><a href="javascript:;" data-bind="click: setPage.bind($data,(currentPage()+1))"><span class="visuallyhidden">&raquo;</span><span class="icon icon-arrow-right"></span></a></li>\
                </ul>\
            </div>'
});;

(function (translationService, commonService) {
    ko.components.register('pobox-validation', {
        viewModel: function (params) {
            var self = this;
            self.value = params.value;

            self.isValid = ko.pureComputed(function () {
                if (!self.value()) {
                    return true;
                }
                var poBoxRegex = /((\b(((p|post|postal)[-.\s]*(o|off|office)[-.\s]*(b|box|bin)?[-.\s]*)(#|n|num|no|number)?[.]?(\s*\d+))|(postbox|box[ |*?].*[0-9].*).*[0-9]+))|(.*(\s*postbox\s*))/i;
                var matches = poBoxRegex.test(self.value());
                if (matches) {
                    return false;
                } else {
                    return true;
                }
            }, self);

            commonService.tooltips();
        },
        template: '<a class="pobox-error" data-bind="visible: !isValid()" style="display:none;" title="' + translationService.getTranslation('Post Office (PO) boxes are designated for postal mail only. A street address is mandatory as some classes require us to send a package containing course materials.') + '">' + translationService.getTranslation('Post Office (PO) boxes are not permitted.') + '</a>'
    });
})(GK.TranslationService, GK.Global);



(function (window, $, userService, translationService, notificationService, errorService, commonService) {
    ko.components.register('login-widget', {
        viewModel: function (params) {
            var self = this;
            self.targetElements = $(params.target);

            var required = {
                required: {
                    message: translationService.getTranslation('This field is required'),
                    params: true
                }
            };
            self.formModel = {};

            self.formModel.username = ko.observable().extend({
                required: required.required,
                email: {
                    message: translationService.getTranslation('Enter a valid email address'),
                    params: true
                }
            });
            self.formModel.password = ko.observable().extend(required);

            self.formModel.errors = ko.validation.group(self.formModel);

            self.errorMessages = ko.observableArray([]);

            self.cancelForm = function () {
                self.formModel.username('');
                self.formModel.username.clearError();
                self.formModel.password('');
                self.formModel.password.clearError();
                $.magnificPopup.close();

            }

            self.submitForm = function (formElement) {
                self.errorMessages.removeAll();
                if (self.formModel.errors().length === 0) {
                    notificationService.notifyLoading(true);
                    userService.login(self.formModel.username(), self.formModel.password(),
                        function (data) {
                            notificationService.notifyLoading(false);
                            if (data && data.RedirectUrl) {
                                window.location = data.RedirectUrl;
                            }
                            $.magnificPopup.close();

                        },
                        function (jqXHR, textStatus, errorThrown, url) {

                            var newErrors = errorService.getModelStateErrors(jqXHR);
                            if (!newErrors || !newErrors.length || newErrors.length === 0) {
                                newErrors = ["Error"];
                            }

                            for (var i = 0; i < newErrors.length; i++) {
                                self.errorMessages.push(translationService.getTranslation(newErrors[i]));
                            }

                            notificationService.notifyLoading(false);
                        }
                    );
                }
                else {
                    self.formModel.errors.showAllMessages();
                }
            }

            //init jquery
            self.targetElements.attr('href','#loginWidget');
            var asd = self.targetElements.magnificPopup({
                type: 'inline',
                preloader: false,
                focus: '#login-username',
                modal: true,
                mainClass: "loginPopup"
            });

            $(document).on('click', '.popup-modal-dismiss', function (e) {
                e.preventDefault();
                $.magnificPopup.close();
            });
        },
        template: { load: '/umbraco/surface/AccountSurface/LoginWidget' }
    });
})(window, jQuery, GK.UserService, GK.TranslationService, GK.NotificationService, GK.ErrorService, GK.Global);
//site namespace
var GK = GK || {};


//-----------------
// Course Offerings Specific 
//-----------------
GK.CartMenu = (function (window, undefined) {
    function init(m) {
        var viewModel = new CartMenuViewModel();
        var controller = new CartMenuController(viewModel);
        ko.applyBindings(controller, document.getElementById(m.containerId));
    }

    //return public functions
    return { init: init };

})(window);

//view model
var CartMenuViewModel = (function () {
    return (function () {

        //init model properties
        var self = this;
        self.cartItems = ko.observableArray([]);
        self.currencyCode = ko.observable("");

        //function to map data to model propeties
        self.map = function(data) {
            data.Items.sort(function(a, b) {
                return a.Name > b.Name;
            });

            self.cartItems(data.Items);
            self.currencyCode = data.CurrencyCode;
        }
    });
}());

//controller
var CartMenuController = (function ($, commonService, cartService, postbox, localeService, tagManagerService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;
        self.loaded = ko.observable(false);

        //computed
        self.totalQuantity = ko.pureComputed(function() {
            if (!self.loaded()) {
                return '';
            }

            var total = 0;
            for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                total += self.viewModel.cartItems()[i].Quantity;
            }
            return total;
        }, self);

        self.subtotal = ko.pureComputed(function () {
            var total = 0;
            for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                total += self.viewModel.cartItems()[i].Price;
            }
            return total;
        }, self);

        self.hasItems = ko.pureComputed(function() {
            return self.totalQuantity() > 0;
        }, self);

        //actions
        self.removeItem = function (item) {
            self.viewModel.cartItems.remove(item);
            cartService.removeCourse(item.OfferingId, function () {
                tagManagerService.removeFromCart(item.OfferingId, item.Name, item.ProductCode, item.Vendor, item.Price, item.UnitDiscountedPrice, item.Quantity); //tag
            });
        };

        //events
        postbox.subscribe('cartItemAdded', function (message, topic) {
            commonService.openCartMenu();
        });
        postbox.subscribe('cartUpdated', function (message, topic) {
            updateViewModel();
        });

        //private
        var updateViewModel = function () {
            var data = cartService.cartSummary();
            if (data) {
                self.viewModel.map(data);
                self.loaded(true);
            }
        };

        updateViewModel();
    });
}(jQuery, GK.Global, GK.CartService, postbox, GK.LocaleService, GK.TagManagerService));;
//site namespace
var GK = GK || {};


GK.AccountMenu = (function (window, undefined) {
    function init() {
        var viewModel = new AccountMenuViewModel();
        var controller = new AccountMenuController(viewModel);
        ko.applyBindings(controller, document.getElementById('AccountMenuContainer'));
    }

    //return public functions
    return { init: init };

})(window);


var AccountMenuViewModel = (function() {
    return (function () {

        //init model properties
        var self = this;
        self.authenticated = ko.observable(false);
        self.loaded = ko.observable(false);
        self.firstName = ko.observable('');
        self.culture = ko.observable('');

        //function to map data to model propeties
        self.map = function(data) {
            if (data) {
                self.authenticated(data.Authenticated);
                self.firstName(data.FirstName);
                self.culture(data.Culture);
            }

            self.loaded(true);
        }
    });
}());

//controller
var AccountMenuController = (function(userService, localeService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        //load user info
        userService.getCurrentUser().done(self.viewModel.map);

        //check if user is in a different locale
        self.viewModel.culture.subscribe(function (newCulture) {
            if (newCulture) {
                localeService.checkUseCulture(newCulture);
            }
        });

        //events
        postbox.subscribe('login', function (message, topic) {
            self.viewModel.map(message);
        });

    });
}(GK.UserService, GK.LocaleService));;
//site namespace
var GK = GK || {};


//-----------------
// Catalog Specific 
//-----------------
GK.CourseCatalog = (function (commonService, window, tagManagerService, undefined) {
    var topicFilterControls = ["#filterDeliveryFormat", "#filterVendor", '#filterGTR', '#filterLocation'];

    function filterTopics() {
        var $filterOption = $('.filter-topic-option');
        $filterOption.on('click', function (e) {
            if ($(this).hasClass('checked')) {
                $(this).removeClass('checked');
                if ($(this).siblings().size() > 0) {
                    $(this).siblings('ul').find('> li > a').removeClass('checked');
                }
            }
            else {
                $(this).addClass('checked');
            }
        });

        var $filterTopicHeading = $('.filter-topic-heading');
        $filterTopicHeading.on('click', function () {
            var $filterTopic = $(this).parent('.filter-topic');
            if ($filterTopic.hasClass('open')) {
                $filterTopic.removeClass('open');
            }
            else {
                $filterTopic.addClass('open');
            }
        });
    }

    function topicFilterEvents() {
        //any time a filter is clicked trigger the rebuild of the querystring
        $('.filter-topic a.filter-topic-option').on('click', function () {
            reloadPageWithFilters();
        });
        $('.filter-topic select').on('change', function () {
            reloadPageWithFilters();
        });
    }

    function reloadPageWithFilters() {
        //this loops through all of the filter controls to grab the used values
        var filterQuerystringItems = [];
        $.each(topicFilterControls, function (index, value) {
            var control = $(value);
            var selected = control.find('.filter-topic-option.checked, option:selected');

            //if the control has any selected options then we pull out the ids
            if (selected.length > 0) {
                var key = control.attr('data-querystring-key');
                var selectedIds = [];
                //grab the id for each selected item
                selected.each(function (index) {
                    var val = $(this).attr('data-value') || $(this).attr('value');
                    selectedIds.push(val);
                });
                if (selectedIds.length > 0 && selectedIds[0]) {
                    //combine the ids with the querystring key
                    filterQuerystringItems.push(key + "=" + selectedIds.join());
                }
            }
        });

        var promo = $('#filterPromo');
        if (promo.val()) {
            filterQuerystringItems.push(promo.attr('data-querystring-key') + "=" + promo.val());
        }

        //combine all of the generated values and push new querystring to window
        window.location.replace(document.location.pathname + "?" + filterQuerystringItems.join('&') + "#catalog");
        return false;
    }

    function tagEvents(model) {
        tagManagerService.viewItemList(model.listId(), model.listName(), model.courses());

        model.courses().forEach(course => {
            $('#course_' + course.Id).on('click', function () {
                tagManagerService.selectItem(model.listId(), model.listName(), course);
            });
        });
    }

    function bindEvents() {
        //back to top link
        $("a[href='#top']").click(function (event) {
            event.preventDefault();

            $('html, body').animate({
                scrollTop: $("#catalog").offset().top,
            }, 1000);

        });
    }

    function init(model) {
        var controller = new CatalogController(model);
        ko.applyBindings(controller, document.getElementById('catalog'));
        controller.init();

        filterTopics();
        topicFilterEvents();
        tagEvents(model);
        bindEvents();

        //show filter tags
        commonService.displayResultFilterTags(topicFilterControls, reloadPageWithFilters);
    }

    //return public functions
    return { init: init };

})(GK.Global, window, GK.TagManagerService);


//view model
var CatalogViewModel = (function (translationService) {
    return (function (data) {

        //init model properties
        var self = this;
        self.viewDefaults = { ListViewStyle: "grid", PageSize: 10, SortOrder: "featured" };

        self.listId = ko.observable('');
        self.listName = ko.observable('');
        self.courses = ko.observableArray([]);
        self.sortOrder = ko.observable(self.viewDefaults.SortOrder);
        self.listViewStyle = ko.observable(self.viewDefaults.ListViewStyle);

        //required for paging
        self.pageSize = ko.observable(self.viewDefaults.PageSize);
        self.currentPage = ko.observable(1);

        var featuredSort = function (a, b) {
            if (a.IsPromoted && !b.IsPromoted) {
                return -1;
            }
            if (!a.IsPromoted && b.IsPromoted) {
                return 1;
            }
            if (a.SearchWeight > b.SearchWeight) {
                return -1;
            }
            else if (a.SearchWeight < b.SearchWeight) {
                return 1;
            }

            return a.PageTitle > b.PageTitle ? 1 : -1;
        }

        //other props
        self.sortOptions = [
            { value: "featured", text: translationService.getTranslation("Featured Courses"), sort: featuredSort },
            { value: "asc", text: translationService.getTranslation("Order: A-Z"), sort: function (a, b) { return a.PageTitle > b.PageTitle ? 1 : -1 } },
            { value: "dec", text: translationService.getTranslation("Order: Z-A"), sort: function (a, b) { return a.PageTitle < b.PageTitle ? 1 : -1 } }//,
            //{ value: "high", text: translationService.getTranslation("Price: High to Low"), sort: function (a, b) { return (a.StartingPrice || { Amount: -1 }).Amount < (b.StartingPrice || { Amount: -1 }).Amount ? 1 : -1 } },
            //{ value: "low", text: translationService.getTranslation("Price: Low to High"), sort: function (a, b) { return (a.StartingPrice || { Amount: -1 }).Amount > (b.StartingPrice || { Amount: -1 }).Amount ? 1 : -1 } }
        ];

        self.pageSizeOptions = [
            { value: 10, text: "10" },
            { value: 40, text: "40" },
            { value: 80, text: "80" }
        ];

        //function to map data to model propeties
        self.map = function (data) {
            self.listId(data.listId),
            self.listName(data.listName),
            self.courses(data.courses);
            self.sortOrder(data.sortOrder);
            self.pageSize(data.pageSize);
            self.currentPage(1);
            self.listViewStyle(data.listViewStyle);
        }

        //apply init data
        if (data) self.map(data);
    });

}(GK.TranslationService));


//view model
var CatalogController = (function ($) {
    return (function (viewModel) {
        //properties
        var self = this;
        self.viewModel = viewModel;

        //
        //computed properties
        //
        self.pagedCourses = ko.pureComputed(function () {

            self.viewModel.courses.sort(self.sortOrderMethod());
            var startIndex = self.viewModel.pageSize() * (self.viewModel.currentPage() - 1);
            var endIndex = startIndex + self.viewModel.pageSize();
            return self.viewModel.courses().slice(startIndex, endIndex);
        }, self);

        //required for paging
        self.pagedItemsLength = ko.pureComputed(function () {
            return self.viewModel.courses().length;
        }, self);

        self.sortOrderText = ko.pureComputed(function () {
            var methods = self.viewModel.sortOptions.filter(function (opt) {
                return opt.value == self.viewModel.sortOrder();
            });
            if (!methods || methods.length == 0) {
                return self.viewModel.sortOptions[0].text;
            }

            return methods[0].text;
        }, self);

        self.sortOrderMethod = ko.pureComputed(function () {
            var methods = self.viewModel.sortOptions.filter(function (opt) {
                return opt.value == self.viewModel.sortOrder();
            });
            if (!methods || methods.length == 0) {
                return self.viewModel.sortOptions[0].sort;
            }

            return methods[0].sort;
        }, self);

        self.pageSizeText = ko.pureComputed(function () {
            return self.viewModel.pageSizeOptions.filter(function (opt) {
                return opt.value == self.viewModel.pageSize();
            })[0].text;
        }, self);

        self.catalogResultsCss = ko.pureComputed(function () {
            return self.viewModel.listViewStyle() + '-view';
        }, self);


        //
        //functions
        //
        function updateStyles() {
            //$('.result > a').matchHeight();
            $('.result .result-info').matchHeight({ byRow: false, property: 'height', target: null, remove: false });
            $('.result .result-details').matchHeight({ byRow: true, property: 'height', target: null, remove: false });
        }
        function updateCatalogViewSettings(changedValue) {
            //Properties: ListViewStyle, PageSize, SortOrder

            var key = 'catalog-view-settings';
            $.cookie.json = true;
            //grab cookie value
            var cookieVal = $.cookie(key);
            //set defaults if missing
            cookieVal = cookieVal || self.viewModel.viewDefaults;
            //apply changed values
            cookieVal = $.extend(cookieVal, changedValue);
            //save changes
            $.cookie(key, cookieVal);
        }
        function resultsTransition(fnAction) {
            //fade transition for results and fixes styling
            $('.results').animate({ opacity: 0 }, 100).promise().done(function () {
                fnAction();
                $('.results').stop().animate({ opacity: 1 }, 100);
                updateStyles();
            });
        }

        //
        //actions
        //
        self.setPageSize = function (num) {
            resultsTransition(function () {
                self.viewModel.currentPage(1);
                self.viewModel.pageSize(num);
            });
            updateCatalogViewSettings({ PageSize: num });
            return false;
        };

        self.setPage = function (num) {
            resultsTransition(function () {
                self.viewModel.currentPage(num);
            });
            return false;
        };

        self.setSortOrder = function (sort) {
            resultsTransition(function () {
                self.viewModel.sortOrder(sort);
            });
            updateCatalogViewSettings({ SortOrder: sort });
            return false;
        };

        self.setPageLayout = function (layout) {
            resultsTransition(function () {
                self.viewModel.listViewStyle(layout);
            });
            updateCatalogViewSettings({ ListViewStyle: layout });
            return false;
        };


        //
        //init
        //
        self.init = function () {
            updateStyles();
        };

    });
}(jQuery));;
//site namespace
var GK = GK || {};


//-----------------
// Course Offerings Specific 
//-----------------
GK.CourseOverview = (function (window, userService, referralService, recentCoursesService) {

    function setupModalityToggle() {
        
        $('section.modality-toggle select').change(function () {
            
            var section = $(this).closest("section");
            var modId = $(this).val();

            section.find('section').hide();

            var details = $("#" + section.attr('id') + '-' + modId);

            details.show();

            $("#" + section.attr('id') + "-title").text(details.data('title'));

        });
    }

    function setupModalityToggleNew() {
        
        $('details.modality-toggle select').change(function () {
            
            var section = $(this).closest("details");
            var modId = $(this).val();

            section.find('section').hide();

            var details = $("#" + section.attr('id') + '-' + modId);

            details.show();

            $("#" + section.attr('id') + "-title").text(details.data('title'));

        });
    }

    function init(model) {
        setupModalityToggle();
        setupModalityToggleNew();
        var controller = new CourseOverviewController(model);
        ko.applyBindings(controller, document.getElementById('courseOverviewContainer'));
        userService.getCurrentUser().done(function(user) {
            referralService.createProductReferralCTA(user);
        });
        recentCoursesService.addCurrentCourse();
    }

    //return public functions
    return { init: init };

})(window, GK.UserService, GK.ReferralService, GK.RecentCoursesService);

var CourseOverviewViewModel = (function() {
    return (function(data) {
        //init model properties
        var self = this;
        self.offerings = ko.observableArray();
        self.generateQuoteUrl = data.generateQuoteUrl;

        if (!data || !data.offerings || data.offerings.length < 1) {
            return;
        }

        for (var o = 0; o < data.offerings.length; o++) {
            //fix date formats - sets time to beginning of day
            if (data.offerings[o].StartDate) data.offerings[o].StartDate = new Date(data.offerings[o].StartDate).setHours(24, 0, 0, 0);

            //reference modality
            data.offerings[o].modality = data.modalities.filter(function (opt) {
                return opt.ModalityType === data.offerings[o].ModalityType;
            })[0];

            //set address
            var facility = data.offerings[o].FacilityAddress;
            var address = {};
            if (facility) {

                var line3 = '';
                if (facility.City && facility.State) {
                    line3 += facility.City + ', ' + facility.State;
                }
                else if (facility.City) {
                    line3 += facility.City;
                }
                else if (facility.State) {
                    line3 += facility.State;
                }

                line3 = $.trim(line3 + ' ' + facility.PostalCode);

                var lines = [facility.AddressLine1, facility.AddressLine2, line3];
                address.lines = lines.filter(function(v) { return v != undefined && $.trim(v) != ''; });
            }

            data.offerings[o].address = address;
            data.offerings[o].hasAddress = address && address.lines && address.lines.length > 0;
            data.offerings[o].quantity = ko.observable(1);
        }

        self.offerings(data.offerings);
    });
})();

var CourseOverviewController = (function (cartService, notificationService, translationService, tagManagerService, globalService, window) {
    return (function(viewModel) {

        //properties
        var self = this;
        self.viewModel = viewModel;

        //methods
        self.addToCart = function (offering) {
            var isValid = /^[1-9]\d*$/.test(offering.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            } else {
                notificationService.notifyLoading(true);
                var promo = globalService.getUrlParameter('promo') || null;
                cartService.addToCart(offering.Id, offering.quantity(), promo, 
                    function () {
                        tagManagerService.addToCart(offering.Id, offering.CourseName, offering.ProductCode, offering.Vendor, offering.Price.Amount, offering.quantity()); //tag
                    }, null,
                    function () {
                        notificationService.notifyLoading(false);
                    });
            }
        }

        self.generateQuote = function (offering) {
            window.top.location = (self.viewModel.generateQuoteUrl + offering.Id);

        }
    });
})(GK.CartService, GK.NotificationService, GK.TranslationService, GK.TagManagerService, GK.Global, window);
;
//site namespace
var GK = GK || {};


//-----------------
// Course Offerings Specific 
//-----------------
GK.CourseOverviewNew = (function (window, userService, referralService, recentCoursesService, tagManagerService) {

    function init(model) {
        var controller = new CourseOverviewNewController(model);
        ko.applyBindings(controller, document.getElementById('courseOverviewNewContainer'));
        userService.getCurrentUser().done(function (user) {
            referralService.createProductReferralCTA(user);
        });
        recentCoursesService.addCurrentCourse();

        // tag
        tagManagerService.viewItem(model.offerings());
    }

    //return public functions
    return { init: init };

})(window, GK.UserService, GK.ReferralService, GK.RecentCoursesService, GK.TagManagerService);

var CourseOverviewNewViewModel = (function () {
    return (function (data) {
        //init model properties
        var self = this;
        self.offerings = ko.observableArray([]);
        self.filteredOfferings = ko.observableArray([]);
        self.selectedOfferingId = ko.observable("");
        self.selectedOffering = ko.observable(null);
        self.selectedTab = ko.observable("");
        self.gtrFilter = ko.observable(false);
        self.promo = ko.observable();
        self.generateQuoteUrl = data.generateQuoteUrl;
        self.checkoutUrl = data.checkoutUrl;
        self.gkShareUrl = data.gkShareUrl;
        self.selectingCourse = ko.observable(false);

        if (!data || !data.offerings || data.offerings.length < 1) {
            self.selectedTab("GroupTraining");
            return;
        }

        var filteredOfferings = [];

        var setClassroomLiveFilter = false;
        var setVirtualClassroomLiveFilter = false;

        for (var o = 0; o < data.offerings.length; o++) {
            //fix date formats - sets time to beginning of day
            if (data.offerings[o].StartDate) data.offerings[o].StartDate = new Date(data.offerings[o].StartDate).setHours(24, 0, 0, 0);

            //reference modality
            data.offerings[o].modality = data.modalities.filter(function (opt) {
                return opt.ModalityType === data.offerings[o].ModalityType;
            })[0];

            //set address
            var facility = data.offerings[o].FacilityAddress;
            var address = {};
            if (facility) {

                var line3 = '';
                if (facility.City && facility.State) {
                    line3 += facility.City + ', ' + facility.State;
                }
                else if (facility.City) {
                    line3 += facility.City;
                }
                else if (facility.State) {
                    line3 += facility.State;
                }

                line3 = $.trim(line3 + ' ' + facility.PostalCode);

                var lines = [facility.AddressLine1, facility.AddressLine2, line3];
                address.lines = lines.filter(function (v) { return v != undefined && $.trim(v) != ''; });
            }

            data.offerings[o].address = address;
            data.offerings[o].hasAddress = address && address.lines && address.lines.length > 0;
            data.offerings[o].quantity = ko.observable(1);

            if (data.offerings[o].LiveInstructor) {
                self.selectedTab("Live");
                if (!data.offerings[o].IsFull) {
                    if (data.offerings.filter(x => !x.IsFull && x.LiveInstructor && x.StartDate < data.offerings[o].StartDate).length == 0) {
                        self.selectedOffering(data.offerings[o]);
                        self.selectedOfferingId(data.offerings[o].Id);
                    }
                }
            } else if (self.selectedTab() !== 'Live' && data.offerings[o].OnDemand) {
                self.selectedTab("OnDemand");
            } else if (!self.selectedTab() && data.offerings[o].GroupTraining) {
                self.selectedTab("GroupTraining");
            }

            if (data.offerings[o].ClassroomLive || data.offerings[o].VirtualClassroomLive) {
                filteredOfferings.push(data.offerings[o]);

                if (data.offerings[o].ClassroomLive) {
                    setClassroomLiveFilter = true;
                }

                if (data.offerings[o].VirtualClassroomLive) {
                    setVirtualClassroomLiveFilter = true;
                }
            }
        }

        self.virtualClassroomFilter = ko.observable(setVirtualClassroomLiveFilter);
        self.classroomLiveFilter = ko.observable(setClassroomLiveFilter);
        self.offerings(data.offerings);
        self.filteredOfferings(filteredOfferings);
        if (self.selectedTab() === "OnDemand") {
            var offering = self.offerings().filter(function (x) { return x.OnDemand == true })[0];
            if (offering && !offering.IsFull) {
                self.selectedOfferingId(offering.Id);
                self.selectedOffering(offering);
            }
        } else if (self.selectedTab() === "") {
            self.selectedTab("GroupTraining");
        }
    });
})();

var CourseOverviewNewController = (function (cartService, notificationService, translationService, tagManagerService, globalService, window) {
    return (function (viewModel) {

        //properties
        var self = this;
        self.viewModel = viewModel;

        var promo = globalService.getUrlParameter('promo') || null;
        self.viewModel.promo(promo);

        //methods
        self.addToCart = function (offering) {
            if (!offering) { return; }
            var isValid = /^[1-9]\d*$/.test(offering.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            } else {
                notificationService.notifyLoading(true);
                var promo = globalService.getUrlParameter('promo') || null;
                cartService.addToCart(offering.Id, offering.quantity(), promo,
                    function () {
                        tagManagerService.addToCart(offering.Id, offering.CourseName, offering.ProductCode, offering.Vendor, offering.Price.Amount, offering.quantity()); //tag
                    }, null,
                    function () {
                        notificationService.notifyLoading(false);
                    }, false);
            }
        }

        self.gkShare = function (offering) {
            if (!offering) { return; }
            var isValid = /^[1-9]\d*$/.test(offering.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            } else {
                notificationService.notifyLoading(true);
                var promo = globalService.getUrlParameter('promo') || null;
                cartService.addToCart(offering.Id, offering.quantity(), promo,
                    function () {
                        tagManagerService.addToCart(offering.Id, offering.CourseName, offering.ProductCode, offering.Vendor, offering.Price.Amount, offering.quantity()); //tag
                        window.top.location = self.viewModel.gkShareUrl;
                    }, null,
                    function () {
                        notificationService.notifyLoading(false);
                    }, true);
            }
        }

        self.buyNow = function (offering) {
            if (!offering) { return; }
            var isValid = /^[1-9]\d*$/.test(offering.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            } else {
                notificationService.notifyLoading(true);
                var promo = globalService.getUrlParameter('promo') || null;
                cartService.addToCart(offering.Id, offering.quantity(), promo,
                    function () {
                        tagManagerService.addToCart(offering.Id, offering.CourseName, offering.ProductCode, offering.Vendor, offering.Price.Amount, offering.quantity()); //tag
                        window.top.location = self.viewModel.checkoutUrl;
                    }, null,
                    function () {
                        notificationService.notifyLoading(false);
                    }, true);
            }
        }

        self.generateQuote = function (offering) {
            if (!offering) { return; }
            window.top.location = (self.viewModel.generateQuoteUrl + offering.Id);
        }

        self.liveInstructor = function () {
            self.viewModel.filteredOfferings(ko.utils.arrayFilter(self.viewModel.offerings(), function (each) {
                return each.LiveInstructor === true;
            }));
            setSelectedOffering();
            self.viewModel.selectedTab("Live");
        }

        self.onDemand = function () {
            self.viewModel.selectedTab("OnDemand");
            var offering = self.viewModel.offerings().filter(function (x) { return x.OnDemand == true })[0];

            if (offering && !offering.IsFull) {
                self.viewModel.selectedOfferingId(offering.Id);
                self.viewModel.selectedOffering(offering);
            }
        }

        self.groupTraining = function () {
            self.viewModel.selectedTab("GroupTraining");
        }

        self.virtualClassroom = function () {
            if (!self.viewModel.classroomLiveFilter() && self.viewModel.virtualClassroomFilter()) {
                return;
            }
            self.viewModel.virtualClassroomFilter(!self.viewModel.virtualClassroomFilter());
            applyFilters();
            setSelectedOffering();
        }

        self.classroomLive = function () {
            if (self.viewModel.classroomLiveFilter() && !self.viewModel.virtualClassroomFilter()) {
                return;
            }
            self.viewModel.classroomLiveFilter(!self.viewModel.classroomLiveFilter());
            applyFilters();
            setSelectedOffering();
        }

        self.guaranteedToRun = function () {
            self.viewModel.gtrFilter(!self.viewModel.gtrFilter());
            applyFilters();

            setSelectedOffering();
        }

        self.getClassCount = function () {
            return self.viewModel.offerings().filter(function (x) { return x.ClassroomLive && (!self.viewModel.gtrFilter() || x.GuaranteedToRun) }).length;
        }

        self.getVirtualCount = function () {
            return self.viewModel.offerings().filter(function (x) { return x.VirtualClassroomLive && (!self.viewModel.gtrFilter() || x.GuaranteedToRun) }).length;
        }

        applyFilters = function () {
            self.viewModel.filteredOfferings(ko.utils.arrayFilter(self.viewModel.offerings(), function (each) {
                var filtersPassed = false;

                if (self.viewModel.classroomLiveFilter() && each.ClassroomLive) {
                    filtersPassed = true;
                }

                if (self.viewModel.virtualClassroomFilter() && each.VirtualClassroomLive) {
                    filtersPassed = true;
                }

                if (self.viewModel.gtrFilter() && !each.GuaranteedToRun) {
                    filtersPassed = false;
                }

                if (filtersPassed) {
                    return each;
                }
            }));
        }

        setSelectedOffering = function () {
            var validOffering = self.viewModel.filteredOfferings().filter(function (o) {
                return !o.IsFull;
            });
            if (validOffering && validOffering.length > 0) {
                self.viewModel.selectedOfferingId(validOffering[0].Id);
                self.viewModel.selectedOffering(validOffering[0]);
            } else {
                self.viewModel.selectedOffering(null);
                self.viewModel.selectedOfferingId("");
            }
        }

        self.selectCourse = function () {
            self.viewModel.selectingCourse(!self.viewModel.selectingCourse());
        }

        self.selectOffering = function (validOffering) {
            self.viewModel.selectedOfferingId(validOffering.Id);
            self.viewModel.selectedOffering(validOffering);
            self.viewModel.selectingCourse(false);
        }
    });
})(GK.CartService, GK.NotificationService, GK.TranslationService, GK.TagManagerService, GK.Global, window);
;
//site namespace
var GK = GK || {};

//-----------------
// Course Offerings Specific 
//-----------------
GK.CourseOfferings = (function (translationService, window, undefined) {

    function toggleInstance() {
        var toggle = $('.catalog-results');
        var defaultValue = $('.toggle-info > a em').first().text() || translationService.getTranslation('View Course Details');
        toggle.on('click', '.toggle-info', function (event) {
            event.preventDefault();
            var current = $(this).parents('.instance');
            var btnText = $(this).find('em');
            current.find('.offering-address').toggleClass('hidden');
            current.toggleClass('open');
            if (current.hasClass('open')) {
                btnText.text(translationService.getTranslation('Close'));
            } else {
                btnText.text(defaultValue);
            }
        });
    }

    function calendarInit() {
        //Trigger the Datepicker (http://amsul.ca/pickadate.js/date/)
        var from_$input = $('#date-range-input').pickadate({
            weekdaysShort: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
            showMonthsShort: false,
            labelMonthNext: translationService.getTranslation('Go to the next month'),
            labelMonthPrev: translationService.getTranslation('Go to the previous month'),
            labelMonthSelect: translationService.getTranslation('Pick a month from the dropdown'),
            labelYearSelect: translationService.getTranslation('Pick a year from the dropdown')
        });
        var from_picker = from_$input.pickadate('picker');

        var to_$input = $('#date-range-input2').pickadate({
            weekdaysShort: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
            showMonthsShort: false,
            labelMonthNext: translationService.getTranslation('Go to the next month'),
            labelMonthPrev: translationService.getTranslation('Go to the previous month'),
            labelMonthSelect: translationService.getTranslation('Pick a month from the dropdown'),
            labelYearSelect: translationService.getTranslation('Pick a year from the dropdown')
        });
        var to_picker = to_$input.pickadate('picker');


        // Check if there’s a “from” or “to” date to start with.
        if (from_picker.get('value')) {
            to_picker.set('min', from_picker.get('select'));
        }
        if (to_picker.get('value')) {
            from_picker.set('max', to_picker.get('select'));
        }

        // When something is selected, update the “from” and “to” limits.
        from_picker.on('set', function (event) {
            if (event.select) {
                to_picker.set('min', from_picker.get('select'));
            } else if ('clear' in event) {
                to_picker.set('min', false);
            }
        });

        to_picker.on('set', function (event) {
            if (event.select) {
                from_picker.set('max', to_picker.get('select'));
            } else if ('clear' in event) {
                from_picker.set('max', false);
            }
        });
    }

    function init(model) {
        var controller = new CourseOfferingsController(model);
        ko.applyBindings(controller, document.getElementById('courseOfferingContainer'));
        calendarInit();
        toggleInstance();
    }

    //return public functions
    return { init: init };

})(GK.TranslationService, window);

//view model
var CourseOfferingsViewModel = (function (translationService) {
    return (function (data) {

        //init model properties
        var self = this;

        //function to map data to model propeties
        self.map = function (data) {

            //TODO: needs refactored so values observables are not lost

            self.readOnlyMode = data.readOnlyMode;

            self.generateQuoteUrl = data.generateQuoteUrl;

            self.locationOptions = [];
            self.languageOptions = [];

            //fix offerings data
            //and collect option data
            for (var o = 0; o < data.offerings.length; o++) {
                //fix date formats - sets time to beginning of day
                if (data.offerings[o].StartDate) data.offerings[o].StartDate = new Date(data.offerings[o].StartDate).setHours(24, 0, 0, 0);

                //reference modality
                data.offerings[o].modality = data.modalities.filter(function (opt) {
                    return opt.ModalityType === data.offerings[o].ModalityType;
                })[0];

                //collect locations
                if (self.locationOptions.indexOf(data.offerings[o].MetroLocation) == -1) {
                    self.locationOptions.push(data.offerings[o].MetroLocation);
                }

                //collect languages
                if (self.languageOptions.indexOf(data.offerings[o].Language) == -1) {
                    self.languageOptions.push(data.offerings[o].Language);
                }

                var facility = data.offerings[o].FacilityAddress;
                var address = {};
                if (facility) {

                    var line3 = '';
                    if (facility.City && facility.State) {
                        line3 += facility.City + ', ' + facility.State;
                    }
                    else if (facility.City) {
                        line3 += facility.City;
                    }
                    else if (facility.State) {
                        line3 += facility.State;
                    }

                    line3 = $.trim(line3 + ' ' + facility.PostalCode);

                    var lines = [facility.AddressLine1, facility.AddressLine2, line3];
                    address.lines = lines.filter(function (v) { return v != undefined && $.trim(v) != ''; });
                }

                data.offerings[o].address = address;
                data.offerings[o].hasAddress = address && address.lines && address.lines.length > 0;
                data.offerings[o].quantity = ko.observable(1);
            }

            //sort and add default options
            self.languageOptions.sort();
            self.languageOptions.unshift(translationService.getTranslation('All')); ///insert default
            self.locationOptions.sort();
            self.locationOptions.unshift(translationService.getTranslation('All')); ///insert default

            //data properties
            self.offerings = ko.observableArray(data.offerings);
            self.modalities = data.modalities;

            //other props
            self.sortOptions = [
                {
                    text: translationService.getTranslation("Start Date: Ascending"), sort: function (a, b) {
                        // order by date then metro location
                        if (a.StartDate === b.StartDate) {
                            return a.MetroLocation > b.MetroLocation ? 1 : -1;
                        }
                        return (a.StartDate > b.StartDate) ? 1 : -1
                    }
                },
                {
                    text: translationService.getTranslation("Start Date: Descending"), sort: function (a, b) {
                        // order by date then metro location
                        if (a.StartDate === b.StartDate) {
                            return a.MetroLocation > b.MetroLocation ? 1 : -1;
                        }
                        return (b.StartDate > a.StartDate) ? 1 : -1
                    }
                },
                { text: translationService.getTranslation("Price: High to Low"), sort: function (a, b) { return (a.Price || { Amount: 0 }).Amount < (b.Price || { Amount: 0 }).Amount ? 1 : -1 } },
                { text: translationService.getTranslation("Price: Low to High"), sort: function (a, b) { return (a.Price || { Amount: 0 }).Amount > (b.Price || { Amount: 0 }).Amount ? 1 : -1 } }
            ];

            var qv = self.queryValues();

            //input properties
            self.selectedModalities = ko.observableArray(qv.format || []);
            self.startDate = ko.observable(qv.start || "");//dates are text until until converted during filtering
            self.endDate = ko.observable(qv.end || "");
            self.selectedLanguage = ko.observable(qv.language || self.languageOptions[0]);
            self.selectedLocations = ko.observableArray(qv.location || []);
            self.selectedSort = ko.observable(self.sortOptions[qv.sort || 0]);
            self.gkDelivered = ko.observable(qv.gk || false);
            self.vendorCreditsFilter = ko.observable(qv.vendorCreditsEligible || false);

            //required for paging
            self.pageSize = ko.observable(10);
            self.currentPage = ko.observable(1);

        }

        self.queryValues = function () {
            var vals = {};
            var qs = window.location.search;
            if (qs) {
                vals = deparam(qs.substring(1));
                if (vals.format && !$.isArray(vals.format)) { vals.format = [vals.format]; }
                if (vals.location && !$.isArray(vals.location)) { vals.location = [vals.location]; }
            }
            return vals;
        }

        self.filterValues = function () {
            var sort = 0;
            for (var i = 0; i < self.sortOptions.length; i++) {
                if (self.sortOptions[i] == self.selectedSort()) {
                    sort = i;
                }
            }

            return {
                format: self.selectedModalities(),
                start: self.startDate(),
                end: self.endDate(),
                language: self.selectedLanguage(),
                location: self.selectedLocations(),
                gk: self.gkDelivered(),
                sort: sort,
                vendorCreditsEligible: self.vendorCreditsFilter()
            };
        };

        //apply init data
        if (data) self.map(data);
    });

}(GK.TranslationService));

//controller
var CourseOfferingsController = (function ($, cartService, notificationService, translationService, tagManagerService, globalService, window, history) {
    return (function (viewModel) {

        ///properties
        var self = this;
        self.viewModel = viewModel;

        var allText = translationService.getTranslation('All');

        //
        //computed properties
        //
        self.filteredOfferings = ko.pureComputed(function () {
            //item collection
            var elements = [];

            //filtering - items are only included if they pass all checks
            for (var i = 0; i < self.viewModel.offerings().length; i++) {
                var item = self.viewModel.offerings()[i];

                //modality filter
                if (self.viewModel.selectedModalities().length !== 0 && !self.isSelectedModality(item.ModalityType)) {
                    continue;
                }

                //date filter
                if (self.viewModel.startDate() != '') {
                    var start = Date.parse(self.viewModel.startDate());
                    if (item.StartDate && item.StartDate < start) {
                        continue;
                    }
                    if (self.viewModel.endDate() != '') {
                        var end = Date.parse(self.viewModel.endDate());
                        if (item.StartDate && item.StartDate > end) {
                            continue;
                        }
                    }
                }

                //language filter
                if (self.viewModel.selectedLanguage() != '' && self.viewModel.selectedLanguage() != allText) {
                    if (item.Language != self.viewModel.selectedLanguage()) {
                        continue;
                    }
                }

                //location filter
                if (self.viewModel.selectedLocations().length !== 0 && !self.isSelectedLocation(item.MetroLocation)) {
                    continue;
                }

                //gk delivered filter
                if (self.viewModel.gkDelivered() && !item.GKDelivered) {
                    continue;
                }

                //vendor credits filter
                if (self.viewModel.vendorCreditsFilter() && (!item.VendorCredits || item.VendorCredits.length < 1)) {
                    continue;
                }

                elements.push(item);
            }

            //sort
            elements.sort(self.viewModel.selectedSort().sort);

            return elements;
        }, self);

        self.pagedFilteredOfferings = ko.pureComputed(function () {
            var elements = self.filteredOfferings();
            //paging
            var startIndex = self.viewModel.pageSize() * (self.viewModel.currentPage() - 1);
            var endIndex = startIndex + self.viewModel.pageSize();
            elements = elements.slice(startIndex, endIndex);
            return elements;
        }, self);

        //required for paging
        self.pagedItemsLength = ko.pureComputed(function () {
            return self.filteredOfferings().length;
        }, self);


        self.offeringsRendered = function (elements, data) {
            //fix tooltips on new list items
            globalService.tooltips();
        }
        //
        //display functions
        //
        self.isSelectedModality = function (modalityType) {
            return self.viewModel.selectedModalities.indexOf(modalityType) > -1;
        };

        self.isSelectedLocation = function (location) {
            return self.viewModel.selectedLocations.indexOf(location) > -1;
        };

        self.selectedLocationText = function () {
            if (self.viewModel.selectedLocations().length === 0) {
                return translationService.getTranslation('All');
            } else if (self.viewModel.selectedLocations().length === 1) {
                return self.viewModel.selectedLocations()[0];
            } else {
                return translationService.getTranslation('Multiple');
            }
        };

        //
        //actions
        //
        self.toggleModality = function (modality) {
            var modalityType = modality.ModalityType;
            if (self.isSelectedModality(modalityType)) {
                self.viewModel.selectedModalities.remove(modalityType);
            } else {
                self.viewModel.selectedModalities.push(modalityType);
            }
            self.updateHistory();
            return false;
        };

        self.toggleLocation = function (location) {
            if (location == translationService.getTranslation('All')) {
                self.viewModel.selectedLocations.removeAll();
            }
            else if (self.isSelectedLocation(location)) {
                self.viewModel.selectedLocations.remove(location);
            } else {
                self.viewModel.selectedLocations.push(location);
            }
            self.updateHistory();
            return false;
        };

        self.toggleLanguage = function (language) {

            self.viewModel.selectedLanguage(language);
            self.updateHistory();
            return false;
        };

        self.toggleSort = function (sort) {
            self.viewModel.selectedSort(sort);
            self.updateHistory();
            return false;
        };

        self.toggleGKDelivered = function () {
            self.viewModel.gkDelivered(!self.viewModel.gkDelivered());
            self.updateHistory();
        };

        self.toggleVendorCreditsFilter = function () {
            self.viewModel.vendorCreditsFilter(!self.viewModel.vendorCreditsFilter());
            self.updateHistory();
        };

        self.addToCart = function (offering) {
            var isValid = /^[1-9]\d*$/.test(offering.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            } else {
                notificationService.notifyLoading(true);
                var promo = globalService.getUrlParameter('promo') || null;
                cartService.addToCart(offering.Id, offering.quantity(), promo, 
                    function () {
                        tagManagerService.addToCart(offering.Id, offering.CourseName, offering.ProductCode, offering.Vendor, offering.Price.Amount, offering.quantity()); //tag
                    }, null,
                    function () {
                        notificationService.notifyLoading(false);
                    });
            }
        }

        self.generateQuote = function (offering) {
            window.top.location = (self.viewModel.generateQuoteUrl + offering.Id);

        }

        self.updateHistory = function () {
            if (!history) { return; }

            var queryParts = {};
            var filters = self.viewModel.filterValues();
            for (var f in filters) {
                if (filters.hasOwnProperty(f)
                    && (f == 'isAscending' || filters[f])
                    && (filters[f] !== allText)
                    && (!$.isArray(filters[f]) || ($.isArray(filters[f]) && filters[f].length > 0))) { //--The property must have a value
                    queryParts[f] = filters[f];
                }
            }

            var query = $.param(queryParts, true);
            var url = window.location.href.split('?')[0];
            if (query) {
                url += '?' + query;
            }
            history.replaceState("", "", url);
        };

        // subscriptions
        viewModel.startDate.subscribe(function (newValue) {
            self.updateHistory();
        });

        viewModel.endDate.subscribe(function (newValue) {
            self.updateHistory();
        });
    });

}(jQuery, GK.CartService, GK.NotificationService, GK.TranslationService, GK.TagManagerService, GK.Global, window, history));;
//site namespace
var GK = GK || {};

//-----------------
// Schedule By Course Offerings Specific 
//-----------------
GK.ScheduleByCourseOfferings = (function (translationService, window, undefined) {

    function toggleInstance() {
        var toggle = $('.catalog-results');
        var defaultValue = $('.toggle-info > a em').first().text() || translationService.getTranslation('View Course Details');
        toggle.on('click', '.toggle-info', function (event) {
            event.preventDefault();
            var current = $(this).parents('.instance');
            var btnText = $(this).find('em');
            current.find('.offering-address').toggleClass('hidden');
            current.toggleClass('open');
            if (current.hasClass('open')) {
                btnText.text(translationService.getTranslation('Close'));
            } else {
                btnText.text(defaultValue);
            }
        });
    }

    function init(model) {
        var controller = new ScheduleByCourseOfferingsController(model);
        ko.applyBindings(controller, document.getElementById('scheduleByCourseOfferingContainer'));
        toggleInstance();
    }

    //return public functions
    return { init: init };

})(GK.TranslationService, window);

//view model
var ScheduleByCourseOfferingsViewModel = (function (translationService) {
    return (function (data) {
        var categoryNameSort = function (a, b) {
            var aName = a.Name.replace(/[()]/g, '');
            var bName = b.Name.replace(/[()]/g, '');
            if (aName < bName) return -1;
            if (aName > bName) return 1;
            return 0;
        };

        //init model properties
        var self = this;
        self.readOnlyMode = data.readOnlyMode;
        self.generateQuoteUrl = data.generateQuoteUrl;

        self.languageOptions = data.metadata.Languages || [];
        self.languageOptions.sort();
        self.languageOptions.unshift({ Name: translationService.getTranslation('All') }); ///insert default

        self.locationOptions = data.metadata.MetroLocations || [];
        self.locationOptions.sort();
        self.locationOptions.unshift(translationService.getTranslation('All')); ///insert default

        self.topicOptions = data.metadata.Topics || [];
        self.topicOptions.sort(categoryNameSort);
        self.topicOptions.unshift({ Name: translationService.getTranslation('All') }); ///insert default

        self.brandOptions = data.metadata.Brands || [];
        self.brandOptions.sort(categoryNameSort);
        self.brandOptions.unshift({ Name: translationService.getTranslation('All') }); ///insert default
       
        self.modalities = data.metadata.DeliveryFormats || [];

        self.offerings = ko.observableArray([]);

        self.selectedModalities = ko.observableArray(data.defaultFilter.DeliveryFormats || []);
        var startDate;
        if (data.defaultFilter.EarliestDate) {
            startDate = new Date(data.defaultFilter.EarliestDate);
        }
        self.startDate = ko.observable(startDate ? startDate : '');
        var endDate;
        if (data.defaultFilter.LatestDate) {
            endDate = new Date(data.defaultFilter.LatestDate);
        }
        self.endDate = ko.observable(endDate ? endDate : '');
        self.selectedLocations = ko.observableArray(data.defaultFilter.MetroLocations || []);
        self.guaranteedDates = ko.observable(data.defaultFilter.GuaranteedDates);
        self.gkDelivered = ko.observable(data.defaultFilter.GKDelivered);
        self.vendorCreditsFilter = ko.observable(data.defaultFilter.VendorCreditsEligible);
        self.selectedBrands = ko.observableArray(ko.utils.arrayFilter(self.brandOptions, function(brand) {
            return (data.defaultFilter.Brands || []).indexOf(brand.NodeId) > -1;
        }));
        self.selectedTopics = ko.observableArray(ko.utils.arrayFilter(self.topicOptions, function (topic) {
            return (data.defaultFilter.Topics || []).indexOf(topic.NodeId) > -1;
        }));

        self.selectedLanguage = ko.observable(self.languageOptions[0]);
        if (data.defaultFilter.Language) {
            var defaultLanguage = ko.utils.arrayFilter(self.languageOptions, function(language) {
                return language.ISOLanguageCode == data.defaultFilter.Language;
            });
            if (defaultLanguage && defaultLanguage.length == 1) {
                self.selectedLanguage(defaultLanguage[0]);
            }
        }
        self.promo = data.defaultFilter.Promo;

        //required for paging
        self.pageSize = ko.observable(30);
        self.totalResultCount = ko.observable(0);
        self.currentPage = ko.observable(1);
        if (data.defaultFilter.PageNumber && data.defaultFilter.PageNumber > 0) {
            self.currentPage(data.defaultFilter.PageNumber);
        }

        self.sortOptions = [
                { text: translationService.getTranslation("Start Date: Ascending"), sortKey: 'StartDate', ascending: true },
                { text: translationService.getTranslation("Start Date: Descending"), sortKey: 'StartDate', ascending: false },
                { text: translationService.getTranslation("Price: High to Low"), sortKey: 'Price', ascending: false },
                { text: translationService.getTranslation("Price: Low to High"), sortKey: 'Price', ascending: true }
        ];

        self.selectedSort = ko.observable();
        if (data.defaultFilter.Sort) {
            var sort = ko.utils.arrayFilter(self.sortOptions, function (option) {
                return option.sortKey == data.defaultFilter.Sort && option.ascending == data.defaultFilter.IsAscending;
            });
            if (sort.length > 0) {
                self.selectedSort(sort[0]);
            }
        }
        if(!self.selectedSort()) {
            self.selectedSort(self.sortOptions[0]);
        }

        //function to map data to model propeties
        self.map = function (offeringPagingData) {
            
            //fix offerings data
            //and collect option data
            for (var o = 0; o < offeringPagingData.Results.length; o++) {
                //fix date formats - sets time to beginning of day
                if (offeringPagingData.Results[o].StartDate) {
                    offeringPagingData.Results[o].StartDate = new Date(offeringPagingData.Results[o].StartDate).setHours(24, 0, 0, 0);
                }

                //reference modality
                offeringPagingData.Results[o].modality = self.modalities.filter(function (opt) {
                    return opt.ModalityType === offeringPagingData.Results[o].ModalityType;
                })[0];

                var facility = offeringPagingData.Results[o].FacilityAddress;
                var address = {};
                if (facility) {
                    
                    var line3 = '';
                    if (facility.City && facility.State) {
                        line3 += facility.City + ', ' + facility.State;
                    }
                    else if (facility.City) {
                        line3 += facility.City;
                    }
                    else if (facility.State) {
                        line3 += facility.State;
                    }

                    line3 = $.trim(line3 + ' ' + facility.PostalCode);

                    var lines = [facility.AddressLine1, facility.AddressLine2, line3];
                    address.lines = lines.filter(function (v) { return v != undefined && $.trim(v) != ''; });
                }

                offeringPagingData.Results[o].address = address;
                offeringPagingData.Results[o].hasAddress = address && address.lines && address.lines.length > 0;
                offeringPagingData.Results[o].quantity = ko.observable(1);
            }
            
            self.totalResultCount(offeringPagingData.TotalResults);

            //data properties
            self.offerings(offeringPagingData.Results || []);
        }

        self.selectedBrandIds = ko.pureComputed(function () {
            var ids = ko.utils.arrayMap(self.selectedBrands(), function(brand) {
                return brand.NodeId;
            });
            return ids;
        }, this);

        self.selectedTopicIds = ko.pureComputed(function () {
            var ids = ko.utils.arrayMap(self.selectedTopics(), function (topic) {
                return topic.NodeId;
            });
            return ids;
        }, this);

        self.getStartDate = function () {
            var startDate = self.startDate();

            if (startDate) {
                var dateParsed = Date.parse(startDate);
                if (dateParsed) {
                    var newDate = new Date(dateParsed);
                    var isoString = newDate.toISOString();
                    return isoString;
                }
            }

            return undefined;
        };

        self.getEndDate = function () {
            var endDate = self.endDate();

            if (endDate) {
                var dateParsed = Date.parse(endDate);
                if (dateParsed) {
                    var newDate = new Date(dateParsed);
                    var isoString = newDate.toISOString();
                    return isoString;
                }
            }

            return undefined;
        };
    });

}(GK.TranslationService));

//controller
var ScheduleByCourseOfferingsController = (function ($, cartService, notificationService, translationService, tagManagerService, offeringService, localeService, globalService, window) {
    return (function(viewModel) {

        ///properties
        var self = this;
        self.initialized = ko.observable(false);
        self.loadingResults = ko.observable(false);
        self.viewModel = viewModel;
        self.filter = {};

        var allText = translationService.getTranslation('All');

        //
        //computed properties
        //

        //required for paging
        self.pagedItemsLength = self.viewModel.offerings().length;

        //
        //display functions
        //
        self.isSelectedModality = function (id) {
            return self.viewModel.selectedModalities.indexOf(id) > -1;
        };

        self.isSelectedLocation = function (location) {
            return self.viewModel.selectedLocations().indexOf(location) > -1;
        };

        self.isSelectedBrand = function (brand) {
            return self.viewModel.selectedBrandIds().indexOf(brand.NodeId) > -1;
        };

        self.isSelectedTopic = function (topic) {
            return self.viewModel.selectedTopicIds().indexOf(topic.NodeId) > -1;
        };

        self.selectedLocationText = function () {
            if (self.viewModel.selectedLocations().length === 0) {
                return allText;
            } else if (self.viewModel.selectedLocations().length === 1) {
                return self.viewModel.selectedLocations()[0];
            } else {
                return translationService.getTranslation('Multiple');
            }
        };

        self.selectedBrandText = function () {
            if (self.viewModel.selectedBrands().length === 0) {
                return allText;
            } else if (self.viewModel.selectedBrands().length === 1) {
                return self.viewModel.selectedBrands()[0].Name;
            } else {
                return translationService.getTranslation('Multiple');
            }
        };

        self.selectedTopicText = function () {
            if (self.viewModel.selectedTopics().length === 0) {
                return allText;
            } else if (self.viewModel.selectedTopics().length === 1) {
                return self.viewModel.selectedTopics()[0].Name;
            } else {
                return translationService.getTranslation('Multiple');
            }
        };

        //
        //actions
        //
        self.toggleModality = function (modality) {
            if (self.isSelectedModality(modality.Id)) {
                self.viewModel.selectedModalities.remove(modality.Id);
            } else {
                self.viewModel.selectedModalities.push(modality.Id);
            }
            return false;
        };

        self.toggleLocation = function (location) {
            if (location == allText) {
                self.viewModel.selectedLocations.removeAll();
            }
            else if (self.isSelectedLocation(location)) {
                self.viewModel.selectedLocations.remove(location);
            } else {
                self.viewModel.selectedLocations.push(location);
            }
            return false;
        };

        self.toggleBrand = function (toggledBrand) {
            if (toggledBrand.Name == allText) {
                self.viewModel.selectedBrands.removeAll();
            }
            else if (self.isSelectedBrand(toggledBrand)) {
                var newList = ko.utils.arrayFilter(self.viewModel.selectedBrands(), function(brand) {
                    return brand.NodeId != toggledBrand.NodeId;
                });
                self.viewModel.selectedBrands(newList);
            } else {
                self.viewModel.selectedBrands.push(toggledBrand);
            }
            return false;
        };

        self.toggleTopic = function (toggledTopic) {
            if (toggledTopic.Name == allText) {
                self.viewModel.selectedTopics.removeAll();
            }
            else if (self.isSelectedTopic(toggledTopic)) {
                var newList = ko.utils.arrayFilter(self.viewModel.selectedTopics(), function (topic) {
                    return topic.NodeId != toggledTopic.NodeId;
                });
                self.viewModel.selectedTopics(newList);
            } else {
                self.viewModel.selectedTopics.push(toggledTopic);
            }
            return false;
        };

        self.toggleGuaranteedDates = function() {
            viewModel.guaranteedDates(!viewModel.guaranteedDates());
        };

        self.toggleGKDelivered = function () {
            viewModel.gkDelivered(!viewModel.gkDelivered());
        };

        self.toggleVendorCredits = function () {
            viewModel.vendorCreditsFilter(!viewModel.vendorCreditsFilter());
        };

        self.addToCart = function (offering) {
            var isValid = /^[1-9]\d*$/.test(offering.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            } else {
                notificationService.notifyLoading(true);
                var promo = globalService.getUrlParameter('promo') || null;
                cartService.addToCart(offering.Id, offering.quantity(), promo,
                    function() {
                        tagManagerService.addToCart(offering.Id, offering.CourseName, offering.ProductCode, offering.Vendor, offering.Price.Amount, offering.quantity()); //tag
                    }, null,
                    function() {
                        notificationService.notifyLoading(false);
                    });
            }
        }

        self.generateQuote = function (offering) {
            window.top.location = (self.viewModel.generateQuoteUrl + offering.Id);
        }

        var updateHistory = function() {
            if (history) {
                if (!self.initialized()) {
                    //--Set the inital filter
                    history.replaceState(self.filter, "");
                }
                else {
                    //--Don't alter the url on first load
                    var queryParts = {};
                    for (var f in self.filter) {
                        if (f != 'cultureCode' && f != 'pageSize' //--These properties do not go in the URL
                            && self.filter.hasOwnProperty(f)
                            && (f == 'isAscending' || self.filter[f])
                            && (!$.isArray(self.filter[f]) || ($.isArray(self.filter[f]) && self.filter[f].length > 0))) { //--The property must have a value
                            queryParts[f] = self.filter[f];
                        }
                    }

                    var query = $.param(queryParts, true);
                    var url = window.location.href;
                    if (url.indexOf('?') >= 0) {
                        url = url.substring(0, url.indexOf('?'));
                    }

                    url += '?' + query;

                    history.pushState(self.filter, "", url);
                }
            }
        };

        self.applyFilter = function () {
            if (self.initialized()) {
                self.viewModel.currentPage(1);
            }

            self.filter = {
                deliveryFormats: self.viewModel.selectedModalities(),
                earliestDate: self.viewModel.getStartDate(),
                latestDate: self.viewModel.getEndDate(),
                language: self.viewModel.selectedLanguage() ? self.viewModel.selectedLanguage().ISOLanguageCode : undefined,
                metroLocations: self.viewModel.selectedLocations(),
                brands: self.viewModel.selectedBrandIds(),
                topics: self.viewModel.selectedTopicIds(),
                guaranteedDates: self.viewModel.guaranteedDates(),
                gkDelivered: self.viewModel.gkDelivered(),
                vendorCreditsEligible: self.viewModel.vendorCreditsFilter(),
                sort: self.viewModel.selectedSort().sortKey,
                isAscending: self.viewModel.selectedSort().ascending,
                cultureCode: localeService.getCurrentCulture(),
                pageNumber: self.viewModel.currentPage(),
                pageSize: self.viewModel.pageSize(),
                promo: self.viewModel.promo
            };

            updateHistory();
            self.getOfferings();
        };

        var mapFilterToViewModel = function() {
            //--reset the view model when changing pages so that the filter matches the result
            self.viewModel.selectedModalities(self.filter.deliveryFormats);

            if (self.filter.earliestDate && self.filter.earliestDate.length > 0) {
                self.viewModel.startDate(new Date(self.filter.earliestDate));
            }
            else {
                self.viewModel.startDate(undefined);
            }

            if (self.filter.latestDate && self.filter.latestDate.length > 0) {
                self.viewModel.endDate(new Date(self.filter.latestDate));
            }
            else {
                self.viewModel.endDate(undefined);
            }

            if (self.filter.language) {
                var language = ko.utils.arrayFiler(self.viewModel.languageOptions, function(language) {
                    return language.ISOLanguageCode == self.filter.language;
                });
                if (language && language.length == 1) {
                    self.viewModel.selectedLanguage(language);
                }
                else {
                    self.viewModel.selectedLanguage(self.viewModel.languageOptions[0]);
                }
            }
            else {
                self.viewModel.selectedLanguage(self.viewModel.languageOptions[0]);
            }
            self.viewModel.selectedLocations(self.filter.metroLocations);

            self.viewModel.selectedBrands(ko.utils.arrayFilter(self.viewModel.brandOptions, function (brand) {
                return self.filter.brands.indexOf(brand.NodeId) > -1;
            }));
            self.viewModel.selectedTopics(ko.utils.arrayFilter(self.viewModel.topicOptions, function (topic) {
                return self.filter.topics.indexOf(topic.NodeId) > -1;
            }));
            self.viewModel.guaranteedDates(self.filter.guaranteedDates);
            self.viewModel.gkDelivered(self.filter.gkDelivered);
            self.viewModel.vendorCreditsFilter(self.filter.vendorCreditsEligible);
            self.viewModel.currentPage(self.filter.pageNumber);

            var sort = ko.utils.arrayFilter(self.viewModel.sortOptions, function (option) {
                return option.sortKey == self.filter.sort && option.ascending == self.filter.isAscending;
            });
            self.viewModel.selectedSort(sort[0]);
        };

        self.changePage = function (pageNumber) {
            if (pageNumber != self.viewModel.currentPage()) {
                self.filter.pageNumber = pageNumber;
                mapFilterToViewModel();

                updateHistory();
                self.getOfferings();
            }
        };

        self.getOfferings = function() {
            self.loadingResults(true);
            offeringService.getScheduledOfferings(self.filter, function(data) {
                self.viewModel.map(data);
                self.initialized(true);
            }, null, function() {
                self.loadingResults(false);
            });
        };

        self.applySort = function(sort) {
            self.viewModel.selectedSort(sort);
            self.filter.sort = sort.sortKey;
            self.filter.isAscending = sort.ascending;
            updateHistory();
            self.getOfferings();
        };

        window.onpopstate = function (event) {
            if (event.state) {
                self.filter = event.state;
                mapFilterToViewModel();
                self.getOfferings();
            }
        };

        //
        //init
        //
        self.applyFilter();
    });

}(jQuery, GK.CartService, GK.NotificationService, GK.TranslationService, GK.TagManagerService, GK.OfferingService, GK.LocaleService, GK.Global, window));;
//site namespace
var GK = GK || {};

GK.PrimaryPathway = (function (window, undefined) {

    function init() {

        $('select.category-options').on('change.PrimaryPathway', function() {
            var $this = $(this);
            if ($this.val() && $this.val().length > 0) {
                window.location = $this.val();
            }
        });
    }

    //return public functions
    return { init: init };

})(window);;
//site namespace
var GK = GK || {};

//-----------------
// Cart Specific 
//-----------------
GK.CheckoutOverview = (function (window, document, tagManagerService, cartService, translationService, notificationService) {

    function init(model) {
        //let the user know they were redirected from the payment screen
        if (window.location.search.indexOf("redirect") > -1) {
            notificationService.notifyInfo(translationService.getTranslation("Redirected due to inactivity"));
        }

        if (model && model.hadInactiveItems()) {
            cartService.notifyInactiveItemsRemovedFromCart();
        }
        //tag this step
        tagManagerService.checkoutStep(1, model.cartItems());

        var controller = new CheckoutOverviewController(model);
        ko.applyBindings(controller, document.getElementById('checkoutOverview'));
    };

    //return public functions
    return { init: init };

})(window, document, GK.TagManagerService, GK.CartService, GK.TranslationService, GK.NotificationService);

var CheckoutOverviewViewModel = (function(translationService) {
    return (function (data) {
        //init model properties
        var self = this;
        
        //function to map data to model propeties
        self.loadCartItems = function (cartItems) {
            cartItems.sort(function (a, b) {
                return a.Name != b.Name ? a.Name > b.Name : a.OfferingId > b.OfferingId;
            });

            //make cart items observable
            for (var i = 0; i < cartItems.length; i++) {
                var cartItem = cartItems[i];
                cartItem.quantity = ko.observable(cartItem.Quantity);
                cartItem.originalQuantity = ko.observable(cartItem.Quantity);

                cartItem.isUpdatingQuantity = ko.observable(false);

                cartItem.quantityChanged = ko.computed(function () {
                        return this.quantity() != this.originalQuantity();
                }, cartItem);

                cartItem.hasDiscount = ko.pureComputed(function() {
                    return this.UnitDiscountAmount > 0;
                }, cartItem);
            }
            
            self.cartItems(cartItems);
        }

        //apply init data
        if (data) {
            self.cartItems = ko.observableArray([]);
            self.loadCartItems(data.cartItems);

            self.hadInactiveItems = ko.observable(data.hadInactiveItems);
            self.promoCode = ko.observable("");
            self.promoErrorMessage = ko.observable("");
            self.promoSuccessMessage = ko.observable("");
            self.promoCatalogLinkPromo = ko.observable("");
        }
    })
}(GK.TranslationService));

var CheckoutOverviewController = (function (window, cartService, translationService, tagManagerService) {
    return (function (viewModel) {

        ///properties
        var self = this;
        self.viewModel = viewModel;

        self.modal = {
            deleteCourse: {
                courseToDelete: ko.observable({}),
                show: ko.observable(false),
                isWaiting: ko.observable(false)
            }
        }

        //promo response messages
        var invalidCode = function () { self.viewModel.promoErrorMessage(translationService.getTranslation("Invalid promo code entered")); }
        var allIneligibleCode = function () { self.viewModel.promoErrorMessage(translationService.getTranslation("Course(s) not eligible for promo")); };
        var allEligibleCode = function () { self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code applied to cart")); };
        var partialAppliedCode = function () { self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code applied to eligible courses")); };
        var tooManyBogo = function () { self.viewModel.promoErrorMessage(translationService.getTranslation("BOGO offer valid for two courses per order. Please adjust your cart to proceed.")); }
        var tooFewBogo = function (pc) { self.viewModel.promoCatalogLinkPromo(pc); self.viewModel.promoErrorMessage(translationService.getTranslation("This promotion requires two courses. Please select a second course.")); }

        var promoEligibilityMessages = [];
        promoEligibilityMessages[0] = invalidCode;
        promoEligibilityMessages[1] = allIneligibleCode;
        promoEligibilityMessages[2] = allEligibleCode;
        promoEligibilityMessages[3] = partialAppliedCode;
        promoEligibilityMessages[4] = tooManyBogo;
        promoEligibilityMessages[5] = tooFewBogo;

        self.clearMessages = function () {
            self.viewModel.promoErrorMessage("");
            self.viewModel.promoSuccessMessage("");
            self.viewModel.promoCatalogLinkPromo("");
        }

        self.subtotal = ko.pureComputed(function () {
            var total = 0;
            for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                var item = self.viewModel.cartItems()[i];
                total += item.UnitPrice * item.originalQuantity();
            }

            return total;
        }, self);

        self.discountAmount = ko.pureComputed(function() {
            var discount = 0;
            for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                var item = self.viewModel.cartItems()[i];
                discount += item.UnitDiscountAmount * item.originalQuantity();
            }
            
            return discount;
        }, self);

        self.hasDiscount = ko.pureComputed(function () {
                return (self.discountAmount() || 0) > 0;
            },
            self);

        self.orderTotal = ko.pureComputed(function() {
                return self.subtotal() - self.discountAmount();
            }, self);

        self.hasPromo = ko.pureComputed(function () {
            for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                var item = self.viewModel.cartItems()[i];
                if (item.PromoCode) {
                    return true;
                }
            }

            return false;
        }, self);

        self.showConfirmRemoveCourse = function (course) {
            if (course.isUpdatingQuantity()) {
                return false;
            }

            self.modal.deleteCourse.courseToDelete(course);
            self.modal.deleteCourse.show(true);
        }

        self.hideConfirmRemoveCourse = function (course) {
            self.modal.deleteCourse.show(false);
            self.modal.deleteCourse.isWaiting(false);

            if (course().quantity().length == 0
                || course().quantity() == 0) {
                //Cancelled delete, so restore quantity
                course().quantity(course().originalQuantity());
            }
        }

        self.removeCourse = function (course) {
            self.clearMessages();
            self.modal.deleteCourse.isWaiting(true);
            self.modal.deleteCourse.show(false);

            cartService.removeCourse(course().OfferingId,
                function (data) {
                    self.hideConfirmRemoveCourse(course);
                    tagManagerService.removeFromCart(course.OfferingId, course.Name, course.ProductCode, course.Vendor, course.Price, course.UnitDiscountedPrice, course.Quantity); //tag
                    self.viewModel.loadCartItems(data.cart.Items);
                },
                undefined,
                function () {
                    self.modal.deleteCourse.isWaiting(false);
                });
        };

        self.updateQuantity = function (course) {
            self.clearMessages();

            if (course.quantity().length == 0
                || course.quantity() == 0) {
                self.showConfirmRemoveCourse(course);
                return;
            }

            var isValid = /^[1-9]\d*$/.test(course.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            }
            else {
                course.isUpdatingQuantity(true);
                cartService.updateQuantity(course.OfferingId,
                    course.quantity(),
                    function (data) {
                        self.viewModel.loadCartItems(data.cart.Items);
                    },
                    undefined,
                    function () {
                        course.isUpdatingQuantity(false);
                    });

            }
        };

        self.applyPromoCode = function () {
            self.clearMessages();

            if (!self.viewModel.promoCode || !self.viewModel.promoCode()) {
                invalidCode();
                return;
            }

            cartService.applyPromoCode(self.viewModel.promoCode(),
                function (data) {
                    self.viewModel.loadCartItems(data.cart.Items);
                    if (data.promoEligibility && promoEligibilityMessages[data.promoEligibility]) {
                        promoEligibilityMessages[data.promoEligibility](self.viewModel.promoCode());
                    } else {
                        invalidCode();
                    }
                    self.viewModel.promoCode("");
                },
                function () {
                    invalidCode();
                    self.viewModel.promoCode("");
                },
                function () {

                });

            
        };

        self.clearPromoCode = function () {
            self.viewModel.promoErrorMessage("");
            self.viewModel.promoSuccessMessage("");
            cartService.clearPromoCode(
                function (data) {
                    self.viewModel.loadCartItems(data.Items);
                    self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code cleared from cart"));
                },
                function () {
                },
                function () {

                });


        };

    })
}(window, GK.CartService, GK.TranslationService, GK.TagManagerService));;
//site namespace
var GK = GK || {};

//-----------------
// Cart Specific
//-----------------
GK.CheckoutRegistration = (function (window, $, document, tagManagerService) {
    //-----------------
    // Checkout Registration Specific
    //-----------------
    var $studentContainer = $('.checkout-table');

    var updateProgress = function () {
        var currentStudent = $(this);
        if (!currentStudent.hasClass('student')) {
            currentStudent = $(this).parents('.student');
        }
        var displayPercentage = currentStudent.find('.percentage');

        var fields = currentStudent.find('input:not(".selectize-input input"):not("[type=hidden]")[required], select[required]').not('.country select');
        var fieldCount = fields.length; // Exclues/ignore country dropdown 

        var hasValue = function () {
            var $this = $(this);
            if ($this.is('[type="checkbox"]')) {
                return $this.is(':checked');
            }
            return $.trim($this.val()).length > 0;
        };
        var completeCount = fields.filter("input").filter(hasValue).not('.error').length + fields.filter("select:has(option:selected)").filter(hasValue).length;
        var percentComplete = Math.round((completeCount / fieldCount) * 100);
        displayPercentage.text(percentComplete + '%');
        //update the progress bar
        css3RadialProgressBar(percentComplete, currentStudent);
        if (percentComplete >= 100) {
            currentStudent.addClass('complete');
        }
        else {
            currentStudent.removeClass('complete');
        }

        submitProgress();
    }

    var submitProgress = function () {
        //if all students are complete then change submit button format
        if ($studentContainer.find('.student').length === $studentContainer.find('.student.complete').length) {
            $('.btn-checkout').removeClass('disabled');
            $('.checkout-note').hide();
        }
        else {
            $('.btn-checkout').addClass('disabled');
            $('.checkout-note').show();
        }
    }

    var updateAllProgress = function () {
        $studentContainer.find('.student').each(updateProgress);
    }

    function css3RadialProgressBar(percent, current) {
        var xvaluenow = percent;
        var currentStudent = current;
        var rotatenum = 'rotate(' + xvaluenow * 1.8 + 'deg)';
        var percentage = currentStudent.find('.percentage');
        var progress_circle = currentStudent.find('.progress-circle');

        /* Fix: Cover gap with shadow */
        var shadowfix = "1px";
        if (xvaluenow == 0) {
            shadowfix = "0";
        }

        /* Inserting values */
        percentage.innerHTML = xvaluenow + '%';
        progress_circle.attr("aria-valuenow", xvaluenow);
        progress_circle.find('.p-h span, .p-f, .p-f span').css({
            transform: rotatenum,
            'box-shadow': '0 0 0' + shadowfix + '#828282'
        });
    }

    function studentRegistrationToggle() {
        $studentContainer.on('click', '.student .name-label, .student-toggler, .student-icon', function (event) {
            event.preventDefault();
            var container = $(this).parents('.student');
            container.toggleClass('open');
            //Put focus on first input when user opens student info
            if (container.hasClass('open')) {
                container.find('input[type=text]').first().focus();
            }
        });
    }

    var initUI = function () {

        studentRegistrationToggle();

        //events to auto trigger upgradeProgress
        $studentContainer.on('keyup blur', '.student .student-form input', updateProgress);
        $studentContainer.on('change', '.student .student-form select, .student .student-form input[type="checkbox"]', updateProgress);

        //Opens student form if user tabs through previous form into the next
        $studentContainer.on('focus', 'input, select', function () {
            if (!$(this).parents('.student').hasClass('open')) {
                $(this).parents('.student').addClass('open');
            }
        });

        updateAllProgress();
    }

    function init(model) {

        tagManagerService.setCheckoutType(model.guest);
        tagManagerService.checkoutStep(1, model.cartItems, model.guest);

        var viewModel = new CheckoutRegistrationViewModel(model);
        var controller = new CheckoutRegistrationController(viewModel, { updateAllProgress: updateAllProgress });
        ko.applyBindings(controller, document.getElementById('checkoutRegistration'));
        initUI();
    };

    //return public functions
    return {
        init: init,
        initUI: initUI
    };
})(window, jQuery, document, GK.TagManagerService);

var CheckoutRegistrationViewModel = (function (translationService, localeService) {
    return (function (data) {
        var self = this;
        self.mapCartItem = function (course, existingStudents) {
            course.quantity = ko.observable(course.Quantity);
            course.originalQuantity = ko.observable(course.Quantity);

            course.isUpdatingQuantity = ko.observable(false);

            course.students = existingStudents || ko.observableArray(ko.utils.arrayMap(course.Students, self.mapStudent));//capital S when coming from json

            //copy promo changes
            ko.utils.arrayForEach(course.students(), function (stud) {
                stud().PromoCode(course.PromoCode);
            });

            course.quantityChanged = ko.computed(function () {
                return course.quantity() != course.originalQuantity();
            });

            course.addStudent = function (student) {
                course.students.push(self.mapStudent(student));
            }

            course.isValid = function () {
                var errors = ko.validation.group(course.students(), { deep: true });

                if (errors().length > 0) {
                    errors.showAllMessages(true);
                    return false;
                }

                return true;
            }

            course.hasDiscount = ko.pureComputed(function () {
                return this.UnitDiscountAmount > 0;
            }, course);

            course.addOns = ko.observableArray([]);
            return course;
        }

        self.mapStudent = function (student) {
            var required = {
                required: {
                    message: translationService.getTranslation('This field is required'),
                    params: true
                }
            };
            var billingRequired = {
                required: {
                    onlyIf: function () {
                        return student.Purchaser() && student.billingDifferentThanShipping();
                    },
                    message: translationService.getTranslation('This field is required'),
                    params: true
                }
            };

            student.Purchaser = ko.observable(self.guest() && student.Purchaser);

            student.billingDifferentThanShipping = ko.observable(!student.BillingSameAsShipping);
            student.BillingSameAsShipping = ko.pureComputed(function () {
                return !student.billingDifferentThanShipping();
            }, student);

            student.ShippingAddress.availableStates = ko.observableArray([]);
            student.BillingAddress = student.BillingAddress || {};
            student.BillingAddress.availableStates = ko.observableArray([]);
            student.CompanyName = ko.observable(student.CompanyName).extend({
                required: {
                    onlyIf: function () {
                        return student.Purchaser();
                    },
                    message: translationService.getTranslation('This field is required'),
                    params: true
                },
                minLength: {
                    message: translationService.getTranslation('Company name must be at least 2 characters long.'),
                    params: 2
                }
            });
            student.FirstName = ko.observable(student.FirstName).extend(required);
            student.LastName = ko.observable(student.LastName).extend(required);
            
            student.EmailAddress = ko.observable(student.EmailAddress).extend({
                required: {
                    message: translationService.getTranslation('Business email address required'),
                    params: true
                },
                email: {
                    message: translationService.getTranslation('Enter a valid email address'),
                    params: true
                },
                emailBlacklist: {
                    message: translationService.getTranslation('Business email address required'),
                    params: true
                }
            });

            student.Phone = ko.observable(student.Phone).extend(required);

            student.ShippingAddress.AddressLine1 = ko.observable(student.ShippingAddress.AddressLine1).extend(required);
            student.ShippingAddress.City = ko.observable(student.ShippingAddress.City).extend(required);
            student.ShippingAddress.StateCode = ko.observable(student.ShippingAddress.StateCode || '').extend({
                requiredIfNotEmpty: {
                    message: translationService.getTranslation('This field is required'),
                    params: student.ShippingAddress.availableStates
                }
            });

            student.ShippingAddress.PostalCode = ko.observable(student.ShippingAddress.PostalCode).extend(required);
            student.ShippingAddress.CountryCode = ko.observable(student.ShippingAddress.CountryCode || localeService.getCurrentCountry()).extend(required);
            student.hasShippingStates = ko.computed(function () {
                if (student.ShippingAddress.availableStates().length > 1) {//1 state = NA so just select it and hide
                    return true;
                }

                return null;
            });

            student.BillingAddress.AddressLine1 = ko.observable(student.BillingAddress.AddressLine1).extend(billingRequired);
            student.BillingAddress.City = ko.observable(student.BillingAddress.City).extend(billingRequired);
            student.BillingAddress.StateCode = ko.observable(student.BillingAddress.StateCode || '').extend({
                required: {
                    onlyIf: function () {
                        //--billing is required and there are states to choose from
                        return student.Purchaser() && student.billingDifferentThanShipping() && student.BillingAddress.availableStates && student.BillingAddress.availableStates().length > 0;
                    },
                    message: translationService.getTranslation('This field is required'),
                    params: true
                }
            });
            student.BillingAddress.PostalCode = ko.observable(student.BillingAddress.PostalCode).extend(billingRequired);
            student.BillingAddress.CountryCode = ko.observable(student.BillingAddress.CountryCode || localeService.getCurrentCountry()).extend(billingRequired);
            student.hasBillingStates = ko.computed(function () {
                if (student.BillingAddress.availableStates().length > 1) {//1 state = NA so just select it and hide
                    return true;
                }

                return null;
            });

            student.billingDifferentThanShipping.subscribe(function (newValue) {
                if (newValue) {
                    //--Don't show validation errors when opening the billing address
                    ko.validation.group([
                        student.BillingAddress.AddressLine1,
                        student.BillingAddress.City,
                        student.BillingAddress.StateCode,
                        student.BillingAddress.PostalCode,
                        student.BillingAddress.CountryCode
                    ]).showAllMessages(false);
                }
                else {
                    //Clear the billing address when it should be the same as the shipping address
                    student.BillingAddress.AddressLine1('');
                    student.BillingAddress.City('');
                    student.BillingAddress.StateCode('');
                    student.BillingAddress.PostalCode('');
                    student.BillingAddress.CountryCode(localeService.getCurrentCountry());
                }
            });

            student.ontarioDisclaimerDismissed = ko.observable(false);
            student.showOntarioDisclaimer = ko.pureComputed(function () {
                return student.ShippingAddress.CountryCode() == 'CA'
                    && student.ShippingAddress.StateCode() == 'ON'
                    && !student.ontarioDisclaimerDismissed();
            }, student);

            student.dismissOntarioDisclaimer = function () {
                student.ontarioDisclaimerDismissed(true);
            };

            student.PromoCode = ko.observable(student.PromoCode);

            student.studentLabel = ko.pureComputed(function () {
                if (student.FirstName()) { return student.FirstName() + ' ' + (student.LastName() || ''); }
                return translationService.getTranslation('Enter Student Info');
            }, student);

            student.isAddressEmpty = ko.pureComputed(function () {
                return !student.ShippingAddress.AddressLine1() &&
                    !student.ShippingAddress.City() &&
                    !student.ShippingAddress.StateCode() &&
                    !student.ShippingAddress.PostalCode();
            }, student);

            return ko.validatedObservable(student);
        }

        self.hadInactiveItems = data.hadInactiveOfferings;
        self.culture = data.culture;
        self.defaultCountry = data.defaultCountry;
        self.guest = ko.observable(data.guest || false);
        self.copyShippingAddress = ko.observable(false);
        self.paymentUrl = ko.pureComputed(function () {
            return self.guest() ? data.guestPaymentUrl : data.paymentUrl;
        }, self);
        self.noPaymentUrl = ko.pureComputed(function () {
            return self.guest() ? data.guestNoPaymentUrl : data.noPaymentUrl;
        }, self);

        //promo
        self.promoCode = ko.observable("");
        self.promoErrorMessage = ko.observable("");
        self.promoSuccessMessage = ko.observable("");
        self.promoCatalogLinkPromo = ko.observable("");

        self.modal = {
            deleteCourse: {
                courseToDelete: ko.observable({}),
                show: ko.observable(false),
                isWaiting: ko.observable(false)
            },
            deleteStudent:
            {
                studentToDelete: ko.observable({}),
                course: null,
                show: ko.observable(false),
                isWaiting: ko.observable(false)
            },
            addOnDetails:
            {
                addOn: ko.observable({}),
                show: ko.observable(false),
                isWaiting: ko.observable(false)
            }
        };

        data.cartItems.sort(function (a, b) {
            return a.Name != b.Name ? a.Name > b.Name : a.OfferingId > b.OfferingId;
        });

        self.cartItems = ko.observableArray(ko.utils.arrayMap(data.cartItems, function (course) { return self.mapCartItem(course, null); }));

        self.subtotal = ko.pureComputed(function () {
            var total = 0;
            for (var i = 0; i < self.cartItems().length; i++) {
                var item = self.cartItems()[i];
                total += item.UnitPrice * item.originalQuantity();
            }
            return total;
        }, self);

        self.discountAmount = ko.pureComputed(function () {
            var discount = 0;
            for (var i = 0; i < self.cartItems().length; i++) {
                var item = self.cartItems()[i];
                discount += item.UnitDiscountAmount * item.originalQuantity();
            }

            return discount;
        }, self);

        self.hasDiscount = ko.pureComputed(function () {
            return (self.discountAmount() || 0) > 0;
        },
            self);

        self.orderTotal = ko.pureComputed(function () {
            return self.subtotal() - self.discountAmount();
        }, self);

        self.hasPromo = ko.pureComputed(function () {
            for (var i = 0; i < self.cartItems().length; i++) {
                var item = self.cartItems()[i];
                if (item.PromoCode) {
                    return true;
                }
            }

            return false;
        }, self);

        self.isValid = function () {
            var isValid = true;
            for (var i = 0; i < self.cartItems().length; i++) {
                if (!self.cartItems()[i].isValid()) {
                    isValid = false;
                }
            }

            return isValid;
        }

        self.hasMultipleRegistrations = ko.pureComputed(function () {
            return self.cartItems().length > 1 || self.cartItems()[0].students().length > 1;
        }, self);

        const getFirstStudentWithEmail = (emailAddress) => {
            let match;
            const cartItems = self.cartItems();
            for (var i = 0; i < cartItems.length; i++) {
                const cartItem = cartItems[i];
                const students = cartItem?.Students;
                if (!students) continue;
                for (var j = 0; j < students.length; j++) {
                    const student = students[j];
                    if (student.EmailAddress() === emailAddress) {
                        match = student;
                        break;
                    }
                }
            }
            return match;
        }
        // Setup Zoominfo to map the incoming State Name to the State Code
        // https://tech-docs.zoominfo.com/formcomplete-implementation-guide.pdf
        if (!window._zi_fc) {
            window._zi_fc = {}
        }
        let zoomInfoCurrentEmailRequest;
        // window._zi_fc.onReady = (data) => {} // Callback that fires on first load
        window._zi_fc.onRequestSent = (data) => {
            zoomInfoCurrentEmailRequest = data.email;
        }
        window._zi_fc.onMatch = (data) => {
            if (!data || !data['zoominfo-state']) return;

            const setStateCode = (s) => {
                const stateCode = s.ShippingAddress.availableStates().filter(x => x.DisplayName === data['zoominfo-state'])[0]?.ISOStateCode ?? '';
                s.ShippingAddress.StateCode(stateCode);
            }

            const studentEmail = zoomInfoCurrentEmailRequest;
            const student = getFirstStudentWithEmail(studentEmail);

            if (!student) return;

            if (student.ShippingAddress.availableStates().length <= 1) {
                // Delay setting the state so changes due to country being updated (ie new state list built) are propagated
                setTimeout(function () { setStateCode(student) }, 100);
            } else {
                setStateCode(student);
            }
        }
    });
}(GK.TranslationService, GK.LocaleService));

var CheckoutRegistrationController = (function ($, cartService, localeService, translationService, notificationService, errorService, tagManagerService, userService, offeringService, window) {
    return (function (viewModel, uiEvents) {
        var copying = false;
        var self = this;
        self.viewModel = viewModel;
        self.uiEvents = uiEvents;

        //events
        postbox.subscribe('login', function (message, topic) {
            self.viewModel.guest(false);
            userService.getDefaultAddress(function (data) {
                for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                    var item = self.viewModel.cartItems()[i];
                    for (var j = 0; j < item.students().length; j++) {
                        var stud = item.students()[j]();
                        if (stud.isAddressEmpty()) {
                            self.setDefaultAddress(stud, data.DefaultShippingAddress);
                        }
                        if (stud.Purchaser() && !stud.CompanyName()) {
                            stud.CompanyName(data.CompanyName);
                        }
                    }
                }
                uiEvents.updateAllProgress();
            });
        });

        if (self.viewModel && self.viewModel.hadInactiveItems) {
            cartService.notifyInactiveItemsRemovedFromCart();
        }

        var loadStates = function (address) {
            if (!address.CountryCode()
                || address.CountryCode().length == 0) {
                //--Can't update states since there is no country
                return;
            }

            localeService.getStatesForCountry(address.CountryCode(),
                self.viewModel.culture,
                function (states) {
                    address.availableStates(states);
                    if (states.length === 1) {// 1 state = NA so just select it 
                        address.StateCode(states[0].ISOStateCode);
                    }
                });
        }

        var readyStudent = function (student) {
            loadStates(student.ShippingAddress);
            loadStates(student.BillingAddress);

            student.ShippingAddress.CountryCode.subscribe((function (address) {
                return (function (newValue) {
                    address.StateCode('');
                    loadStates(address);
                });
            }(student.ShippingAddress)));
            student.BillingAddress.CountryCode.subscribe((function (address) {
                return (function (newValue) {
                    address.StateCode('');
                    loadStates(address);
                });
            }(student.BillingAddress)));
        }

        var ensurePurchaserExists = function () {
            for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                var item = self.viewModel.cartItems()[i];
                for (var j = 0; j < item.students().length; j++) {
                    if (item.students()[j]().Purchaser()) {
                        return item.students()[j]();
                    }
                }
            }

            if (self.viewModel.cartItems().length > 0) {
                var purchaser = self.viewModel.cartItems()[0].students()[0]();
                purchaser.Purchaser(true);
                purchaser.billingDifferentThanShipping(false); //--Ensure defaulted to same as shipping
                //--make sure percentage complete is accurate
                uiEvents.updateAllProgress();
                return purchaser;
            }
        };

        self.viewModel.guest.subscribe(function (newValue) {
            if (newValue) {
                ensurePurchaserExists();
            }
            else {
                //--Unset the purchaser since they only exist on guest checkouts
                for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                    var item = self.viewModel.cartItems()[i];
                    for (var j = 0; j < item.students().length; j++) {
                        return item.students()[j]().Purchaser(false);
                    }
                }
            }
        });

        for (var i = 0; i < self.viewModel.cartItems().length; i++) {
            var course = self.viewModel.cartItems()[i];
            course.students.subscribe(function (changes) {
                changes.forEach(function (change) {
                    if (change.status === 'added') {
                        var student = change.value();
                        readyStudent(student);
                    }
                });
            }, null, 'arrayChange');

            for (var j = 0; j < course.students().length; j++) {
                var student = course.students()[j]();
                readyStudent(student);
            }
        }

        if (self.viewModel.guest()) {
            ensurePurchaserExists();
        }

        var updateCourseStudents = function (course, courseItems) {
            if (!courseItems || courseItems.length == 0) {
                self.viewModel.cartItems.remove(course);
                return;
            }

            var studentsToAdd = [];
            for (var i = 0; i < courseItems.length; i++) {
                var found = false;
                for (var j = 0; j < course.students().length; j++) {
                    if (courseItems[i].Id == course.students()[j]().Id) {
                        found = true;
                        break;
                    }
                }

                if (!found) {
                    studentsToAdd.push(courseItems[i]);
                }
            }

            for (var i = 0; i < studentsToAdd.length; i++) {
                course.addStudent(studentsToAdd[i]);
            }

            var studentsToRemove = [];
            for (var i = 0; i < course.students().length; i++) {
                var found = false;
                for (var j = 0; j < courseItems.length; j++) {
                    if (courseItems[j].Id == course.students()[i]().Id) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    studentsToRemove.push(course.students()[i]);
                }
            }

            for (var i = 0; i < studentsToRemove.length; i++) {
                course.students.remove(studentsToRemove[i]);
            }
        }

        var updateCartItems = function (updatedCartItems) {
            //replaces the cart items with the new cart items while keeping student data
            var newCartItems = ko.utils.arrayMap(updatedCartItems, function (updatedItem) {
                var existingStudents = null;
                for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                    var item = self.viewModel.cartItems()[i];
                    if (item.OfferingId == updatedItem.OfferingId) {
                        existingStudents = item.students;
                        break;
                    }
                }
                return self.viewModel.mapCartItem(updatedItem, existingStudents);
            });
            newCartItems.sort(function (a, b) {
                return a.Name != b.Name ? a.Name > b.Name : a.OfferingId > b.OfferingId;
            });
            self.viewModel.cartItems(newCartItems);
            uiEvents.updateAllProgress();
            self.loadAddOns();
        }

        self.showConfirmRemoveStudent = function (student, course) {
            if (course.isUpdatingQuantity()) {
                return false;
            }

            self.viewModel.modal.deleteStudent.studentToDelete(student);
            self.viewModel.modal.deleteStudent.course = course;
            self.viewModel.modal.deleteStudent.show(true);
        }

        self.showConfirmClearStudent = function (student, course) {
            if (course.isUpdatingQuantity()) {
                return false;
            }
            self.clearStudentInfo(student);
            self.clearAddress(student);
            uiEvents.updateAllProgress();
        }

        self.hideConfirmRemoveStudent = function () {
            self.viewModel.modal.deleteStudent.show(false);
            self.viewModel.modal.deleteStudent.isWaiting(false);
        }

        self.removeStudent = function (student, course) {
            self.viewModel.modal.deleteStudent.isWaiting(true);
            self.viewModel.modal.deleteStudent.show(false);

            cartService.removeStudent(course.OfferingId,
                student.Id,
                function (data) {
                    updateCartItems(data.cart.Items)
                    updateCourseStudents(course, data.courseItems);
                    if (self.viewModel.guest()) {
                        ensurePurchaserExists();
                    }
                    tagManagerService.removeFromCart(course.OfferingId, course.Name, course.ProductCode, course.Vendor, course.Price, course.UnitDiscountedPrice, course.Quantity); //tag
                    self.hideConfirmRemoveStudent();
                    uiEvents.updateAllProgress();
                },
                undefined,
                function () {
                    self.viewModel.modal.deleteStudent.isWaiting(false);
                });
        }

        self.addStudent = function (course) {
            if (course.isUpdatingQuantity()) {
                return false;
            }
            course.quantity(parseInt(course.originalQuantity()) + 1);
            self.updateQuantity(course);
        }

        self.hideConfirmRemoveCourse = function (course) {
            self.viewModel.modal.deleteCourse.show(false);
            self.viewModel.modal.deleteCourse.isWaiting(false);

            if (course().quantity().length == 0
                || course().quantity() == 0) {
                //Cancelled delete, so restore quantity
                course().quantity(course().originalQuantity());
            }
        }

        self.removeCourse = function (course) {
            self.viewModel.modal.deleteCourse.isWaiting(true);
            self.viewModel.modal.deleteCourse.show(false);

            cartService.removeCourse(course().OfferingId,
                function (data) {
                    self.hideConfirmRemoveCourse(course);
                    updateCartItems(data.cart.Items)
                    if (self.viewModel.guest()) {
                        ensurePurchaserExists();
                    }
                },
                undefined,
                function () {
                    self.viewModel.modal.deleteCourse.isWaiting(false);
                });
        };

        self.updateQuantity = function (course) {
            if (course.quantity().length == 0
                || course.quantity() == 0) {
                self.showConfirmRemoveCourse(course);
                return;
            }

            var isValid = /^[1-9]\d*$/.test(course.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            }
            else {
                course.isUpdatingQuantity(true);
                cartService.updateQuantity(course.OfferingId,
                    course.quantity(),
                    function (data) {
                        var existingStudent = self.viewModel.cartItems()[0].students()[0]();
                        updateCartItems(data.cart.Items)
                        updateCourseStudents(course, data.courseItems, data.cart.Items);

                        if (existingStudent && self.viewModel.copyShippingAddress()) {
                            self.copyStudentAddressToAll(existingStudent);
                        }
                    },
                    undefined,
                    function () {
                        course.isUpdatingQuantity(false);
                        uiEvents.updateAllProgress();
                    });
            }
        }

        self.phoneInputMask = function (student) {
            if (student.ShippingAddress.CountryCode() === "US" || student.ShippingAddress.CountryCode() === "CA") {
                return "(000) 000-0000";
            } else {
                return "+00 9999 99999999999";
            }
        };

        self.clearStudentInfo = function (student) {
            if (student.Purchaser()) {
                self.resetField(student.CompanyName, '');
            }
            self.resetField(student.FirstName, '');
            self.resetField(student.LastName, '');
            self.resetField(student.EmailAddress, '');
            self.resetField(student.Phone, '');
        }

        self.clearAddress = function (student) {
            self.resetField(student.ShippingAddress.CountryCode, self.viewModel.defaultCountry);
            self.resetField(student.ShippingAddress.AddressLine1, '');
            self.resetField(student.ShippingAddress.City, '');
            self.resetField(student.ShippingAddress.PostalCode, '');
            self.resetField(student.ShippingAddress.StateCode, '');

            self.viewModel.copyShippingAddress(false);
        }
        self.resetField = function (field, value) {
            //clears the field and prevents the required message from showing up
            field(value);
            var propValGroup = ko.validation.group(field, { deep: false });
            propValGroup.showAllMessages(false);
        }

        self.setDefaultAddress = function (student, address) {
            student.ShippingAddress.CountryCode(address.Country);
            student.ShippingAddress.AddressLine1(address.AddressLine1);
            student.ShippingAddress.City(address.City);
            student.ShippingAddress.PostalCode(address.PostalCode);

            setTimeout(function () {
                // Delay setting the state so changes due to country being updated (ie new state list built) are propagated
                student.ShippingAddress.StateCode(address.State);
            }, 100);
        }

        self.toggleCopyStudentAddressToAll = function (targetStudent) {
            copying = true;
            self.viewModel.copyShippingAddress(!self.viewModel.copyShippingAddress());//toggle state
            self.copyStudentAddressToAll(targetStudent);
            copying = false;
            return true;
        }

        self.copyStudentAddressToAll = function (targetStudent) {
            copying = true;
            if (self.viewModel.copyShippingAddress()) {
                for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                    var item = self.viewModel.cartItems()[i];
                    for (var j = 0; j < item.students().length; j++) {
                        var student = item.students()[j]();
                        if (student != targetStudent) {
                            student.ShippingAddress.CountryCode(targetStudent.ShippingAddress.CountryCode());
                            student.ShippingAddress.AddressLine1(targetStudent.ShippingAddress.AddressLine1());
                            student.ShippingAddress.City(targetStudent.ShippingAddress.City());
                            student.ShippingAddress.PostalCode(targetStudent.ShippingAddress.PostalCode());
                            student.ShippingAddress.StateCode(targetStudent.ShippingAddress.StateCode());
                        }
                    }
                }
                uiEvents.updateAllProgress();
            }
            copying = false;
            return true;
        }

        self.toggleBillingDifferentThanShipping = function (student) {
            student.billingDifferentThanShipping(!student.billingDifferentThanShipping());
            uiEvents.updateAllProgress();
        }

        self.uncheckCopyAll = function (item) {
            //check to see if we are currently copying to prevent the auto uncheck
            if (!copying) {
                self.viewModel.copyShippingAddress(false);
            }
        }

        self.validateStudentRegistrations = function () {
            if (!self.viewModel.isValid()) {
                var errorClass = ko.validation.configuration.errorClass;
                var $fields = $('.student .field.' + errorClass + ' input, .student .field.' + errorClass + ' select');
                $fields.parents('.student').addClass('open');
                $fields.first().focus();
                return false;
            }
            return true;
        }

        self.registerStudents = function (fnCallback) {

            if (!self.validateStudentRegistrations()) return;

            notificationService.notifyLoading(true);
            var studentMap = [];
            var purchaser = null;
            for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                var cartItem = self.viewModel.cartItems()[i];
                var students = [];
                for (var j = 0; j < cartItem.students().length; j++) {
                    var student = cartItem.students()[j]();
                    if (!purchaser && student.Purchaser()) {
                        //--One purchaser to rule them all
                        var data = ko.toJS(student);

                        //--Reduce payload size
                        delete data.ShippingAddress.availableStates;
                        if (data.BillingAddress) {
                            if (!data.billingDifferentThanShipping) {
                                delete data.BillingAddress;
                            }
                            else {
                                delete data.BillingAddress.availableStates;
                            }
                        }

                        purchaser = {
                            purchaser: data,
                            offeringId: self.viewModel.cartItems()[i].OfferingId
                        };
                    }
                    else {
                        var data = ko.toJS(student);
                        delete data.ShippingAddress.availableStates;
                        delete data.BillingAddress;
                        students.push(data);
                    }
                }

                studentMap.push({
                    offeringId: self.viewModel.cartItems()[i].OfferingId,
                    students: students
                });
            }

            cartService.registerStudents(studentMap, purchaser, fnCallback,
                function (jqXHR, textStatus, errorThrown, url) {
                    notificationService.notifyLoading(false);
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                });
        };

        self.submitRegistration = function () {
            self.registerStudents(function () {
                //redirect to next step
                window.location = self.viewModel.paymentUrl();
            });
        }

        self.submitNonePaymentRegistration = function () {
            //redirect out of iframe to success page
            var successFn = function () {
                top.location = self.viewModel.noPaymentUrl();
            };
            self.registerStudents(function () {
                //30 seconds fallback if tag manager redirect failed
                //window.setTimeout(successFn, 30000);
                tagManagerService.purchase(
                    tagManagerService.generatedUUID(),
                    self.viewModel.cartItems(),
                    0, // Items are free
                    self.viewModel.discountAmount(),
                    self.viewModel.guest(),
                    successFn //this should trigger the redirect
                )
            });
        };

        //promo 
        var promoEligibilityMessages = (function () {
            var msgs = {
                invalidCode: function () { self.viewModel.promoErrorMessage(translationService.getTranslation("Invalid promo code entered")); },
                allIneligibleCode: function () { self.viewModel.promoErrorMessage(translationService.getTranslation("Course(s) not eligible for promo")); },
                allEligibleCode: function () { self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code applied to cart")); },
                partialAppliedCode: function () { self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code applied to eligible courses")); },
                tooManyBogo: function () { self.viewModel.promoErrorMessage(translationService.getTranslation("BOGO offer valid for two courses per order. Please adjust your cart to proceed.")); },
                tooFewBogo: function (pc) { self.viewModel.promoCatalogLinkPromo(pc); self.viewModel.promoErrorMessage(translationService.getTranslation("This promotion requires two courses. Please select a second course.")); }
            };
            // promo messages based on enum
            msgs[0] = msgs.invalidCode;
            msgs[1] = msgs.allIneligibleCode;
            msgs[2] = msgs.allEligibleCode;
            msgs[3] = msgs.partialAppliedCode;
            msgs[4] = msgs.tooManyBogo;
            msgs[5] = msgs.tooFewBogo;
            return msgs;
        })();

        self.clearPromoMessages = function () {
            self.viewModel.promoErrorMessage("");
            self.viewModel.promoSuccessMessage("");
            self.viewModel.promoCatalogLinkPromo("");
        }

        self.applyPromoCode = function () {
            self.clearPromoMessages();

            if (!self.viewModel.promoCode || !self.viewModel.promoCode()) {
                promoEligibilityMessages.invalidCode();
                return;
            }

            cartService.applyPromoCode(self.viewModel.promoCode(),
                function (data) {
                    updateCartItems(data.cart.Items);
                    if (data.promoEligibility && promoEligibilityMessages[data.promoEligibility]) {
                        promoEligibilityMessages[data.promoEligibility](self.viewModel.promoCode());
                    } else {
                        promoEligibilityMessages.invalidCode();
                    }
                    self.viewModel.promoCode("");
                },
                function () {
                    promoEligibilityMessages.invalidCode();
                    self.viewModel.promoCode("");
                }, function () { });
        };

        self.clearPromoCode = function () {
            self.clearPromoMessages();
            cartService.clearPromoCode(
                function (data) {
                    updateCartItems(data.Items);
                    self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code cleared from cart"));
                }, function () { }, function () { });
        };

        self.hasPromo = ko.pureComputed(function () {
            for (var i = 0; i < self.viewModel.cartItems().length; i++) {
                var item = self.viewModel.cartItems()[i];
                if (item.PromoCode) {
                    return true;
                }
            }
            return false;
        }, self);

        //add-ons
        self.addOnCourse = function (offeringId) {
            notificationService.notifyLoading(true);
            cartService.addOn(offeringId, 1,
                function (data) {
                    updateCartItems(data.cart.Items);
                    var course = ko.utils.arrayFirst(self.viewModel.cartItems(), function (item) {
                        return item.OfferingId == offeringId;
                    });
                    updateCourseStudents(course, data.courseItems);
                    readyStudent(course.students()[0]());
                },
                undefined,
                function () {
                    notificationService.notifyLoading(false);
                });
        }

        self.openAddOnModal = function (addOn) {
            self.viewModel.modal.addOnDetails.addOn(addOn);
            self.viewModel.modal.addOnDetails.show(true);
        }

        self.loadAddOns = function () {
            var offeringIds = ko.utils.arrayMap(self.viewModel.cartItems(), function (courseOffering) {
                courseOffering.addOns.removeAll();
                return courseOffering.OfferingId;
            });

            offeringService.getAddOns(offeringIds, self.viewModel.culture,
                function (data) {
                    if (data && data.AddOns) {
                        for (var i = 0; i < data.AddOns.length; i++) {
                            for (var c = 0; c < self.viewModel.cartItems().length; c++) {
                                if (self.viewModel.cartItems()[c].OfferingId == data.AddOns[i].ForOfferingId) {
                                    self.viewModel.cartItems()[c].addOns.push(data.AddOns[i]);
                                }
                            }
                        }
                    }
                }, undefined,
                function () {

                });
        }

        self.loadAddOns();

    });
})(jQuery, GK.CartService, GK.LocaleService, GK.TranslationService, GK.NotificationService, GK.ErrorService, GK.TagManagerService, GK.UserService, GK.OfferingService, window);
;
            //site namespace
var GK = GK || {};

//-----------------
// Cart Specific
//-----------------
GK.CheckoutPayment = (function (window, $, cartService, notificationService, tagManagerService, translationService, undefined) {
    var cartItems = [];
    var total;
    var totalDiscountAmount;
    var transId;
    var overviewUrl = "";
    var paymentUrl = "";
    var hadInactiveOfferings;
    var fiveMinWarningTimeOut;
    var redirectTimeOut;
    var maxTime = 900000;
    var isGuest = false;
    var guestPurchaser;
    var promoCode;

    function complete(completeUrl) {
        //redirect out of iframe to success page
        var successFn = function () {
            top.location = completeUrl;
        };

        if (sessionStorage && sessionStorage.setItem && typeof sessionStorage.setItem === 'function') {
            sessionStorage.setItem('orderDetails', JSON.stringify({ id: transId, total: total, guestPurchaser: guestPurchaser, promoCode: promoCode }));
        }

        tagManagerService.checkoutStep(3, cartItems, isGuest);
        tagManagerService.purchase(transId, cartItems, total, totalDiscountAmount, isGuest, successFn);//this should trigger the redirect

        //10 seconds fallback if tag manager redirect failed
        window.setTimeout(successFn, 10000);
    }

    //-----------------
    // Checkout Payment Specific
    //-----------------
    function toggles() {
        var summaryToggle = $('.m-toggle-summary');
        summaryToggle.on('click', function () {
            $(this).parent('.cart-summary-wrap').toggleClass('open');
        });

        var studentToggle = $('.course-details .students > a');
        studentToggle.on('click', function (e) {
            e.preventDefault();
            $(this).parent('.students').toggleClass('open');
        });
    }
    

    function paymentTabs() {
        //For desktop tabs
        var paymentOptions = $('.payment-options li a');
        var paymentTypes = $('.payment-wrap .payment');
        var mobileChooser = $('.mobile-choice select');

        var updatePayment = function(target) {
            var option = paymentOptions.filter('[href=' + target + ']');
            if (option.hasClass('checked')) {
                return;
            }

            paymentOptions.removeClass('checked');
            paymentTypes.removeClass('active');

            option.addClass('checked');
            var activePayment = paymentTypes.filter(target).addClass('active');
            var firstInput = activePayment.find('input:text').first();
            if (!firstInput.parent().hasClass('selectize-input')) {
                firstInput.focus();
            }

            // Update mobile changer
            if (mobileChooser[0].selectize) {
                mobileChooser[0].selectize.setValue(target);
            }
            mobileChooser.val(target);
        }

        paymentOptions.on('click tap', function (event) {
            event.preventDefault();
            updatePayment($(this).attr('href'));
        });
        mobileChooser.on('change', function() {
            updatePayment($(this).val());
        });
    }

    function redirectToOverviewPage() {
        var newPath = window.location.pathname.replace(paymentUrl, overviewUrl + "?redirect");
        var overviewPage = window.location.protocol + '//' + window.location.host + newPath;
        window.location.assign(overviewPage);
    }

    function init(model) {
        cartItems = model.cartItems;
        transId = model.transId;
        total = model.total;
        totalDiscountAmount = model.totalDiscountAmount;
        overviewUrl = model.overviewUrl;
        paymentUrl = model.paymentUrl;
        hadInactiveOfferings = model.hadInactiveOfferings;
        isGuest = model.guest || false;
        guestPurchaser = model.guestPurchaser;
        promoCode = model.promoCode;
        
        toggles();
        paymentTabs();
        tagManagerService.setCheckoutType(isGuest);
        tagManagerService.checkoutStep(2, cartItems, isGuest);
        
        if (hadInactiveOfferings) {
            cartService.notifyInactiveItemsRemovedFromCart();
        }

        $(document).mousemove(function () {
            clearTimeout(fiveMinWarningTimeOut);
            clearTimeout(redirectTimeOut);

            fiveMinWarningTimeOut = setTimeout(function () {
                notificationService.notifyInfo(translationService.getTranslation("You have 5 minutes left to finish the payment"));
            }, maxTime - 300000); //5 min left

            redirectTimeOut = setTimeout(function () {
                redirectToOverviewPage();
            }, maxTime);
        });
    }

    //return public functions
    return { init: init, complete: complete };

})(window, jQuery, GK.CartService, GK.NotificationService, GK.TagManagerService, GK.TranslationService);


GK.CheckoutPaymentCreditCard = (function (window, paymentService, paymentPage) {
    var self = this;

    function init(model) {
        self.viewModel = model;
    }

    function complete() {
        paymentPage.complete(self.viewModel.completeUrl);
    }
    return { init: init, complete: complete };

})(window, GK.PaymentService, GK.CheckoutPayment);




var CheckoutPaymentVendorViewModel = (function(translationService) {
    return (function(data) {
        var self = this;
        self.vendors = ko.observableArray(data.vendors);
        self.transactionId = data.transactionId;
        self.completeUrl = data.completeUrl;
        self.grecaptchaSiteKey = data.grecaptchaSiteKey;
        self.grecaptcha = data.grecaptcha;
        self.recaptchaElementId = data.recaptchaElementId;

        self.selectedVendor = ko.pureComputed(function() {
            if (!self.vendorId()) {
                return undefined;
            }

            return ko.utils.arrayFirst(self.vendors(), function(vendor) {
                return vendor.Id && vendor.Id.toString() == self.vendorId();
            });
        }, self);

        self.vendorCodeRegex = ko.pureComputed(function() {
            var selectedVendor = self.selectedVendor();
            if (selectedVendor && selectedVendor.Regex) {
                return selectedVendor.Regex;
            }

            return '.*';
        }, self);

        self.vendorCodeMask = ko.pureComputed(function() {
            var selectedVendor = self.selectedVendor();
            if (selectedVendor && selectedVendor.Mask) {
                return selectedVendor.Mask;
            }

            return undefined;
        }, self);

        self.vendorCodeExample = ko.pureComputed(function() {
            var selectedVendor = self.selectedVendor();
            if (selectedVendor && selectedVendor.Placeholder) {
                return selectedVendor.Placeholder;
            }

            return '';
        }, self);

        self.vendorId = ko.observable('').extend({
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        });

        self.vendorCode = ko.observable(undefined).extend({
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            },
            pattern: {
                message: translationService.getTranslation('Unsupported vendor code'),
                params: self.vendorCodeRegex
            }
        });

        self.isValid = function() {
            var errors = ko.validation.group(self, { deep: true });
            if (errors().length > 0) {
                errors.showAllMessages(true);
                return false;
            }

            return true;
        };

    });
}(GK.TranslationService));

var CheckoutPaymentVendorController = (function(window, paymentService, paymentPage, notificationService, errorService) {
    return (function(viewModel) {
        var self = this;
        self.viewModel = viewModel;

        self.resetVendorCode = function() {
            if (self.viewModel.vendorCode()
                && self.viewModel.vendorCode().length > 0) {
                self.viewModel.vendorCode('');
            }
        }

        var submitVoucherCaptchaCallback = (token) => {
            notificationService.notifyLoading(true);
            paymentService.submitVendorVoucherPayment(self.viewModel.vendorId(),
                self.viewModel.vendorCode(),
                self.viewModel.transactionId,
                token,
                function () {
                    paymentPage.complete(self.viewModel.completeUrl);
                },
                function (jqXHR, textStatus, errorThrown, url) {
                    notificationService.notifyLoading(false);
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                });
        };

        let voucherCaptchaWidgetId;
        self.viewModel.grecaptcha.ready(() => {
            voucherCaptchaWidgetId = self.viewModel.grecaptcha.render(self.viewModel.recaptchaElementId, {
                'sitekey': self.viewModel.grecaptchaSiteKey,
                'size': 'invisible',
                'callback': submitVoucherCaptchaCallback
            });
        })

        self.submit = function () {
            if (!self.viewModel.isValid()) {
                //--form has validation errors
                return;
            }
            self.viewModel.grecaptcha.execute(voucherCaptchaWidgetId);
        }
    });
})(window, GK.PaymentService, GK.CheckoutPayment, GK.NotificationService, GK.ErrorService);

GK.CheckoutPaymentVendor = (function(document) {

    function init(model) {
        var controller = new CheckoutPaymentVendorController(model);
        ko.applyBindings(controller, document.getElementById('payment-vv-wrap'));
    }

    return { init: init };
})(document);

var CheckoutPaymentPrepaidViewModel = (function (translationService) {
    return (function (data) {
        var self = this;
        self.culture = data.culture;
        self.transactionId = data.transactionId;
        self.completeUrl = data.completeUrl;

        self.companyName = ko.observable(undefined).extend({
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        });

        self.departmentName = ko.observable(undefined);

        self.isValid = function () {
            return self.companyName.isValid();
        }

    });
}(GK.TranslationService));

var CheckoutPaymentPrepaidController = (function (paymentService, paymentPage, notificationService, errorService) {
    return (function (viewModel) {
        var self = this;
        self.viewModel = viewModel;

        self.submit = function (formElement) {
            if (!self.viewModel.isValid()) {
                //--form has validation errors
                return;
            }

            notificationService.notifyLoading(true);
            paymentService.submitPrePaidPayment(self.viewModel.companyName(),
                self.viewModel.departmentName(),
                self.viewModel.transactionId,
                function () {
                    paymentPage.complete(self.viewModel.completeUrl);
                },
                function (jqXHR, textStatus, errorThrown, url) {
                    notificationService.notifyLoading(false);
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                });
        }
    });
})(GK.PaymentService, GK.CheckoutPayment, GK.NotificationService, GK.ErrorService);

GK.CheckoutPaymentPrepaid = (function (document) {

    function init(model) {
        var controller = new CheckoutPaymentPrepaidController(model);
        ko.applyBindings(controller, document.getElementById('payment-ppc-wrap'));
    }

    return { init: init };
})(document);

var CheckoutPaymentPOViewModel = (function(translationService) {
    return (function(data) {
        var self = this;
        self.transactionId = data.transactionId;
        self.completeUrl = data.completeUrl;
        self.completeWithFileUrl = data.completeWithFileUrl;
        self.allowedFileExtensions = data.allowedFileExtensions;
        self.maxFileSizeBytes = data.maxFileSizeBytes;
        self.grecaptchaSiteKey = data.grecaptchaSiteKey;
        self.grecaptcha = data.grecaptcha;
        self.recaptchaElementId = data.recaptchaElementId;

        self.poNumber = ko.observable(undefined).extend({
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        });

        self.fileData = ko.observable({
            file: ko.observable(),
            dataURL: ko.observable()
        }).extend({
            fileSize: {
                params: self.maxFileSizeBytes,
                message: translationService.getTranslation('File is too large')
            },
            fileExtension: {
                params: self.allowedFileExtensions,
                message: translationService.getTranslation('Allowed file types for PO are:') + ' ' + self.allowedFileExtensions.join(", ")
            }
        });

        self.file = ko.pureComputed(function() {
            return self.fileData().file();
        }, self);

        self.fileName = ko.pureComputed(function() {
            if (self.file()) {
                return self.file().name;
            }

            return '';
        }, self);

        self.isValid = function() {
            var errors = ko.validation.group(self, { deep: true });
            if (errors().length > 0) {
                errors.showAllMessages(true);
                return false;
            }

            return true;
        };

        self.canUpload = ko.pureComputed(function () {
            return (typeof FormData !== 'undefined' && FormData);//checks if the FormData object exists. Doesnt in IE9 and less.
        }, self);

    });
}(GK.TranslationService));

var CheckoutPaymentPOController = (function(window, paymentService, paymentPage, notificationService, errorService) {
    return (function(viewModel) {
        var self = this;
        self.viewModel = viewModel;

        self.removeFile = function() {
            self.viewModel.fileData().clear();
        };

        var submitPOCaptchaCallback = (token) => {
            notificationService.notifyLoading(true);
            paymentService.submitPOPayment(self.viewModel.poNumber(),
                self.viewModel.file(),
                self.viewModel.transactionId,
                token,
                function () {
                    if (self.viewModel.file()) {
                        paymentPage.complete(self.viewModel.completeWithFileUrl);
                    }
                    else {
                        paymentPage.complete(self.viewModel.completeUrl);
                    }
                },
                function (jqXHR, textStatus, errorThrown, url) {
                    notificationService.notifyLoading(false);
                    errorService.handleError(jqXHR, textStatus, errorThrown, url);
                });
        };

        let poCaptchaWidgetId; 
        self.viewModel.grecaptcha.ready(() => {
            poCaptchaWidgetId = self.viewModel.grecaptcha.render(self.viewModel.recaptchaElementId, {
                'sitekey': self.viewModel.grecaptchaSiteKey,
                'size': 'invisible',
                'callback': submitPOCaptchaCallback
            });
        })

        self.submit = function () {
            if (!self.viewModel.isValid()) {
                //--form has validation errors
                return;
            }
            self.viewModel.grecaptcha.execute(poCaptchaWidgetId);
        }

    });
})(window, GK.PaymentService, GK.CheckoutPayment, GK.NotificationService, GK.ErrorService);

GK.CheckoutPaymentPO = (function(document) {

    function init(model) {
        $('.payment-po-file > a.btn').on('click', function() {
            $('.payment-po-upload').click();
        });

        var controller = new CheckoutPaymentPOController(model);
        ko.applyBindings(controller, document.getElementById('payment-po-wrap'));

        // The below is a hack to trigger the load function of the payment-cc iframe after the payment PO has initialized
        // as a fix where the init.js iframe.on('load') function was not always being called after the iframe had loaded due to what 
        // I believe is a race condition
        $('.payment-cc iframe').trigger('load'); 
    }

    return { init: init };
})(document);;
//site namespace
var GK = GK || {};

GK.CheckoutComplete = (function (userService, referralService) {

    function init() {
        referral();
    }

    function referral() {
        var clearData = function () {
            if (orderData && sessionStorage.removeItem && typeof sessionStorage.removeItem === 'function') {
                //--remove so that order completion is tracked once
                sessionStorage.removeItem('orderDetails');
            }
        }
        var getData = function () {
            if (sessionStorage && sessionStorage.getItem && typeof sessionStorage.getItem === 'function') {
                var orderData = sessionStorage.getItem('orderDetails');
                if (orderData) {
                    return JSON.parse(orderData);
                }
            }
            return null;
        }

        var orderData = getData();

        if (!orderData || !orderData.id || !orderData.total) {
            return;
        }

        //user data gets passed in when its a guest checkout. otherwise pull from the api
        if (orderData.guestPurchaser) {
            orderData.guestPurchaser.Id = generatedUUID();
            referralService.showOrderCompletionReferral(orderData.guestPurchaser, orderData.id, orderData.total);
            clearData();
        } else {
            userService.getCurrentUser().done(function (user) {
                if (!user || !user.Authenticated) {
                    return;
                }
                referralService.showOrderCompletionReferral(user, orderData.id, orderData.total);
                clearData();
            });
        }
    }

    var generatedUUID = function () {
        var uuid = 'fcxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
        return uuid;
    }

    //return public functions
    return { init: init };

})(GK.UserService, GK.ReferralService);;
//site namespace
var GK = GK || {};


//-----------------
// Course Offerings Specific 
//-----------------
GK.AccountRegistration = (function (window, undefined) {
    function init(model) {
        var controller = new AccountRegistrationController(model);
        ko.applyBindings(controller, document.getElementById('registerForm'));
    }

    //return public functions
    return { init: init };

})(window);

//view model
var AccountRegistrationViewModel = (function (translationService) {
    return (function (data) {

        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };

        //init model properties
        var self = this;
        self.states = ko.observableArray(data.model.CompanyStates);
        self.countries = ko.observableArray(data.model.Countries);

        self.companyName = ko.observable(data.model.CompanyName).extend(required);
        self.companyAddress = ko.observable(data.model.CompanyAddress).extend(required);
        self.companyCity = ko.observable(data.model.CompanyCity).extend(required);
        self.companyPostalCode = ko.observable(data.model.CompanyPostalCode).extend(required);
        self.firstName = ko.observable(data.model.FirstName).extend(required);
        self.lastName = ko.observable(data.model.LastName).extend(required);
        self.primaryPhone = ko.observable(data.model.PrimaryPhone).extend(required);
        self.cellPhone = ko.observable(data.model.CellPhone);
        self.emailAddress = ko.observable(data.model.EmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            }
        });
        self.confirmEmailAddress = ko.observable(data.model.ConfirmEmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            },
            equal: {
                message: translationService.getTranslation('Email addresses do not match'),
                params: self.emailAddress
            }
        });
        self.password = ko.observable().extend(required);
        self.confirmPassword = ko.observable().extend({
            required: required.required,
            equal: {
                message: translationService.getTranslation('Passwords do not match'),
                params: self.password
            }
        });

        self.selectedCompanyState = ko.observable(data.model.CompanyStateCode || undefined).extend({
            requiredIfNotEmpty: {
                message: translationService.getTranslation('This field is required'),
                params: self.states
            }
        });
        self.selectedCompanyCountry = ko.observable(data.model.CompanyCountryCode || data.defaultCountry).extend(required);

        self.culture = data.culture;
    });

}(GK.TranslationService));

//controller
var AccountRegistrationController = (function ($, commonService, localService, notificationService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        self.viewModel.errors = ko.validation.group(self.viewModel);

        //events
        self.submitForm = function (formElement) {
            if (viewModel.errors().length === 0) {
                notificationService.notifyLoading(true);
                formElement.submit();
            }
            else {
                viewModel.errors.showAllMessages();
            }
        }

        self.viewModel.selectedCompanyCountry.subscribe(function (countryCode) {
            getStatesForCountry(countryCode);
        });

        self.phoneInputMask = ko.computed(function () {
            if (self.viewModel.selectedCompanyCountry() === "US" || self.viewModel.selectedCompanyCountry() === "CA") {
                return "(000) 000-0000";
            } else {
                return "+00 9999 99999999999";
            }
        });

        self.phoneExample = ko.computed(function () {
            if (self.viewModel.selectedCompanyCountry() === "US" || self.viewModel.selectedCompanyCountry() === "CA") {
                return "(999) 999-9999";
            } else {
                return "+99 9999 999999";
            }
        });

        //private
        var getStatesForCountry = function (countryCode) {
            localService.getStatesForCountry(countryCode, self.viewModel.culture,
                function(states) {
                    if (states.length !== 0) {
                        var prevState = self.viewModel.selectedCompanyState();

                        self.viewModel.states(states);

                        if (states.length === 1) {// only 1 state = NA so just select it. used for countries without states
                            self.viewModel.selectedCompanyState(states[0].ISOStateCode);
                        } else {
                            self.viewModel.selectedCompanyState(prevState);
                        }

                    } else {
                        self.viewModel.states([]);
                    }
                },
                function(error) {
                    
                });
        }
    });
}(jQuery, GK.Global, GK.LocaleService, GK.NotificationService));;
//site namespace
var GK = GK || {};


//-----------------
// Demo Registration Specific 
//-----------------
GK.DemoRegistration = (function (window, undefined) {
    function init(model) {
        var controller = new DemoRegistrationController(model);
        ko.applyBindings(controller, document.getElementById('registerForm'));
    }

    //return public functions
    return { init: init };

})(window);

//view model
var DemoRegistrationViewModel = (function (translationService) {
    return (function (data) {

        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };

        //init model properties
        var self = this;
        self.states = ko.observableArray(data.model.CompanyStates);
        self.countries = ko.observableArray(data.model.Countries);

        self.companyName = ko.observable(data.model.CompanyName).extend(required);
        self.companyAddress = ko.observable(data.model.CompanyAddress).extend(required);
        self.companyCity = ko.observable(data.model.CompanyCity).extend(required);
        self.companyPostalCode = ko.observable(data.model.CompanyPostalCode).extend(required);
        self.firstName = ko.observable(data.model.FirstName).extend(required);
        self.lastName = ko.observable(data.model.LastName).extend(required);
        self.primaryPhone = ko.observable(data.model.PrimaryPhone).extend(required);
        self.cellPhone = ko.observable(data.model.CellPhone);
        self.emailAddress = ko.observable(data.model.EmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            }
        });
        self.confirmEmailAddress = ko.observable(data.model.ConfirmEmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            },
            equal: {
                message: translationService.getTranslation('Email addresses do not match'),
                params: self.emailAddress
            }
        });

        self.selectedCompanyState = ko.observable(data.model.CompanyStateCode || undefined).extend({
            requiredIfNotEmpty: {
                message: translationService.getTranslation('This field is required'),
                params: self.states
            }
        });
        self.selectedCompanyCountry = ko.observable(data.model.CompanyCountryCode || data.defaultCountry).extend(required);

        self.culture = data.culture;
    });

}(GK.TranslationService));

//controller
var DemoRegistrationController = (function ($, commonService, localService, notificationService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        self.viewModel.errors = ko.validation.group(self.viewModel);

        //events
        self.submitForm = function (formElement) {
            if (viewModel.errors().length === 0) {
                notificationService.notifyLoading(true);
                formElement.submit();
            }
            else {
                viewModel.errors.showAllMessages();
            }
        }

        self.viewModel.selectedCompanyCountry.subscribe(function (countryCode) {
            getStatesForCountry(countryCode);
        });

        self.phoneInputMask = ko.computed(function () {
            if (self.viewModel.selectedCompanyCountry() === "US" || self.viewModel.selectedCompanyCountry() === "CA") {
                return "(000) 000-0000";
            } else {
                return "+00 9999 99999999999";
            }
        });

        self.phoneExample = ko.computed(function () {
            if (self.viewModel.selectedCompanyCountry() === "US" || self.viewModel.selectedCompanyCountry() === "CA") {
                return "(999) 999-9999";
            } else {
                return "+99 9999 999999";
            }
        });

        //private
        var getStatesForCountry = function (countryCode) {
            localService.getStatesForCountry(countryCode, self.viewModel.culture,
                function(states) {
                    if (states.length !== 0) {
                        var prevState = self.viewModel.selectedCompanyState();

                        self.viewModel.states(states);

                        if (states.length === 1) {// only 1 state = NA so just select it. used for countries without states
                            self.viewModel.selectedCompanyState(states[0].ISOStateCode);
                        } else {
                            self.viewModel.selectedCompanyState(prevState);
                        }

                    } else {
                        self.viewModel.states([]);
                    }
                },
                function(error) {
                    
                });
        }
    });
}(jQuery, GK.Global, GK.LocaleService, GK.NotificationService));;
//site namespace
var GK = GK || {};


//-----------------
// Course Offerings Specific 
//-----------------
GK.EditProfile = (function (window, undefined) {
    function init(model) {
        var controller = new EditProfileController(model);
        ko.applyBindings(controller, document.getElementById('editProfileForm'));
    }

    //return public functions
    return { init: init };

})(window);

//view model
var EditProfileViewModel = (function (translationService) {
    return (function (data) {

        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };

        //init model properties
        var self = this;
        self.companyStates = ko.observableArray(data.model.CompanyStates);
        self.companyCountries = ko.observableArray(data.model.Countries);
        self.shippingStates = ko.observableArray(data.model.ShippingStates);
        self.shippingCountries = ko.observableArray(data.model.Countries);

        self.culture = data.culture;
        self.id = data.model.Id;

        self.fileData = ko.observable({
            file: ko.observable(),
            dataURL: ko.observable() // FileReader.readAsDataURL(Blob|File) - The result property will contain the file/blob's data encoded as a data URL.
        }).extend({
            fileSize: {
                params: 3000000,
                message: translationService.getTranslation('File must be smaller than 3MB')
            },
            mimeType: {
                params: "image",
                message: translationService.getTranslation("Upload must be of type image.")
            }
        });

        var shippingSameAsCompany = data.model.ShippingAddress === null || data.model.SameAsCompanyAddress;

        this.shippingValidationEnabled = ko.observable(!shippingSameAsCompany);

        self.imageUrl = ko.observable(data.model.ImageUrl);
        self.imagePath = ko.observable(data.model.ImagePath);
        self.companyName = ko.observable(data.model.CompanyName).extend(required);
        self.displayName = ko.observable(data.model.DisplayName);
        self.isDisplayNameAvailable = ko.observable(null);
        self.firstName = ko.observable(data.model.FirstName).extend(required);
        self.lastName = ko.observable(data.model.LastName).extend(required);
        self.primaryPhone = ko.observable(data.model.PrimaryPhone).extend(required);
        self.cellPhone = ko.observable(data.model.CellPhone);
        self.companyAddress = ko.observable(data.model.CompanyAddress).extend(required);
        self.companyCity = ko.observable(data.model.CompanyCity).extend(required);
        self.companyPostalCode = ko.observable(data.model.CompanyPostalCode).extend(required);
        self.shippingAddress = ko.observable(data.model.ShippingAddress).extend({
            required: {
                onlyIf: self.shippingValidationEnabled,
                message: translationService.getTranslation('This field is required'),
            }
        });
        self.shippingCity = ko.observable(data.model.ShippingCity).extend({
            required: {
                onlyIf: self.shippingValidationEnabled,
                message: translationService.getTranslation('This field is required'),
            }
        });
        self.shippingPostalCode = ko.observable(data.model.ShippingPostalCode).extend({
            required: {
                onlyIf: self.shippingValidationEnabled,
                message: translationService.getTranslation('This field is required'),
            }
        });
        self.sameAsCompanyAddress = ko.observable(shippingSameAsCompany); // check if sameAsCompany was checked

        self.selectedCompanyState = ko.observable(data.model.CompanyStateCode || undefined).extend({
            requiredIfNotEmpty: {
                message: translationService.getTranslation('This field is required'),
                params: self.companyStates
            }
        });
        self.selectedCompanyCountry = ko.observable(data.model.CompanyCountryCode || data.defaultCountry).extend(required);

        self.shippingStateValidation = ko.pureComputed(function() {
            return self.shippingValidationEnabled() && self.shippingStates().length > 0;
        }, self);

        self.selectedShippingState = ko.observable(data.model.ShippingStateCode || self.selectedCompanyState()).extend({
            required: {
                message: translationService.getTranslation('This field is required'),
                onlyIf: self.shippingStateValidation
            }
        });

        self.selectedShippingCountry = ko.observable(data.model.ShippingCountryCode || self.selectedCompanyCountry()).extend({
            required: {
                onlyIf: self.shippingValidationEnabled,
                message: translationService.getTranslation('This field is required'),
            }
        });

        self.errors = ko.validation.group(self, { deep: true });
    });

}(GK.TranslationService));

//controller
var EditProfileController = (function ($, commonService, localService, userService, translationService, notificationService) {

    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        //events
        self.submitForm = function (formElement) {
            if (viewModel.errors().length === 0) {
                notificationService.notifyLoading(true);
                formElement.submit();
            }
            else {
                self.viewModel.errors.showAllMessages(true);
            }
        }

        self.toggleSameAsCompanyAddress = ko.computed(function () {
            //Remove and add validation
            if (self.viewModel.sameAsCompanyAddress()) {
                self.viewModel.shippingValidationEnabled(false);
            } else {
                self.viewModel.shippingValidationEnabled(true);
            }
        });

        self.phoneInputMask = ko.computed(function () {
            if (self.viewModel.selectedCompanyCountry() === "US" || self.viewModel.selectedCompanyCountry() === "CA") {
                return "(000) 000-0000";
            } else {
                return "+00 9999 99999999999";
            }
        });

        self.phoneExample = ko.computed(function () {
            if (self.viewModel.selectedCompanyCountry() === "US" || self.viewModel.selectedCompanyCountry() === "CA") {
                return "(999) 999-9999";
            } else {
                return "+99 9999 999999";
            }
        });


        self.displayNameIsAvailable = ko.computed(function () {
            if (self.viewModel.id && self.viewModel.displayName()) {
                userService.checkIfDisplayNameAvailable(self.viewModel.id, self.viewModel.displayName(), function(data) {
                    self.viewModel.isDisplayNameAvailable(Boolean(data));
                },
                function (error) {

                });
            }
        });

        self.viewModel.selectedCompanyCountry.subscribe(function (countryCode) {
            getStatesForCountry(countryCode, true);
        });

        self.viewModel.selectedShippingCountry.subscribe(function (countryCode) {
            getStatesForCountry(countryCode, false);
        });

        //private
        var getStatesForCountry = function (countryCode, isCompanyAddress) {
            if (isCompanyAddress) {
                localService.getStatesForCountry(countryCode, self.viewModel.culture,
                    function(states) {
                        if (states.length !== 0) {
                            var prevState = self.viewModel.selectedCompanyState();

                            self.viewModel.companyStates(states);
                            
                            if (states.length === 1) {// only 1 state = NA so just select it. used for countries without states
                                self.viewModel.selectedCompanyState(states[0].ISOStateCode);
                            } else {
                                self.viewModel.selectedCompanyState(prevState);
                            }

                        } else {
                            self.viewModel.companyStates([]);
                        }
                    },
                    function(error) {

                    });
            } else {
                localService.getStatesForCountry(countryCode, self.viewModel.culture,
                    function(states) {
                        if (states.length !== 0) {
                            var prevState = self.viewModel.selectedShippingState();

                            self.viewModel.shippingStates(states);

                            if (states.length === 1) {// only 1 state = NA so just select it. used for countries without states
                                self.viewModel.selectedShippingState(states[0].ISOStateCode);
                            } else {
                                self.viewModel.selectedShippingState(prevState);
                            }
                        } else {
                            self.viewModel.shippingStates([]);
                        }
                    },
                    function(error) {

                    });
            }
        }
    });
}(jQuery, GK.Global, GK.LocaleService, GK.UserService, GK.TranslationService, GK.NotificationService));;
//site namespace
var GK = GK || {};


//-----------------
// Account Info Specific
//-----------------
GK.AccountInfo = (function (notificationService, window, undefined) {
    function emailFormToggle() {
        var emailWidth = $('.current-email > span').outerWidth() + 30;
        $('.update-email-form > input').css('width', emailWidth);
        $('.email-address .current-email > a').on('click', function (e) {
            e.preventDefault();
            $('.email-address .current-email').toggleClass('hidden');
            $('.update-email-form').toggleClass('hidden');
            $('.update-email-form > input').select();
        });
        $('#cancel-email').on('click', function (e) {
            e.preventDefault();
            $('.email-address .current-email').toggleClass('hidden');
            $('.update-email-form').toggleClass('hidden');
            return false;
        });
    }

    function saveMessage(msg) {
        notificationService.notifySuccess(msg);
    }

    function init() {
        emailFormToggle();
    }

    //return public functions
    return { init: init, saveMessage: saveMessage };

})(GK.NotificationService, window);;
//site namespace
var GK = GK || {};


//-----------------
// Course Offerings Specific 
//-----------------
GK.CreatePrivateGroupTrainingRequest = (function (window, undefined) {
    function init(model) {
        var controller = new CreatePrivateGroupTrainingRequestController(model);
        ko.applyBindings(controller, document.getElementById('requestForm'));
    }

    //return public functions
    return { init: init };

})(window);

//view model
var CreatePrivateGroupTrainingRequestViewModel = (function (translationService) {
    return (function (data) {

        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };

        //init model properties
        var self = this;
        self.fnSubmit = data.fnSubmit;

        self.states = ko.observableArray(data.model.States);
        self.countries = ko.observableArray(data.model.Countries);

        var roles = [];
        ko.utils.arrayForEach(data.roles, function(role) {
            roles.push({
                text: role,
                value: role
            });
        });
        roles.push({
            text: translationService.getTranslation('Other'),
            value: -1
        });
        self.roles = ko.observableArray(roles);

        self.companyName = ko.observable(data.model.CompanyName).extend(required);
        self.companyAddress = ko.observable(data.model.CompanyAddress).extend(required);
        self.companyCity = ko.observable(data.model.CompanyCity).extend(required);
        self.companyPostalCode = ko.observable(data.model.CompanyPostalCode).extend(required);
        self.firstName = ko.observable(data.model.FirstName).extend(required);
        self.lastName = ko.observable(data.model.LastName).extend(required);
        self.isOnSite = ko.observable(data.model.IsOnSite);
        self.emailAddress = ko.observable(data.model.EmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            }
        });
        self.officeTelephone = ko.observable(data.model.OfficeTelephone).extend(required);

        self.selectedRole = ko.observable(data.model.Role || undefined).extend(required);
        self.selectedCourseState = ko.observable(data.model.CourseStateCode);
        self.selectedCompanyState = ko.observable(data.model.CompanyStateCode || undefined).extend({
            requiredIfNotEmpty: {
                message: translationService.getTranslation('This field is required'),
                params: self.states
            }
        });
        self.selectedCompanyCountry = ko.observable(data.model.CompanyCountryCode || data.defaultCountry).extend(required);

        self.culture = data.culture;

        self.whenToAttendCourse = ko.observable(data.model.WhenToAttendCourse);
        self.courseCity = ko.observable(data.model.CourseCity);
        self.isVirtual = ko.observable(data.model.IsVirtual);
        self.numberOfStudents = ko.observable(data.model.NumberOfStudents).extend(required);
        self.additionalComments = ko.observable(data.model.AdditionalComments);
        self.originalCourse = ko.observable(data.model.OriginalCourse);
        self.trainingDeliveryFormat = ko.observable(data.model.TrainingDeliveryFormat).extend(required);

        self.requiresOtherRole = ko.pureComputed(function() {
            return self.selectedRole() && self.selectedRole() == -1;
        }, self);
        self.otherRole = ko.observable(data.model.OtherRole).extend({
            requiredIf: {
                message: translationService.getTranslation('This field is required'),
                params: self.requiresOtherRole
            }
        });

        self.errors = ko.validation.group(self, { deep: true });
    });

}(GK.TranslationService));

//controller
var CreatePrivateGroupTrainingRequestController = (function ($, commonService, localService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        //events
        self.submitForm = function (formElement) {
            if (viewModel.errors().length === 0) {
                //formElement.submit();
                self.viewModel.fnSubmit();
            }
            else {
                viewModel.errors.showAllMessages();
            }
        }

        self.viewModel.selectedCompanyCountry.subscribe(function (countryCode) {
            getStatesForCountry(countryCode);
        });

        //private
        var getStatesForCountry = function(countryCode) {
            localService.getStatesForCountry(countryCode, self.viewModel.culture,
                function(states) {
                    if (states.length !== 0) {
                        var prevState = self.viewModel.selectedCompanyState();

                        self.viewModel.states(states);
                        
                        if (states.length === 1) {// only 1 state = NA so just select it. used for countries without states
                            self.viewModel.selectedCompanyState(states[0].ISOStateCode);
                        } else {
                            self.viewModel.selectedCompanyState(prevState || '');
                        }
                    }
                    else {
                        self.viewModel.states([]);
                    }
                },
                function(error) {

                });
        };
    });
}(jQuery, GK.Global, GK.LocaleService));;
//site namespace
var GK = GK || {};


//-----------------
// Information Request Specific 
//-----------------
GK.CreateInformationRequest = (function (window, undefined) {
    function init(model) {
        var controller = new CreateInformationRequestController(model);
        ko.applyBindings(controller, document.getElementById('requestForm'));
    }

    //return public functions
    return { init: init };

})(window);

//view model
var CreateInformationRequestViewModel = (function (translationService) {
    return (function (data) {

        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };

        //init model properties
        var self = this;
        self.fnSubmit = data.fnSubmit;

        self.countries = ko.observableArray(data.model.Countries);

        var roles = [];
        ko.utils.arrayForEach(data.roles, function(role) {
            roles.push({
                text: role,
                value: role
            });
        });
        roles.push({
            text: translationService.getTranslation('Other'),
            value: -1
        });
        self.roles = ko.observableArray(roles);

        self.companyName = ko.observable(data.model.CompanyName).extend(required);
        self.companyPostalCode = ko.observable(data.model.CompanyPostalCode).extend(required);
        self.firstName = ko.observable(data.model.FirstName).extend(required);
        self.lastName = ko.observable(data.model.LastName).extend(required);

        self.emailAddress = ko.observable(data.model.EmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            }
        });

        self.officeTelephone = ko.observable(data.model.OfficeTelephone);
        self.selectedRole = ko.observable(data.model.Role || undefined).extend(required);
        self.selectedCompanyCountry = ko.observable(data.model.CompanyCountryCode || data.defaultCountry).extend(required);

        self.culture = data.culture;

        self.additionalComments = ko.observable(data.model.AdditionalComments).extend(required);


        self.requiresOtherRole = ko.pureComputed(function() {
            return self.selectedRole() && self.selectedRole() == -1;
        }, self);
        self.otherRole = ko.observable(data.model.OtherRole).extend({
            requiredIf: {
                message: translationService.getTranslation('This field is required'),
                params: self.requiresOtherRole
            }
        });

        self.referral = ko.observable(data.model.Referral);

        self.errors = ko.validation.group(self, { deep: true });
    });

}(GK.TranslationService));

//controller
var CreateInformationRequestController = (function ($, commonService, localService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        //events
        self.submitForm = function (formElement) {
            if (viewModel.errors().length === 0) {
                //formElement.submit();
                self.viewModel.fnSubmit();
            }
            else {
                viewModel.errors.showAllMessages();
            }
        }

    });
}(jQuery, GK.Global, GK.LocaleService));;
//site namespace
var GK = GK || {};


GK.RoomRentalRequest = (function (window, undefined) {
    function init(model) {
        var controller = new RoomRentalRequestController(model);
        ko.applyBindings(controller, document.getElementById('requestForm'));
    }

    //return public functions
    return { init: init };

})(window);

//view model
var RoomRentalRequestViewModel = (function (translationService) {
    return (function (data) {
        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };

        //init model properties
        var self = this;
        self.fnSubmit = data.fnSubmit;
        
        var rentalTypeOptions = [];
        ko.utils.arrayForEach(data.model.RentalTypeOptions, function (rentalType) {
            rentalTypeOptions.push({
                text: rentalType,
                value: rentalType
            });
        });
        self.rentalTypeOptions = ko.observableArray(rentalTypeOptions);

        self.locationOptions = ko.observableArray(data.model.LocationOptions || []);

        self.firstName = ko.observable(data.model.FirstName).extend(required);
        self.lastName = ko.observable(data.model.LastName).extend(required);

        self.companyName = ko.observable(data.model.CompanyName).extend(required);
        self.postalCode = ko.observable(data.model.PostalCode).extend(required);
        

        self.emailAddress = ko.observable(data.model.EmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            }
        });
        self.officeTelephone = ko.observable(data.model.OfficeTelephone).extend(required);

        self.preferredDates = ko.observable(data.model.PreferredDates);
        self.numberOfStudents = ko.observable(data.model.NumberOfStudents).extend(required);


        self.typeOfRental = ko.observable(data.model.TypeOfRental || undefined).extend(required);
        self.locations = ko.observableArray(data.model.LocationList || []).extend(required);

        self.additionalComments = ko.observable(data.model.AdditionalComments);

        self.errors = ko.validation.group(self, { deep: true });
    });

}(GK.TranslationService));

//controller
var RoomRentalRequestController = (function (translationService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        self.selectedLocationText = function () {
            if (self.viewModel.locations().length === 0) {
                return translationService.getTranslation('Select City');
            } else if (self.viewModel.locations().length === 1) {
                return self.viewModel.locations()[0];
            } else {
                return translationService.getTranslation('Multiple');
            }
        };

        //events
        self.submitForm = function (formElement) {
            if (viewModel.errors().length === 0) {
                self.viewModel.fnSubmit();
            }
            else {
                viewModel.errors.showAllMessages();
            }
        }

        self.isSelectedLocation = function (location) {
            return self.viewModel.locations.indexOf(location) > -1;
        };

        self.toggleLocation = function (location) {
            if (location == translationService.getTranslation('All')) {
                self.viewModel.locations.removeAll();
            }
            else if (self.isSelectedLocation(location)) {
                self.viewModel.locations.remove(location);
            } else {
                self.viewModel.locations.push(location);
            }
            return false;
        };

    });
}(GK.TranslationService));;
//site namespace
var GK = GK || {};


//-----------------
// Course Offerings Specific 
//-----------------
GK.CreateNewsletterRequest = (function (window, undefined) {
    function init(model) {
        var controller = new CreateNewsletterRequestController(model);
        ko.applyBindings(controller, document.getElementById('requestForm'));
    }

    //return public functions
    return { init: init };

})(window);

//view model
var CreateNewsletterRequestViewModel = (function (translationService) {
    return (function (data) {

        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };

        //init model properties
        var self = this;
        self.fnSubmit = data.fnSubmit;

        self.states = ko.observableArray(data.model.States);
        self.countries = ko.observableArray(data.model.Countries);

        self.firstName = ko.observable(data.model.FirstName).extend(required);
        self.lastName = ko.observable(data.model.LastName).extend(required);
        self.emailAddress = ko.observable(data.model.EmailAddress).extend(required);
        
        self.selectedState = ko.observable(data.model.StateCode);
        self.selectedCountry = ko.observable(data.model.CountryCode || data.defaultCountry).extend(required);
        self.receiveGlobalNews = ko.observable(data.model.ReceiveGlobalNews);
        self.receiveSavingsUpdates = ko.observable(data.model.ReceiveGlobalNews);
        self.trainingInterests = ko.observableArray(data.model.TrainingInterests);

        self.culture = data.culture;

        self.errors = ko.validation.group(self);
    });

}(GK.TranslationService));

//controller
var CreateNewsletterRequestController = (function ($, commonService, localService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

       // self.viewModel.errors = ko.validation.group(self.viewModel);

        //events
        self.submitForm = function (formElement) {
            if (viewModel.errors().length === 0) {
                self.viewModel.fnSubmit();
                //formElement.submit();
            }
            else {
                viewModel.errors.showAllMessages();
            }
        }

        self.viewModel.selectedCountry.subscribe(function (countryCode) {
            getStatesForCountry(countryCode);
        });

        //private
        var getStatesForCountry = function (countryCode) {
            localService.getStatesForCountry(countryCode, self.viewModel.culture,
                function (states) {
                    if (states.length !== 0) {
                        var prevState = self.viewModel.selectedState();

                        self.viewModel.states(states);
                        self.viewModel.selectedState(prevState);
                    } else {
                        self.viewModel.states([]);
                    }
                },
                function (error) {

                });
        }
    });
}(jQuery, GK.Global, GK.LocaleService));;
//site namespace
var GK = GK || {};


//-----------------
// Course Offerings Specific 
//-----------------
GK.GenerateQuote = (function (window, undefined) {
    function init(model) {
        var controller = new GenerateQuoteController(model);
        ko.applyBindings(controller, document.getElementById('requestForm'));
    }

    //return public functions
    return {
        init: init
    };
})(window);

//view model
var GenerateQuoteViewModel = (function (translationService) {
    return (function (data) {
        //init model properties
        var self = this;

        self.mapCourseItem = function (course) {
            course.quantity = ko.observable(course.Quantity);
            course.originalQuantity = ko.observable(course.Quantity);
            course.unitDiscountAmount = ko.observable(course.UnitDiscountAmount);
            course.promoCode = ko.observable(course.PromoCode);

            course.isUpdatingQuantity = ko.observable(false);

            course.quantityChanged = ko.computed(function () {
                return course.quantity() != course.originalQuantity();
            });

            course.hasDiscount = ko.pureComputed(function () {
                return this.unitDiscountAmount() > 0;
            }, course);

            course.originalQuantity.subscribe(function (newVal) {
                course.Quantity = newVal;
            });

            return course;
        }

        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };


        //-- data
        self.payNow = ko.observable(false);
        self.guest = ko.observable(data.guest || false);
        self.culture = data.culture;
        self.buyerStates = ko.observableArray(data.model.BuyerStates);
        self.countries = ko.observableArray(data.model.Countries);

        //--buyer
        self.buyerFirstName = ko.observable(data.model.BuyerFirstName).extend(required);
        self.buyerLastName = ko.observable(data.model.BuyerLastName).extend(required);
        self.buyerEmailAddress = ko.observable(data.model.BuyerEmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            }
        });

        self.buyerCompanyName = ko.observable(data.model.BuyerCompanyName).extend(required);
        self.buyerCompanyAddress = ko.observable(data.model.BuyerCompanyAddress).extend(required);
        self.buyerCompanyCity = ko.observable(data.model.BuyerCompanyCity).extend(required);
        self.selectedBuyerCompanyState = ko.observable(data.model.BuyerCompanyStateCode || undefined).extend({
            requiredIfNotEmpty: {
                message: translationService.getTranslation('This field is required'),
                params: self.buyerStates
            }
        });
        self.buyerCompanyPostalCode = ko.observable(data.model.BuyerCompanyPostalCode).extend(required);
        self.selectedBuyerCompanyCountry = ko.observable(data.model.BuyerCompanyCountryCode || data.defaultCountry).extend(required);
        self.buyerOfficeTelephone = ko.observable(data.model.BuyerOfficePhone).extend(required);

        //-- quote items
        data.model.QuoteItems.sort(function (a, b) {
            return a.Name != b.Name ? a.Name > b.Name : a.OfferingId > b.OfferingId;
        });

        self.quoteItems = ko.observableArray(ko.utils.arrayMap(data.model.QuoteItems, function (course) { return self.mapCourseItem(course); }));

        //-- discounts
        self.gsaPricing = ko.observable(data.model.GSAPricing);
        self.accountLevelDiscount = ko.observable(data.model.AccountLevelDiscount);
        self.governmentOrEducationPricing = ko.observable(data.model.GovernmentOrEducationPricing);

        //-- promo
        self.promoCode = ko.observable("");
        self.promoErrorMessage = ko.observable("");
        self.promoSuccessMessage = ko.observable("");
        self.promoCatalogLinkPromo = ko.observable("");

        self.modal = {
            deleteCourse: {
                courseToDelete: ko.observable({}),
                show: ko.observable(false),
                isWaiting: ko.observable(false)
            }
        };

        //-- calculated
        self.subtotal = ko.pureComputed(function () {
            var total = 0;
            for (var i = 0; i < self.quoteItems().length; i++) {
                var item = self.quoteItems()[i];
                total += item.UnitPrice * item.originalQuantity();
            }
            return total;
        }, self);

        self.discountAmount = ko.pureComputed(function () {
            var discount = 0;
            for (var i = 0; i < self.quoteItems().length; i++) {
                var item = self.quoteItems()[i];
                discount += item.UnitDiscountAmount * item.originalQuantity();
            }
            return discount;
        }, self);

        self.hasDiscount = ko.pureComputed(function () {
            return (self.discountAmount() || 0) > 0;
        },
            self);

        self.quoteTotal = ko.pureComputed(function () {
            return self.subtotal() - self.discountAmount();
        }, self);

        self.hasPromo = ko.pureComputed(function () {
            for (var i = 0; i < self.quoteItems().length; i++) {
                var item = self.quoteItems()[i];
                if (item.promoCode()) {
                    return true;
                }
            }

            return false;
        }, self);

        self.appliedPromoCode = ko.pureComputed(function () {
            for (var i = 0; i < self.quoteItems().length; i++) {
                var item = self.quoteItems()[i];
                if (item.promoCode()) {
                    return item.promoCode();
                }
            }
            return null;
        }, self);

        self.clearPromo = function () {
            self.promoCode("");
            var items = self.quoteItems();
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                item.UnitDiscountAmount = 0;
                item.unitDiscountAmount(0);
                item.UnitDiscountPrice = item.UnitPrice;
                item.PromoCode = null;
                item.promoCode(null);
                
            }
            self.quoteItems(items);
        };

        self.hasCustomPricing = ko.pureComputed(function () {
            return self.gsaPricing() || self.accountLevelDiscount() || self.governmentOrEducationPricing();
        }, self);

        self.allRequiredFieldsFilled = ko.pureComputed(function () {
            var fields = [self.buyerFirstName, self.buyerLastName, self.buyerEmailAddress, self.buyerCompanyName, self.buyerCompanyAddress, self.buyerCompanyCity, self.selectedBuyerCompanyState, self.buyerCompanyPostalCode, self.selectedBuyerCompanyCountry, self.buyerOfficeTelephone];
            for (var i = 0; i < fields.length; i++) {
                if (!fields[i]()) {
                    return false;
                }
            }
            return true;
        }, self);

        self.errors = ko.validation.group(self, { deep: true });
    });

}(GK.TranslationService));

//controller
var GenerateQuoteController = (function ($, commonService, localService, translationService, userService, quoteService, notificationService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        //events
        self.submitQuoteForm = function (formElement, payNow) {
            self.viewModel.payNow(payNow || false);
            if (viewModel.errors().length === 0) {
                notificationService.notifyLoading(true);
                formElement.submit();
            }
            else {
                viewModel.errors.showAllMessages(true);
                commonService.scrollToError();
            }
        }

        self.redeemLater = function () {
            self.submitQuoteForm($('#GenerateQuoteForm')[0], false);
        }

        self.payNow = function () {
            self.submitQuoteForm($('#GenerateQuoteForm')[0], true);
        }

        self.togglePricingCheck = function (check) {//for gsa and gov/edu since both are in different locales
            if (!viewModel.hasPromo() && !viewModel.accountLevelDiscount()) {
                self.viewModel.clearPromo();
                check(!check());
            }
        }

        self.toggleALDCheck = function (check) {
            if (!viewModel.hasPromo() && !viewModel.gsaPricing() && !viewModel.governmentOrEducationPricing()) {
                self.viewModel.clearPromo();
                check(!check());
            }
        }

        self.showConfirmRemoveCourse = function (course) {
            if (course.isUpdatingQuantity()) {
                return false;
            }
            self.viewModel.modal.deleteCourse.courseToDelete(course);
            self.viewModel.modal.deleteCourse.show(true);
        }

        self.hideConfirmRemoveCourse = function (course) {
            self.viewModel.modal.deleteCourse.show(false);
            self.viewModel.modal.deleteCourse.isWaiting(false);

            if (course().quantity().length == 0
                || course().quantity() == 0) {
                //Cancelled delete, so restore quantity
                course().quantity(course().originalQuantity());
            }
        }

        self.removeCourse = function (course) {
            self.viewModel.modal.deleteCourse.isWaiting(true);
            self.viewModel.modal.deleteCourse.show(false);
            course().isUpdatingQuantity(true);
            course().quantity(0)
            course().originalQuantity(0);
            updatePricing(null, null, function () { self.viewModel.modal.deleteCourse.isWaiting(false); });
        };

        self.updateQuantity = function (course) {
            if (course.quantity().length == 0
                || course.quantity() == 0) {
                self.showConfirmRemoveCourse(course);
                return;
            }

            var isValid = /^[1-9]\d*$/.test(course.quantity());
            if (!isValid) {
                alert(translationService.getTranslation('Please set an integer quantity of at least 1.'));
            }
            else {
                course.isUpdatingQuantity(true);
                course.originalQuantity(course.quantity());
                updatePricing(null, null, function () { course.isUpdatingQuantity(false); });
            }
        }

        self.viewModel.selectedBuyerCompanyCountry.subscribe(function (countryCode) {
            getStatesForCountry(countryCode, self.viewModel.buyerStates, self.viewModel.selectedBuyerCompanyState);
        });

        //private
        var getStatesForCountry = function (countryCode, stateArr, selectedProp) {
            localService.getStatesForCountry(countryCode, self.viewModel.culture,
                function (states) {
                    if (states.length !== 0) {
                        var prevState = selectedProp();
                        stateArr(states);
                        selectedProp(prevState || '');
                    }
                    else {
                        stateArr([]);
                    }
                },
                function (error) {

                });
        };

        self.viewModel.gsaPricing.subscribe(function (newVal) {
            updatePricing();
        });

        var updatePricing = function (successCallback, errorCallback, alwaysCallback) {
            self.clearPromoMessages();
            var rm = {
                governmentOrEducationPricing: self.viewModel.governmentOrEducationPricing(),
                gsaPricing: self.viewModel.gsaPricing(),
                accountLevelDiscount: self.viewModel.accountLevelDiscount(),
                promoCode: self.viewModel.promoCode(),
                quoteItems: ko.toJS(self.viewModel.quoteItems),
                culture: self.viewModel.culture
            };

            notificationService.notifyLoading(true);
            quoteService.updatePricing(rm,
                function (data) {
                    self.viewModel.quoteItems(ko.utils.arrayMap(data.QuoteItems, function (course) { return self.viewModel.mapCourseItem(course); }));
                    if (data.PromoEligibility) {
                        if (promoEligibilityMessages[data.PromoEligibility]) {
                            promoEligibilityMessages[data.PromoEligibility](self.viewModel.promoCode());
                        } else {
                            promoEligibilityMessages.invalidCode();
                        }
                    }
                    self.viewModel.promoCode("");
                    if (typeof successCallback === "function") {
                        successCallback(data);
                    }
                },
                errorCallback,
                function () {
                    notificationService.notifyLoading(false);
                    if (typeof alwaysCallback === "function") {
                        alwaysCallback();
                    }
                });
        }

        //promo 
        var promoEligibilityMessages = (function () {
            var msgs = {
                invalidCode: function () { self.viewModel.promoErrorMessage(translationService.getTranslation("Invalid promo code entered")); },
                allIneligibleCode: function () { self.viewModel.promoErrorMessage(translationService.getTranslation("Course(s) not eligible for promo")); },
                allEligibleCode: function () { self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code applied to cart")); },
                partialAppliedCode: function () { self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code applied to eligible courses")); },
                tooManyBogo: function () { self.viewModel.promoErrorMessage(translationService.getTranslation("BOGO offer valid for two courses per order. Please adjust your cart to proceed.")); },
                tooFewBogo: function (pc) { self.viewModel.promoCatalogLinkPromo(pc); self.viewModel.promoErrorMessage(translationService.getTranslation("This promotion requires two courses. Please select a second course.")); }
            };
            // promo messages based on enum
            msgs[0] = msgs.invalidCode;
            msgs[1] = msgs.allIneligibleCode;
            msgs[2] = msgs.allEligibleCode;
            msgs[3] = msgs.partialAppliedCode;
            msgs[4] = msgs.tooManyBogo;
            msgs[5] = msgs.tooFewBogo;
            return msgs;
        })();

        self.clearPromoMessages = function () {
            self.viewModel.promoErrorMessage("");
            self.viewModel.promoSuccessMessage("");
            self.viewModel.promoCatalogLinkPromo("");
        }

        self.applyPromoCode = function () {
            self.clearPromoMessages();

            if (!self.viewModel.promoCode || !self.viewModel.promoCode()) {
                promoEligibilityMessages.invalidCode();
                return;
            }
            updatePricing();
        };

        self.clearPromoCode = function () {
            self.clearPromoMessages();
            self.viewModel.promoCode("");
            self.viewModel.clearPromo();
            self.viewModel.promoSuccessMessage(translationService.getTranslation("Promo code cleared from cart"));
        };

        self.scrollToSubmit = function () {
            var btn = $('#btnSubmitForm');
            $('html, body').animate({
                scrollTop: btn.offset().top + btn.outerHeight(true) - $(window).height()
            }, 750);
        }

        //events
        postbox.subscribe('login', function (message, topic) {

            if (message.Authenticated) {
                self.viewModel.guest(false);
                self.viewModel.buyerFirstName(message.FirstName);
                self.viewModel.buyerLastName(message.LastName);
                self.viewModel.buyerEmailAddress(message.EmailAddress);

                userService.getDefaultAddress(function (data) {
                    self.viewModel.buyerCompanyName(data.CompanyName);
                    self.viewModel.buyerOfficeTelephone(data.PrimaryPhone);
                    self.viewModel.selectedBuyerCompanyCountry(data.DefaultShippingAddress.Country);
                    self.viewModel.buyerCompanyAddress(data.DefaultShippingAddress.AddressLine1);
                    self.viewModel.buyerCompanyCity(data.DefaultShippingAddress.City);
                    self.viewModel.buyerCompanyPostalCode(data.DefaultShippingAddress.PostalCode);

                    setTimeout(function () {
                        // Delay setting the state so changes due to country being updated (ie new state list built) are propagated
                        self.viewModel.selectedBuyerCompanyState(data.DefaultShippingAddress.State);
                    }, 200);
                });
            }

        });
    });
}(jQuery, GK.Global, GK.LocaleService, GK.TranslationService, GK.UserService, GK.QuoteService, GK.NotificationService));;
//site namespace
var GK = GK || {};



GK.JobGrant = (function () {
    function init(model) {
        
        var controller = new JobGrantController(model);
        ko.applyBindings(controller, document.getElementById('jobGrantForm'));
        
    }

    //return public functions
    return { init: init };

})();

//view model
var JobGrantViewModel = (function (translationService) {
    return (function (data) {

        var required = {
            required: {
                message: translationService.getTranslation('This field is required'),
                params: true
            }
        };

        //init model properties
        var self = this;
        self.fnSubmit = data.fnSubmit;

        self.states = ko.observableArray(data.model.States);
        
        self.firstName = ko.observable(data.model.FirstName).extend(required);
        self.lastName = ko.observable(data.model.LastName).extend(required);
        self.jobFunction = ko.observable(data.model.JobFunction).extend(required);
        self.emailAddress = ko.observable(data.model.EmailAddress).extend({
            required: required.required,
            email: {
                message: translationService.getTranslation('Enter a valid email address'),
                params: true
            }
        });

        self.companyName = ko.observable(data.model.CompanyName).extend(required);
        self.companyAddress = ko.observable(data.model.CompanyAddress).extend(required);
        self.companyCity = ko.observable(data.model.CompanyCity).extend(required);
        self.companyPostalCode = ko.observable(data.model.CompanyPostalCode).extend(required);
        self.selectedCompanyState = ko.observable(data.model.CompanyStateCode || undefined).extend(required);
        self.officeTelephone = ko.observable(data.model.OfficeTelephone).extend(required);

        self.trainingDecisionRole = ko.observable(data.model.trainingDecisionRole);
        self.trainingTimeFrame = ko.observable(data.model.trainingTimeFrame);
        self.companyType = ko.observable(data.model.companyType);
        self.companySize = ko.observable(data.model.companySize);
        self.newToGK = ko.observable(data.model.newToGK);

        self.errors = ko.validation.group(self, { deep: true });
    });

}(GK.TranslationService));

//controller
var JobGrantController = (function () {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        //events
        self.submitForm = function (formElement) {
            
            if (viewModel.errors().length === 0) {
                //formElement.submit();
                self.viewModel.fnSubmit();
            }
            else {
                viewModel.errors.showAllMessages();
            }
        }
    });
}());;
//site namespace
var GK = GK || {};

GK.CertPrepLanding = (function (window, undefined) {

    function init() {

        $('select.category-options').on('change.CertPrepLanding', function() {
            var $this = $(this);
            if ($this.val() && $this.val().length > 0) {
                window.location = $this.val();
            }
        });
    }

    //return public functions
    return { init: init };

})(window);;
//site namespace
var GK = GK || {};

GK.SpecialReport = (function () {
    function init(model) {
        var controller = new SpecialReportController(model);
        ko.applyBindings(controller, document.getElementById('special-report'));
    }

    //return public functions
    return { init: init };

})();

//view model
var SpecialReportViewModel = (function() {
    return (function (data) {

        //init model properties
        var self = this;
        self.canDownload = ko.observable(false);
        self.hasFile = ko.observable(data.hasFile);
        self.isExpired = ko.observable(data.isExpired);
        self.loaded = ko.observable(false);
    });

}());

//controller
var SpecialReportController = (function($, window, userService) {
    return (function (viewModel) {

        var self = this;
        self.viewModel = viewModel;

        if (self.viewModel.isExpired()) {
            self.viewModel.canDownload(true);
            self.viewModel.loaded(true);
        } else {
            //events
            $(window).load(function() {
                userService.getCurrentUser().done(function(data) {

                    self.viewModel.canDownload(data && data.Authenticated);
                    self.viewModel.loaded(true);

                });
            });
        }
    });
}(jQuery, window, GK.UserService));;
//site namespace
var GK = GK || {};

//auto executes
GK.CustomPhoneNumber = (function ($, global, translationService, window, undefined) {

    var check = function () {
        var paramKey = 'pn';
        var val = global.getUrlParameter(paramKey);
        if (val) {
            $.cookie(paramKey + "-cookie", val, { path: '/' });
        } else {
            val = $.cookie(paramKey + "-cookie");
        }
        if (val) {
            replace(val);
        }
    }

    var replace = function (key) {
        var phone = translationService.getCustomPhoneNumber(key);
        if (phone) {
            $('a.contact-phone').attr('href', "tel:+" + phone).text(phone);
        }
    }


    function init() {
        window.setTimeout(function () {
            check();
        }, 1);
    }

    $(init);//auto init

})(jQuery, GK.Global, GK.TranslationService, window);;
//site namespace
var GK = GK || {};

GK.Roadblock = (function (window, document) {
    
    function init(model) {
        var viewModel = new RoadblockViewModel(model);
        var controller = new RoadblockController(viewModel);

        ko.applyBindings(controller, document.getElementById('roadblock-modal'));
    };

    //return public functions
    return {
        init: init
    };
})(window, document);

var RoadblockViewModel = (function () {
    return (function(data) {
        var self = this;
        self.allowedToShow = ko.observable(data.show || false);
        self.show = ko.observable(false);
    });
}());


var RoadblockController = (function ($, tagManagerService, window) {
    return (function(viewModel) {
        var self = this;
        var _cookieName = 'gkroadblock';
        self.viewModel = viewModel;

        self.hideRoadblock = function () {
            self.viewModel.show(false);
        };

        var createCookie = function() {
            $.cookie(_cookieName, true, { expires: 1, path: '/' });
        }

        var cookieVal = $.cookie(_cookieName);
        if (!self.viewModel.allowedToShow()) {
            //--They landed on a page that does not allow the roadblock. Prevent them from seeing it on the next page they hit
            if (!cookieVal) {
                createCookie();
            }

            return;
        }
        
        if (cookieVal) {
            //--Has seen the roadblock, so don't show it again
            return;
        }

        self.viewModel.show(true);
        createCookie();
        tagManagerService.roadblockShown();
    });
})(jQuery, GK.TagManagerService, window);;
//site namespace
var GK = GK || {};


//-----------------
// Pardot Interests Form
//-----------------
GK.PardotInterestsForm = (function (window, undefined) {
    function init() {
        var controller = new PardotInterestsFormController();
        ko.applyBindings(controller, document.getElementById('interestForm'));
    }

    //return public functions
    return { init: init };

})(window);

//controller
var PardotInterestsFormController = (function ($, commonService, translationService) {
    return (function () {
        var self = this;

        var buildViewModel = function(){
            var required = {
                required: {
                    message: translationService.getTranslation('This field is required'),
                    params: true
                }
            };

            //init model properties
            var model = {};
            model.email = ko.observable("").extend({
                required: required.required,
                email: {
                    message: translationService.getTranslation('Enter a valid email address'),
                    params: true
                }
            });

            model.errors = ko.validation.group(model, { deep: true });
            return model;
        }

        self.viewModel = buildViewModel();

        //events
        self.submitForm = function (formElement) {
            if (self.viewModel.errors().length === 0) {
                formElement.submit();
            }
            else {
                self.viewModel.errors.showAllMessages();
            }
        }

    });
}(jQuery, GK.Global, GK.TranslationService));;
//site namespace
var GK = GK || {};


//-----------------
// Home Specific 
//-----------------
GK.Home = (function (recentCoursesService, undefined) {
    function init() {
        recentlyViewedCourses();
        ariaControls();
        focusSearch();
    }

    function recentlyViewedCourses() {
        var recentCourses = recentCoursesService.getRecentCourses();
       
        if (recentCourses != null && recentCourses.length > 0) {
            $('#recentlyViewedList').addClass('showthis');
            $('#recentlyViewedList').removeClass('hidethis');
            $('#newUserCourseList').addClass('hidden');
            $('#existingUserCourseList').removeClass('hidden');

          
            var htmlInsert = ''; // Set up an empty variable for pushing strings to
            for (var i = 0; i < recentCourses.length; i++) { // For each of the recently viewed courses ...
                htmlInsert += '<li><a class="recently-viewed-courses" id="recently-viewed' + (i+1) + '" href="' + recentCourses[i].path + '">' + recentCourses[i].name + '</a></li>'; // Push the link into the full html string for insertion
            }
            $('#recentlyViewedList ul').html(htmlInsert);
        } else {
            $('#recentlyViewedList').addClass('hidethis');
            $('#recentlyViewedList').removeClass('showthis');
            $('#newUserCourseList').removeClass('hidden');
            $('#existingUserCourseList').addClass('hidden');
        }
    }

    function ariaControls() {
        
        $('.tab.img-wrapper').on('click', function(){
            
            var selectedId = $(this).attr('id');

            if ($(this).attr('aria-expanded') == "true") {
                $(this).attr('aria-expanded', 'false');
                $('[aria-labelledby="' + selectedId + '"]').attr('aria-hidden', 'true');
            } else {
                $(this).attr('aria-expanded', 'true');
                $('[aria-labelledby="' + selectedId + '"]').attr('aria-hidden', 'false');
            }
        });    

    }

    function focusSearch() {
        window.setTimeout(function () { $('#home-search-field').focus(false); }, 1);
    }


    //return public functions
    return { init: init };

})(GK.RecentCoursesService);
;
//site namespace
var GK = GK || {};


//-----------------
// Search Suggestions
//-----------------
GK.SearchSuggestions = (function ($, searchService, translationService, recentCoursesService, undefined) {
    //auto complete
    var containerClass = '.search-field';
    var defaultSuggestions = {};
    function initAutocomplete() {
        defaultSuggestions = getInitSuggestions();
        $('#home-search-field,#nav-search-field,#sticky-search-field,#search-field').each(function (ind, ele) {
            (function (el) { autocomplete(el) })(ele);
        })
    }

    function autocomplete(inp) {
        var currentFocus;
        var dfFilter = $(inp).parents('form').find('.nav-search-select');
        var listLimit = inp.getAttribute("data-suggestion-limit") || "5";
        var currentSuggestions = defaultSuggestions;

        var triggerSuggestions = function () {
            var val = inp.value;
            if (GK.isMobileDevice) {
                closeAllLists();
                return false;
            }
            if (!val) {//revert to default
                currentSuggestions = defaultSuggestions;
                updateSuggestions(inp, '', listLimit, currentSuggestions);
                return true;
            }
            //get results from api
            var pageType = dfFilter.val() || '';
            searchService.suggestions(val, pageType, function (result) { updateSuggestions(inp, val, listLimit, result); });
        };

        inp.addEventListener("input", function (e) {
            triggerSuggestions();
        });

        dfFilter.change(function () {
            triggerSuggestions();
        });

        function updateSuggestions(inp, val, limit, suggestions) {
            closeAllLists();
            currentFocus = -1;
            currentSuggestions = [];
            if (suggestions && suggestions.Results && suggestions.Results.length > 0) {
                currentSuggestions = suggestions;
                var results = suggestions.Results;
                //create a DIV element that will contain the items (values):
                var a = document.createElement("DIV");
                a.setAttribute("id", inp.id + "autocomplete-list");
                a.setAttribute("class", "autocomplete-list");

                // append the DIV element as a child of the autocomplete container:
                //put it outside of the form
                if (inp.getAttribute('id') === 'nav-search-field' || inp.getAttribute('id') === 'sticky-search-field') {
                    inp.parentNode.parentNode.parentNode.parentNode.appendChild(a);
                } else {
                    inp.parentNode.parentNode.appendChild(a);
                }

                var currentGroup = "";
                //add items
                for (var i = 0; i < results.length; i++) {
                    if (i >= limit) { break; }
                    if (currentGroup != results[i].Group) {
                        currentGroup = results[i].Group;
                        var g = document.createElement("DIV");
                        g.setAttribute("class", "autocomplete-group");
                        g.innerHTML = translationService.getTranslation(currentGroup);
                        a.appendChild(g);
                    }
                    var b = document.createElement("DIV");
                    b.setAttribute("class", "autocomplete-item");

                    //bold keywords
                    var linkTitle = highlightTerm(val, results[i].PageTitle);
                    var linkTerm = document.createElement("A");
                    linkTerm.setAttribute("href", results[i].Url);
                    linkTerm.innerHTML = linkTitle;
                    b.appendChild(linkTerm)

                    if (results[i].viewed) {//allow recently viewed to be to be removed individually
                        var linkRemove = document.createElement("A");
                        linkRemove.innerHTML = 'X';
                        linkRemove.setAttribute("class", "autocomplete-remove");
                        linkRemove.onclick = (function (path) {
                            return function (evt) {
                                //remove, set new default, and update list
                                recentCoursesService.removeRecentCourse(path);
                                defaultSuggestions = getInitSuggestions();
                                triggerSuggestions();
                                evt.preventDefault();
                                return false;
                            }
                        })(results[i].Url);
                        b.appendChild(linkRemove);
                    }
                    a.appendChild(b);
                }

                //build view all link to submit search
                var all = document.createElement("DIV");
                all.setAttribute("class", "autocomplete-all autocomplete-item");
                var allA = document.createElement("A");
                allA.innerText = translationService.getTranslation("View All Search Results");
                allA.setAttribute("href", "#");
                allA.onclick = function () {
                    $(this).closest(containerClass).find("form").submit();
                    return false;
                }
                all.appendChild(allA);
                a.appendChild(all);
            }
        }

        inp.addEventListener("focus", function (e) {
            triggerSuggestions();
        });
        inp.addEventListener("click", function (e) {
            triggerSuggestions();
        });

        //keyboard commands
        inp.addEventListener("keydown", function (e) {
            var list = document.getElementById(this.id + "autocomplete-list");
            if (!list) { return; }
            var items = list.getElementsByClassName("autocomplete-item");
            if (e.keyCode == 40) {//down
                currentFocus++;
                addActive(items);
            } else if (e.keyCode == 38) { //up
                currentFocus--;
                addActive(items);
            } else if (e.keyCode == 13) {//enter
                if (currentFocus > -1) {
                    e.preventDefault();
                    /*and simulate a click on the "active" item:*/
                    if (items[currentFocus]) {
                        var z = items[currentFocus].getElementsByTagName("a");
                        if (z && z[0]) {
                            z[0].click();
                        }
                    }
                }
            }
        });

        $(document).on('click', function (event) {
            if (!$(event.target).closest(containerClass).length
                && event.target.getAttribute("class") != 'autocomplete-remove') {
                closeAllLists();
            }
        });

        function addActive(x) {
            /*a function to classify an item as "active":*/
            if (!x) return false;
            /*start by removing the "active" class on all items:*/
            removeActive(x);
            if (currentFocus >= x.length) currentFocus = -1;
            if (currentFocus < -1) currentFocus = (x.length - 1);
            /*add class "autocomplete-active":*/
            if (currentFocus > -1) {
                x[currentFocus].classList.add("autocomplete-active");
            }
        }
        function removeActive(x) {
            /*a function to remove the "active" class from all autocomplete items:*/
            for (var i = 0; i < x.length; i++) {
                x[i].classList.remove("autocomplete-active");
            }
        }
        function closeAllLists(elmnt) {
            /*close all autocomplete lists in the document,
            except the one passed as an argument:*/
            var x = document.getElementsByClassName("autocomplete-list");
            for (var i = 0; i < x.length; i++) {
                if (elmnt != x[i] && elmnt != inp) {
                    x[i].parentNode.removeChild(x[i]);
                }
            }
        }

        function highlightTerm(term, title) {
            if (!term) return title;
            var linkTitle = title;
            var split = term.split(' ');
            var combined = "";
            for (var s = 0; s < split.length; s++) {
                var v = split[s];
                if (v) {
                    v = v.replace(/[^a-zA-Z0-9]/, '');
                }
                if (v) {
                    if (combined) { combined += "|"; }
                    combined += v;
                }
            }

            linkTitle = linkTitle.replace(new RegExp(combined, 'gi'), function (x) {
                return '<strong>' + x + '</strong>';
            });
            return linkTitle;
        }
    }

    //recommendations
    function getInitSuggestions() {

        var suggestions = [];
        //up to 3 recent and fill the rest in with suggestions

        var recentCourses = recentCoursesService.getRecentCoursesExtend();
        if (recentCourses != null && recentCourses.length > 0) {
            for (var i = 0; i < recentCourses.length; i++) {
                //format records in search suggestion format
                suggestions.push({ "PageTitle": recentCourses[i].name, "Url": recentCourses[i].path, "viewed": recentCourses[i].viewed, "Group": "", "Id": i });
                if (suggestions.length == 3) break;
            }
        }

        var recommendations = translationService.getTreadingCourses();
        if (recommendations && recommendations.length) {
            for (var i = 0; i < recommendations.length; i++) {
                //format records in search suggestion format
                suggestions.push({ "PageTitle": recommendations[i].name, "Url": recommendations[i].path, "Group": "Suggestions", "Id": i + 10 });
                if (suggestions.length == 5) break;
            }
        }


        return { 'Results': suggestions };
    }

    function onSearchSelect() {
        
        $('.nav-search-select').change(function () {
            var label = $(this).parents('form').find('.select-label');
            label.empty();
            var labelVal = '<span class="nav-select-label">' + $(this).children('option:selected').text() + '&nbsp;&nbsp;<i aria-hidden="true" class="fa fa-caret-down"></i>';
            label.append(labelVal);
        });

    }

    //auto init
    $(function () {
        onSearchSelect();
        initAutocomplete();
    });

})(jQuery, GK.SearchService, GK.TranslationService, GK.RecentCoursesService);
;
//site namespace
var GK = GK || {};

//auto executes
GK.RlSocialSharing = (function () {
	function windowPopup(url, width, height) {
		// Calculate the position of the popup so
		// it’s centered on the screen.
		var left = (screen.width / 2) - (width / 2),
			top = (screen.height / 2) - (height / 2);

		window.open(
			url,
			"",
			"menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=" + width + ",height=" + height + ",top=" + top + ",left=" + left
		);
	}

	var init = function () {
		$(".gksocialshare").on("click", function (e) {

			e.preventDefault();
			windowPopup($(this).attr("href"), 700, 500);
		});
	}
	$(function () {
		init();
	});
})();
;
//site namespace
var GK = GK || {};


GK.ResourceLibrary = (function (global, window) {
    //uses attr 'data-filter' to hold base64 urls to prevent mass crawling
	function enableFilterHrefs() {
        $('a[data-filter]').on('click', function (e) {
            e.preventDefault();
            var href = $(this).attr('data-filter');
            if (href) {
                window.location.replace(atob(href));
            }
        });
	}

	var init = function () {
        enableFilterHrefs();
        global.displayResultFilterTags(['#filter-bar'], function (o, a) { window.location.replace(atob(a.attr("data-filter"))); });
	}

    //return public functions
    return { init: init };
}) (GK.Global, window);
;
