<commit_before>/**
 * Garp Frontend library Core functions
 * @author <NAME> | Grrr
 * @author <NAME> | Grrr
 * @package Garp
 */
Garp = typeof Garp == 'undefined' ? { } : Garp;
BASE = typeof BASE == 'undefined' ? '/' : BASE;

// http://jdbartlett.github.com/innershiv | WTFPL License
Garp.innerShiv = (function() {
	var d, r;
	return function(h, u) {
		if (!d) {
			d = document.createElement('div');
			r = document.createDocumentFragment();
			/*@cc_on d.style.display = 'none';@*/
		}
		
		var e = d.cloneNode(true);
		/*@cc_on document.body.appendChild(e);@*/
		e.innerHTML = h.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
		/*@cc_on document.body.removeChild(e);@*/
		
		if (u === false) {
			return e.childNodes;
		}
		
		var f = r.cloneNode(true), i = e.childNodes.length;
		while (i--) {
			f.appendChild(e.firstChild);
		}
		
		return f;
	};
}());

/*!
 * selectivizr v1.0.2 - (c) <NAME>, freely distributable under the terms of the MIT license.
 * selectivizr.com
 */
//(function(j){function A(a){return a.replace(B,h).replace(C,function(a,d,b){for(var a=b.split(","),b=0,e=a.length;b<e;b++){var s=D(a[b].replace(E,h).replace(F,h))+o,l=[];a[b]=s.replace(G,function(a,b,c,d,e){if(b){if(l.length>0){var a=l,f,e=s.substring(0,e).replace(H,i);if(e==i||e.charAt(e.length-1)==o)e+="*";try{f=t(e)}catch(k){}if(f){e=0;for(c=f.length;e<c;e++){for(var d=f[e],h=d.className,j=0,m=a.length;j<m;j++){var g=a[j];if(!RegExp("(^|\\s)"+g.className+"(\\s|$)").test(d.className)&&g.b&&(g.b===!0||g.b(d)===!0))h=u(h,g.className,!0)}d.className=h}}l=[]}return b}else{if(b=c?I(c):!v||v.test(d)?{className:w(d),b:!0}:null)return l.push(b),"."+b.className;return a}})}return d+a.join(",")})}function I(a){var c=!0,d=w(a.slice(1)),b=a.substring(0,5)==":not(",e,f;b&&(a=a.slice(5,-1));var l=a.indexOf("(");l>-1&&(a=a.substring(0,l));if(a.charAt(0)==":")switch(a.slice(1)){case "root":c=function(a){return b?a!=p:a==p};break;case "target":if(m==8){c=function(a){function c(){var d=location.hash,e=d.slice(1);return b?d==i||a.id!=e:d!=i&&a.id==e}k(j,"hashchange",function(){g(a,d,c())});return c()};break}return!1;case "checked":c=function(a){J.test(a.type)&&k(a,"propertychange",function(){event.propertyName=="checked"&&g(a,d,a.checked!==b)});return a.checked!==b};break;case "disabled":b=!b;case "enabled":c=function(c){if(K.test(c.tagName))return k(c,"propertychange",function(){event.propertyName=="$disabled"&&g(c,d,c.a===b)}),q.push(c),c.a=c.disabled,c.disabled===b;return a==":enabled"?b:!b};break;case "focus":e="focus",f="blur";case "hover":e||(e="mouseenter",f="mouseleave");c=function(a){k(a,b?f:e,function(){g(a,d,!0)});k(a,b?e:f,function(){g(a,d,!1)});return b};break;default:if(!L.test(a))return!1}return{className:d,b:c}}function w(a){return M+"-"+(m==6&&N?O++:a.replace(P,function(a){return a.charCodeAt(0)}))}function D(a){return a.replace(x,h).replace(Q,o)}function g(a,c,d){var b=a.className,c=u(b,c,d);if(c!=b)a.className=c,a.parentNode.className+=i}function u(a,c,d){var b=RegExp("(^|\\s)"+c+"(\\s|$)"),e=b.test(a);return d?e?a:a+o+c:e?a.replace(b,h).replace(x,h):a}function k(a,c,d){a.attachEvent("on"+c,d)}function r(a,c){if(/^https?:\/\//i.test(a))return c.substring(0,c.indexOf("/",8))==a.substring(0,a.indexOf("/",8))?a:null;if(a.charAt(0)=="/")return c.substring(0,c.indexOf("/",8))+a;var d=c.split(/[?#]/)[0];a.charAt(0)!="?"&&d.charAt(d.length-1)!="/"&&(d=d.substring(0,d.lastIndexOf("/")+1));return d+a}function y(a){if(a)return n.open("GET",a,!1),n.send(),(n.status==200?n.responseText:i).replace(R,i).replace(S,function(c,d,b,e,f){return y(r(b||f,a))}).replace(T,function(c,d,b){d=d||i;return" url("+d+r(b,a)+d+") "});return i}function U(){var a,c;a=f.getElementsByTagName("BASE");for(var d=a.length>0?a[0].href:f.location.href,b=0;b<f.styleSheets.length;b++)if(c=f.styleSheets[b],c.href!=i&&(a=r(c.href,d)))c.cssText=A(y(a));q.length>0&&setInterval(function(){for(var a=0,c=q.length;a<c;a++){var b=q[a];if(b.disabled!==b.a)b.disabled?(b.disabled=!1,b.a=!0,b.disabled=!0):b.a=b.disabled}},250)}if(!/*@cc_on!@*/true){var f=document,p=f.documentElement,n=function(){if(j.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(a){return null}}(),m=/MSIE (\d+)/.exec(navigator.userAgent)[1];if(!(f.compatMode!="CSS1Compat"||m<6||m>8||!n)){var z={NW:"*.Dom.select",MooTools:"$$",DOMAssistant:"*.$",Prototype:"$$",YAHOO:"*.util.Selector.query",Sizzle:"*",jQuery:"*",dojo:"*.query"},t,q=[],O=0,N=!0,M="slvzr",R=/(\/\*[^*]*\*+([^\/][^*]*\*+)*\/)\s*/g,S=/@import\s*(?:(?:(?:url\(\s*(['"]?)(.*)\1)\s*\))|(?:(['"])(.*)\3))[^;]*;/g,T=/\burl\(\s*(["']?)(?!data:)([^"')]+)\1\s*\)/g,L=/^:(empty|(first|last|only|nth(-last)?)-(child|of-type))$/,B=/:(:first-(?:line|letter))/g,C=/(^|})\s*([^\{]*?[\[:][^{]+)/g,G=/([ +~>])|(:[a-z-]+(?:\(.*?\)+)?)|(\[.*?\])/g,H=/(:not\()?:(hover|enabled|disabled|focus|checked|target|active|visited|first-line|first-letter)\)?/g,P=/[^\w-]/g,K=/^(INPUT|SELECT|TEXTAREA|BUTTON)$/,J=/^(checkbox|radio)$/,v=m>6?/[\$\^*]=(['"])\1/:null,E=/([(\[+~])\s+/g,F=/\s+([)\]+~])/g,Q=/\s+/g,x=/^\s*((?:[\S\s]*\S)?)\s*$/,i="",o=" ",h="$1";(function(a,c){function d(){try{p.doScroll("left")}catch(a){setTimeout(d,50);return}b("poll")}function b(d){if(!(d.type=="readystatechange"&&f.readyState!="complete")&&((d.type=="load"?a:f).detachEvent("on"+d.type,b,!1),!e&&(e=!0)))c.call(a,d.type||d)}var e=!1,g=!0;if(f.readyState=="complete")c.call(a,i);else{if(f.createEventObject&&p.doScroll){try{g=!a.frameElement}catch(h){}g&&d()}k(f,"readystatechange",b);k(a,"load",b)}})(j,function(){for(var a in z){var c,d,b=j;if(j[a]){for(c=z[a].replace("*",a).split(".");(d=c.shift())&&(b=b[d]););if(typeof b=="function"){t=b;U();break}}}})}}})(this);

(function(document){
	
	window.MBP = window.MBP || {}; 
	var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o;
	/*! A fix for the iOS orientationchange zoom bug. Script by @scottjehl, rebound by @wilto.MIT / GPLv2 License.*/
	/*  JSLINT fix by Peter */
	(function(a){
		function m(){
			d.setAttribute("content", g); h = !0;
		}
		function n(){
			d.setAttribute("content", f); h = !1;
		}
		function o(b){
			l = b.accelerationIncludingGravity; i = Math.abs(l.x); j = Math.abs(l.y); k = Math.abs(l.z); 
			((!a.orientation || a.orientation === 180) && (i > 7 || (k > 6 && j < 8 || k < 8 && j > 6) && i > 5) ? h && n() : h || m());
		}
		var b = navigator.userAgent;
		if ((!(/iPhone|iPad|iPod/.test(navigator.platform) && (/OS [1-5]_[0-9_]* like Mac OS X/i.test(b)) && b.indexOf("AppleWebKit") > -1))) {
			return;
		}
		var c = a.document;
		if (!c.querySelector) {
			return;
		}
		var d = c.querySelector("meta[name=viewport]"), e = d && d.getAttribute("content"), f = e + ",maximum-scale=1", g = e + ",maximum-scale=10", h = !0, i, j, k, l;
		if (!d) {
			return;
		}
		a.addEventListener("orientationchange", m, !1);
		a.addEventListener("devicemotion", o, !1);
	})(this);

	// Autogrow
	// http://googlecode.blogspot.com/2009/07/gmail-for-mobile-html5-series.html
	MBP.autogrow = function (element, lh) {
 	 
    	function handler(e){
        	var newHeight = this.scrollHeight,
            	currentHeight = this.clientHeight;
        	if (newHeight > currentHeight) {
            	this.style.height = newHeight + 3 * textLineHeight + "px";
        	}
    	}
 	 
    	var setLineHeight = (lh) ? lh : 12,
        	textLineHeight = element.currentStyle ? element.currentStyle.lineHeight :
                         	 getComputedStyle(element, null).lineHeight;
 	 
    	textLineHeight = (textLineHeight.indexOf("px") == -1) ? setLineHeight :
                     	 parseInt(textLineHeight, 10);
 	 
    	element.style.overflow = "hidden";
		if(element.addEventListener){
			element.addEventListener('keyup', handler, false);
		} else {
			element.attachEvent('onkeyup', handler);
		}
	};	
})(document);

