var cp = {};
cp.video = function(f, id, w, h) {
	if (swfobject && swfobject.embedSWF && f && id && w && h) {
		swfobject.embedSWF('http://www.digifotopro.nl/videoplayer/player.swf', 
				id, w, h, '9.0.0', 'http://www.digifotopro.nl/videoplayer/expressInstall.swf',
				{file: f, frontcolor: 'ffffff', lightcolor: 'cc9900', 
				skin: 'http://www.digifotopro.nl/videoplayer/overlay.swf', controlbar: 'over'},
				{menu: 'false', allowscriptaccess: 'always', allowfullscreen: 'true', wmode: 'opaque'});
	}
};

var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{
			string: navigator.userAgent,
			subString: "Chrome",
			identity: "Chrome"
		},
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari",
			versionSearch: "Version"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			   string: navigator.userAgent,
			   subString: "iPhone",
			   identity: "iPhone/iPod"
	    },
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();


domReady(function fixGoogleAdsIframesOnDOMTreeChange() {
	// in some browsers, scripted iframes turn blank when moved around the DOM
	if (document.addEventListener) {
		document.body.addEventListener('DOMNodeInserted', function(e) {
			if (e.target && e.target.nodeType == 1) {
				var iframes = e.target.getElementsByTagName('iframe');
				var ads = [];
				for (var i = 0; i < iframes.length; i++) {
					var iframe = iframes[i];
					if (iframe.id && 'google_ads_iframe_' == iframe.id.substring(0, 18)) {
						ads[ads.length] = iframe;
					}
				}
				for (var i = 0; i < ads.length; i++) {
					(function() {
						var iframe = ads[i];
						var s = iframe.id.substring(18);
						iframe.parentNode.removeChild(iframe);
						setTimeout(function() {
							if(BrowserDetect.browser != "Firefox") {
								try {
									GA_googleCreateDomIframe('google_ads_div_' + s, s);
								}
								catch(e) { 
									//alert(s);
								}
							}
						}, 1500);
					
					})();
				}
			}
		}, false);
	}
});


/*
Function to enable simple multiple backgrounds and box sizing, where declaration syntax is like:
background: #FFF url(xxx) repeat-y, url(yyy) 0 100% repeat-x, url(zzz) no-repeat;
Note: doesn't work on form fields or img, but most child containing (inline-)block elements should be fine.
*/
(function() {
	var CLASS_NAME_HASH = /([^a-zA-Z0-9_\-])/g;
	
	var insertElementTree = function(ss, n, cn) {
		var insertToElement = function(el, hash) {
			var trees = el.insertedTrees;
			if (!trees) {
				trees = el.insertedTrees = {};
				el.insertedTreesString = '';
			}
			if (!trees[cn]) {
				el.style.display = 'none'; // prevent intermediate repaints
				var parent = el.parentNode;
				var firstDiv = document.createElement('div');
				firstDiv.className = cn + '-' + hash + '-' + n;
				var div = firstDiv;
				for (var i = n - 1; i > 0; i--) {
					if (i > 0) {
						var newDiv = document.createElement('div');
						newDiv.className = cn + '-' + hash + '-' + i;
						div = div.appendChild(newDiv);
					}
				}
				div.insertedTrees = {};
				div.insertedTreesString = '';
				for (var k in trees) {
					if (trees.hasOwnProperty(k) && trees[k] == 'current') {
						div.insertedTrees[k] = trees[k];
						div.insertedTreesString += (k + ':' + trees[k] + ',');
						trees[k] = 'moved';
						el.insertedTreesString += (k + ':' + 'moved,');
					}
				}
				while (el.hasChildNodes()) {
					div.appendChild(el.firstChild);
				}
				el.appendChild(firstDiv);
				el.style.display = '';
				trees[cn] = 'current';
				el.insertedTreesString += (cn + ':' + 'current,');
			}
		};
		
		var insertToElements = function(els, hash) {
			for (var i = 0; i < els.length; i++) {
				insertToElement(els[i], hash);
			}
		};
		
		for (var i = 0; i < ss.length; i++) {
			var hash = ss[i].replace(CLASS_NAME_HASH, '');
			insertToElements(NW.Dom.select(ss[i]), hash);
		}
	};
	
	var fixed = {};
	
	var fixProperties = function(ss, cn) {
		var mbgs = (cn == 'mbgs');
		var boxs = (cn == 'box-sizing');
	
		var fix = [
			['padding', 0], ['padding-top', 0], ['padding-right', 0], ['padding-bottom', 0], ['padding-left', 0],
			['min-width', 0], ['width', 'auto'], ['max-width', 'none'],
			['min-height', 0], ['height', 'auto'], ['max-height', 'none'],
			['background', 'none'], ['background-color', 'none'], ['background-image', 'none'],
			['box-sizing', 'content-box'], ['-moz-box-sizing', 'content-box']
		];
		var s = [];
		
		cssHelper.selectors(function(pss) {
			for (var i = 0; i < ss.length; i++) { // selectors
				var hash = ss[i].replace(CLASS_NAME_HASH, '');
				var rs = pss[ss[i]];
				var paf = false;
				var bb = false;
				for (var i2 = 0; i2 < rs.length; i2++) { // rules affecting those selectors
					var r = rs[i2];
					var pos = r.getPropertyValue('position');
					if (pos == 'absolute' || pos == 'fixed') {
						paf = true;
					}
					else if (pos == 'static' || pos == 'relative') {
						paf = false;
					}
					var box = r.getPropertyValue('box-sizing');
					if (box == 'border-box') {
						bb = true;
					}
					else if (box == 'content-box') {
						bb = false;
					}
					if (mbgs && bb && (ua.ie6 || ua.ie7)) {
						continue;
					}
					var fixes = false;
					var x = [];
					for (var i3 = 0; i3 < fix.length; i3++) {
						var p = fix[i3][0];
						var v = r.getPropertyValue(p);
						if (boxs && bb && (ua.ie6 || ua.ie7) && 'box-sizing' == p.substring(p.length - 10)) {
							continue;
						}
						var w = ('width' == p.substring(p.length - 5));
						var h = ('height' == p.substring(p.length - 6));
						var b = ('background' == p.substring(0, 10));
						if (v !== null &&
								!(bb && (w || b)) &&
								!(paf && (w || h)) &&
								!(!paf && w) &&
								!(mbgs && b) &&
								!(boxs && b)) { // apply fix
							fixes = true;
							x.push(ss[i]);
							x.push('{');
							x.push(p);
							x.push(':');
							x.push(fix[i3][1]);
							x.push('}');
							x.push(ss[i]);
							x.push(' .');
							x.push(cn);
							x.push('-');
							x.push(hash);
							x.push('-1{');
							x.push(p);
							x.push(':');
							x.push(v);
							x.push('}');
						}
					}
					var mql = r.getMediaQueryList();
					if (mql !== null && fixes) {
						s.push(mql.getListText());
						s.push('{');
					}
					if (fixes) {
						s.push(x.join(''));
					}
					if (mql !== null && fixes) {
						s.push('}\n');
					}
				}
			}
			if (s.length > 0) {
				var style = cssHelper.addStyle(s.join('')); // style is added to parsed objects and media queries can be evaluated
				style.addedFor = cn;
			}
		});
	};
	
	domReady(function enableBoxSizing() {
		if (!(cssHelper && ua && (ua.ie6 || ua.ie7))) {
			return;
		}
		
		cssHelper.properties(function(ps) {
			var ds = ps['box-sizing'];
			if (ds) {
				for (var i = 0; i < ds.length; i++) {
					if (ds[i].getValue() == 'border-box') {
						var ss = ds[i].getRule().getSelectors();
						fixProperties(ss, 'box-sizing');
						insertElementTree(ss, 1, 'box-sizing');
					}
				}
			}
			
			var timer;
			cssHelper.addListener('DOMElementInserted', function() {
				clearTimeout(timer);
				timer = setTimeout(function() {
					cssHelper.properties(function(ps) {
						var ds = ps['box-sizing'];
						if (ds) {
							for (var i = 0; i < ds.length; i++) {
								if (ds[i].getValue() == 'border-box') {
									var ss = ds[i].getRule().getSelectors();
									insertElementTree(ss, 1, 'box-sizing');
								}
							}
						}
					});
				}, 100);
			});
		});
		
	});

	
	domReady(function enableSimpleMultipleBackgrounds() {
		if (!(cssHelper && ua && !ua.webkit)) { // TODO properly check if multiple backgrounds is supported, use getcomputedstyle
			return;
		}
		
		var regExp = {
			MULTIPLE_BACKGROUNDS: /(?:[^,\(\)]+(?:\([^\(\)]+\))*)+/g // comma within parentheses allows for rgb(a) values
		};
		
		//function getComputedStyle(el, property) {
		//	if (el.currentStyle)
		//		return el.currentStyle[property];
		//	else if (window.getComputedStyle)
		//		return window.getComputedStyle(el, null).getPropertyValue(property);
		//}
		
		cssHelper.properties(function(ps) {
			
			var createStyle = function(mql, ss, bgs) { // create the style for the new elements
				var s = [];
				
				if (mql !== null) {
					s.push(mql.getListText());
					s.push('{\n');
				}
				
				for (var i = 0; i < ss.length; i++) {
					var hash = ss[i].replace(CLASS_NAME_HASH, '');
					s.push(ss[i]);
					s.push('{background:none}\n');
					for (var i2 = bgs.length - 1; i2 >= 0; i2--) {
						s.push(ss[i]);
						s.push(' .mbgs-');
						s.push(hash);
						s.push('-');
						s.push(i2 + 1);
						s.push('{background:');
						s.push(bgs[i2]);
						s.push(';width:100%;height:100%}\n');
						if (i2 === 0) {
							s.push(ss[i]);
							s.push(' .mbgs-');
							s.push(hash);
							s.push('-1:after{clear:both;display:block;');
							s.push('visibility:hidden;height:0;content:"."}\n');
						}
					}GA_googleCreateDomIframe
				}
				
				if (mql !== null) {
					s.push('}');
				}
				cssHelper.addStyle(s.join(''));
			};
			
			var ds = ps['background'];
			if (ds) {
				for (var i = 0; i < ds.length; i++) {
					var bgs = ds[i].getValue().match(regExp.MULTIPLE_BACKGROUNDS);
					if (bgs !== null && bgs.length > 1) {
						var r = ds[i].getRule();
						var mql = r.getMediaQueryList();
						var ss = r.getSelectors();
						createStyle(mql, ss, bgs);
						fixProperties(ss, 'mbgs');
						insertElementTree(ss, bgs.length, 'mbgs');
					}
				}
			}
			
			var timer;
			cssHelper.addListener('DOMElementInserted', function() {
				clearTimeout(timer);
				timer = setTimeout(function() {
					cssHelper.properties(function(ps) {
						var ds = ps['background'];
						if (ds) {
							for (var i = 0; i < ds.length; i++) {
								var bgs = ds[i].getValue().match(regExp.MULTIPLE_BACKGROUNDS);
								if (bgs !== null && bgs.length > 1) {
									var ss = ds[i].getRule().getSelectors();
									insertElementTree(ss, bgs.length, 'mbgs');
								}
							}
						}
					});
				}, 100);
			});
		});
	});
})();



domReady(function enableSimpleMultiColumnLayoutWithNonBreakingChildren() {
	var createElements = function(el, count) {
		var width = 100 / count + '%';
		var els = [];
		for (var i = 0; i < count; i++) {
			els[i] = document.createElement('div');
			els[i].className = 'multi-column-layout multi-column-layout-' + i;
			var inner = els[i].appendChild(document.createElement('div'));
			inner.className = 'multi-column-layout-inner';
		}
		return els;
	}
	
	var appendElements = function(el, els) {
		for (var i = 0; i < els.length; i++) {
			el.appendChild(els[i]);
		}
	}
	
	var createStyle = function(mql, ss, count, gap) {
		var s = [], c = 0;
		
		if (mql !== null) {
			s[c++] = mql.getListText();
			s[c++] = '{\n';
		}
		
		var width = 100 / count + '%';
		for (var i = 0; i < ss.length; i++) {
			s[c++] = ss[i];
			s[c++] = '{zoom: 1}';
			s[c++] = ss[i];
			s[c++] = ':after{clear:left;display:block;visibility:hidden;height:0;content:"."}';
			s[c++] = ss[i];
			s[c++] = ' .multi-column-layout{float:left;width:';
			s[c++] = width;
			s[c++] = '}';
			s[c++] = ss[i];
			s[c++] = ' .multi-column-layout-inner{margin-right:';
			s[c++] = gap;
			s[c++] = 'px}\n'
		}
		
		if (mql !== null) {
			s[c++] = '}';
		}
		cssHelper.addStyle(s.join(''));
	};
	
	var distribute = function(cols) {
		var origin = cols[0].firstChild;
		if (!origin) {
			return;
		}
		var child = origin.firstChild, elCount = 0;
		while (child && elCount < 2) {
			if (child.nodeType == 1) {
				elCount++;
			}
			child = child.nextSibling;
		}
		if (elCount > 1) {
			var height = Math.round(cols[0].offsetHeight / cols.length);
			for (var i = cols.length - 1, lastCol = cols[i]; lastCol != cols[0]; lastCol = cols[--i]) {
				var destination = lastCol.firstChild;
				while (lastCol.offsetHeight < height && origin.lastChild) {
					if (elCount < 2 && origin.lastChild.nodeType == 1) {
						elCount++;
					}
					if (destination.firstChild) {
						destination.insertBefore(origin.lastChild, destination.firstChild);
					}
					else {
						destination.appendChild(origin.lastChild);
					}
				}
				var diff = Math.abs(height - lastCol.offsetHeight);
				if (!destination.firstChild) { // error!
					return;
				}
				origin.appendChild(destination.firstChild);
				if (Math.abs(height - lastCol.offsetHeight) > diff) {
					destination.insertBefore(origin.lastChild, destination.firstChild);
				}
			}
			var maxheight1 = cols[0].parentNode.offsetHeight;
			// final attempt at distributing as evenly as possible
			for (var i = cols.length - 1, col = cols[i]; col != cols[0]; col = cols[--i]) {
				cols[i - 1].firstChild.appendChild(col.firstChild.firstChild);
			}
			var maxheight2 = cols[0].parentNode.offsetHeight;
			if (maxheight2 > maxheight1) {
				for (var i = cols.length - 1, col = cols[i]; col != cols[0]; col = cols[--i]) {
					cols[i].firstChild.insertBefore(cols[i - 1].firstChild.lastChild, cols[i].firstChild.firstChild);
				}
			}
		}
	};

	var createColumns = function(mql, ss, count) {
		var els = [];
		for (var i = 0; i < ss.length; i++) {
			var s = ss[i];
			cssHelper.selectors(function(css) {
				var rs = css[s + ' > *'];
				for (var i2 = 0; i2 < rs.length; i2++) {
					var r = rs[i2];
					if (r.getMediaQueryList() == mql
							&& r.getPropertyValue('page-break-inside') == 'avoid') {
						var els = NW.Dom.select(s);
						var el = els[0];
						if (el && !el.multiColumnEnabled) {
							var cols = createElements(el, count);
							while (el.firstChild) {
								cols[0].firstChild.appendChild(el.firstChild);
							}
							appendElements(el, cols);
							distribute(cols);
							el.multiColumnEnabled = true;
						}
					}
				}
			});
		}
	};

	cssHelper.properties(function(ps) {
		var ds = ps['column-count'];
		if (ds) {
			for (var i = 0; i < ds.length; i++) {
				var count = parseInt(ds[i].getValue(), 10);
				if (typeof count == 'number' && count > 0) {
					var r = ds[i].getRule();
					var mql = r.getMediaQueryList();
					var ss = r.getSelectors();
					var gap = parseInt(r.getPropertyValue('column-gap')); // must be pixels
					createStyle(mql, ss, count, gap);
					createColumns(mql, ss, count);
				}
			}
		}
	});
	
	var timer;
	cssHelper.addListener('DOMElementInserted', function() {
		clearTimeout(timer);
		timer = setTimeout(function() {
			cssHelper.properties(function(ps) {
				var ds = ps['column-count'];
				if (ds) {
					for (var i = 0; i < ds.length; i++) {
						var count = parseInt(ds[i].getValue(), 10);
						if (typeof count == 'number' && count > 1) {
							var r = ds[i].getRule();
							var mql = r.getMediaQueryList();
							var ss = r.getSelectors();
							var gap = parseInt(r.getPropertyValue('column-gap')); // must be pixels
							createColumns(mql, ss, count);
						}
					}
				}
			});
		}, 100);
	});
});