/**
 * Utility function. Binds receiverObj's properties to senderObj's properties 
 * @param {Object} receiverObj
 * @param {Object} senderObj
 * @return {Object} receiverObj
 */
Garp.apply = function(receiverObj, senderObj){
	for (var i in senderObj) {
		receiverObj[i] = senderObj[i];
	}
	return receiverObj;
};

/**
 * Utility function. Binds receiverObj's properties to senderObj's properties if not already present
 * @param {Object} receiverObj
 * @param {Object} senderObj
 * @return {Object} receiverObj
 */
Garp.applyIf = function(receiverObj, senderObj){
	for (var i in senderObj) {
		if (!receiverObj[i]) {
			receiverObj[i] = senderObj[i];
		}
	}
	return receiverObj;
};

/**
 * Utility string function: use a simple tpl string to format multiple arguments
 * 
 * example:
 * var html = Garp.format('<a href="${1}">${2}</a>"', 'http://www.grrr.nl/', 'Grrr Homepage');
 * 
 * @param {String} tpl  template
 * @param {String} ...n input string(s)
 * @return {String}
 */
Garp.format = function(tpl, o){
	var res = tpl;
	if (typeof o == 'object') {
		Garp.each(o, function(v, k){
			k = k.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&');
			res = res.replace(new RegExp('\\${' + k + '\\}', "g"),v);
		});
	} else {
		for (var i = 1, l = arguments.length; i < l; i++) {
			res = res.replace(new RegExp("\\${" + i + "\\}", "g"), arguments[i]);
		}
	}
	return res;
};

/**
 * Utility function each
 * Calls fn for each ownProperty of obj. fn(property, iterator, obj)
 * 
 * @param {Object} obj to iterate
 * @param {Function} fn to call with each property of obj, the property name and the object from within 'scope'
 * @param {Object} [scope] to execute within
 */
Garp.each = function(obj, fn, scope){
	for(var i in obj){
		if (obj.hasOwnProperty(i)) {
			if (scope) {
				fn.call(scope, obj[i], i, obj);
			} else {
				fn(obj[i], i, obj);
			}
		}
	}
};

/**
 * Creates a Delegate function
 * @param {Function} function
 * @param {Object} scope
 */
Garp.createDelegate = function(fn, scope){
	return function(){
		fn.apply(scope, arguments);
	};
};

/**
 * Parse a query string
 * @return {Object} query params as key/value pairs
 */
Garp.parseQueryString = function(str, decode) {
    str = str || window.location.search;
	if (decode) {
		str = unescape(str);
	}
    var objURL = {};
    str.replace(
        new RegExp( "([^?=&]+)(=([^&]*))?", "g" ),
        function( $0, $1, $2, $3 ){
                objURL[ $1 ] = $3;
        }
    );
    return objURL;
};

/**
 * Equalize the height of several items in a collection.
 * @param {jQuery} collection of items
 * @return {Void}
 */
Garp.equalizeHeight = function(collection) {
	var h = 0;
	collection.each(function() {
		h = Math.max(h, $(this).height());
	});
	collection.height(h);
};

/**
 * Retrieves an object housing in an array by looking for one of its keys and possibly it's value
 * @param {Array} array of objects
 * @param {String} key
 * @param {String} val [optional]
 * 
 * @return {Array | Object} 
 */
Garp.getBy = function(arr, key, val){
	var out = [];
	for(var k in arr){
		var item = arr[k];
		if(item[key]){
			if (!val || (val && item[key] == val)) {
				out.push(item);
			}
		}
	}
	return out.length == 1 ? out[0] : out;
};

/**
 * Garp Flash Messenger. Provides a method to show a 'popup' style message to the user.
 * Convenient for auto-fade out and such.
 * 
 * @param {Object} Config properties. See below for @cfg details:
 * 
 * @example usage: 
 *	var fm = new Garp.FlashMessage({
 *		msg: "Your Message might go here, or you can use... ",
 *		parseCookie: true // ... this option to grab the message from the server
 *	});
 * 
 */
Garp.FlashMessage = function(cfg){

	// Apply defaults:
	Garp.apply(this, {
	
		/**
		 * @cfg: {String} Message to show
		 */
		msg: '',
		
		/**
		 * @cfg: {Boolean} Whether or not too parse (and show) the Garp.FlashMessage cookie
		 */
		parseCookie: false,
		
		/**
		 * @cfg: {String} Cookie name
		 */
		cookieName: 'FlashMessenger',
		
		/**
		 * @cfg {Function} Callback, get's called with scope/this set to FlashMessage  
		 */
		afterShow: function(){
		},
		
		/**
		 * @cfg {Function} Callback, get's called with scope/this set to FlashMessage
		 */
		afterClose: function(){
		},
		
		/**
		 * @cfg {jQuery element}: Provide the element to use
		 */
		elm: (function(){
			var id = 'flashMessage';
			return $('#' + id).length ? $('#' + id) : $('body').append('<div id="' + id + '"></div>').find('#' + id);
		})(this),
		
		/**
		 * @cfg {Boolean} Automatically hide?
		 */
		autoHide: true,
		
		/**
		 * @cfg {Number} Delay for auto hide
		 */
		hideDelay: 6000,
		
		/**
		 * @cfg {Function} Animation to use. Override to do someting else than just a simple fade:
		 */
		hideAnimation: function(){
			if (this.elm) {
				this.elm.fadeOut('slow', Garp.createDelegate(this.afterClose, this));
			} else {
				this.afterClose(this);
			}
		}
	});
	
	// Override with given config:
	Garp.apply(this, cfg);
	
	// Private functions:
	Garp.apply(this, {
	
		/**
		 * Shows (the hidden) flashMessage. Should generally not be needed.
		 */
		show: function(){
			this.elm.show();
			return this;
		},
		
		/**
		 * Hides the flashMessage element by using the possible animation
		 */
		hide: function(){
			if (this.hideAnimation) {
				this.hideAnimation();
			} else {
				this.close();
			}
			return this;
		},
		
		/**
		 * Closes the flashMessage immediately
		 */
		close: function(){
			if (this.elm) {
				this.elm.hide();
			}
			if (this.afterClose) {
				this.afterClose(this);
			}
			return this;
		},
		
		/**
		 * Parses a cookie. Nom,nom,nom.
		 * note; a global COOKIEDOMAIN constant can be set for this function.
		 */
		cookieParser: function(){
			var m = $.parseJSON(unescape(Garp.getCookie(this.cookieName))), out = '';
			if (m && m.messages) {
				for (var i in m.messages) {
					var msg = m.messages[i];
					if (msg) {
						out += '<p>' + msg.replace(/\+/g, ' ') + '</p>';
					}
				}
				var exp = new Date();
				exp.setHours(exp.getHours() - 1);
				Garp.setCookie(this.cookieName, '', exp, (typeof COOKIEDOMAIN !== 'undefined') ? COOKIEDOMAIN : '.' + document.location.host);
				return out;
			}
			return '';
		},
		
		/**
		 * Init
		 */
		init: function(){
			if (this.parseCookie) {
				this.msg += this.cookieParser();
			}
			if (!this.msg) {
				return this;
			}
			if (!this.elm.length) {
				$('body').append(this.elm);
			}
			if (this.msg) {
				this.elm.html(this.msg);
			}
			if (this.autoHide) {
				setTimeout(Garp.createDelegate(this.hide, this), this.hideDelay);
			}
			this.elm.show();
			this.afterShow(this);
			return this;
		}
	});
		
	return this.init();
};
/**
 * Grab a Cookie
 * @param {Object} name
 */
Garp.getCookie = function(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
			c = c.substring(1, c.length);
		}
        if (c.indexOf(nameEQ) === 0) {
			return c.substring(nameEQ.length, c.length);
		}
    }
    return null;
};

/**
 * Give a Cookie
 * @param {Object} name
 * @param {Object} value
 * @param {Date} expiration date 
 * @param {String} domain
 */
Garp.setCookie = function(name, value, date, domain){
	value = escape(value) + "; path=/";
	if (domain) {
		value += "; domain=" + escape(domain);
	} else {
		value += "; domain=" + escape((typeof COOKIEDOMAIN !== 'undefined') ? COOKIEDOMAIN : '.' + document.location.host);
	}
	value += (!date ? "" : "; expires=" + date.toGMTString());
	document.cookie = name + "=" + value;
};

Garp.removeCookie = function(name){
	Garp.setCookie(name,'',new Date('1900'));
};

/**
 * Snippet edit links
 */
$(function(){
	var authCookie = unescape(Garp.getCookie('Garp_Auth'));
	authCookie = jQuery.parseJSON(authCookie);
	if (authCookie && authCookie.userData && authCookie.userData.role) {
		var role = authCookie.userData.role;
		if (role && (role == 'admin' || role == 'developer')) {
			setTimeout(function(){
				var elms = [];
				(function walk(elm){
					if (elm.childNodes && elm.childNodes.length > 0) {
						for (var e in elm.childNodes) {
							walk(elm.childNodes[e]);
						}
					} else {
						elms.push(elm);
					}
				})($('body')[0]);
				for (var e in elms) {
					var elm = elms[e];
					var token = '//garp-snippet//';
					if (elm.nodeType == 8 && elm.nodeValue.indexOf(token) > -1) {
						var snippet = elm.nextSibling;
						var url = BASE + 'g/content/admin/?model=Snippet&id=' + elm.nodeValue.replace(token, '');
						//$(snippet).wrap('<div style="position:relative;">')
						var link = $('<a href="' + url + '" title="edit" target="garp_cms"><img src="' + BASE + 'media/images/garp/icons/pencil.png" /></a>').insertBefore(snippet).css({
							position: 'absolute',
							zIndex: '999999',
							padding: '4px',
							width: '20px',
							height: '20px',
							margin: '-14px 0 0 -14px',
							lineHeight: 0
						//border: '0'
						});
						(function(link){
							function mouseOut(){
								$(link).css({
									opacity: 0.5,
									border: '2px #fff outset',
									background: '#ddd'
								});
							}
							$(link).bind('mouseenter', function(){
								$(link).css({
									opacity: 1,
									border: '2px #fff outset',
									background: '#ddd'
								});
							}).bind('mouseleave', mouseOut);
							mouseOut();
						})(link);
					}
				}
			}, 1000);
		}
	}
});

/**
 * Is our dearest user logged in or what?
 */
Garp.isLoggedIn = function(){
	var c = Garp.getCookie('Garp_Auth');
	if(c){
		c = $.parseJSON(unescape(c));
		if(c.userData){
			return true;
		}
	}
	return false;
};

/**
 * Cookie acceptation stuff 'Cookiewet'
 */
Garp.hasCookiesAccepted = function(){
	return Garp.getCookie('Garp_Accept_Cookies') == 'Garp_Accept_Cookies';
};
Garp.acceptCookies = function(){
	var exp = new Date();
	exp.setYear(exp.getFullYear() + 1);
	Garp.setCookie('Garp_Accept_Cookies', 'Garp_Accept_Cookies', exp, false);
};

/**
 * Constrains number i between min and max. Rolls over.
 * @param {Number} i
 * @param {Number} {optional} min
 * @param {Number} max
 */
Garp.constrain = function(i){
	var max = arguments[2];
	var min = arguments[1];
	if(typeof max != 'Number'){
		min = 0;
		max = arguments[1];
	}
	if (i < min) {
		i = max;
	}
	if (i > max) {
		i = min;
	}
	return i;	
};

Garp.lazyLoader = {
	/**
	 * Register a lazyLoad callback
	 * @param {Object} cfg
	 * @cfg id {String} id of the element
	 * @cfg before {Function} beforeFetch fn. Gets called with scope &amp; fetch arguments
	 * @cfg after {Function} afterFetch fn. Gets called with scope, respone &amp; fetch arguments 
	 */
	reg: function(cfg){
		if (cfg.id && !Garp.lazyLoader[cfg.id]) {
			if (typeof cfg.before == 'function' || typeof cfg.after == 'function') {
				Garp.lazyLoader[cfg.id] = cfg;
			} else {
				throw "No callback function defined in lazyLoader config.";
			}
		} else {
			throw "Can't register lazyLoader. No id property or duplicate entry.";
		}
	},
	
	/**
	 * Deletes a lazyLoad callback
	 * @param {Object} id
	 */
	unreg: function(id){
		delete Garp.lazyLoader[id];
	},


	/**
	 * Collect all lazy-load elements and fetch!
	 */
	init: function(){
	
		var $elm = $('.lazy-load');
		if (!$elm.length) {
			return;
		}
		
		function fetch(e){
			if (e && e.preventDefault) {
				e.preventDefault();
			}
			var con = $(this).attr('data-con');
			if (typeof con == 'undefined') {
				con = '?';
			}
			var id = $(this).attr('id');
			var attributes = $(this).attr('data-attr') || '';
			if (!attributes) {
				con = '';
			}
			var before = $.noop;
			var after = $.noop;
			
			if (Garp.lazyLoader[id]) {
				if (Garp.lazyLoader[id].before) {
					before = Garp.lazyLoader[id].before;
				}
				if (Garp.lazyLoader[id].after) {
					after = Garp.lazyLoader[id].after;
				}
			}
			
			var fetchCB = Garp.createDelegate(fetch, this);
			before(this, fetchCB);
			
			var url = $(this).attr('data-href') + con + attributes;
			var scope = this;
			var isJson = $(this).hasClass('json');
			
			var replace = false;
			if ($(this).attr('data-replace-selector') && $($(this).attr('data-replace-selector')).length) {
				replace = true;
			}
			
			$.get(url, function(resp){
				if (isJson) {
					resp = resp.html;
				}
				if (replace) {
					$($(scope).attr('data-replace-selector')).replaceWith(resp);
				} else {
					$(scope).html(resp);
				}
				after.call(scope, scope, resp, fetchCB);
			}, isJson ? 'json' : 'html');
		}
		
		$elm.each(function(i, el){
			Garp.scrollHandler.register($(el), function(){
				fetch.call(el);
			});
		});
	}
};

/**
 * Loads JS files async
 * @param {String} url
 * @param {String} type ('js' or 'css') (css not implemented yet!)
 * @param {Function} callback
 * @param {Object} scope to call callback from
 */
Garp.asyncLoad = function(url, type, cb, scope){
	if (type === 'js') {
		var s = document.createElement('script');
		if (s.addEventListener) {
			s.addEventListener('load', function(){
				cb.call(scope);
			}, false);
		}
		$('head')[0].appendChild(Garp.applyIf(s, {
			onreadystatechange: function(){
				if (this.readyState == 'loaded' || this.readyState == 'complete') {
					cb.call(scope);
				}
			},
			type: 'text/javascript',
			src: url
		}));
	} else if (type === 'css'){
		// TODO add support for CSS here
	} else {
		throw 'Unsupported type for asyncLoad';
	}
};

/**
 * __ Utility function
 * @param {String} s
 * @return {String} s translated
 */ 
function __(s){ return Garp.locale[s] || s; }

// LOCALES:
Garp.locale = (typeof Garp.locale == 'undefined') ? {} : Garp.locale;

/**
 * Get computed styles
 * @param {Array}
 */
(function( $ ) {

	var getComputedStyle = document.defaultView && document.defaultView.getComputedStyle,
		// The following variables are used to convert camelcased attribute names
		// into dashed names, e.g. borderWidth to border-width
		rupper = /([A-Z])/g,
		rdashAlpha = /-([a-z])/ig,
		fcamelCase = function( all, letter ) {
			return letter.toUpperCase();
		},
		// Returns the computed style for an elementn
		getStyle = function( elem ) {
			if ( getComputedStyle ) {
				return getComputedStyle(elem, null);
			}
			else if ( elem.currentStyle ) {
				return elem.currentStyle;
			}
		},
		// Checks for float px and numeric values
		rfloat = /float/i,
		rnumpx = /^-?\d+(?:px)?$/i,
		rnum = /^-?\d/;

	// Returns a list of styles for a given element
	$.styles = function( el, styles ) {
		if (!el ) {
			return null;
		}
		var  currentS = getStyle(el),
			oldName, val, style = el.style,
			results = {},
			i = 0,
			left, rsLeft, camelCase, name;

		// Go through each style
		for (; i < styles.length; i++ ) {
			name = styles[i];
			oldName = name.replace(rdashAlpha, fcamelCase);

			if ( rfloat.test(name) ) {
				name = jQuery.support.cssFloat ? "float" : "styleFloat";
				oldName = "cssFloat";
			}

			// If we have getComputedStyle available
			if ( getComputedStyle ) {
				// convert camelcased property names to dashed name
				name = name.replace(rupper, "-$1").toLowerCase();
				// use getPropertyValue of the current style object
				val = currentS.getPropertyValue(name);
				// default opacity is 1
				if ( name === "opacity" && val === "" ) {
					val = "1";
				}
				results[oldName] = val;
			} else {
				// Without getComputedStyles
				camelCase = name.replace(rdashAlpha, fcamelCase);
				results[oldName] = currentS[name] || currentS[camelCase];

				// convert to px
				if (!rnumpx.test(results[oldName]) && rnum.test(results[oldName]) ) {
					// Remember the original values
					left = style.left;
					rsLeft = el.runtimeStyle.left;

					// Put in the new values to get a computed value out
					el.runtimeStyle.left = el.currentStyle.left;
					style.left = camelCase === "fontSize" ? "1em" : (results[oldName] || 0);
					results[oldName] = style.pixelLeft + "px";

					// Revert the changed values
					style.left = left;
					el.runtimeStyle.left = rsLeft;
				}

			}
		}

		return results;
	};

	/**
	* @function jQuery.fn.styles
	* @parent jQuery.styles
	* @plugin jQuery.styles
	*
	* Returns a set of computed styles. Pass the names of the styles you want to
	* retrieve as arguments:
	*
	*      $("div").styles('float','display')
	*      // -> { cssFloat: "left", display: "block" }
	*
	* @param {String} style pass the names of the styles to retrieve as the argument list
	* @return {Object} an object of `style` : `value` pairs
	*/
	$.fn.styles = function() {
		// Pass the arguments as an array to $.styles
		return $.styles(this[0], $.makeArray(arguments));
	};
})(jQuery);

/**
 * CSS3 animations if supoprted
 */