cp.requestJSON = function(url, fnSuccess, fnFailure) {
	if (ua.ie && !window.XMLHttpRequest) {
		window.XMLHttpRequest = function () {
			return new ActiveXObject('Microsoft.XMLHTTP');
		};
	}
	if (!XMLHttpRequest) {
		return;
	}
	var r = new XMLHttpRequest();
	r.open('get', url, true);
	var done = false;
	setTimeout(function() {
		done = true;
	}, 5000);
	r.onreadystatechange = function() {
		if (r.readyState == 4 && !done) {
			if (!r.status && location.protocol == 'file:' ||
					(r.status >= 200 && r.status < 300) ||
					r.status == 304 ||
					navigator.userAgent.indexOf('Safari') > -1 && typeof r.status == 'undefined') {
				var s = r.responseText;
				if (s && (s.charAt(0) == '[' || s.charAt(0) == '{')) {
					//var o = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
					//		s.replace(/"(\\.|[^"\\])*"/g, ''))) &&
					//		eval('(' + s + ')');
					var o = eval('(' + s + ')');
					fnSuccess(o);
				}
				else {
					fnSuccess();
				}
			}
			else if (typeof fnFailure == 'function') {
				fnFailure();
			}
			r = null; // avoid memory leaks
		}
	};
	r.send('');
};


domReady(function enhanceSearch() {
	var uri = '/tag/searchsuggestions/';
	var el = document.getElementById('search-tags');
	if (!el) return;
	el.setAttribute('autocomplete', 'off');
	
	var div = document.createElement('div');
	div.id = 'nav-suggestions';
	
	var showing = false;
	var show = function() {
		showing = true;
		div.style.marginTop = '';
	}
	
	var hide = function() {
		showing = false;
		div.style.marginTop = '-32767px';
	};
	
	var showResults = function(o) {
		if (o && o.suggestions) {
			var a = o.suggestions;
			var s = [], c = 0;
			if (a.length > 0) {
				s[c++] = '<ul>';
				for (var i = 0; i < a.length; i++) {
					s[c++] = '<li><a href="';
					s[c++] = a[i].url;
					s[c++] = '"><img src="';
					s[c++] = a[i].image;
					s[c++] = '" alt=""><strong>';
					s[c++] = a[i].title;
					s[c++] = '</strong><br>';
					s[c++] = a[i].lead;
					s[c++] = '</a></li>';
				}
				s[c++] = '</ul><p><a class="more" href="/tag/search/' + o.string + '">Bekijk alle zoekresultaten</a></p>';
			}
			else {
				s[c++] = '<p>Geen resultaten voor deze zoekterm(en).</p>';
			}
			div.innerHTML = s.join('');
			setTimeout(show, 100);
		}
	};
	
	hide();
	el.form.appendChild(div);
	
	var md = document.body.onmousedown || function() {};
	document.body.onmousedown = function() {
		hide();
		md();
	};
	
	div.onmousedown = function(e) {
		if (e) {
			e.stopPropagation();
		}
		else if (window.event) {
			window.event.cancelBubble = true;
		}
	};
	
	var timer;
	if (el.className.indexOf('labelled') == -1) {
		el.previousValue = this.value;
	}
	
	el.onkeyup = function() {
		var s = this.value;
		if (s != this.previousValue) {
			hide();
			clearTimeout(timer);
			if (s.length > 1) {
				timer = setTimeout(function() {
					cp.requestJSON(uri + s, showResults);
				}, 500);
			}
		}
		this.previousValue = s;
	};
	
	el.onkeydown = function(e) {
		if (!e && window.event) {
			e = window.event;
		}
		clearTimeout(timer);
		if (div.hasChildNodes() && (e.keyCode == 38 || e.keyCode == 40)) {
			if (!showing) {
				show();
			}
			else if (e.keyCode == 40) {
				//focusDown();
			}
			else if (e.keyCode == 38) {
				//focusUp();
			}
		}
	};
	
	var kd = document.onkeydown || function() {};
	document.onkeydown = function(e) {
		if (!e && window.event) {
			e = window.event;
		}
		if (e.keyCode == 27) {
			hide();
		}
		kd();
	};
	
	var x = el.onfocus || function() {};
	el.onfocus = function() {
		if (div.hasChildNodes()) {
			show();
		}
		x();
	};
	
	var browse = document.getElementById('to-nav-browse');
	if (browse) {
		browse.onfocus = hide;
	}
	
	el = null;
});


domReady(function spotlight() {
	var el = document.getElementById('spotlight');
	if (!(el && document.body.className == 'digifotopro')) return;
	el.className = 'enhanced';
	var els = el.getElementsByTagName('li');
	for (var i = 0, l = els.length; i < l; i++) {
		var li = els[i];
		li.style.cursor = 'pointer';
		li.onclick = function() {
			var first = el.getElementsByTagName('li')[0];
			if (this == first) {
				location.href = this.getElementsByTagName('a')[0].href;
			}
			else {
				el.insertBefore(first, this);
				first.className = this.className;
				el.insertBefore(this, el.firstChild);
				this.className = 'number-1';
			}
		}
		li = null;
	}
	var x = window.onload || function() {};
	window.onload = function() {
		x();
		setTimeout(function () {
			try {
				for (var i = 0, l = els.length; i < l; i++) {
					var li = els[i];
					var img = li.getElementsByTagName('img')[0];
					if (img && img.offsetHeight > 0) {
						if (ua.ie) {
							var refl = li.appendChild(document.createElement('img'));
							refl.src = img.src;
							refl.className = 'reflection';
						}
						else {
							var canvas = li.appendChild(document.createElement('canvas'));
							canvas.className = 'reflection';
							if (canvas.getContext) {
								var c = canvas.getContext('2d');
								canvas.width = canvas.offsetWidth;
								canvas.height = canvas.offsetHeight;
								c.save();
								c.translate(0, img.offsetHeight - 1);
								c.scale(1, -1);
								c.drawImage(img, 0, 0, canvas.offsetWidth, img.offsetHeight);
								c.restore();
								
								c.globalCompositeOperation = "destination-out";
								var g = c.createLinearGradient(0, 0, 0, canvas.offsetHeight);
								g.addColorStop(1, 'rgba(255, 255, 255, 1)');
								g.addColorStop(0, 'rgba(255, 255, 255, 0)');
								c.fillStyle = g;
								if (ua.webkit) {
									c.fill();
								} else {
									c.fillRect(0, 0, canvas.offsetWidth, canvas.offsetHeight);
								}
							}
						}
					}
				}
			}
			catch(e) {}
		}, 100);
	};
});


domReady(function calcBackorderAmounts() {
	var el = document.getElementById('backorder-amount');
	if (!el) {
		return;
	}
	
	var form = document.getElementById('backorder');
	var total;
	var calc = function() {
		var sum = 0;
		for (var i = 0; i < form.elements.length; i++) {
			var input = form.elements[i];
			if (input.name && input.name.substring(0, 9) == 'magazine[' && input.checked) {
				var price = input.parentNode.getElementsByTagName('span')[0];
				if (price) {
					var number = parseFloat(price.innerHTML.replace(',', '.'));
					if (number > 0) {
						sum += number;
					}
				}
			}
		}
		if (sum > 0) {
			var s = new String(Math.round(sum * 100, 10));
			s = s.substring(0, s.length - 2) + ',' + s.substring(s.length - 2);
		}
		else {
			var s = '0,00';
		}
		total.innerHTML = '&euro;&nbsp;' + s;
	};
	
	// add total container
	var table = document.createElement('table');
	var tr = document.createElement('tr');
	tr.className = 'total';
	var th = tr.appendChild(document.createElement('th'));
	th.appendChild(document.createTextNode('Totaal:'));
	total = tr.appendChild(document.createElement('td'));
	table.appendChild(tr);
	el.insertBefore(table, el.firstChild);
	
	// add event handlers
	for (var i = 0; i < form.elements.length; i++) {
		var input = form.elements[i];
		if (input.name && input.name.substring(0, 9) == 'magazine[') {
			input.onclick = calc;
		}
		input = null;
	}
	
	// run initially
	calc();
});


domReady(function faq() {
	var i = 0;
	var el = document.getElementById('faq-' + i);
	while (el) {
		el.style.display = 'none';
		var link = el.parentNode.getElementsByTagName('a')[0];
		(function(el) {
			link.onclick = function() {
				el.style.display = el.style.display == 'none' ? '' : 'none';
				return false;
			};
		})(el);
		i++;
		el = document.getElementById('faq-' + i);
	}
});


domReady(function dynamicInputLabels() {
	
	var showLabel = function(f) {
		if (f && (f.value == '' || f.value == f.innerLabel)) {
			f.className += ' labelled';
			f.value = f.innerLabel;
		}
	};
	
	var hideLabel = function(f) {
		if (f && f.value == f.innerLabel) {
			f.value = '';
		}
		if (f && f.value != f.innerLabel) {
			f.className = f.className.replace('labelled', '');
		}
	};
	
	var hideLabelsInForm = function(form) {
		var ls = form.getElementsByTagName('label');
		for (var i = 0; i < ls.length; i++) {
			hideLabel(document.getElementById(ls[i].htmlFor));
		}
	};
	
	var createInnerLabels = function(ls) {
		for (var i = 0; i < ls.length; i++) {
			(function() { // closure needed to induce scope
				var l = ls[i];
				
				// copy label text to empty field
				var f = document.getElementById(l.htmlFor);
				if (f && (f.type == 'text' || f.type == 'password' || f.tagName == 'TEXTAREA') && l.firstChild.nodeType == 3) {
					// hide label element
					l.style.position = 'absolute';
					l.style.top = l.style.left = '-9999em';
					if (f.type == 'password') {
						f.title = l.firstChild.nodeValue || 'Wachtwoord';
						f.innerLabel = 'xx';
					}
					else {
						f.title = f.innerLabel = l.firstChild.nodeValue;
					}
					showLabel(f);
					
					// remove label on field focus
					var x = f.onfocus || function() {};
					f.onfocus = function() {
						hideLabel(f);
						x();
					};
					
					// return label on field blur
					var y = f.onblur || function() {};
					f.onblur = function() {
						y();
						showLabel(f);
					};
					
					// remove labels on form submit
					var form = f.form;
					if (typeof form.handledByDynamicInputLabels == 'undefined') {
						form.handledByDynamicInputLabels = true;
						var z = form.onsubmit || function() {};
						form.onsubmit = function() {
							hideLabelsInForm(form);
							z();
						};
					}
				}
			})();
		}
	};

	var els = document.body.getElementsByTagName('fieldset');
	for (var i = 0; i < els.length; i++) {
		if (els[i].className.indexOf('few-fields') > -1) {
			createInnerLabels(els[i].getElementsByTagName('label'));
		}
	}
});


domReady(function addPrintButton() {
	var el = document.getElementById('container');
	if (el && (el.className.indexOf('article ') > -1 && el.className.indexOf('article-type-7') == -1 || el.className.indexOf('page ') > -1)) {
		el = document.getElementById('generic-options').getElementsByTagName('ul')[0];
		var li = document.createElement('li');
		li.className = 'print';
		li.innerHTML = '<a href="javascript:window.print()">Druk af</a>';
		if (el.firstChild) {
			el.insertBefore(li, el.firstChild);
		}
		else {
			el.appendChild(li);
		}
	}
});


domReady(function enhanceArchiveSelect() {
	var select = document.getElementById('select-archive');
	if (!select) {
		return;
	}
	select.onchange = function() {
		this.form.submit();
	};
	var els = select.parentNode.getElementsByTagName('input');
	for (var i = 0; i < els.length; i++) {
		if (els[i].type == 'submit') {
			els[i].style.display = 'none';
		}
	}
	select = null;
});

if (ua.ie6) {
	domReady(function alertOutdatedBrowser() {
		var el = document.getElementById('feedback-message');
		if (!el) {
			var c = document.getElementById('container');
			el = c.insertBefore(document.createElement('div'), c.firstChild);
			el.id = 'feedback-message';
		}
		var warning = el.appendChild(document.createElement('div'));
		warning.innerHTML = '<strong>Waarschuwing: verouderde webbrowser!</strong> Uw Internet Explorer versie is 7 jaar oud, onveilig en niet in staat om een moderne website in haar volle glorie te bekijken. U wordt aangeraden uw webbrowser op te waarderen naar de nieuwste versie van <a href="http://www.microsoft.nl/ie">Internet Explorer</a>, <a href="http://www.mozilla-europe.org/firefox">Mozilla Firefox</a>, <a href="http://www.google.com/chrome">Google Chrome</a>, <a href="http://www.apple.nl/safari">Apple Safari</a> of <a href="http://www.opera.com/">Opera</a>.';
	});
	
	domReady(function energizeNavigation() {
		var el = document.getElementById('to-nav-browse');
		var nav = document.getElementById('nav-browse');
		if (el) {
			el.onmouseover = nav.onmouseover = function () {
				nav.style.marginTop = 0;
				return false;
			};
			nav.onmouseout = function () {
				nav.style.marginTop = '';
				return false;
			};
		}
	});
}

if (ua.ie6 || ua.ie7) {
	domReady(function fixThumbnailClick () {
		var prim = document.getElementById('primary');
		if (prim) {
			var els = prim.getElementsByTagName('span');
			for (var i = 0; i < els.length; i++) {
				var el = els[i];
				if (el.className === 'image'
						&& el.parentNode.tagName.toLowerCase() === 'a'
						&& el.parentNode.parentNode.parentNode.className === 'article-lead') {
					el.onclick = function () {
						window.location = this.parentNode.href;
						return false;
					};
					el.style.cursor = 'pointer';
					el = null;
				}
			}
		}
	});
}

domReady(function enhanceFeedbackMessage() {
	var el = document.getElementById('feedback-message');
	if (!el) {
		return;
	}
	var close = document.createElement('div');
	close.appendChild(document.createTextNode('Sluit'));
	close.className = 'close';
	close.onclick = function() {
		el.style.display = 'none';
	};
	el.appendChild(close);
	close = null;
});


domReady(function enhanceHighlights() {
	var el = document.getElementById('highlights');
	if (!el) {
		return;
	}
	var active;
	var els = el.getElementsByTagName('div');
	for (var i = 0; i < els.length; i++) {
		(function() {
			var item = els[i];
			if (item.className == 'item') {
				if (i > 0) {
					item.style.display = 'none';
				}
				else {
					active = item;
				}
				var button = item.button = el.appendChild(document.createElement('div'));
				if (i > 0) {
					button.className = 'button';
				}
				else {
					button.className = 'button button-active';
				}
				button.innerHTML = (i + 1);
				button.onclick = function() {
					active.style.display = 'none';
					active.button.className = 'button';
					item.style.display = '';
					this.className = 'button button-active';
					active = item;
				};
				button = null;
			}
		})();
	}
});