(function ($) {

	// Overwrites `jQuery.fn.animate` to use CSS 3 animations if possible

	var
		// The global animation counter
		animationNum = 0,
		// The stylesheet for our animations
		styleSheet = null,
		// The animation cache
		cache = [],
		// Stores the browser properties like transition end event name and prefix
		browser = null,
		// Store the original $.fn.animate
		oldanimate = $.fn.animate,

		// Return the stylesheet, create it if it doesn't exists
		getStyleSheet = function () {
			if(!styleSheet) {
				var style = document.createElement('style');
				style.setAttribute("type", "text/css");
				style.setAttribute("media", "screen");

				document.getElementsByTagName('head')[0].appendChild(style);
				if (!window.createPopup) { /* For Safari */
					style.appendChild(document.createTextNode(''));
				}

				styleSheet = style.sheet;
			}

			return styleSheet;
		},

		//removes an animation rule from a sheet
		removeAnimation = function (sheet, name) {
			for (var j = sheet.cssRules.length - 1; j >= 0; j--) {
				var rule = sheet.cssRules[j];
				// 7 means the keyframe rule
				if (rule.type === 7 && rule.name == name) {
					sheet.deleteRule(j);
					return;
				}
			}
		},

		// Returns whether the animation should be passed to the original $.fn.animate.
		passThrough = function (props, ops) {
			var nonElement = !(this[0] && this[0].nodeType),
				isInline = !nonElement && $(this).css("display") === "inline" && $(this).css("float") === "none";

			for (var name in props) {
				// jQuery does something with these values
				if (props[name] == 'show' || props[name] == 'hide' || props[name] == 'toggle' || $.isArray(props[name]) || props[name] < 0 || name == 'zIndex' || name == 'z-index') {
					return true;
				}
			}

			return props.jquery === true || getBrowser() === null ||
				// Animating empty properties
				$.isEmptyObject(props) ||
				// We can't do custom easing
				ops.length == 4 || typeof ops[2] == 'string' ||
				// Second parameter is an object - we can only handle primitives
				$.isPlainObject(ops) ||
				// Inline and non elements
				isInline || nonElement;
		},

		// Gets a CSS number (with px added as the default unit if the value is a number)
		cssValue = function(origName, value) {
			if (typeof value === "number" && !$.cssNumber[ origName ]) {
				value += "px";
			}
			return value;
		},

		// Feature detection borrowed by http://modernizr.com/
		getBrowser = function(){
			if(!browser) {
				var t,
					el = document.createElement('fakeelement'),
					transitions = {
						'transition': {
							transitionEnd : 'transitionEnd',
							prefix : ''
						},
//						'OTransition': {
//							transitionEnd : 'oTransitionEnd',
//							prefix : '-o-'
//						},
//						'MSTransition': {
//							transitionEnd : 'msTransitionEnd',
//							prefix : '-ms-'
//						},
						'MozTransition': {
							transitionEnd : 'animationend',
							prefix : '-moz-'
						},
						'WebkitTransition': {
							transitionEnd : 'webkitAnimationEnd',
							prefix : '-webkit-'
						}
					};

				for(t in transitions){
					if( el.style[t] !== undefined ){
						browser = transitions[t];
					}
				}
			}
			return browser;
		},

		// Properties that Firefox can't animate if set to 'auto':
		// https://bugzilla.mozilla.org/show_bug.cgi?id=571344
		// Provides a converter that returns the actual value
		ffProps = {
			top : function(el) {
				return el.position().top;
			},
			left : function(el) {
				return el.position().left;
			},
			width : function(el) {
				return el.width();
			},
			height : function(el) {
				return el.height();
			},
			fontSize : function(el) {
				return '1em';
			}
		},

		// Add browser specific prefix
		addPrefix = function(properties) {
			var result = {};
			$.each(properties, function(name, value) {
				result[getBrowser().prefix + name] = value;
			});
			return result;
		},

		// Returns the animation name for a given style. It either uses a cached
		// version or adds it to the stylesheet, removing the oldest style if the
		// cache has reached a certain size.
		getAnimation = function(style) {
			var sheet, name, last;

			// Look up the cached style, set it to that name and reset age if found
			// increment the age for any other animation
			$.each(cache, function(i, animation) {
				if(style === animation.style) {
					name = animation.name;
					animation.age = 0;
				} else {
					animation.age += 1;
				}
			});

			if(!name) { // Add a new style
				sheet = getStyleSheet();
				name = "jquerypp_animation_" + (animationNum++);
				// get the last sheet and insert this rule into it
				sheet.insertRule("@" + getBrowser().prefix + "keyframes " + name + ' ' + style,
					(sheet.cssRules && sheet.cssRules.length) || 0);
				cache.push({
					name : name,
					style : style,
					age : 0
				});

				// Sort the cache by age
				cache.sort(function(first, second) {
					return first.age - second.age;
				});

				// Remove the last (oldest) item from the cache if it has more than 20 items
				if(cache.length > 20) {
					last = cache.pop();
					removeAnimation(sheet, last.name);
				}
			}

			return name;
		};

	/**
	* @function $.fn.animate
	* @parent $.animate
	*
	* Animate CSS properties using native CSS animations, if possible.
	* Uses the original [$.fn.animate()](http://api.$.com/animate/) otherwise.
	*
	* @param {Object} props The CSS properties to animate
	* @param {Integer|String|Object} [speed=400] The animation duration in ms.
	* Will use $.fn.animate if a string or object is passed
	* @param {Function} [callback] A callback to execute once the animation is complete
	* @return {jQuery} The jQuery element
	*/
	$.fn.animate = function (props, speed, easing, callback) {
		//default to normal animations if browser doesn't support them
		if (passThrough.apply(this, arguments)) {
			return oldanimate.apply(this, arguments);
		}

		var optall = jQuery.speed(speed, easing, callback);

		// Add everything to the animation queue
		this.queue(optall.queue, function(done) {
			var
				//current CSS values
				current,
				// The list of properties passed
				properties = [],
				to = "",
				prop,
				self = $(this),
				duration = optall.duration,
				//the animation keyframe name
				animationName,
				// The key used to store the animation hook
				dataKey,
				//the text for the keyframe
				style = "{ from {",
				// The animation end event handler.
				// Will be called both on animation end and after calling .stop()
				animationEnd = function (currentCSS, exec) {
					self.css(currentCSS);
					
					self.css(addPrefix({
						"animation-duration" : "",
						"animation-name" : "",
						"animation-fill-mode" : "",
						"animation-play-state" : ""
					}));

					// Call the original callback
					if (optall.old && exec) {
						// Call success, pass the DOM element as the this reference
						optall.old.call(self[0], true);
					}

					$.removeData(self, dataKey, true);
				};

			for(prop in props) {
				properties.push(prop);
			}

			if(getBrowser().prefix === '-moz-') {
				// Normalize 'auto' properties in FF
				$.each(properties, function(i, prop) {
					var converter = ffProps[$.camelCase(prop)];
					if(converter && self.css(prop) == 'auto') {
						self.css(prop, converter(self));
					}
				});
			}

			// Use $.styles
			current = self.styles.apply(self, properties);
			$.each(properties, function(i, cur) {
				// Convert a camelcased property name
				var name = cur.replace(/([A-Z]|^ms)/g, "-$1" ).toLowerCase();
				style += name + " : " + cssValue(cur, current[cur]) + "; ";
				to += name + " : " + cssValue(cur, props[cur]) + "; ";
			});

			style += "} to {" + to + " }}";

			animationName = getAnimation(style);
			dataKey = animationName + '.run';

			// Add a hook which will be called when the animation stops
			$._data(this, dataKey, {
				stop : function(gotoEnd) {
					// Pause the animation
					self.css(addPrefix({
						'animation-play-state' : 'paused'
					}));
					// Unbind the animation end handler
					self.off(getBrowser().transitionEnd, animationEnd);
					if(!gotoEnd) {
						// We were told not to finish the animation
						// Call animationEnd but set the CSS to the current computed style
						animationEnd(self.styles.apply(self, properties), false);
					} else {
						// Finish animaion
						animationEnd(props, true);
					}
				}
			});

			// set this element to point to that animation
			self.css(addPrefix({
				"animation-duration" : duration + "ms",
				"animation-name" : animationName,
				"animation-fill-mode": "forwards"
			}));

			// Attach the transition end event handler to run only once
			self.one(getBrowser().transitionEnd, function() {
				// Call animationEnd using the passed properties
				animationEnd(props, true);
				done();
			});

		});

		return this;
	};
})(jQuery);

/**
 * __ Utility function
 * @param {String} s
 * @return {String} s translated
 */ 
function __(s){ return Garp.locale[s] || s; }

// LOCALES:
Garp.locale = (typeof Garp.locale == 'undefined') ? {} : Garp.locale;
Garp.apply(Garp.locale, {

	// Garp.relativeDate
	'years': 'jaren',
	'year': 'jaar',
	'months': 'maanden',
	'month': 'maand',
	'weeks': 'weken',
	'week': 'week',
	'days': 'dagen',
	'day': 'dag',
	'hours': 'uur',
	'hour': 'uur',
	'minutes': 'minuten',
	'minute': 'minuut',
	'ago' : 'geleden',
	'less than a minute': 'minder dan een minuut',

	//Garp.FormHelper.Duplicator
	'Add': 'Toevoegen',
	'Remove': 'Verwijderen',

	//Garp.FormHelper.Validator
	// IMPORTANT: Keep synced with /garp/application/data/i18n/nl.php
	"'${1}' is not valid" : "'${1}' is niet geldig",
	"Value is required and can't be empty" : "Dit veld is verplicht",
	"'${1}' does not appear to be a postal code" : "'${1}' is geen geldige postcode",
	"'${1}' does not match against pattern '${2}'" : "Het ingevulde '${1}' komt niet overeen met het patroon voor veld '${2}",
	"'${1}' is not a valid email address in the basic format local-part@hostname" : "'${1}' is geen geldig e-mailadres in het formaat <EMAIL>",
	"'${1}' is not a valid Dutch postcode" : "'${1}' is geen geldige Nederlandse postcode",
	"'${1}' is not a valid URL" : "'${1}' is geen geldige URL",
	"'${1}' is not a valid number for this field" : "'${1}' is geen geldig nummer",
	"Value doesn't match": "Waarde komt niet overeen",
	
	//qq.FileUploader
	"{file} has invalid extension. Only {extensions} are allowed.": "{file} heeft niet de juiste extensie. De volgende extensie(s) zijn toegestaan: {extensions}",
	"{file} is too large, maximum file size is {sizeLimit}.": "{file} is te groot. Maximum grootte is {sizeLimit}",
	"{file} is too small, minimum file size is {minSizeLimit}.": "{file} is te klein. Minimale grootte is {minSizeLimit}",
	"{file} is empty, please select files again without it.": "{file} is leeg.",
	"The files are being uploaded, if you leave now the upload will be cancelled.": "Bestanden worden geüpload. Het uploaden wordt onderbroken als u weggaat.",
	"Something went wrong while uploading. Please try again later" : "Er ging iets mis met uploaden. Probeer 't later opnieuw"
});<commit_msg>Tweaks to new vacature form<commit_after>/**
 * Garp Frontend library Core functions
 * @author <NAME> | Grrr
 * @author <NAME> | Grrr
 * @package Garp
 */