domReady(function targetExternalLinks() {
	var els = document.body.getElementsByTagName('a');
	for (var i = 0; i < els.length; i++) {
		if (els[i].className.indexOf('external') > -1) {
			els[i].target = '_blank';
		}
	}
});


domReady(function enhanceCommentPosting() {
	var user = document.getElementById('comment-user');
	var manual = document.getElementById('comment-manual');
	var message = document.getElementById('fieldset-message');
	if (user && manual) {
		var selected;
		user.linkedFieldset = document.getElementById('fieldset-user');
		manual.linkedFieldset = document.getElementById('fieldset-manual');
		var show = function(el) {
			el.linkedFieldset.style.display = '';
		};
		var hide = function(el) {
			el.linkedFieldset.style.display = 'none';
		};
		user.onclick = manual.onclick = function() {
			if (selected != this) {
				if (selected) {
					hide(selected);
				}
				show(this);
				selected = this;
			}
			message.style.display = '';
		};
		hide(user);
		hide(manual);
		message.style.display = 'none';
		if (user.checked) {
			user.onclick();
		}
		else if (manual.checked) {
			manual.onclick();
		}
	}
});


domReady(function enhanceRegistration() {
	var bar = document.getElementById('personalbar');
    if (!bar) {
        return;
    }
	bar.className += ' js-enhanced';
	var initForm = function(s) {
		var el = document.getElementById(s + '-heading');
		if (el) {
			var form = el.parentNode;
			form.className += ' collapsed';
			form.collapsed = true;
			var x = document.body.onclick || function() {};
			form.show = function() {
				form.className = form.className.replace('collapsed', 'expanded');
				form.collapsed = false;
				if (bar.expandedForm && bar.expandedForm != form) {
					bar.expandedForm.hide();
				}
				bar.expandedForm = form;
				// timeout needed to not make below handlers function immediately
				setTimeout(function() {
					document.body.onclick = function() {
						x();
						if (!el.collapsed) {
							form.hide();
						}
					};
					form.onclick = function(e) {
						if (window.event) {
							window.event.cancelBubble = true;
						}
						else if (e) {
							e.stopPropagation();
						}
					};
					bar.className += ' something-expanded';
				}, 50);
			};
			form.hide = function() {
				form.className = form.className.replace('expanded', 'collapsed');
				bar.className = bar.className.replace(/something-expanded/g, '');
				form.collapsed = true;
				bar.expandedForm = null;
				document.body.onclick = x;
				form.onclick = function() {};
			};
			el.onclick = function() {
				if (form.collapsed) {
					form.show();
				}
				else {
					form.hide();
				}
			};
			var addFocusHandler = function(f) {
				if (form.collapsed) {
					var y = f.onfocus || function() {};
					f.onfocus = function() {
						y();
						form.show();
					};
				}
			};
			for (var i = 0; i < form.elements.length; i++) {
				addFocusHandler(form.elements[i]);
			}
		}
	};
	initForm('forgot');
	initForm('register');
});


function paginateSections() {
	var ul = document.getElementById('article-toc');
	if (!ul) return;
	var idx = location.href.indexOf('#');
	var requestAnchor = '';
	if (idx != -1)
		requestAnchor = location.href.substring(idx + 1);
		
	ul.style.display = 'none';
	var el = document.createElement('select');
	el.onchange = function() {
		for (var i = 0, l = this.values.length; i < l; i++) {
			document.getElementById(this.values[i]).style.display = 'none';
		}
		var id = this.value;
		var sectionSelected = id.substring(0, 8) == 'section-';
		if (!sectionSelected || id == 'section-' + this.lastSectionIndex) {
			for (var i = this.lastSectionIndex, l = this.values.length; i < l; i++) {
				document.getElementById(this.values[i]).style.display = '';
			}
		}
		else
			document.getElementById(id).style.display = '';
		if (sectionSelected)
			window.scrollTo(0, 0);
		else
			window.location = '#' + id;
		for (var i = 0, l = linkBlocks.length; i < l; i++) {
			linkBlocks[i].goPage(this.selectedIndex, true);
		}
	};
	el.values = [];
	for (var nl = ul.getElementsByTagName('a'), i = 0, n = nl[0]; n; n = nl[++i]) {
		var opt = document.createElement('option');
		opt.appendChild(n.firstChild.cloneNode(false));
		var id = n.href.substring(n.href.indexOf('#') + 1);
		opt.value = el.values[el.values.length] = id;
		if (i == 0)
			currentSectionPage = document.getElementById(id);
		else
			document.getElementById(id).style.display = 'none';
		if (typeof el.lastSectionIndex == 'undefined' && id.substring(0, 8) != 'section-')
			el.lastSectionIndex = i - 1;
		el.appendChild(opt);
	}
	ul.parentNode.insertBefore(el, ul);
	
	
	function PaginationLinkBlock() {
		var that = this;
		var currentPage = 0;
		var pages = document.createElement('p');
		pages.className = 'pages';
		var prev = pages.appendChild(document.createElement('a'));
		prev.href = '';
		prev.className = 'prev';
		prev.style.display = 'none';
		prev.appendChild(document.createTextNode('Vorige'));
		prev.onclick = function() {
			that.goPrevPage();
			return false;
		};
		var numbers = pages.appendChild(document.createElement('span'));
		numbers.className = 'numbers';
		var numberLinks = [];
		for (var i = 0, l = el.values.length; i < l && el.values[i].substring(0, 8) == 'section-'; i++) {
			var n = numbers.appendChild(document.createElement('a'));
			n.href = '';
			n.pageNumber = i;
			if (i == 0)
				n.className = 'current';
			n.appendChild(document.createTextNode(i + 1));
			n.onclick = function() {
				that.goPage(this.pageNumber);
				return false;
			};
			numberLinks[numberLinks.length] = n;
		}
		var next = pages.appendChild(document.createElement('a'));
		next.href = '';
		next.className = 'next';
		next.appendChild(document.createTextNode('Volgende'));
		next.onclick = function() {
			that.goNextPage();
			return false;
		};
		
		this.goPage = function(i, numbersOnly) {
			i = i > numberLinks.length - 1 ? numberLinks.length - 1 : i;
			prev.style.display = (i == 0) ? 'none' : '';
			next.style.display = (i == numberLinks.length - 1) ? 'none' : '';
			numberLinks[currentPage].className = '';
			numberLinks[i].className = 'current';
			el.value = el.values[i];
			if (!numbersOnly)
				el.onchange();
			currentPage = i;
		}
		
		this.goPrevPage = function() {
			this.goPage(currentPage - 1);
		}
		
		this.goNextPage = function() {
			this.goPage(currentPage + 1);
		}
		
		this.element = pages;
	}
	
	var linkBlocks = [new PaginationLinkBlock(), new PaginationLinkBlock()];
	ul.parentNode.insertBefore(linkBlocks[0].element, ul);
	var article = document.getElementById('article');
	var ref = document.getElementById('banner-article-bottom');
	if (ref) {
		var div = ref.parentNode.insertBefore(document.createElement('div'), ref);
	}
	else {
		var div = article.appendChild(document.createElement('div'));
	}
	div.className = 'pagination';
	div.appendChild(linkBlocks[1].element);
	
	var commentsLink = document.getElementById('to-comments')
	var goComments = function() {
		el.value = 'comments';
		el.onchange();
		return false;
	};
	commentsLink.onclick = goComments;
	var goComment = function(i) {
		el.value = 'comments';
		el.onchange();
		window.location = '#comment-' + i;
	};
	var goPostComment = function() {
		el.value = 'comments';
		el.onchange();
		window.location = '#post-comment';
	};
	var goRating = function() {
		el.value = 'comments';
		el.onchange();
		window.location = '#rating';
	};
	if (requestAnchor == 'comments')
		goComments();
	else if (requestAnchor.substring(0, 8) == 'comment-')
		goComment(requestAnchor.substring(8));
	else if (requestAnchor == 'post-comment')
		goPostComment();
	else if (requestAnchor == 'rating')
		goRating();
}
domReady(paginateSections);