Garp = typeof Garp == 'undefined' ? { } : Garp;
BASE = typeof BASE == 'undefined' ? '/' : BASE;

// http://jdbartlett.github.com/innershiv | WTFPL License
Garp.innerShiv = (function() {
	var d, r;
	return function(h, u) {
		if (!d) {
			d = document.createElement('div');
			r = document.createDocumentFragment();
			/*@cc_on d.style.display = 'none';@*/
		}

		var e = d.cloneNode(true);
		/*@cc_on document.body.appendChild(e);@*/
		e.innerHTML = h.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
		/*@cc_on document.body.removeChild(e);@*/

		if (u === false) {
			return e.childNodes;
		}

		var f = r.cloneNode(true), i = e.childNodes.length;
		while (i--) {
			f.appendChild(e.firstChild);
		}

		return f;
	};
}());

/*!
 * selectivizr v1.0.2 - (c) <NAME>, freely distributable under the terms of the MIT license.
 * selectivizr.com
 */
//(function(j){function A(a){return a.replace(B,h).replace(C,function(a,d,b){for(var a=b.split(","),b=0,e=a.length;b<e;b++){var s=D(a[b].replace(E,h).replace(F,h))+o,l=[];a[b]=s.replace(G,function(a,b,c,d,e){if(b){if(l.length>0){var a=l,f,e=s.substring(0,e).replace(H,i);if(e==i||e.charAt(e.length-1)==o)e+="*";try{f=t(e)}catch(k){}if(f){e=0;for(c=f.length;e<c;e++){for(var d=f[e],h=d.className,j=0,m=a.length;j<m;j++){var g=a[j];if(!RegExp("(^|\\s)"+g.className+"(\\s|$)").test(d.className)&&g.b&&(g.b===!0||g.b(d)===!0))h=u(h,g.className,!0)}d.className=h}}l=[]}return b}else{if(b=c?I(c):!v||v.test(d)?{className:w(d),b:!0}:null)return l.push(b),"."+b.className;return a}})}return d+a.join(",")})}function I(a){var c=!0,d=w(a.slice(1)),b=a.substring(0,5)==":not(",e,f;b&&(a=a.slice(5,-1));var l=a.indexOf("(");l>-1&&(a=a.substring(0,l));if(a.charAt(0)==":")switch(a.slice(1)){case "root":c=function(a){return b?a!=p:a==p};break;case "target":if(m==8){c=function(a){function c(){var d=location.hash,e=d.slice(1);return b?d==i||a.id!=e:d!=i&&a.id==e}k(j,"hashchange",function(){g(a,d,c())});return c()};break}return!1;case "checked":c=function(a){J.test(a.type)&&k(a,"propertychange",function(){event.propertyName=="checked"&&g(a,d,a.checked!==b)});return a.checked!==b};break;case "disabled":b=!b;case "enabled":c=function(c){if(K.test(c.tagName))return k(c,"propertychange",function(){event.propertyName=="$disabled"&&g(c,d,c.a===b)}),q.push(c),c.a=c.disabled,c.disabled===b;return a==":enabled"?b:!b};break;case "focus":e="focus",f="blur";case "hover":e||(e="mouseenter",f="mouseleave");c=function(a){k(a,b?f:e,function(){g(a,d,!0)});k(a,b?e:f,function(){g(a,d,!1)});return b};break;default:if(!L.test(a))return!1}return{className:d,b:c}}function w(a){return M+"-"+(m==6&&N?O++:a.replace(P,function(a){return a.charCodeAt(0)}))}function D(a){return a.replace(x,h).replace(Q,o)}function g(a,c,d){var b=a.className,c=u(b,c,d);if(c!=b)a.className=c,a.parentNode.className+=i}function u(a,c,d){var b=RegExp("(^|\\s)"+c+"(\\s|$)"),e=b.test(a);return d?e?a:a+o+c:e?a.replace(b,h).replace(x,h):a}function k(a,c,d){a.attachEvent("on"+c,d)}function r(a,c){if(/^https?:\/\//i.test(a))return c.substring(0,c.indexOf("/",8))==a.substring(0,a.indexOf("/",8))?a:null;if(a.charAt(0)=="/")return c.substring(0,c.indexOf("/",8))+a;var d=c.split(/[?#]/)[0];a.charAt(0)!="?"&&d.charAt(d.length-1)!="/"&&(d=d.substring(0,d.lastIndexOf("/")+1));return d+a}function y(a){if(a)return n.open("GET",a,!1),n.send(),(n.status==200?n.responseText:i).replace(R,i).replace(S,function(c,d,b,e,f){return y(r(b||f,a))}).replace(T,function(c,d,b){d=d||i;return" url("+d+r(b,a)+d+") "});return i}function U(){var a,c;a=f.getElementsByTagName("BASE");for(var d=a.length>0?a[0].href:f.location.href,b=0;b<f.styleSheets.length;b++)if(c=f.styleSheets[b],c.href!=i&&(a=r(c.href,d)))c.cssText=A(y(a));q.length>0&&setInterval(function(){for(var a=0,c=q.length;a<c;a++){var b=q[a];if(b.disabled!==b.a)b.disabled?(b.disabled=!1,b.a=!0,b.disabled=!0):b.a=b.disabled}},250)}if(!/*@cc_on!@*/true){var f=document,p=f.documentElement,n=function(){if(j.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(a){return null}}(),m=/MSIE (\d+)/.exec(navigator.userAgent)[1];if(!(f.compatMode!="CSS1Compat"||m<6||m>8||!n)){var z={NW:"*.Dom.select",MooTools:"$$",DOMAssistant:"*.$",Prototype:"$$",YAHOO:"*.util.Selector.query",Sizzle:"*",jQuery:"*",dojo:"*.query"},t,q=[],O=0,N=!0,M="slvzr",R=/(\/\*[^*]*\*+([^\/][^*]*\*+)*\/)\s*/g,S=/@import\s*(?:(?:(?:url\(\s*(['"]?)(.*)\1)\s*\))|(?:(['"])(.*)\3))[^;]*;/g,T=/\burl\(\s*(["']?)(?!data:)([^"')]+)\1\s*\)/g,L=/^:(empty|(first|last|only|nth(-last)?)-(child|of-type))$/,B=/:(:first-(?:line|letter))/g,C=/(^|})\s*([^\{]*?[\[:][^{]+)/g,G=/([ +~>])|(:[a-z-]+(?:\(.*?\)+)?)|(\[.*?\])/g,H=/(:not\()?:(hover|enabled|disabled|focus|checked|target|active|visited|first-line|first-letter)\)?/g,P=/[^\w-]/g,K=/^(INPUT|SELECT|TEXTAREA|BUTTON)$/,J=/^(checkbox|radio)$/,v=m>6?/[\$\^*]=(['"])\1/:null,E=/([(\[+~])\s+/g,F=/\s+([)\]+~])/g,Q=/\s+/g,x=/^\s*((?:[\S\s]*\S)?)\s*$/,i="",o=" ",h="$1";(function(a,c){function d(){try{p.doScroll("left")}catch(a){setTimeout(d,50);return}b("poll")}function b(d){if(!(d.type=="readystatechange"&&f.readyState!="complete")&&((d.type=="load"?a:f).detachEvent("on"+d.type,b,!1),!e&&(e=!0)))c.call(a,d.type||d)}var e=!1,g=!0;if(f.readyState=="complete")c.call(a,i);else{if(f.createEventObject&&p.doScroll){try{g=!a.frameElement}catch(h){}g&&d()}k(f,"readystatechange",b);k(a,"load",b)}})(j,function(){for(var a in z){var c,d,b=j;if(j[a]){for(c=z[a].replace("*",a).split(".");(d=c.shift())&&(b=b[d]););if(typeof b=="function"){t=b;U();break}}}})}}})(this);

(function(document){

	window.MBP = window.MBP || {};
	var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o;
	/*! A fix for the iOS orientationchange zoom bug. Script by @scottjehl, rebound by @wilto.MIT / GPLv2 License.*/
	/*  JSLINT fix by Peter */
	(function(a){
		function m(){
			d.setAttribute("content", g); h = !0;
		}
		function n(){
			d.setAttribute("content", f); h = !1;
		}
		function o(b){
			l = b.accelerationIncludingGravity; i = Math.abs(l.x); j = Math.abs(l.y); k = Math.abs(l.z);
			((!a.orientation || a.orientation === 180) && (i > 7 || (k > 6 && j < 8 || k < 8 && j > 6) && i > 5) ? h && n() : h || m());
		}
		var b = navigator.userAgent;
		if ((!(/iPhone|iPad|iPod/.test(navigator.platform) && (/OS [1-5]_[0-9_]* like Mac OS X/i.test(b)) && b.indexOf("AppleWebKit") > -1))) {
			return;
		}
		var c = a.document;
		if (!c.querySelector) {
			return;
		}
		var d = c.querySelector("meta[name=viewport]"), e = d && d.getAttribute("content"), f = e + ",maximum-scale=1", g = e + ",maximum-scale=10", h = !0, i, j, k, l;
		if (!d) {
			return;
		}
		a.addEventListener("orientationchange", m, !1);
		a.addEventListener("devicemotion", o, !1);
	})(this);

	// Autogrow
	// http://googlecode.blogspot.com/2009/07/gmail-for-mobile-html5-series.html
	MBP.autogrow = function (element, lh) {

    	function handler(e){
        	var newHeight = this.scrollHeight,
            	currentHeight = this.clientHeight;
        	if (newHeight > currentHeight) {
            	this.style.height = newHeight + 3 * textLineHeight + "px";
        	}
    	}

    	var setLineHeight = (lh) ? lh : 12,
        	textLineHeight = element.currentStyle ? element.currentStyle.lineHeight :
                         	 getComputedStyle(element, null).lineHeight;

    	textLineHeight = (textLineHeight.indexOf("px") == -1) ? setLineHeight :
                     	 parseInt(textLineHeight, 10);

    	element.style.overflow = "hidden";
		if(element.addEventListener){
			element.addEventListener('keyup', handler, false);
		} else {
			element.attachEvent('onkeyup', handler);
		}
	};
})(document);

/**
 * Utility function. Binds receiverObj's properties to senderObj's properties
 * @param {Object} receiverObj
 * @param {Object} senderObj
 * @return {Object} receiverObj
 */
Garp.apply = function(receiverObj, senderObj){
	for (var i in senderObj) {
		receiverObj[i] = senderObj[i];
	}
	return receiverObj;
};

/**
 * Utility function. Binds receiverObj's properties to senderObj's properties if not already present
 * @param {Object} receiverObj
 * @param {Object} senderObj
 * @return {Object} receiverObj
 */
Garp.applyIf = function(receiverObj, senderObj){
	for (var i in senderObj) {
		if (!receiverObj[i]) {
			receiverObj[i] = senderObj[i];
		}
	}
	return receiverObj;
};

/**
 * Utility string function: use a simple tpl string to format multiple arguments
 *
 * example:
 * var html = Garp.format('<a href="${1}">${2}</a>"', 'http://www.grrr.nl/', 'Grrr Homepage');
 *
 * @param {String} tpl  template
 * @param {String} ...n input string(s)
 * @return {String}
 */
Garp.format = function(tpl, o){
	var res = tpl;
	if (typeof o == 'object') {
		Garp.each(o, function(v, k){
			k = k.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&');
			res = res.replace(new RegExp('\\${' + k + '\\}', "g"),v);
		});
	} else {
		for (var i = 1, l = arguments.length; i < l; i++) {
			res = res.replace(new RegExp("\\${" + i + "\\}", "g"), arguments[i]);
		}
	}
	return res;
};

/**
 * Utility function each
 * Calls fn for each ownProperty of obj. fn(property, iterator, obj)
 *
 * @param {Object} obj to iterate
 * @param {Function} fn to call with each property of obj, the property name and the object from within 'scope'
 * @param {Object} [scope] to execute within
 */
Garp.each = function(obj, fn, scope){
	for(var i in obj){
		if (obj.hasOwnProperty(i)) {
			if (scope) {
				fn.call(scope, obj[i], i, obj);
			} else {
				fn(obj[i], i, obj);
			}
		}
	}
};

/**
 * Creates a Delegate function
 * @param {Function} function
 * @param {Object} scope
 */
Garp.createDelegate = function(fn, scope){
	return function(){
		fn.apply(scope, arguments);
	};
};

/**
 * Parse a query string
 * @return {Object} query params as key/value pairs
 */
Garp.parseQueryString = function(str, decode) {
    str = str || window.location.search;
	if (decode) {
		str = unescape(str);
	}
    var objURL = {};
    str.replace(
        new RegExp( "([^?=&]+)(=([^&]*))?", "g" ),
        function( $0, $1, $2, $3 ){
                objURL[ $1 ] = $3;
        }
    );
    return objURL;
};

/**
 * Equalize the height of several items in a collection.
 * @param {jQuery} collection of items
 * @return {Void}
 */
Garp.equalizeHeight = function(collection) {
	var h = 0;
	collection.each(function() {
		h = Math.max(h, $(this).height());
	});
	collection.height(h);
};

/**
 * Retrieves an object housing in an array by looking for one of its keys and possibly it's value
 * @param {Array} array of objects
 * @param {String} key
 * @param {String} val [optional]
 *
 * @return {Array | Object}
 */
Garp.getBy = function(arr, key, val){
	var out = [];
	for(var k in arr){
		var item = arr[k];
		if(item[key]){
			if (!val || (val && item[key] == val)) {
				out.push(item);
			}
		}
	}
	return out.length == 1 ? out[0] : out;
};

/**
 * Garp Flash Messenger. Provides a method to show a 'popup' style message to the user.
 * Convenient for auto-fade out and such.
 *
 * @param {Object} Config properties. See below for @cfg details:
 *
 * @example usage:
 *	var fm = new Garp.FlashMessage({
 *		msg: "Your Message might go here, or you can use... ",
 *		parseCookie: true // ... this option to grab the message from the server
 *	});
 *
 */
Garp.FlashMessage = function(cfg){

	// Apply defaults:
	Garp.apply(this, {

		/**
		 * @cfg: {String} Message to show
		 */
		msg: '',

		/**
		 * @cfg: {Boolean} Whether or not too parse (and show) the Garp.FlashMessage cookie
		 */
		parseCookie: false,

		/**
		 * @cfg: {String} Cookie name
		 */
		cookieName: 'FlashMessenger',

		/**
		 * @cfg {Function} Callback, get's called with scope/this set to FlashMessage
		 */
		afterShow: function(){
		},

		/**
		 * @cfg {Function} Callback, get's called with scope/this set to FlashMessage
		 */
		afterClose: function(){
		},

		/**
		 * @cfg {jQuery element}: Provide the element to use
		 */
		elm: (function(){
			var id = 'flashMessage';
			return $('#' + id).length ? $('#' + id) : $('body').append('<div id="' + id + '"></div>').find('#' + id);
		})(this),

		/**
		 * @cfg {Boolean} Automatically hide?
		 */
		autoHide: true,

		/**
		 * @cfg {Number} Delay for auto hide
		 */
		hideDelay: 6000,

		/**
		 * @cfg {Function} Animation to use. Override to do someting else than just a simple fade:
		 */
		hideAnimation: function(){
			if (this.elm) {
				this.elm.fadeOut('slow', Garp.createDelegate(this.afterClose, this));
			} else {
				this.afterClose(this);
			}
		}
	});

	// Override with given config:
	Garp.apply(this, cfg);

	// Private functions:
	Garp.apply(this, {

		/**
		 * Shows (the hidden) flashMessage. Should generally not be needed.
		 */
		show: function(){
			this.elm.show();
			return this;
		},

		/**
		 * Hides the flashMessage element by using the possible animation
		 */
		hide: function(){
			if (this.hideAnimation) {
				this.hideAnimation();
			} else {
				this.close();
			}
			return this;
		},

		/**
		 * Closes the flashMessage immediately
		 */
		close: function(){
			if (this.elm) {
				this.elm.hide();
			}
			if (this.afterClose) {
				this.afterClose(this);
			}
			return this;
		},

		/**
		 * Parses a cookie. Nom,nom,nom.
		 * note; a global COOKIEDOMAIN constant can be set for this function.
		 */
		cookieParser: function(){
			var m = $.parseJSON(unescape(Garp.getCookie(this.cookieName))), out = '';
			if (m && m.messages) {
				for (var i in m.messages) {
					var msg = m.messages[i];
					if (msg) {
						out += '<p>' + msg.replace(/\+/g, ' ') + '</p>';
					}
				}
				var exp = new Date();
				exp.setHours(exp.getHours() - 1);
				Garp.setCookie(this.cookieName, '', exp, (typeof COOKIEDOMAIN !== 'undefined') ? COOKIEDOMAIN : '.' + document.location.host);
				return out;
			}
			return '';
		},

		/**
		 * Init
		 */
		init: function(){
			if (this.parseCookie) {
				this.msg += this.cookieParser();
			}
			if (!this.msg) {
				return this;
			}
			if (!this.elm.length) {
				$('body').append(this.elm);
			}
			if (this.msg) {
				this.elm.html(this.msg);
			}
			if (this.autoHide) {
				setTimeout(Garp.createDelegate(this.hide, this), this.hideDelay);
			}
			this.elm.show();
			this.afterShow(this);
			return this;
		}
	});

	return this.init();
};
/**
 * Grab a Cookie
 * @param {Object} name
 */
Garp.getCookie = function(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
			c = c.substring(1, c.length);
		}
        if (c.indexOf(nameEQ) === 0) {
			return c.substring(nameEQ.length, c.length);
		}
    }
    return null;
};

/**
 * Give a Cookie
 * @param {Object} name
 * @param {Object} value
 * @param {Date} expiration date
 * @param {String} domain
 */
Garp.setCookie = function(name, value, date, domain){
	value = escape(value) + "; path=/";
	if (domain) {
		value += "; domain=" + escape(domain);
	} else {
		value += "; domain=" + escape((typeof COOKIEDOMAIN !== 'undefined') ? COOKIEDOMAIN : '.' + document.location.host);
	}
	value += (!date ? "" : "; expires=" + date.toGMTString());
	document.cookie = name + "=" + value;
};

Garp.removeCookie = function(name){
	Garp.setCookie(name,'',new Date('1900'));
};

/**
 * Snippet edit links
 */
$(function(){
	var authCookie = unescape(Garp.getCookie('Garp_Auth'));
	authCookie = jQuery.parseJSON(authCookie);
	if (authCookie && authCookie.userData && authCookie.userData.role) {
		var role = authCookie.userData.role;
		if (role && (role == 'admin' || role == 'developer')) {
			setTimeout(function(){
				var elms = [];
				(function walk(elm){
					if (elm.childNodes && elm.childNodes.length > 0) {
						for (var e in elm.childNodes) {
							walk(elm.childNodes[e]);
						}
					} else {
						elms.push(elm);
					}
				})($('body')[0]);
				for (var e in elms) {
					var elm = elms[e];
					var token = '//garp-snippet//';
					if (elm.nodeType == 8 && elm.nodeValue.indexOf(token) > -1) {
						var snippet = elm.nextSibling;
						var url = BASE + 'g/content/admin/?model=Snippet&id=' + elm.nodeValue.replace(token, '');
						//$(snippet).wrap('<div style="position:relative;">')
						var link = $('<a href="' + url + '" title="edit" target="garp_cms"><img src="' + (BASE ? BASE : '/') + 'media/images/garp/icons/pencil.png" /></a>').insertBefore(snippet).css({
							position: 'absolute',
							zIndex: '999999',
							padding: '4px',
							width: '20px',
							height: '20px',
							margin: '-14px 0 0 -14px',
							lineHeight: 0
						//border: '0'
						});
						(function(link){
							function mouseOut(){
								$(link).css({
									opacity: 0.5,
									border: '2px #fff outset',
									background: '#ddd'
								});
							}
							$(link).bind('mouseenter', function(){
								$(link).css({
									opacity: 1,
									border: '2px #fff outset',
									background: '#ddd'
								});
							}).bind('mouseleave', mouseOut);
							mouseOut();
						})(link);
					}
				}
			}, 1000);
		}
	}
});