/*
 * Copyright (C) 2007-2008 Diego Perini
 * All rights reserved.
 *
 * nwmatcher.js - A fast selector engine not using XPath
 *
 * Author: Diego Perini <diego.perini at gmail com>
 * Version: 1.0
 * Created: 20070722
 * Release: 20080824
 *
 * License:
 *	http://javascript.nwbox.com/NWMatcher/MIT-LICENSE
 * Download:
 *	http://javascript.nwbox.com/NWMatcher/nwmatcher.js
 */

window.NW || (window.NW = {});

NW.Dom = function() {

	var version = '1.0',

	// selection functions returning collections
	compiledSelectors = { },

	// matching functions returning booleans
	compiledMatchers = { },

	// cached selection results
	cachedResults = {
		from: [ ],
		items: [ ]
	},

	// attribute names may be passed case insensitive
	// accepts chopped attributes like "class" and "for"
	camelProps = [
		'htmlFor','className','tabIndex','accessKey','maxLength',
		'readOnly','longDesc','frameBorder','isMap','useMap','noHref','noWrap',
		'colSpan','rowSpan','cellPadding','cellSpacing','marginWidth','marginHeight'
	],

	// child pseudo selector (CSS3)
	child_pseudo = /\:(nth|first|last|only)\-/,
	// of-type pseudo selectors (CSS3)
	oftype_pseudo = /\-(of-type)/,

	// trim whitespaces
	TR = /^\s+|\s+$/g,

	// precompiled Regular Expressions
	Patterns = {
		// nth child pseudos
		npseudos: /^\:(nth-)?(first|last|only)?-?(child)?-?(of-type)?(\((?:even|odd|[^\)]*)\))?(.*)/,
		// simple pseudos
		spseudos: /^\:([\w]+)(\(([\x22\x27])?(.*?(\(.*?\))?[^(]*?)\3\))?(.*)/,
		// E > F
		children: /^\s*\>\s*(.*)/,
		// E + F
		adjacent: /^\s*\+\s*(.*)/,
		// E ~ F
		relative: /^\s*\~\s*(.*)/,
		// E F
		ancestor: /^(\s+)(.*)/,
		// attribute
		attribute: /^\[([-\w]*:?[-\w]+)\s*(?:([!^$*~|])?(\=)?\s*([\x22\x27])?([^\4]*?)\4|([^\4][^\]]*?))\](.*)/,
		// all
		all: /^\*(.*)/,
		// id
		id: /^\#([-\w]+)(.*)/,
		// tag
		tagName: /^([-\w]+)(.*)/,
		// class
		className: /^\.([-\w]+)(.*)/
	},

	// initial optimizations
	Optimizations = {
		// all elements
		all: /(^\*)$/,
		// single class, id, tag
		id: /^\#([-\w]+)$/,
		tagName: /^([\w]+)$/,
		className: /^\.([-\w]+)$/
	},

	// convert nodeList to array
	toArray =
		function(iterable) {
			var length = iterable.length, array = new Array(length);
			while (length--) {
				array[length] = iterable[length];
			}
			return array;
		},

	// compile a CSS3 string selector into
	// ad-hoc javascript matching function
	compileSelector =
		// @mode boolean true for select, false for match
		function(selector, source, mode) {

			var a, b, i,
					// building placeholders
					compare, match, param, test, type,
					attributeValue, attributePresence;

			while (selector) {

				// * match all
				if ((match = selector.match(Patterns.all))) {
					// on IE remove comment nodes to avoid this
					source = 'if(e.nodeType==1){' + source + '}';
				}
				// #Foo Id case sensitive
				else if ((match = selector.match(Patterns.id))) {
					// this is necessary because form elements using reserved words as id/name can overwrite form properties (ex. name="id")
					source = 'if(e.id&&e.id=="' + match[1] + '"||((a=e.getAttributeNode("id"))&&a.value=="' + match[1] + '")){' + source + '}';
				}
				// Foo Tag case insensitive
				else if ((match = selector.match(Patterns.tagName))) {
					source = 'if(e.nodeName.toLowerCase()=="' + match[1].toLowerCase() + '"){' + source + '}';
				}
				// .Foo Class case sensitive
				else if ((match = selector.match(Patterns.className))) {
					source = 'if(e.className&&((" "+e.className).replace(/\\s+/g," ") + " ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}';
				}
				// [attr] [attr=value] [attr="value"] and !=, *=, ~=, |=, ^=, $=
				else if ((match = selector.match(Patterns.attribute))) {

					// fix common misCased attribute names
					compare = match[1];
					for (i = 0; i < camelProps.length; ++i) {
						if (camelProps[i].toLowerCase().indexOf(match[1]) == 0) {
							compare = camelProps[i];
							break;
						}
					}

					if (/\w+:\w+/.test(match[1])) {
						// XML namespaced attributes
						attributeValue = '(((a=e.getAttribute("' + match[1] + '",1))&&a)||"")';
					} else if ("|action|data|href|longdesc|lowsrc|src|".indexOf(match[1]) > -1) {
						// specific URI attributes
						attributeValue = '(((a=e.getAttribute("' + match[1] + '",2))&&a)||"")';
					} else {
						// others by property value
						attributeValue = '(e.' + compare + '||"")';
					}

					if (typeof document.fileSize != 'undefined') {
						// on IE check the "specified" property on the attribute node
						attributePresence = '((a=e.getAttributeNode("' + match[1] + '"))&&a.specified)';
					} else {
						attributePresence = 'e.hasAttribute("' + match[1] + '")';
					}

					// match[1] - attribute name
					// match[2] - operator type
					// match[3] - equal sign
					// match[4] - quotes
					// match[5] - value

					// no "*" operator in these conditionals
					// .match() will handle it by exclusion
					// for case insensitive matches use "|"
					source = 'if(' +
						// match attribute or property
						(match[2] && match[3] && match[5] && match[2] != '!' ?
							// replace possible whitespaces with space in properties values
							// and build a "-propval-" or " propval " string to exactly match
							(match[2] == '~' ? '(" "+' : (match[2] == '|' ? '("-"+' : '')) + attributeValue +
								(match[2] == '!' || match[2] == '~' ? '.replace(/\\s+/g," ")' : '') +
							(match[2] == '~' ? '+" ")' : (match[2] == '|' ? '+"-")' : '')) +
								// BEGIN: add an indexOf() or match() where it applies ( ! and ~ use indexOf)
								(match[2] == '!' || match[2] == '~' ? '.indexOf("' : '.match(/') +
									// build the content of the indexOf search or the match
									(match[2] == '^' ? '^' : match[2] == '~' ? ' ' : match[2] == '|' ? '-' : '') +
										match[5] +
									(match[2] == '$' ? '$' : match[2] == '~' ? ' ' : match[2] == '|' ? '-' : '') +
								// END: close the indexOf or match()
								(match[2] == '!' || match[2] == '~' ? '")>-1' : (match[2] == '|' ? '/i' : '/') + ')') :
							// add rigth side of comparison when no indexOf / match
							// when we have to exactly match or not a value ( ! or = )
							(match[3] && match[5] ?
								attributeValue + (match[2] == '!' ? '!' : '=') + '="' + match[5] + '"' :
									// or just check for attribute presence
									attributePresence)) +
					'){' + source + '}';
				}
				// E + F (F adiacent sibling of E)
				else if ((match = selector.match(Patterns.adjacent))) {
					source = 'while(e.previousSibling){e=e.previousSibling;if(e.nodeType==1){' + source + 'break;}}';
				}
				// E ~ F (F relative sibling of E)
				else if ((match = selector.match(Patterns.relative))) {
					source = 'while(e.previousSibling){e=e.previousSibling;if(e.nodeType==1){' + source.replace(/\}$/, 'break;}') + '}}';
				}
				// E > F (F children of E)
				else if ((match = selector.match(Patterns.children))) {
					source = 'while(e.parentNode.nodeType==1){e=e.parentNode;' + source + 'break;}';
				}
				// E F (E ancestor of F)
				else if ((match = selector.match(Patterns.ancestor))) {
					source = 'while(e.parentNode.nodeType==1){e=e.parentNode;' + source.replace(/\}$/, 'break;}') + '}';
				}
				// :first-child, :last-child, :only-child,
				// :first-child-of-type, :last-child-of-type, :only-child-of-type,
				// :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type()
				else if ((match = selector.match(Patterns.npseudos)) && (match[2] || match[5])) {
					// snapshot collection type to use Twin or Child
					type = match[4] == 'of-type' ? 'Twin' : 'Child';

					if (match[5]) {
						// remove the ( ) grabbed above
						match[5] = match[5].replace(/\(|\)/g, '');

						if (match[5] == 'even') {
							a = 2;
							b = 0;
						} else if (match[5] == 'odd') {
							a = 2;
							b = 1;
						} else {
							// assumes correct "an+b" format
							a = match[5].match(/^-/) ? -1 : match[5].match(/^n/) ? 1 : 0;
							a = a || ((param = match[5].match(/(-?\d{1,})n/)) ? parseInt(param[1], 10) : 0);
							b = 0 || ((param = match[5].match(/(-?\d{1,})$/)) ? parseInt(param[1], 10) : 0);
						}

						compare =
							(match[2] == 'last' ?
								'(s.' + type + 'Lengths[s.' + type + 'Parents[u]]' +
								(match[4] == 'of-type' ? '[e.nodeName.toUpperCase()]' : '') + '-' + (b - 1) + ')' : b);

						// handle 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type)
						test = match[5] == 'even' ||
							match[5] == 'odd' ||
							a > Math.abs(b) ?
								('%' + a + '==' + b) :
							a < 0 ?
								'<=' + compare :
							a > 0 ?
								'>=' + compare :
							a == 0 ?
								'==' + compare :
								'';

						if (mode) {
							// add function for select method (mode=true)
							// requires prebuilt array get[Childs|Twins]
							source = 'u=s.getIndex(e)+1;' +
								'if(s.' + type + 'Indexes[u]' + test + '){' + source + '}';
						} else {
							// add function for "match" method (mode=false)
							// this will not be in a loop, this is faster
							// for "match" but slower for "select" and it
							// also does not require prebuilt node array
							source = 'if((n=e)){' +
								'u=1' + (match[4] == 'of-type' ? ',t=e.nodeName;' : ';') +
								'while((n=n.' + (match[2] == 'last' ? 'next' : 'previous') + 'Sibling)){' +
									'if(n.node' + (match[4] == 'of-type' ? 'Name==t' : 'Type==1') + '){++u;}' +
								'}' +
								'if(u' + test + '){' + source + '}' +
							'}';
						}
					} else {
						// handle 6 cases: 3 (first, last, only) x 1 (child) x 2 (-of-type)
						compare =
							's.' + type + 'Lengths[s.' + type + 'Parents[u]]' +
							(match[4] == 'of-type' ? '[e.nodeName.toUpperCase()]' : '');

						if (mode) {
							// add function for select method (mode=true)
							source = 'u=s.getIndex(e)+1;' +
								'if(' +
									(match[2] == 'first' ?
										's.' + type + 'Indexes[u]==1' :
										match[2] == 'only' ?
											compare + '==1' :
											match[2] == 'last' ?
												's.' + type + 'Indexes[u]==' + compare : '') +
								'){' + source + '}';
						} else {
							// add function for match method (mode=false)
							source = 'if((n=e)){' +
								(match[4] ? 't=e.nodeName;' : '') +
								'while((n=n.' + (match[2] == 'first' ? 'previous' : 'next') + 'Sibling)&&' +
									'n.node' + (match[4] ? 'Name!=t' : 'Type!=1') + ');' +
								'if(!n&&(n=e)){' +
									(match[2] == 'first' || match[2] == 'last' ?
										'{' + source + '}' :
										'while((n=n.' + (match[2] == 'first' ? 'next' : 'previous') + 'Sibling)&&' +
												'n.node' + (match[4] ? 'Name!=t' : 'Type!=1') + ');' +
										'if(!n){' + source + '}') +
								'}' +
							'}';
						}
					}
				}
				// CSS3 :not, :root, :empty, :contains, :enabled, :disabled, :checked, :target
				// CSS2 :active, :focus, :hover (no way yet)
				// CSS1 :link, :visited
				else if ((match = selector.match(Patterns.spseudos))) {
					switch (match[1]) {
						// CSS3 part of structural pseudo-classes
						case 'not':
							source = compileSelector(match[2].replace(/\((.*)\)/, '$1'), source, mode).replace(/if([^\{]+)/, 'if(!$1)');
							break;
						case 'root':
							source = 'if(e==(e.ownerDocument||e.document||e).documentElement){' + source + '}';
							break;
						case 'empty':
							// IE does not support empty text nodes, HTML white spaces and CRLF are not in the DOM
							source = 'if(/^\\s*$/.test(e.innerHTML)&&!/\\r|\\n/.test(e.innerHTML)){' + source + '}';
							break;
						case 'contains':
							source = 'if((e.textContent||e.innerText||"").indexOf("' + match[2].replace(/\(|\)/g, '') + '")!=-1){' + source + '}';
							break;
						// CSS3 part of UI element states
						case 'enabled':
							source = 'if(e.type&&e.type!="hidden"&&!e.disabled){' + source + '}';
							break;
						case 'disabled':
							source = 'if(e.type&&e.type!="hidden"&&e.disabled){' + source + '}';
							break;
						case 'checked':
							source = 'if(e.type&&e.type!="hidden"&&e.checked){' + source + '}';
							break;
						// CSS3 target element
						case 'target':
							source = 'if(e.id==location.href.match(/#([_-\w]+)$/)[1]){' + source + '}';
							break;
						// CSS1 & CSS2 link
						case 'link':
							source = 'if(e.nodeName.toUpperCase()=="A"&&e.href){' + source + '}';
							break;
						case 'visited':
							source = 'if(e.nodeName.toUpperCase()=="A"&&e.visited){' + source + '}';
							break;
						// CSS1 & CSS2 user action
						case 'active':
							// IE & FF3 have native method, others may have it emulated,
							// this may be done in the event manager setting activeElement
							source = 'var d=(e.ownerDocument||e.document);' +
											 'if(d.activeElement&&e===d.activeElement){' + source + '}';
							break;
						case 'hover':
							// IE & FF3 have native method, other browser may achieve a similar effect
							// by delegating mouseover/mouseout handling to document/documentElement
							source = 'var d=(e.ownerDocument||e.document);' +
											 'if(d.hoverElement&&e===d.hoverElement){' + source + '}';
							break;
						case 'focus':
							// IE, FF3 have native method, others may have it emulated,
							// this may be done in the event manager setting focusElement
							source = 'var d=(e.ownerDocument||e.document);' +
											 'if(e.type&&e.type!="hidden"&&' +
												 '((e.hasFocus&&e.hasFocus())||' +
												 '(d.focusElement&&e===d.focusElement))){' + source + '}';
							break;
						default:
							break;
					}
				}
				else throw new Error('NW.Dom.compileSelector: syntax error, unknown selector rule "' + selector + '"');

				selector = match[match.length - 1];
			}

			return source;
		},

	// compile a comma separated group of selector
	// @mode boolean true for select, false for match
	compileGroup =
		function(selector, mode) {
			var i = 0, source = '', token, cachedTokens = {}, parts = selector.split(',');
			// for each selector in the group
			for ( ; i < parts.length; ++i) {
				token = parts[i].replace(TR, '');
				// if we have a selector string
				if (token && token.length > 0) {
					// avoid repeating the same functions
					if (!cachedTokens[token]) {
						cachedTokens[token] = token;
						// insert corresponding mode function
						if (mode) {
							source += compileSelector(token, 'r[r.length]=c[k];', mode);
						} else {
							source += compileSelector(token, 'return true;', mode);
						}
					}
				}
			}
			if (mode) {
				// for select method
				return new Function('c,s', 'var k=-1,e,r=[],n,j,u,t,a;while((e=c[++k])){' + source + '}return r;');
			} else {
				// for match method
				return new Function('e', 'var n,u,a;' + source	+ 'return false;');
			}
		},

	// snapshot of elements contained in rootElement
	// also contains maps to make nth lookups faster
	// updated when the elements in the DOM change
	Snapshot = {
		Elements: [],
		TwinIndexes: [],
		TwinLengths: [],
		TwinParents: [],
		ChildIndexes: [],
		ChildLengths: [],
		ChildParents: [],
		hasElements: false,
		hasTwinIndexes: false,
		hasChildIndexes: false,
		getIndex:
			function(e) {
				return getIndex(this.Elements, e);
			}
	},

	// get element index in a node array
	getIndex =
		function(array, element) {
			// IE only (too slow in opera)
			if (typeof document.fileSize != 'undefined') {
				getIndex = function(array, element) {
					return element.sourceIndex || -1;
				};
			// gecko, webkit have native array indexOf
			} else if (array.indexOf) {
				getIndex = function(array, element) {
					return array.indexOf(element);
				};
			// other browsers will use this replacement
			} else {
				getIndex = function(array, element) {
					var i = array.length;
					while (--i >= 0) {
						if (element == array[i]) {
							break;
						}
					}
					return i;
				};
			}
			return getIndex(array, element);
		},

	// build a twin index map by tag position
	getTwins =
		function(f, c) {
			var k = 0, e, r, p, s, x,
				h = [f], b = [0], i = [0], l = [0];
			while ((e = c[k++])) {
				h[k] = e;
				l[k] = 0;
				p = e.parentNode;
				r = e.nodeName;
				if (s != p) {
					x = getIndex(h, s = p);
				}
				b[k] = x;
				l[x] = l[x] || {};
				l[x][r] = l[x][r] || 0;
				i[k] = ++l[x][r];
			}
			Snapshot.TwinParents = b;
			Snapshot.TwinIndexes = i;
			Snapshot.TwinLengths = l;
		},

	// build a child index map by child position
	getChilds =
		function(f, c) {
			var	k = 0, e, p, s, x,
				h = [f], b = [0], i = [0], l = [0];
			while ((e = c[k++])) {
				h[k] = e;
				l[k] = 0;
				p = e.parentNode;
				if (s != p) {
					x = getIndex(h, s = p);
				}
				b[k] = x;
				i[k] = ++l[x];
			}
			Snapshot.ChildParents = b;
			Snapshot.ChildIndexes = i;
			Snapshot.ChildLengths = l;
		},

	// set this to true to always enable or
	// switch manually using setCache(true)
	cachingEnabled = false,

	// enable caching system
	// @d optional document context
	setCache =
		function(enable, d) {
			expireCache();
			d || (d = document);
			if (!cachingEnabled && enable) {
				// FireFox/Opera/Safari/KHTML support both Mutation Events
				d.addEventListener('DOMNodeInserted', expireCache, false);
				d.addEventListener('DOMNodeRemoved', expireCache, false);
				cachingEnabled = true;
			} else if (cachingEnabled) {
				d.removeEventListener('DOMNodeInserted', expireCache, false);
				d.removeEventListener('DOMNodeRemoved', expireCache, false);
				cachingEnabled = false;
			}
		},

	// expose the private method
	expireCache =
		function() {
			Snapshot.hasElements = false;
			Snapshot.hasTwinIndexes = false;
			Snapshot.hasChildIndexes = false;
			cachedResults = {
				from: [],
				items: []
			};
		};

	if (
		document.implementation.hasFeature("MutationEvents", "2.0") ||
		document.implementation.hasFeature("Events", "2.0") &&
		document.implementation.hasFeature("Core", "2.0")) {
		// enable caching on browser supporting either Mutation Events (FF3/Safari/Opera/Konqueror)
		// or Core Events 2.0 (FF2 supports Mutation Events but are not shown in the implementation)
		setCache(true);
		// on page unload remove event listeners and cleanup
		window.addEventListener('beforeunload', function() {
				window.removeEventListener('beforeunload', arguments.callee, false);
				setCache(false);
			}, false
		);
	}

	return {

		// for testing purposes only!
		compile:
			function(selector) {
				return compileGroup(selector, true).toString();
			},

		// expose caching methods
		setCache: setCache,

		expireCache: expireCache,

		// element match selector return boolean true/false
		match:
			function(element, selector) {

				// make sure an element node was passed
				if (!(element && element.nodeType == 1)) {
					return false;
				}

				if (typeof selector == 'string' && selector.length) {

					// cache compiled matchers
					if (!compiledMatchers[selector]) {
						compiledMatchers[selector]=compileGroup(selector, false);
					}

					// result of compiled matcher
					return compiledMatchers[selector](element);

				} else throw new Error('NW.Dom.match: "' + selector + '" is not a valid CSS selector.');

				return false;
			},

		// elements matching selector optionally starting from node
		select:
			function(selector, from) {

				var elements = [], match;

				if (!(from && (from.nodeType == 1 || from.nodeType == 9))) {
					from = document;
				}

				if (typeof selector == 'string' && selector.length) {

					// BEGIN REDUCE/OPTIMIZE
					// * (all elements selector)
					if ((match = selector.match(Optimizations.all))) {
						var nodes, node, i = -1;
						// fix IE comments as element
						nodes = from.getElementsByTagName('*');
						while ((node = nodes[++i])) {
							if (node.nodeType == 1) {
								elements[elements.length] = node;
							}
						}
						return elements;
					}
					// #Foo Id (single id selector)
					else if ((match = selector.match(Optimizations.id))) {
						var element = from.getElementById(match[1]);
						return element ? [element] : [];
					}
					// Foo Tag (single tag selector)
					else if ((match = selector.match(Optimizations.tagName))) {
						return toArray(from.getElementsByTagName(match[1]));
					}
					// END REDUCE/OPTIMIZE

					if (cachingEnabled && Snapshot.hasElements) {
						elements = Snapshot.Elements;
					} else {
						elements = toArray(from.getElementsByTagName('*'));
						Snapshot.Elements = elements;
						Snapshot.hasTwinIndexes = false;
						Snapshot.hasChildIndexes = false;
					}

					// normal nth/child pseudo selectors
					if (selector.match(child_pseudo)) {
						if (!cachingEnabled || !Snapshot.hasChildIndexes) {
							getChilds(from, elements);
							Snapshot.hasChildIndexes = true;
						}
					}

					// special of-type pseudo selectors
					if (selector.match(oftype_pseudo)) {
						if (!cachingEnabled || !Snapshot.hasTwinIndexes) {
							getTwins(from, elements);
							Snapshot.hasTwinIndexes = true;
						}
					}

					Snapshot.hasElements = true;

					// cache compiled selectors
					if (!compiledSelectors[selector]) {
						compiledSelectors[selector] = compileGroup(selector, true);
					}

					if (cachingEnabled) {

						if (!(cachedResults.items[selector] && cachedResults.from[selector] == from)) {
							cachedResults.items[selector] = compiledSelectors[selector](elements, Snapshot);
							cachedResults.from[selector] = from;
						}
						// a previously cached selection of the same selector
						return cachedResults.items[selector];

					} else {

						// a live selection of the requested selector
						return compiledSelectors[selector](elements, Snapshot);

					}

				} else throw new Error('NW.Dom.select: "' + selector + '" is not a valid CSS selector.');

				return [];
			}
	};

}();