/**
 * Is our dearest user logged in or what?
 */
Garp.isLoggedIn = function(){
	var c = Garp.getCookie('Garp_Auth');
	if(c){
		c = $.parseJSON(unescape(c));
		if(c.userData){
			return true;
		}
	}
	return false;
};

/**
 * Cookie acceptation stuff 'Cookiewet'
 */
Garp.hasCookiesAccepted = function(){
	return Garp.getCookie('Garp_Accept_Cookies') == 'Garp_Accept_Cookies';
};
Garp.acceptCookies = function(){
	var exp = new Date();
	exp.setYear(exp.getFullYear() + 1);
	Garp.setCookie('Garp_Accept_Cookies', 'Garp_Accept_Cookies', exp, false);
};

/**
 * Constrains number i between min and max. Rolls over.
 * @param {Number} i
 * @param {Number} {optional} min
 * @param {Number} max
 */
Garp.constrain = function(i){
	var max = arguments[2];
	var min = arguments[1];
	if(typeof max != 'Number'){
		min = 0;
		max = arguments[1];
	}
	if (i < min) {
		i = max;
	}
	if (i > max) {
		i = min;
	}
	return i;
};

Garp.lazyLoader = {
	/**
	 * Register a lazyLoad callback
	 * @param {Object} cfg
	 * @cfg id {String} id of the element
	 * @cfg before {Function} beforeFetch fn. Gets called with scope &amp; fetch arguments
	 * @cfg after {Function} afterFetch fn. Gets called with scope, respone &amp; fetch arguments
	 */
	reg: function(cfg){
		if (cfg.id && !Garp.lazyLoader[cfg.id]) {
			if (typeof cfg.before == 'function' || typeof cfg.after == 'function') {
				Garp.lazyLoader[cfg.id] = cfg;
			} else {
				throw "No callback function defined in lazyLoader config.";
			}
		} else {
			throw "Can't register lazyLoader. No id property or duplicate entry.";
		}
	},

	/**
	 * Deletes a lazyLoad callback
	 * @param {Object} id
	 */
	unreg: function(id){
		delete Garp.lazyLoader[id];
	},


	/**
	 * Collect all lazy-load elements and fetch!
	 */
	init: function(){

		var $elm = $('.lazy-load');
		if (!$elm.length) {
			return;
		}

		function fetch(e){
			if (e && e.preventDefault) {
				e.preventDefault();
			}
			var con = $(this).attr('data-con');
			if (typeof con == 'undefined') {
				con = '?';
			}
			var id = $(this).attr('id');
			var attributes = $(this).attr('data-attr') || '';
			if (!attributes) {
				con = '';
			}
			var before = $.noop;
			var after = $.noop;

			if (Garp.lazyLoader[id]) {
				if (Garp.lazyLoader[id].before) {
					before = Garp.lazyLoader[id].before;
				}
				if (Garp.lazyLoader[id].after) {
					after = Garp.lazyLoader[id].after;
				}
			}

			var fetchCB = Garp.createDelegate(fetch, this);
			before(this, fetchCB);

			var url = $(this).attr('data-href') + con + attributes;
			var scope = this;
			var isJson = $(this).hasClass('json');

			var replace = false;
			if ($(this).attr('data-replace-selector') && $($(this).attr('data-replace-selector')).length) {
				replace = true;
			}

			$.get(url, function(resp){
				if (isJson) {
					resp = resp.html;
				}
				if (replace) {
					$($(scope).attr('data-replace-selector')).replaceWith(resp);
				} else {
					$(scope).html(resp);
				}
				after.call(scope, scope, resp, fetchCB);
			}, isJson ? 'json' : 'html');
		}

		$elm.each(function(i, el){
			Garp.scrollHandler.register($(el), function(){
				fetch.call(el);
			});
		});
	}
};

/**
 * Loads JS files async
 * @param {String} url
 * @param {String} type ('js' or 'css') (css not implemented yet!)
 * @param {Function} callback
 * @param {Object} scope to call callback from
 */
Garp.asyncLoad = function(url, type, cb, scope){
	if (type === 'js') {
		var s = document.createElement('script');
		if (s.addEventListener) {
			s.addEventListener('load', function(){
				cb.call(scope);
			}, false);
		}
		$('head')[0].appendChild(Garp.applyIf(s, {
			onreadystatechange: function(){
				if (this.readyState == 'loaded' || this.readyState == 'complete') {
					cb.call(scope);
				}
			},
			type: 'text/javascript',
			src: url
		}));
	} else if (type === 'css'){
		// TODO add support for CSS here
	} else {
		throw 'Unsupported type for asyncLoad';
	}
};

/**
 * __ Utility function
 * @param {String} s
 * @return {String} s translated
 */
function __(s){ return Garp.locale[s] || s; }

// LOCALES:
Garp.locale = (typeof Garp.locale == 'undefined') ? {} : Garp.locale;

/**
 * Get computed styles
 * @param {Array}
 */
(function( $ ) {

	var getComputedStyle = document.defaultView && document.defaultView.getComputedStyle,
		// The following variables are used to convert camelcased attribute names
		// into dashed names, e.g. borderWidth to border-width
		rupper = /([A-Z])/g,
		rdashAlpha = /-([a-z])/ig,
		fcamelCase = function( all, letter ) {
			return letter.toUpperCase();
		},
		// Returns the computed style for an elementn
		getStyle = function( elem ) {
			if ( getComputedStyle ) {
				return getComputedStyle(elem, null);
			}
			else if ( elem.currentStyle ) {
				return elem.currentStyle;
			}
		},
		// Checks for float px and numeric values
		rfloat = /float/i,
		rnumpx = /^-?\d+(?:px)?$/i,
		rnum = /^-?\d/;

	// Returns a list of styles for a given element
	$.styles = function( el, styles ) {
		if (!el ) {
			return null;
		}
		var  currentS = getStyle(el),
			oldName, val, style = el.style,
			results = {},
			i = 0,
			left, rsLeft, camelCase, name;

		// Go through each style
		for (; i < styles.length; i++ ) {
			name = styles[i];
			oldName = name.replace(rdashAlpha, fcamelCase);

			if ( rfloat.test(name) ) {
				name = jQuery.support.cssFloat ? "float" : "styleFloat";
				oldName = "cssFloat";
			}

			// If we have getComputedStyle available
			if ( getComputedStyle ) {
				// convert camelcased property names to dashed name
				name = name.replace(rupper, "-$1").toLowerCase();
				// use getPropertyValue of the current style object
				val = currentS.getPropertyValue(name);
				// default opacity is 1
				if ( name === "opacity" && val === "" ) {
					val = "1";
				}
				results[oldName] = val;
			} else {
				// Without getComputedStyles
				camelCase = name.replace(rdashAlpha, fcamelCase);
				results[oldName] = currentS[name] || currentS[camelCase];

				// convert to px
				if (!rnumpx.test(results[oldName]) && rnum.test(results[oldName]) ) {
					// Remember the original values
					left = style.left;
					rsLeft = el.runtimeStyle.left;

					// Put in the new values to get a computed value out
					el.runtimeStyle.left = el.currentStyle.left;
					style.left = camelCase === "fontSize" ? "1em" : (results[oldName] || 0);
					results[oldName] = style.pixelLeft + "px";

					// Revert the changed values
					style.left = left;
					el.runtimeStyle.left = rsLeft;
				}

			}
		}

		return results;
	};

	/**
	* @function jQuery.fn.styles
	* @parent jQuery.styles
	* @plugin jQuery.styles
	*
	* Returns a set of computed styles. Pass the names of the styles you want to
	* retrieve as arguments:
	*
	*      $("div").styles('float','display')
	*      // -> { cssFloat: "left", display: "block" }
	*
	* @param {String} style pass the names of the styles to retrieve as the argument list
	* @return {Object} an object of `style` : `value` pairs
	*/
	$.fn.styles = function() {
		// Pass the arguments as an array to $.styles
		return $.styles(this[0], $.makeArray(arguments));
	};
})(jQuery);

/**
 * CSS3 animations if supoprted
 */
(function ($) {

	// Overwrites `jQuery.fn.animate` to use CSS 3 animations if possible

	var
		// The global animation counter
		animationNum = 0,
		// The stylesheet for our animations
		styleSheet = null,
		// The animation cache
		cache = [],
		// Stores the browser properties like transition end event name and prefix
		browser = null,
		// Store the original $.fn.animate
		oldanimate = $.fn.animate,

		// Return the stylesheet, create it if it doesn't exists
		getStyleSheet = function () {
			if(!styleSheet) {
				var style = document.createElement('style');
				style.setAttribute("type", "text/css");
				style.setAttribute("media", "screen");

				document.getElementsByTagName('head')[0].appendChild(style);
				if (!window.createPopup) { /* For Safari */
					style.appendChild(document.createTextNode(''));
				}

				styleSheet = style.sheet;
			}

			return styleSheet;
		},

		//removes an animation rule from a sheet
		removeAnimation = function (sheet, name) {
			for (var j = sheet.cssRules.length - 1; j >= 0; j--) {
				var rule = sheet.cssRules[j];
				// 7 means the keyframe rule
				if (rule.type === 7 && rule.name == name) {
					sheet.deleteRule(j);
					return;
				}
			}
		},

		// Returns whether the animation should be passed to the original $.fn.animate.
		passThrough = function (props, ops) {
			var nonElement = !(this[0] && this[0].nodeType),
				isInline = !nonElement && $(this).css("display") === "inline" && $(this).css("float") === "none";

			for (var name in props) {
				// jQuery does something with these values
				if (props[name] == 'show' || props[name] == 'hide' || props[name] == 'toggle' || $.isArray(props[name]) || props[name] < 0 || name == 'zIndex' || name == 'z-index') {
					return true;
				}
			}

			return props.jquery === true || getBrowser() === null ||
				// Animating empty properties
				$.isEmptyObject(props) ||
				// We can't do custom easing
				ops.length == 4 || typeof ops[2] == 'string' ||
				// Second parameter is an object - we can only handle primitives
				$.isPlainObject(ops) ||
				// Inline and non elements
				isInline || nonElement;
		},

		// Gets a CSS number (with px added as the default unit if the value is a number)
		cssValue = function(origName, value) {
			if (typeof value === "number" && !$.cssNumber[ origName ]) {
				value += "px";
			}
			return value;
		},

		// Feature detection borrowed by http://modernizr.com/
		getBrowser = function(){
			if(!browser) {
				var t,
					el = document.createElement('fakeelement'),
					transitions = {
						'transition': {
							transitionEnd : 'transitionEnd',
							prefix : ''
						},
//						'OTransition': {
//							transitionEnd : 'oTransitionEnd',
//							prefix : '-o-'
//						},
//						'MSTransition': {
//							transitionEnd : 'msTransitionEnd',
//							prefix : '-ms-'
//						},
						'MozTransition': {
							transitionEnd : 'animationend',
							prefix : '-moz-'
						},
						'WebkitTransition': {
							transitionEnd : 'webkitAnimationEnd',
							prefix : '-webkit-'
						}
					};

				for(t in transitions){
					if( el.style[t] !== undefined ){
						browser = transitions[t];
					}
				}
			}
			return browser;
		},

		// Properties that Firefox can't animate if set to 'auto':
		// https://bugzilla.mozilla.org/show_bug.cgi?id=571344
		// Provides a converter that returns the actual value
		ffProps = {
			top : function(el) {
				return el.position().top;
			},
			left : function(el) {
				return el.position().left;
			},
			width : function(el) {
				return el.width();
			},
			height : function(el) {
				return el.height();
			},
			fontSize : function(el) {
				return '1em';
			}
		},

		// Add browser specific prefix
		addPrefix = function(properties) {
			var result = {};
			$.each(properties, function(name, value) {
				result[getBrowser().prefix + name] = value;
			});
			return result;
		},

		// Returns the animation name for a given style. It either uses a cached
		// version or adds it to the stylesheet, removing the oldest style if the
		// cache has reached a certain size.
		getAnimation = function(style) {
			var sheet, name, last;

			// Look up the cached style, set it to that name and reset age if found
			// increment the age for any other animation
			$.each(cache, function(i, animation) {
				if(style === animation.style) {
					name = animation.name;
					animation.age = 0;
				} else {
					animation.age += 1;
				}
			});

			if(!name) { // Add a new style
				sheet = getStyleSheet();
				name = "jquerypp_animation_" + (animationNum++);
				// get the last sheet and insert this rule into it
				sheet.insertRule("@" + getBrowser().prefix + "keyframes " + name + ' ' + style,
					(sheet.cssRules && sheet.cssRules.length) || 0);
				cache.push({
					name : name,
					style : style,
					age : 0
				});

				// Sort the cache by age
				cache.sort(function(first, second) {
					return first.age - second.age;
				});

				// Remove the last (oldest) item from the cache if it has more than 20 items
				if(cache.length > 20) {
					last = cache.pop();
					removeAnimation(sheet, last.name);
				}
			}

			return name;
		};

	/**
	* @function $.fn.animate
	* @parent $.animate
	*
	* Animate CSS properties using native CSS animations, if possible.
	* Uses the original [$.fn.animate()](http://api.$.com/animate/) otherwise.
	*
	* @param {Object} props The CSS properties to animate
	* @param {Integer|String|Object} [speed=400] The animation duration in ms.
	* Will use $.fn.animate if a string or object is passed
	* @param {Function} [callback] A callback to execute once the animation is complete
	* @return {jQuery} The jQuery element
	*/
	$.fn.animate = function (props, speed, easing, callback) {
		//default to normal animations if browser doesn't support them
		if (passThrough.apply(this, arguments)) {
			return oldanimate.apply(this, arguments);
		}

		var optall = jQuery.speed(speed, easing, callback);

		// Add everything to the animation queue
		this.queue(optall.queue, function(done) {
			var
				//current CSS values
				current,
				// The list of properties passed
				properties = [],
				to = "",
				prop,
				self = $(this),
				duration = optall.duration,
				//the animation keyframe name
				animationName,
				// The key used to store the animation hook
				dataKey,
				//the text for the keyframe
				style = "{ from {",
				// The animation end event handler.
				// Will be called both on animation end and after calling .stop()
				animationEnd = function (currentCSS, exec) {
					self.css(currentCSS);

					self.css(addPrefix({
						"animation-duration" : "",
						"animation-name" : "",
						"animation-fill-mode" : "",
						"animation-play-state" : ""
					}));

					// Call the original callback
					if (optall.old && exec) {
						// Call success, pass the DOM element as the this reference
						optall.old.call(self[0], true);
					}

					$.removeData(self, dataKey, true);
				};

			for(prop in props) {
				properties.push(prop);
			}

			if(getBrowser().prefix === '-moz-') {
				// Normalize 'auto' properties in FF
				$.each(properties, function(i, prop) {
					var converter = ffProps[$.camelCase(prop)];
					if(converter && self.css(prop) == 'auto') {
						self.css(prop, converter(self));
					}
				});
			}

			// Use $.styles
			current = self.styles.apply(self, properties);
			$.each(properties, function(i, cur) {
				// Convert a camelcased property name
				var name = cur.replace(/([A-Z]|^ms)/g, "-$1" ).toLowerCase();
				style += name + " : " + cssValue(cur, current[cur]) + "; ";
				to += name + " : " + cssValue(cur, props[cur]) + "; ";
			});

			style += "} to {" + to + " }}";

			animationName = getAnimation(style);
			dataKey = animationName + '.run';

			// Add a hook which will be called when the animation stops
			$._data(this, dataKey, {
				stop : function(gotoEnd) {
					// Pause the animation
					self.css(addPrefix({
						'animation-play-state' : 'paused'
					}));
					// Unbind the animation end handler
					self.off(getBrowser().transitionEnd, animationEnd);
					if(!gotoEnd) {
						// We were told not to finish the animation
						// Call animationEnd but set the CSS to the current computed style
						animationEnd(self.styles.apply(self, properties), false);
					} else {
						// Finish animaion
						animationEnd(props, true);
					}
				}
			});

			// set this element to point to that animation
			self.css(addPrefix({
				"animation-duration" : duration + "ms",
				"animation-name" : animationName,
				"animation-fill-mode": "forwards"
			}));

			// Attach the transition end event handler to run only once
			self.one(getBrowser().transitionEnd, function() {
				// Call animationEnd using the passed properties
				animationEnd(props, true);
				done();
			});

		});

		return this;
	};
})(jQuery);

/**
 * __ Utility function
 * @param {String} s
 * @return {String} s translated
 */
function __(s){ return Garp.locale[s] || s; }

// LOCALES:
Garp.locale = (typeof Garp.locale == 'undefined') ? {} : Garp.locale;
Garp.apply(Garp.locale, {

	// Garp.relativeDate
	'years': 'jaren',
	'year': 'jaar',
	'months': 'maanden',
	'month': 'maand',
	'weeks': 'weken',
	'week': 'week',
	'days': 'dagen',
	'day': 'dag',
	'hours': 'uur',
	'hour': 'uur',
	'minutes': 'minuten',
	'minute': 'minuut',
	'ago' : 'geleden',
	'less than a minute': 'minder dan een minuut',

	//Garp.FormHelper.Duplicator
	'Add': 'Toevoegen',
	'Remove': 'Verwijderen',

	//Garp.FormHelper.Validator
	// IMPORTANT: Keep synced with /garp/application/data/i18n/nl.php
	"'${1}' is not valid" : "'${1}' is niet geldig",
	"Value is required and can't be empty" : "Dit veld is verplicht",
	"'${1}' does not appear to be a postal code" : "'${1}' is geen geldige postcode",
	"'${1}' does not match against pattern '${2}'" : "Het ingevulde '${1}' komt niet overeen met het patroon voor veld '${2}",
	"'${1}' is not a valid email address in the basic format local-part@hostname" : "'${1}' is geen geldig e-mailadres in het formaat <EMAIL>",
	"'${1}' is not a valid Dutch postcode" : "'${1}' is geen geldige Nederlandse postcode",
	"'${1}' is not a valid URL" : "'${1}' is geen geldige URL",
	"'${1}' is not a valid number for this field" : "'${1}' is geen geldig nummer",
	"Value doesn't match": "Waarde komt niet overeen",

	//qq.FileUploader
	"{file} has invalid extension. Only {extensions} are allowed.": "{file} heeft niet de juiste extensie. De volgende extensie(s) zijn toegestaan: {extensions}",
	"{file} is too large, maximum file size is {sizeLimit}.": "{file} is te groot. Maximum grootte is {sizeLimit}",
	"{file} is too small, minimum file size is {minSizeLimit}.": "{file} is te klein. Minimale grootte is {minSizeLimit}",
	"{file} is empty, please select files again without it.": "{file} is leeg.",
	"The files are being uploaded, if you leave now the upload will be cancelled.": "Bestanden worden geüpload. Het uploaden wordt onderbroken als u weggaat.",
	"Something went wrong while uploading. Please try again later" : "Er ging iets mis met uploaden. Probeer 't later opnieuw"
});
{'fraction_non_alphanumeric': 0.19829976495094934, 'fraction_numerical': 0.006558853748821236, 'mean_word_length': 3.2443249701314216, 'pattern_counts': {'":': 34, '<': 59, '<?xml version=': 0, '>': 71, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4857297', 'n_tokens_mistral': 29233, 'n_tokens_neox': 25153, 'n_words': 5847}