(function() {	
	"use strict";
	
	function getRequestRandom()
	{
		return Math.floor(Math.random() * (10000000000000 + 1));
	}	
	
	function loadScripts(uris,whenDone){
		if (!uris.length) whenDone && whenDone();
		else{
			for (var wait=[],i=uris.length;i--;){
				var tag  = document.createElement('script');
				tag.type = 'text/javascript';
				tag.src  = uris[i];
				if (whenDone){
					wait.push(tag)
					tag.onload = maybeDone; 
					tag.onreadystatechange = maybeDone; // For IE8-
				}
				document.head.appendChild(tag);
			}
		}
		function maybeDone(){
			if (this.readyState===undefined || this.readyState==='complete'){
				for (var i=wait.length;i--;) if (wait[i]==this) wait.splice(i,1);
				if (!wait.length) whenDone();
			}
		}
	}
	
	function getPrototypeName(obj) {
	   var funcNameRegex = /function (.{1,})\(/;
	   var results = (funcNameRegex).exec((obj).constructor.toString());
	   return (results && results.length > 1) ? results[1] : "";
	};
	
	function isFunction(functionToCheck) {
		 var getType = {};
		 return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
	}
	
	function getWindowFromElement(element)
	{
		var $e = $(element);
		var e = $e[0];
		var doc = e.ownerDocument;
		var win = doc.defaultView || doc.parentWindow;
		return win;
	}
	
	function del(obj, prop)
	{
		if (obj && obj[prop])
		{
			if (obj[prop].unload && isFunction(obj[prop].unload))
			{
				obj[prop].unload();
			}
			
			//log('deleting ' + prop + ' from ' + getPrototypeName(obj));
			delete obj[prop];
		}
	}
	
	function deleteObject(obj)
	{
		for (var member in obj) delete obj[member];
	}
	
	function loadScriptsNoCache(uris,whenDone)
	{	
		var i;
		for(i=0;i<uris.length;i+=1)
		{
			uris[i] = uris[i] + '?_='+getRequestRandom();
		}
		
		loadScripts(uris, whenDone);
	}
	
	function supportsStorage() {
	  try {
		return 'localStorage' in window && window['localStorage'] !== null;
	  } catch (e) {
		return false;
	  }
	}
	
	function toAbsoluteURL(url) 
	{
		  // Handle absolute URLs (with protocol-relative prefix)
		  // Example: //domain.com/file.png
		  if (url.search(/^\/\//) != -1) {
		    return window.location.protocol + url
		  }

		  // Handle absolute URLs (with explicit origin)
		  // Example: http://domain.com/file.png
		  if (url.search(/:\/\//) != -1) {
		    return url
		  }

		  // Handle absolute URLs (without explicit origin)
		  // Example: /file.png
		  if (url.search(/^\//) != -1) {
		    return window.location.origin + url
		  }
		  
		  var href = window.location.href.split('?')[0].split('#')[0];

		  // Handle relative URLs
		  // Example: file.png
		  var base = href.match(/(.*\/)/)[0]
		  return base + url
	}
	
	function waitFor(condition, callback, interval) {
		var interval = interval || 100;
		var isLoaded = condition();
		if (isLoaded) {
			callback();
		}
		else {
			setTimeout(function () {
				waitFor(condition, callback, interval);
			}, interval);
		}
	};
	
	function setLocal(key, obj)
	{
		if (supportsStorage)
		{		
			var item = obj;
			if (obj != null) 
			{
				item = JSON.stringify(obj);
			}
			localStorage.setItem(key, item);
		}
	}
	
	function getLocal(key)
	{		
		if (supportsStorage)
		{					
			var item = localStorage.getItem(key);
			if (item)
			{
				return JSON.parse(item);
			}
		}
		return null;
	}
	
	function limitOpacity(opacity)
	{
		if (opacity < 0.001) return 0.001;
		if (opacity > 0.999) return 0.999;
		return opacity;
	}
	
	function tncModule() 
	{
	    // module variable, alle publieke functies aan variable toekennen
	    var module = {};   
	    
	    window.TNC = module;
	    var mainWindow = window;
	    
	    var $ = jQuery;
		
		$.fn.redraw = function() {
			return this.hide(0, function() {
				$(this).show(0);
			});
		};
		
		
	    var getId = function (id) {
			return document.getElementById(id);
		};
		module.params = getQueryParams();
	    var debug = module.params['debug'];   
	    var scriptDbg = debug && window.console && console.info;
	    var isInternetExplorer = navigator.appVersion.indexOf('MSIE') != -1;   
	    var templateTransitionsEnabled = true;
		module.enableEdit = false;
		module.exampleItems = false;
		module.selectLayer = false;
		module.stillPlaying = true;
		module.fileID = '';
		
		function globalErrorHandler(message, url, lineNumber) {  
			//save error and send to server for example.	  
			if (!scriptDbg)
			{
				console.info('deactivate the global error handler to see errors');	  
				return true;
			}
			return false;
		};
		
		window.onerror = globalErrorHandler;

	    var preventEvent = function(e) { e.preventDefault(); return false; };	    
	   	    
	    function format(str)
		{
	    	var i;
			for(i = 1; i < arguments.length; i++)
			{
				str = str.replace('{' + (i - 1) + '}', arguments[i]);
			}
			return str;
		}
	
		function pad(number, length) 
		{   
			var str = '' + number;
			while (str.length < length) {
				str = '0' + str;
			}
			return str;
		}
	
		function getTimeStr() 
		{
			var time = new Date(); //.getTime()
			return format('{0}:{1}:{2}.{3}', pad(time.getHours(), 2) , pad(time.getMinutes(),2), pad(time.getSeconds(),2), pad(time.getMilliseconds(), 3));	
		}	
		/*
		function toStr(obj, lev)
		{			
			var lev = lev || 1;			
			if (lev <= 0) return '';
			
		    var objStr = '';		    		    
		    
		    if (obj instanceof Error)
		    {
		    	objStr = obj.toString();
		    }
		    
		    for (var member in obj) {
		    	
		    	if (obj.hasOwnProperty(member))
		    	{
			    	var memberStr = '';
			    	if (typeof obj[member] === 'object')
			        {
			            memberStr = toStr(obj[member], lev-1);		        	
			        }	
			        else
			        {
			            memberStr = '"'+obj[member]+'" : '+ typeof obj[member];
			        }
			    	
			        objStr += (objStr ? ',\n': '')+
			            member + ' = ' + memberStr + '';	
		    	}
		    }   
			return '{'+objStr+'}';
		}*/

	    
		function log() 
		{ 
			if (scriptDbg) 
			{ 	
				var args = [].slice.call(arguments); 
				
				var i;
				for(i=0;i<args.length;i+=1)
				{
					if (typeof args[i] === 'object')
					{											
						try
						{
							if (args[i] instanceof Error)
							{
								args[i] = args[i].toString();
							}
							else
							{
								args[i] = JSON.stringify(args[i]);
							}
						}
						catch(e) {
							// Could not convert to JSON
						}
					}
				}
				args.unshift(getTimeStr());				
				console.info(args);
			} 
		}
		
		function globalMousedown()
		{
			if (TncLayer && TncLayer.prototype && TncLayer.prototype.activeLayer)
			{
				TncLayer.prototype.activeLayer.deactivate();				
			}	
			return false;				
		}
		
		function globalClick()
		{			
			return false;
		}
		
		function globalKeydown(key)
		{
			var leftArrow = 37;
			var upArrow = 38;
			var rightArrow = 39;
		    var downArrow = 40;
		    var spaceBar = 32;
			var deleteKey = 46;
			var enterKey = 13;
			var escapeKey = 27;
			
			
		    if (key == spaceBar) {
		    	if(module.currentTemplate)
		        {
		    		if (!module.currentTemplate.paused)
		    		{
		    			module.currentTemplate.pause();
		    		}
		    		else
		    		{
		    			module.currentTemplate.play(true);
		    		}
		        }
		    	else
		        {
		        	log('Error: no template loaded');		        	
		        }
		    }		    
		    if (key == downArrow) {
		        log('you pressed DOWN, saving template to store');
		        
		        if(module.currentTemplate)
		        { 	
		        	setLocal('templateSettings', module.currentTemplate.getSettings() );		        	
		        }
		        else
		        {
		        	log('Error: no template loaded');		        	
		        }
		        return true;
		    }
		    if (key == upArrow) {
		    	log('you pressed UP, loading template from store');
		    	
		    	var settings = getLocal('templateSettings');
		    	if (settings)
		    	{
		    		loadTemplateFromSettings(settings);
					settings = undefined;
		    	}
		    	else
		    	{
		    		log('Error: no template stored!');
		    	}
		    	return true;
		    }
		    if (key == leftArrow) {
		    	
		    	if(module.currentTemplate)
		        {
		        	var template = module.currentTemplate;
		        	log('calculating duration');
		        	log('duration', template.getDuration());
		        }
		    	
		    	return true;
		    }
		    if (key == rightArrow) {
		    	
		    	if(module.currentTemplate)
		        {		        	
		        	module.currentTemplate.play();
		        }
		    }
			if (key == deleteKey)
			{
				if (TncLayer && TncLayer.prototype && TncLayer.prototype.activeLayer)
				{
					sendMessage('key', { key: 'delete', layer: TncLayer.prototype.activeLayer.id });
				}
			}
			if (key == escapeKey)
			{
				if (TncLayer && TncLayer.prototype && TncLayer.prototype.activeLayer)
				{
					sendMessage('key', { key: 'escape', layer: TncLayer.prototype.activeLayer.id });
				}
			}
		    	
		    return false;
		}
		
		module.sendQueue = [];
		module.sending = false;
		
		function processSendQueue()
		{		
			//log('processSendQueue', module.sending);	
			if (!module.sending)
			{								
				var message = module.sendQueue.pop();
				if (message)
				{
					module.sending = true;
					var data = message.data;											
					
					//log(message.url, message.data);
					
					try
					{
					
						var r = $.ajax(message.url, { data: message.data, cache: false });					
						//r.always(function(){ 				
						setTimeout(function() {
							module.sending = false;	
						//	log('sending completed');
							processSendQueue();
						}, 100);
						//});
					}
					catch(e)
					{
						module.sending = false;
						log(e);							
					}
				} 
			}
		}
		
		function isUrl(url)
		{
			var str = getStr(url, '');
			if (str != '')
			{
				return 
					str.indexOf("://") >= 0 || 
					str.substring(0,1) == '/' ||
					str.indexOf("$relativepath") >= 0;					
			}
			return false;
		}
		
		function resourceUrl(url)
		{
			var str = getStr(url, '');
									
			if (str.indexOf("://") < 0)
			{
				if (str.indexOf("$relativepath") >= 0)
				{
					str = str.replace(/\\/g, "/");
					if (module.folder && module.folder != '')
					{
						str = str.replace("$relativepath", module.folder);
					}
					else
					{
						str = str.replace("$relativepath", '');	
					}
				}
				else
				{
					if (module.folder && module.folder != '')
					{
						str = module.folder + '/' + str;
					}
				}
				
				str = toAbsoluteURL(str);				
			}
			
			log('resource url:', str);
			
			return str;
		}		
		
		function sendMessage(type, object, type2, object2)
		{
			if (debug) return;
			
			var url = 'http://cefhost.local/';
			
			var data = {};
			
			if (typeof(object) === 'string')
			{
				data[type] = object;
			}
			else
			{
				data[type] = JSON.stringify(object);
			}
			
			if (type2 && object2)
			{
				if (typeof(object2) === 'string')
				{
					data[type2] = object2;
				}
				else
				{
					data[type2] = JSON.stringify(object2);
				}
			}
			
			module.sendQueue.push({ url: url, data: data });
			//log(module.sendQueue);
			processSendQueue();
		}
		
		function getLayerById(id)
		{
			var template = module.currentTemplate;
			var layer = null;
			if (template)
			{			
				var i;
				for(i=0;i<template.layers.length;i+=1)
				{
					if (template.layers[i].id === id)
					{
						layer = template.layers[i];
					}						
				}
			}			
			else
			{
				log('Error: no active template');
			}
			return layer;
		}
		
		function receiveMessage(type, object)
		{
			// Bij elke message direct edit mode aanzetten
			//module.enableEdit = true;
			
			log({type: type, object:object});
			if (type === 'template' && object === 'refresh')
			{
				if (TncLayer.prototype.activeLayer)
				{
					module.selectLayer = TncLayer.prototype.activeLayer.id;			
					log({selectedLayer : module.selectLayer});
				}
				templateTransitionsEnabled = false;
				module.sammy.refresh(); // Rerun route (reloads template)											
			}
			else if (type === 'layer')
			{
				var settings = JSON.parse(object);
				if (settings && settings.id)
				{
					var layer = getLayerById(settings.id);										
					if (layer)
					{
						layer.opacity = getNum(settings.opacity, 1);
						layer.backgroundColor = getColor(settings.backgroundColor, 'transparent');						
						layer.border = new TncBorder(settings.border);
						
						layer.applySettings();						
					}
				}
			}
			else if (type === 'selection')
			{
				var id = object;
				module.selectLayer = id;

				if (module.selectLayer != '')
				{
					var layer = getLayerById(id);					
					if (layer)
					{
						layer.setActive();
						module.selectLayer = false;
					}
				}
				else
				{
					if (TncLayer.prototype.activeLayer)
					{																			
						TncLayer.prototype.activeLayer.deactivate();									
					}
				}
				
				template = undefined;
			}
			else if (type === 'position')
			{
				var settings = JSON.parse(object);
										        
	        	if (TncLayer.prototype.activeLayer)
	        	{
	        		var layer = TncLayer.prototype.activeLayer;
	        			        		
	        		layer.position.init(settings);
	        		layer.position.applyPosition(layer.$element);
	        	}
	        	else
	        	{
	        		log('Error: no active layer');
	        	}		        
			}
			else if (type === 'addlayer')
			{							
				var settings = JSON.parse(object);
				
				if (module.currentTemplate)
				{
					module.currentTemplate.addLayer(settings);
				}			
			}
			else if (type === 'deletelayer')
			{				
				var layerId = object;				
				if (module.currentTemplate)
				{
					module.currentTemplate.deleteLayer(layerId);
				}	        	
			}
		}
		
		window.GetCefMessage = receiveMessage;		
		
		module.log = log;
		
		function updateQueryString(url, key, value) {
			if (!url) url = window.location.href;
			var re = new RegExp('([?&])' + key + '=.*?(&|#|$)(.*)', 'gi');
	
			if (re.test(url)) {
				if (typeof value !== 'undefined' && value !== null)
					return url.replace(re, '$1' + key + '=' + value + '$2$3');
				else {
					var hash = url.split('#');
					url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '');
					if (typeof hash[1] !== 'undefined' && hash[1] !== null) 
						url += '#' + hash[1];
					return url;
				}
			}
			else {
				if (typeof value !== 'undefined' && value !== null) {
					var separator = url.indexOf('?') !== -1 ? '&' : '?',
						hash = url.split('#');
					url = hash[0] + separator + key + '=' + value;
					if (typeof hash[1] !== 'undefined' && hash[1] !== null) 
						url += '#' + hash[1];
					return url;
				}
				else
					return url;
			}
	}
		
		/**
		 * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
		 * @param obj1
		 * @param obj2
		 * @returns obj3 a new object based on obj1 and obj2
		 */
		function mergeOptions(a, b){
		    var c = {};
		    for (var at in a) { c[at] = a[at]; }
		    for (var at in b) { c[at] = b[at]; }
		    return c;
		}
	    
		// Shuffle array random using Fisher-Yates
	    function shuffle(array) {
	        var tmp, current, top = array.length;
	        if (top) {
	            while (--top) {
	                current = Math.floor(Math.random() * (top + 1));
	                tmp = array[current];
	                array[current] = array[top];
	                array[top] = tmp;
	            }
	        }
	        return array;
	    }		
	    
	    function preloadImages(images, done) 
		{			
			var arr = images;
			var arrLoaded = [];		
			
			function loadImage(src)			
			{
				var src = src;
				// Wait for the image to be fully loaded
				var img = new Image();
				img.src = src;
				img.onload = function () {
					arrLoaded.push(src);
					
					if (arrLoaded.length == arr.length)
					{
						done();
					}
				};
        img.onerror = function () {
					arrLoaded.push(src);
					
					if (arrLoaded.length == arr.length)
					{
						done();
					}
				};
			}				
			for(var i=0;i<arr.length;i+=1)
			{		
				loadImage(arr[i]);					
			}
		}	    
	
		function genClips($e, rows, cols) 
		{
			function genClip($e, r, c, width, height, totalWidth, totalHeight)
			{
				// new element met $e.html();
				var $clip = $new('div', $clipParent);
				var $clipContent = $new('div', $clip);
				$clipContent.html(html);
			
				var x = c * width;				
				var y = r * height;
				var z = c * r;							
			
				$clip.css({ 
					position: 'absolute', 
					overflow:'hidden',
					left: x, 
					top: y, 
					width: width, 
					height:  height, 
					zIndex: z,						
					//clip: 'rect('+y+'px, '+(x + width)+'px, '+(y + height)+'px, '+x+'px)' // top right bottom left
					});
				$clipContent.css({ position: 'absolute', left:-x, top: -y, width: totalWidth, height: totalHeight });
				
				return $clip;
			}
			
			var clips = [];
			
			var totalWidth = $e.width();
			var totalHeight = $e.height(); 
			
			// Get the width of each clipped rectangle.
			var width = Math.ceil(totalWidth / cols);
			var height = Math.ceil(totalHeight / rows);
			
			// The total is the square of the amount
			var totalClips = rows * cols;			
			var html = $e.html();
			
			var $clipParent = $new('div', $e, null, ['clipParent']);
			$clipParent.css({ position: 'absolute', left:0, top: 0, width: totalWidth, height: totalHeight });
	
			for(var r = 0; r < rows; r+=1) 
			{ 
				for(var c = 0; c < cols; c+=1) 
				{ 										
					clips.push( { r: r, c: c, $e : genClip($e, r, c, width, height, totalWidth, totalHeight) });				
				}
			}		
			return clips;		
		}			
		
		var SimpleCarrousel =
	        function (settings) {
	            this.init(settings);
	    }
	
	    SimpleCarrousel.prototype = {
	        init: function (settings) 
				{				
					var settings = settings;
					var self = this;
					var $ = jQuery;
					
					var defaultSettings = {
						slides: [],
						shuffleSlides: false,
						imageSize: 'cover',
						slideDelay: 2000,
						effectTime: 500,
						autoStart: true,
						backgroundColor: '#000'
					};				
					settings = mergeOptions(defaultSettings, settings); 
					
					self.settings = settings;
					
					if (settings.shuffleSlides)
					{
						shuffle(settings.slides);
					}
					
					self.parent = $(settings.element);
											
					var slideCss = { 
						'position': 'absolute',
						'left': '0','top': '0',
						'right': '0','bottom': '0',	
						//'background-color': settings.backgroundColor || '#000'
						};
						
					var slideImgCss = {
						'position': 'absolute',
						'left': '0','top': '0',
						'right': '0','bottom': '0',										
						'background-size': settings.imageSize || 'cover',
						'background-repeat': 'no-repeat',
						'background-position': settings.imagePosition || '50% 50%'
						};
					
					self.slideElements = [];
													
					
					self.slides = settings.slides;
					self.index = -1;
					self.timeout = null;				
					self.first = true;
					self.doFadeFirst = false;
					
					function createSlides()
					{
						self.allSlides = $();
					
						for(var i=0;i<self.slides.length;i+=1)
						{
							var $slideElement = $new('div', self.parent, null, ['slide']);
							$slideElement.css(slideCss);																	
							$slideElement.css('z-index', self.slides.length - i);
							
							var $content = $new('div', $slideElement, null, ['slideContent']);
							$content.css({ position: 'absolute', left:0, top:0, width:$slideElement.width() - 20, height:$slideElement.height() - 20, border: '10px solid yellow', borderRadius: '20px', overflow: 'hidden' });
								
							if (isString(self.slides[i]))
							{
								var $imageEl = $new('div', $content);
	
								$imageEl.css(slideImgCss);
								var imageSrc = "url('" + self.slides[i] + "')";
								$imageEl.css('background-image', imageSrc);							
							}
							else
							{
								$content.html( self.slides[i].html );
							}
							
							self.slideElements[i] = $slideElement;					
							self.allSlides.add($slideElement);
						}
					}
						
					createSlides();
					
					self.start = function () {
						self.slide();
					}
					
					self.getIndex = 
						function(index) {				
							if (index >= 0) return index % self.slides.length;
							if (index < 0) return (self.slides.length-1) - ((Math.abs(index)-1) % self.slides.length);
						};
					
					self.slide =
						function () {																								
							if (self.slides.length !== 0) 
							{							
								for(var i=0;i<self.slides.length;i+=1)
								{
									self.slideElements[i].css( { zIndex: 0, display: 'none' }); 									
								}
														
								var $currentSlide = null;
								var $nextSlide =  null;
								
								if (self.first)
								{
									self.first = false;
									$nextSlide = self.slideElements[self.getIndex(self.index)];
								} 
								else
								{
									$currentSlide = self.slideElements[self.getIndex(self.index)];
									$currentSlide.css({ zIndex: 1, display: 'block' });								
									self.index = self.getIndex(self.index+1);
									
									$nextSlide = self.slideElements[self.getIndex(self.index)];								
								}														
								$nextSlide.css({ zIndex: 2, display: 'block' });
								
								settings.slideTransition(settings, $currentSlide, $nextSlide);																									
	
								// Set timeout for next slide
								if (!self.onlyFirst) {
									self.timeout = setTimeout(function () {
										self.slide();
									}, settings.slideDelay + settings.effectTime);
								}
							} else {
								if (self.first) {
									self.doFadeFirst = true;
								}
								self.timeout = setTimeout(function () {
									self.slide();
								}, 50);
							}
					}
	
					setTimeout(function () {
						self.start()
					}, 160); // start after N ms (time to load first image from cache)				
				}
				
			};
		
		function getQueryParams() {
	        var url = window.location.href;
	        var query = url.split('?')[1];
	        var queryPattern = /([^&=]+)=?([^&]*)/g
	        var match;
	
	        var params = {};
	
	        while (match = queryPattern.exec(query)) {
	            var key = decodeURIComponent(match[1].replace(/\+/g, '%20'));
	            var val = match[2];
	            if (val) {
	                val = decodeURIComponent(match[2].replace(/\+/g, '%20'));
	            } else {
	                val = '';
	            }
	            params[key] = val;
	        }
	        return params;
	    }
	
		// Helper functie die een nieuw html element aanmaakt en deze als jQuery object terug geeft   
		function $new(type, parent, attributes, classNames)
	    {
			var k, i;
	        var el = document.createElement(type);        
	        if (attributes && attributes['type'])
	        {
				el.type = attributes['type'];
			}        
	                       
	        $(parent).append(el);
			var jqEl = $(el);
	
	        if (attributes)
	        {
	            for(k in attributes)
	            {
					if (k !== 'type')
					{
						jqEl.attr(k, attributes[k]);
					}
	            }
	        }
	        
	        if (classNames && classNames instanceof Array)
	        {
	            for(i=0;i<classNames.length;i+=1)
	            {
	                jqEl.addClass(classNames[i]);
	            }
	        }
	        
	        return jqEl;
	    }
		
		function $newCss(file, $head)
		{
			var $h = $('head')
			
			if ($head)
			{
				$h = $head;
			}
			
			var $e = $new('link', $h, {'rel': 'stylesheet', 'type': 'text/css', 'href': file});
			return $e;
		}
		
		function $newJs(file, $head)
		{
			var $h = $('head')
			
			if ($head)
			{
				$h = $head;
			}
			
			var $e = $new('script', $h, { 'type': 'text/javascript', 'src': file});
			return $e;
		}
		
	
		function getQuery(arr)
		{
			var qry = '?';
			var first = true;
			for(var key in arr)
			{
				if (first)
				{
					first = false;
				} 
				else
				{
					qry += '&';
				}
				qry += (encodeURIComponent( key ) + '=' + encodeURIComponent( arr[key]));
			}
			return qry;
		}
			
		function getQueryVar(key, defaultValue)
		{
			var search = unescape(location.search);
			if (search == '')
			{
				return defaultValue;
			}
			search = search.substr(1);
			var params = search.split('&');
			for (var i = 0; i < params.length; i++)
			{
				var pairs = params[i].split('=');
				if(pairs[0] == key)
				{
					return pairs[1];
				}
			}
			return defaultValue;
		}
		
		var fontScaleElements = $();	
		
		function setFontScaleRelative()
		{						
			var setBodyScale = function() {		
				fontScaleElements.each(function()
					{		
						var $e = $(this);
					
						var scaleFactor = 0.25,
							scaleSource = $e.width(),
							maxScale = 600,
							minScale = 30;
	
						var fontSize = scaleSource * scaleFactor; //Multiply the width of the body by the scaling factor:
	
						//if (fontSize > maxScale) fontSize = maxScale;
						//if (fontSize < minScale) fontSize = minScale; //Enforce the minimum and maximums
	
						$e.css('font-size', fontSize + '%');
					});
			}
		
			$(window).resize(function(){
				setBodyScale();
			});
			
			setTimeout(function() {
				//Fire it when the page first loads:
				setBodyScale();
				}, 500);
		}
		
		
		function sendStillPlaying()
		{			 
			sendMessage('status', { 'still-playing': module.stillPlaying, id: module.fileID });
			log({ 'still-playing': module.stillPlaying, id: module.fileID });
		}
		
		function documentReady()
		{
			//setFontScaleRelative();		
			module.$templateParent = $new('div', $('body'));
			module.$templateParent.addClass('tncTemplateParent');
			
			$newCss('theme/tnc-template.css?_='+getRequestRandom());
			initSammy();	

			setInterval(sendStillPlaying, 5000);
			
			// Here's a custom Knockout binding that makes elements shown/hidden via jQuery's fadeIn()/fadeOut() methods
			// Could be stored in a separate utility library
			ko.bindingHandlers.fadeVisible = {
				init: function(element, valueAccessor) {
					// Initially set the element to be instantly visible/hidden depending on the value
					var value = valueAccessor();
					$(element).toggle(ko.unwrap(value)); // Use "unwrapObservable" so we can handle values that may or may not be observable
				},
				update: function(element, valueAccessor) {
					// Whenever the value subsequently changes, slowly fade the element in or out
					var value = valueAccessor();
					ko.unwrap(value) ? $(element).fadeIn() : $(element).fadeOut();
				}
			};
			
			function formatDate(date, format)
			{
				var dateAsString = format;
				dateAsString = dateAsString.replace('yyyy', date.getFullYear());
				dateAsString = dateAsString.replace('yy', pad(date.getYear(), 2) );
				dateAsString = dateAsString.replace('y', date.getYear());
				
				dateAsString = dateAsString.replace('MM', pad(date.getMonth(), 2) );
				dateAsString = dateAsString.replace('M', date.getMonth());
				
				dateAsString = dateAsString.replace('dd', pad(date.getDate(), 2) );
				dateAsString.replace('d', date.getDate());
				
				dateAsString = dateAsString.replace('HH', pad(date.getHours(), 2) );
				dateAsString = dateAsString.replace('H', date.getHours());
				
				dateAsString = dateAsString.replace('hh', pad(date.getHours() % 12, 2) );
				dateAsString = dateAsString.replace('h', date.getHours() % 12);
				
				dateAsString = dateAsString.replace('mm', pad(date.getMinutes(), 2) );
				dateAsString = dateAsString.replace('m', date.getMinutes());
				
				dateAsString = dateAsString.replace('ss', pad(date.getSeconds(), 2) );
				dateAsString = dateAsString.replace('s', date.getSeconds());
				
				dateAsString = dateAsString.replace('fff', pad(date.getMilliseconds(), 3));						
				return dateAsString
			}
			
			ko.bindingHandlers.date = {
				update: function(element, valueAccessor, allBindings) {
					// First get the latest data that we're bound to
					var value = valueAccessor();
			 
					// Next, whether or not the supplied model property is observable, get its current value
					var valueUnwrapped = ko.unwrap(value);
			 
					// Grab some more data from another binding property
					var dateFormat = allBindings.get('dateFormat') || 'yyyy-MM-dd HH-mm-ss'; // 400ms is default duration unless otherwise specified			 
					var dateAsTimestamp = valueUnwrapped;
					
					var dateAsString = formatDate(new Date(Date(dateAsTimestamp)), dateFormat);
			 
					// Now manipulate the DOM element
					$(element).text(dateAsString);
				}
			};
			
			ko.unapplyBindings = function ($node, remove) {
			    // unbind events
			    $node.find("*").each(function () {
			        $(this).unbind();
			    });

			    // Remove KO subscriptions and references
			    if (remove) {
			        ko.removeNode($node[0]);
			    } else {
			        ko.cleanNode($node[0]);
			    }
			};
		}

		function initSammy()
		{
			var sammy = new Sammy.Application();
			module.sammy = sammy;
			
			sammy.get('#/page/:page',
	                        function () {
								var page = this.params.page;
								loadTemplate(page);
	                        });
			
			sammy.get('#/page/:folder/:page',
	                        function () {
								var page = this.params.page;
								var folder = this.params.folder;
								loadTemplate(page, folder);
	                        });
							
			sammy.notFound = function (context) {
	
				//var location = location.hash
				var path = location.hash;
				
				if (!path || path === '' || path === '/' || path === '#/') {
					log('Sammy: Empty route, to default');
					
				
					sammy.setLocation('#/page/index');						
				}
				else {
					log('Sammy: Route not found\n' + path);				
				}
			};
			
			sammy.run();
		}
		
		function calculateSizeToFit(targetWidth, targetHeight, currentWidth, currentHeight)
		{
			var ratioX = targetWidth / currentWidth;
			var ratioY = targetHeight / currentHeight;
			var ratio = Math.min(ratioX, ratioY);
	
			var newWidth = Math.floor(currentWidth * ratio);
			var newHeight = Math.floor(currentHeight * ratio);
			
			var centerX = Math.floor((targetWidth - newWidth) / 2);
			var centerY = Math.floor((targetHeight - newHeight) / 2);
			
			return { 
				newWidth: newWidth, newHeight: newHeight, 
				centerX: centerX, centerY: centerY  
				};
			
		}
		
		/*
		// requestAnim shim layer by Paul Irish
	    window.requestAnimFrame = (function(){
	      return  window.requestAnimationFrame       || 
	              window.webkitRequestAnimationFrame || 
	              window.mozRequestAnimationFrame    || 
	              window.oRequestAnimationFrame      || 
	              window.msRequestAnimationFrame     || 
	              function(callback, element){
	                window.setTimeout(callback, 1000 / 60);
	              };
	    })();
	    */
		
		function limit(num, min, max)
		{
			return Math.min(Math.max(num, min), max);			
		}
		
		function limitPerc(num)
		{			
			return limit(num, 0, 100);			
		}
		
		function roundPerc(number)
		{
			return Math.round(number * 100) / 100;					
		}
		
		function isOverflowing($e)
		{
			if ($e[0].scrollWidth >  $e.innerWidth()) {
				return true;
			}
			return false;
		}
		
		function isString(o)
		{
			if (typeof o === 'string' || o instanceof String)
			{
				return true;
			}
			return false;
		}
		
		function isNumber(o)
		{
			if (typeof o === 'number')
			{
				return !isNaN(o);
			}
			return false;
		}	
		
		function getHex(num)
		{
			return parseInt(num, 16);
		}

		function getColor(setting, def)
		{		
			if (typeof setting === 'string' && setting != '') 
			{
				if (setting.length == 9)				
				{
					var rgbaHex = /#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})/gi
					var match = rgbaHex.exec(setting)		
					if(match)
					{								
						var color = format('rgba({0}, {1}, {2}, {3})', getHex(match[1]), getHex(match[2]), getHex(match[3]), roundPerc(getHex(match[4]) / 255.0));						
						return color;
					}					
				}
			
				return setting;
			}
			else
			{
				return def;
			}
		}
		
		function getStr(setting, def)
		{
			if (typeof setting === 'string' && setting != '') 
			{
				return setting;
			}
			else
			{
				return def;
			}
		}
		
		function getArray(setting, def)
		{
			if (setting && setting.constructor === Array) 
			{
				return setting;
			}
			return def;
		}
		
		function getNum(setting, def)
		{			
			if (isNumber(setting)) 
			{
				return setting;
			}
			else if (isString(setting) && setting != '') 
			{
				var f = parseFloat(setting); 
				if (!isNaN(f))
				{
					return f;
				}
				return def;
			}		
			else
			{
				return def;
			}
		}
		
		$(mainWindow).keydown(function (event) {
			var handled = globalKeydown(event.which);
			if (handled) event.preventDefault();
		});
		
		$(mainWindow).click(function(event) { 
			var handled = globalClick();
			if (handled) event.preventDefault();			
		});
		
		$(mainWindow).mousedown(function(event) { 
			var handled = globalMousedown();
			if (handled) event.preventDefault();			
		});
		
		
		var aTemplateIsAlreadyLoading = false;
		function loadTemplateFromSettings(settings)
		{
			function templateReady()
			{
				var template = module.currentTemplate;										
				//template.play(); // afspelen gebeurt al wanneer het template geladen word!
				
				log('setting old layer', module.selectLayer);
				
				if (module.selectLayer)
				{												
					var layerId = module.selectLayer;
					module.selectLayer = false;
					var layer = null;
					var i;
					for(i=0;i<template.layers.length;i+=1)
					{
						if (template.layers[i].id === layerId)
						{
							layer = template.layers[i];
							break;
						}						
					}
					
					if (layer)
					{
						layer.setActive();
						//sendMessage('selection', layer.id);
					}
					else
					{
						sendMessage('selection', '');
					}
				}

				template = undefined;
			}
			
			function templateLoaded()
			{					
				var currentTemplate = module.currentTemplate;
				newTemplate.play(); // << afspelen gebeurt al voordat het template weergegeven word!
				
				setTimeout(function() {
					newTemplate.showTemplate(currentTemplate, function() {								
						if (currentTemplate)
						{
							currentTemplate.unload();
							currentTemplate = undefined;
						}
						module.currentTemplate = newTemplate;	
						newTemplate	= undefined;
						aTemplateIsAlreadyLoading = false;
						templateReady();						
					});		
				}, 1000); 
			}
			
			module.stillPlaying = true;
			
			var settings = settings;		
			
			if (!aTemplateIsAlreadyLoading)
			{
				aTemplateIsAlreadyLoading = true;
			
			
				var newTemplate = new TncTemplate(settings);
				settings = undefined;
				
								
				
				newTemplate.load(module.$templateParent, templateLoaded );	
			}
			else
			{
				log('abort: a template is already loading!');
			}
		}
		
		function loadTemplate(page, folder)
		{
			var path = page;
			module.fileID = page;
			var folder = folder;
			if (folder) 
			{
				path = folder +'/'+page;
			}
			log(path);
			
			module.folder = folder;
			
			function loadTemplateFromResponse(response)
			{
				try
				{
					var settings = JSON.parse(response);						
					settings.css = path + '.css'; 				
					loadTemplateFromSettings(settings);
				}
				catch(e)
				{
					log(e);
				}						
			}
						
			var r = $.ajax({
					url: path + '.json', 
					cache: false 
				});		
			
			r.always(function() {			
				if (r.responseText)
				{
					loadTemplateFromResponse(r.responseText);
				}
				else
				{
					log('Page not found: '+ path);			
				}
			});
		}
		
		
		// =======
	    // Template
	    function TncTemplate(settings) {
			this.init(settings);
		}
	
		TncTemplate.prototype.init = function (settings) 
		{
			var ok = (settings && typeof settings == 'object');
			if (!ok)
			{
				log('error: TncTemplate: settings is not an object');
			}			
			
			var self = this;
			self.settings = settings || {};							
			
			self.css = self.settings.css;			
			self.aspectRatio = getStr(self.settings.aspectRatio, '');			
			self.aspectRatioNumeric = null;
			
			var p = self.aspectRatio.split('/');
			if (p.length > 1)
			{
				self.aspectRatioNumeric = getNum(p[0].trim(), 1) / getNum(p[1].trim(), 1);
			}					
			
			self.backgroundImage = getStr(self.settings.backgroundImage, '');
			self.backgroundColor = getColor(self.settings.backgroundColor, 'transparent');
			self.templateVersion = getNum(self.settings.templateVersion, 0);				
			
			self.transition = new TncTransition(self.settings.transition, self);					
			self.transition.transitionDisabled = !templateTransitionsEnabled;
			
			module.enableEdit = module.params['edit'] ? true : false;
			module.exampleItems = module.params['exampleitems'] ? true : false;
			
			self.isPlaying = false;
			self.paused = false;
			
			self.layers = [];
			
			for(var i=0;i<self.settings.layers.length;i+=1)
			{
				var layer = new TncLayer(self.settings.layers[i]);			
				self.layers.push(layer);			
			}
			
		};			
		
		TncTemplate.prototype.addLayer = function(settings)
		{
			var self = this;
			var layer = new TncLayer(settings);
			self.layers.push(layer);
			layer.load(self.$content, true);			
			layer.play();
			layer.setActive();
		};

		TncTemplate.prototype.deleteLayer = function(layerId)
		{	
			var self = this;
			var index = -1;
		
			for(var i=0;i<self.layers.length;i+=1)
			{
				if(self.layers[i].id == layerId)
				{
					index = i;
					break;
				}
			}
		
			if (index > -1) {
				self.layers[i].unload();
				self.layers.splice(index, 1);
			}
			else
			{
				log(format('Layer {0} not found, not deleted', layerId));
			}
		};
		
		TncTemplate.prototype.load = function($parent, callback, reload)
		{
			var self = this;			
			var reload = reload;
			
			
			if(TncLayer.prototype.activeLayer)
			{
				TncLayer.prototype.activeLayer.deactivate();
			}
			
			// force reload whole template
			if (reload && self.$element)
			{	
				self.$element.remove();
				self.$element = null;
			}											
			
			if (!self.$element)
			{			
				self.$element = $new('div', $parent);
				self.$element.addClass('tncTemplate');
				
				self.$panel = $new('div', self.$element);
				self.$panel.css({ opacity: 0.001 });
				self.$panel.addClass('tncTemplatePanel');
				
				self.$frame = $new('iframe', self.$panel);	
				self.$frame.addClass('tncTemplateFrame');

				
				self.$panel.data('transitionType', self.transition.transitionType);
				self.$panel.on('beforeTransition', function() {
					var $this = $(this);
					var vertical = false;
					switch($this.data('transitionType'))
					{
						case 'rotate3d-top':
						case 'rotate3d-bottom':
							vertical = true;
							break;
					}	
					module.create3dpanel($this, 4, vertical);									
				});				
				
				var iframeInitialized = false;
				
				var iframeReady = function()
				{
					if (!iframeInitialized)
					{	
						iframeInitialized = true;

						self.$body = self.$frame.contents().find('body');
						self.$content = $new('div', self.$body);			
						self.$content.addClass('tncTemplateContent');
						
						var doc = self.$body[0].ownerDocument;
						
						var win = doc.parentWindow || doc.defaultView;
						win.onerror = globalErrorHandler;
						
						self.$doc = $(doc);
												
						self.keyDownHandler = function (event) {
							var handled = globalKeydown(event.which);
							if (handled) event.preventDefault();
						};
						
						self.clickHandler = function (event) {
							var handled = globalClick();
							if (handled) event.preventDefault();
						};
						
						self.mouseDownHandler = function (event) {
							var handled = globalMousedown();
							if (handled) event.preventDefault();
						};
						
						// forward key events to main frame
						self.$doc.on('keydown', self.keyDownHandler);						
						self.$doc.on('click', self.clickHandler);
						self.$doc.on('mousedown', self.mouseDownHandler);
						
						//Load template css here in the iframe
						var $head = self.$frame.contents().find('head');
						$newCss('theme/bootstrap-icons.css?_='+getRequestRandom(), $head);
						$newCss('theme/tnc-template-content.css?_='+getRequestRandom(), $head);
						log('css', self.css);
						$newCss(self.css, $head);
						
						self.applySettings();
						self.loadLayers(reload);
						
						if (callback)
						{							
							setTimeout(function() {
								callback();
							}, 1000);
						}
					}
				}
				
				setTimeout(function() {
					self.$frameDoc = self.$frame.contents(); //.contentWindow.document	
					
					self.$frameDoc.ready(iframeReady);
				}, 50); // kleine timeout anders werkt het niet in firefox				
			}
			else
			{
				self.applySettings();
				self.loadLayers(reload);
				if (callback)
				{
					callback();
				}
			}
		};
		
		TncTemplate.prototype.loadLayers = function (reload)
		{
			var self = this;
			for(var i=0;i<self.layers.length;i+=1)
			{
				var layer = self.layers[i];			
				layer.load(self.$content, reload);			
			}	
		};
		
		TncTemplate.prototype.applySettings = function ()
		{
			var self = this;
			
			// Apply settings						
			if (self.aspectRatioNumeric)
			{
				var ratio = self.aspectRatioNumeric;
				var css = { 
				'width': '100vw',
				'height': (100 / ratio) + 'vw',
				'max-height': '100vh',
				'max-width': (100 * ratio) + 'vh' };						
				self.$content.css(css);						
			}

			self.$body.css('background-color', self.backgroundColor);	
			if (self.backgroundImage !== '')
			{
				var folder = module.folder || '';
			
				if (folder != '')
				{
					folder = folder + '/'; 
				}				
				self.$body.css('background-image', 'url(' + resourceUrl(self.backgroundImage) + ')');				
				self.$body.css('background-size', 'cover');
			}		
		};
		
		TncTemplate.prototype.getSettings = function()
		{
			var self = this;
						
			function getLayerSettings()
			{
				var layerSettings = [];
				var i;
				for(i=0;i<self.layers.length;i+=1)
				{
					layerSettings.push(self.layers[i].getSettings());
				}
				return layerSettings;
			}
			
			// Return (updated) settings from elements
			return { 
				templateVersion : self.templateVersion, 
				aspectRatio : self.aspectRatio,
				css : self.css,
				backgroundColor : self.backgroundColor,
				backgroundImage : self.backgroundImage,
				transition : self.transition.getSettings(),
				layers : getLayerSettings()
			};						
		};
		
		
		TncTemplate.prototype.showTemplate = function(currentTemplate, callback)
		{
			var self = this;
			
			self.$element.css({ zIndex: 100 });
			
			var $new = self.$panel;
			var $old = null;
			
			if (currentTemplate)
			{
				currentTemplate.$element.css({ zIndex: 10 });
				$old = currentTemplate.$panel;
			}
		
			self.transition.applyTransition(self, currentTemplate, $new, $old, callback);		
		};
		
		
		TncTemplate.prototype.getDuration = function()
		{
			var self = this;
			
			var duration = 0;
			duration +=  200; // reserved template load time
			duration += self.transition.duration;		
			
			var i;
			for(i=0;i<self.layers.length;i+=1)
			{
				duration += self.layers[i].getDuration();
			}
			
			return duration;
		};
		
		
		
		TncTemplate.prototype.pause = function()
		{
			var self = this;
			
			self.paused = true;
			var i;
			for(i=0;i<self.layers.length;i+=1)
			{
				self.layers[i].pause();
			}
		}
		
		TncTemplate.prototype.play = function(resume)
		{
			var self = this;
			
			if (!self.isPlaying || (self.paused && resume))
			{
				if (self.paused)
				{
					log('playing resumed..');
				}
				else
				{
					log('start playing..');
				}
				self.paused = false;
				self.isPlaying = true;
							
				var layerPlayCount = self.layers.length;
				
				self.onLayerReady = function()
				{
					layerPlayCount -= 1;
					if (layerPlayCount <= 0)
					{
						log('>>> template ready! <<<');
						self.isPlaying = false;	
						module.stillPlaying = false;
						sendStillPlaying();
					}
				}
				
				var i;
				for(i=0;i<self.layers.length;i+=1)
				{
					self.layers[i].play(resume, self.onLayerReady);
				}
			}
			else
			{
				log('error: already playing');
			}
		};
		
		
		TncTemplate.prototype.unload = function()
		{
			var self = this;
			
			for(var i=0;i<self.layers.length;i+=1)
			{
				self.layers[i].unload();			
			}			
			delete self.layers;			
			
			self.$element.remove();
			delete self.$element;
			
			if (self.$doc)
			{
				self.$doc.unbind();
				delete self.$doc;
			}
			
			if (self.$panel) 
			{
				self.$panel.remove();
				delete self.$panel;
			}
			
			if (self.$frame)
			{
				self.$frame.remove();
				delete self.$frame;
			}
			
			if (self.$body)
			{
				self.$body.remove();
				delete self.$body;
			}
			
			if (self.$content)
			{
				self.$content.remove();
				delete self.$content;
			}
			
			if (self.$frameDoc)
			{
				self.$frameDoc.remove();
				delete self.$frameDoc;
			}
			
			del(self, 'margin');
			del(self, 'transition');
			
			
			delete self.onLayerReady;
			delete self.keyDownHandler;			
			delete self.clickHandler;
			delete self.settings;										
			delete self.css;
			delete self.aspectRatio;
			delete self.aspectRatioNumeric;
			delete self.backgroundColor;
			delete self.templateVersion;			
			delete self.isPlaying;
			delete self.paused;			
			
			self.unloaded = true;
			
			log('Template unloaded', self);
			self = null;
		};		
	    
		// =======
	    // Layer	
	    function TncLayer(settings) 
	    {
			this.init(settings);
		}
			 
	    TncLayer.prototype.init = function (settings) 
		{
			var self = this;
			self.settings = settings || {};
			
			self.id = getStr(self.settings.id, '');
			self.name = getStr(self.settings.name, '');
			self.backgroundColor = getColor(self.settings.backgroundColor, 'transparent');
			self.border = new TncBorder(self.settings.border);
			self.opacity = limitOpacity(getNum(self.settings.opacity, 1));
			self.repeating = self.settings.repeating || false;
			self.transition = new TncTransition(self.settings.transition, self);
			
			self.settings.widgets = self.settings.widgets || [];
			
			self.widgets = [];
					
			for(var i=0;i<self.settings.widgets.length;i+=1)
			{
				var widget = new TncWidget(self.settings.widgets[i]);			
				self.widgets.push(widget);			
			}
			
			self.widgets.sort(function(a,b) { return (a.sorting - b.sorting);  });
			
			self.position = new TncPosition(self.settings.position);
					
			self.defaultWidgetDuration = getNum(self.settings.defaultWidgetDuration, 0);
			//self.margin = new TncMargin(self.settings.margin); // LAYER MARGIN  ON PURPOSE DISABLED 
			
			self.paused = false;
			self.index = 0;
		};
		
		TncLayer.prototype.load = function($parent, reload)
		{
			var self = this;			
			
			var reload = reload;
			
			// force reload whole widget
			if (reload && self.$element)
			{	
				self.$element.remove();
				self.$element = null;
			}
			
			// create layer			
			if (!self.$element)
			{	
				self.$element = $new('div', $parent);
				self.$element.addClass('tncLayerParent');
				
				self.$layer = $new('div', self.$element);
				self.$layer.addClass('tncLayer');
				self.$layer.addClass(self.id);
				
				self.$overlay = $new('div', self.$element);
				self.$overlay.addClass('tncLayerOverlay');
				//self.$overlay.css('display','none'); // LET OP: TIJDELIJK
				
				self.$perspective = $new('div', self.$layer);
				self.$perspective.addClass('tncLayerPerspective');
								
				self.$container = $new('div', self.$perspective);				
				self.$container.addClass('tncLayerContainer');											
				
				self.$panelParent =  $new('div', self.$container);
				self.$panelParent.addClass('tncLayerPanelParent');
				
				self.$panel =  $new('div', self.$panelParent);
				self.$panel.addClass('tncLayerPanel');
				self.$panel.css('box-sizing', 'border-box');
				
				self.$content =  $new('div', self.$panel);
				self.$content.addClass('tncLayerContent');										
								
				if (module.enableEdit)
				{					
					self.$overlay.click(function(e) {					
						self.setActive();
						e.preventDefault();
						return false;
					});
				}
				
				self.$element.click(preventEvent);
				
				self.$panel.data('transitionType', self.transition.transitionType);
				self.$panel.on('beforeTransition', function() {
					var $this = $(this);
					var vertical = false;
					switch($this.data('transitionType'))
					{
						case 'rotate3d-top':
						case 'rotate3d-bottom':
							vertical = true;
							break;
					}	
					module.create3dpanel($this, 4, vertical);									
				});
				
				
			}
			
			
			// Apply all settings
			self.applySettings();	
			//self.margin.applyMargin(self.$content); // LAYER MARGIN ON PURPOSE DISABLED
			
			self.hide();
				
			/* widget loading disabled
			for(var i=0;i<self.widgets.length;i+=1)
			{
				var widget = self.widgets[i];			
				widget.load(self.$content); // force reload
			}*/
		};
		
		TncLayer.prototype.applySettings = function()		
		{
			var self = this;
			
			self.$layer.css({ opacity: self.opacity });	
			self.$panel.css({ backgroundColor: self.backgroundColor });			
			self.position.applyPosition( self.$element );						
			self.border.applyBorder(self.$panel);		
		};
		
		TncLayer.prototype.show = function()
		{
			var self = this;
			self.$panel.css({ opacity: limitOpacity(1) });			
		};
		
		TncLayer.prototype.hide = function()
		{
			var self = this;	
			self.$panel.css({ opacity: limitOpacity(0) });			
		};
		
		TncLayer.prototype.setActive = function()
		{			
			var self = this;
			log('setactive', self.id);
			if (TncLayer.prototype.activeLayer)
			{												
				if (TncLayer.prototype.activeLayer !== self)
				{
					TncLayer.prototype.activeLayer.deactivate(true);
					TncLayer.prototype.activeLayer = self;
					self.registerDragMove();
					
					sendMessage('selection', self.id);
				}
			}
			else
			{					
				TncLayer.prototype.activeLayer = self;
				self.registerDragMove();
				sendMessage('selection', self.id );
			}	
		}
		
		TncLayer.prototype.deactivate = function(newSelection)
		{
			var self = this;
			if (TncLayer.prototype.activeLayer === self)
			{
				TncLayer.prototype.activeLayer.unregisterDragMove();
				TncLayer.prototype.activeLayer = null;
				
				if (!newSelection)
				{
					sendMessage('selection', '');	
				}
			}
		}
		
		TncLayer.prototype.getSettings = function()
		{
			var self = this;
						
			function getWidgetSettings()
			{
				var widgetSettings = [];
				var i;
				for(i=0;i<self.widgets.length;i+=1)
				{
					widgetSettings.push(self.widgets[i].getSettings());
				}
				return widgetSettings;
			}
			
			// Return (updated) settings from elements
			return {
				id : self.id,
				name : self.name,
				backgroundColor : self.backgroundColor,
				border : self.border.getSettings(),
				position : self.position.getSettings(),
				opacity : self.opacity,
				repeating : self.repeating,
				widgets : getWidgetSettings(),
				transition : self.transition.getSettings()
			};						
		};	
		
		TncLayer.prototype.getDuration = function()
		{
			var self = this;
			
			var duration = 0;
			duration += self.transition.duration;
			
			var i;
			for(i=0;i<self.widgets.length;i+=1)
			{
				duration += self.widgets[i].getDuration();
			}
			
			return duration;
		};
		
		TncLayer.prototype.registerDragMove = function()
		{
			var self = this;
			
			var $target = self.$element;
			var $dragHandle = $new('div', $target).addClass('jqHandle').addClass('jqDrag');
			
			var $dragSE = $new('div', $target).addClass('sizeHandle').addClass('rsSE');
			var $dragS = $new('div', $target).addClass('sizeHandle').addClass('rsS');
			var $dragSW = $new('div', $target).addClass('sizeHandle').addClass('rsSW');
			var $dragW = $new('div', $target).addClass('sizeHandle').addClass('rsW');
			var $dragNW = $new('div', $target).addClass('sizeHandle').addClass('rsNW');
			var $dragN = $new('div', $target).addClass('sizeHandle').addClass('rsN');
			var $dragNE = $new('div', $target).addClass('sizeHandle').addClass('rsNE');
			var $dragE = $new('div', $target).addClass('sizeHandle').addClass('rsE');
							
			var handles = [];
			handles.push({ $e: $dragHandle, move: jqDrag.handleDrag });
			
			handles.push({ $e: $dragSE, move: jqDrag.handleResize_SouthEast });
			handles.push({ $e: $dragS, move: jqDrag.handleResize_South });
			handles.push({ $e: $dragSW, move: jqDrag.handleResize_SouthWest });
			handles.push({ $e: $dragW, move: jqDrag.handleResize_West });
			handles.push({ $e: $dragNW, move: jqDrag.handleResize_NorthWest });
			handles.push({ $e: $dragN, move: jqDrag.handleResize_North });
			handles.push({ $e: $dragNE, move: jqDrag.handleResize_NorthEast });
			handles.push({ $e: $dragE, move: jqDrag.handleResize_East });
			
			function callback()
			{
				var parentWidth = self.$element.parent().width();
				var parentHeight = self.$element.parent().height();
				
				var position = self.$element.position();
				var selfWidth = self.$element.width();
				var selfHeight = self.$element.height()
				
				var percentageLeft = roundPerc( ( position.left  / parentWidth) * 100 );
				var percentageTop = roundPerc(( position.top  / parentHeight) * 100 );							
				var percentageWidth = roundPerc(( selfWidth  / parentWidth) * 100 );
				var percentageHeight = roundPerc(( selfHeight / parentHeight) * 100 );
				
				self.position.fromTopLeft(percentageLeft, percentageTop, percentageWidth, percentageHeight)					
				self.position.applyPosition( self.$element );					
				
				sendMessage('position', self.position.getSettings(), 'layer', self.id);
				
				log({ 
					layer: self.id,
					position: self.position.getSettings()
				});				
			}
			
			self.$actions = $new('div', $target);
			self.$actions.addClass('layerActions');
			
			var $action, $icon;				
									
			self.$actions.mouseup(preventEvent);
			self.$actions.mousemove(preventEvent);
			self.$actions.mousedown(preventEvent);
			
			$action = $new('div', self.$actions);
			$action.addClass('layerAction');
			$icon = $new('span', $action);						
			$icon.addClass('glyphicon').addClass('glyphicon-plus');		
			$action.click(function(e) { e.preventDefault(); sendMessage('add', 'widget'); });					
			
			$action = $new('div', self.$actions);
			$action.addClass('layerAction');
			$icon = $new('span', $action);						
			$icon.addClass('glyphicon').addClass('glyphicon-arrow-up');		
			$action.click(function(e) { e.preventDefault(); sendMessage('zindex', 'up'); });
			$action.mouseup(function(e) { e.preventDefault(); });
			
			$action = $new('div', self.$actions);
			$action.addClass('layerAction');
			$icon = $new('span', $action);						
			$icon.addClass('glyphicon').addClass('glyphicon-arrow-down');		
			$action.click(function(e) { e.preventDefault(); sendMessage('zindex', 'down'); });
			$action.mouseup(function(e) { e.preventDefault(); });					
			
			var $container = $('.tncTemplateContent', self.$element.parent().parent());
			self.interactiveBox = new jqDrag.InteractiveBox($target, handles, callback, $container);  			
		};
		
		TncLayer.prototype.unregisterDragMove = function()
		{
			var self = this;
			
			if (self.interactiveBox)
			{
				self.interactiveBox.unbind();		
			}
			
			if (self.$actions)
			{
				self.$actions.remove();				
			}
		};
		
		TncLayer.prototype.pause = function()
		{			
			var self = this;
			
			if (module.enableEdit)
			{
				log('paused');				
				self.paused = true;			
			}
		}
		
		TncLayer.prototype.play = function(resume, callback)
		{			
			var self = this;
			
			self.paused = false;
			if (!resume)
			{	
				self.transition.applyTransition(self, null, self.$panel, null);
				self.resetPlayList();				
			}										
			self.playWidget(callback);
		};
		
		
		TncLayer.prototype.playWidget = function(callback)
		{	
			var self = this;
			if (callback)
			{
				self.playCallback = callback;
			}
			
			if (!self.widgets) return;
			
			if (self.paused)
			{
				return;
			}
		
			var currentWidgetIndex = self.index;				
			if (currentWidgetIndex < self.widgets.length)
			{
				var currentWidget = self.widgets[currentWidgetIndex];
				currentWidget.widgetIndex = currentWidgetIndex
				if (!currentWidget.data) return; // widget is not loaded
				
				var widgetInstance = currentWidget.getWidgetInstance();				
				currentWidget = undefined;
				widgetInstance.load(self.$content);
				
				var loaded = function()
				{				
					self.index += 1;
					widgetInstance.onWidgetInstanceReady = function() { self.playWidget(); };
					widgetInstance.play(self.prevWidget);
					self.prevWidget = widgetInstance;
					widgetInstance = undefined;
				}										
				widgetInstance.beforePlay(loaded);				
			}
			else
			{
				log('layer \'' + self.id + '\' ready!');				
				if (self.repeating && self.widgets.length > 0)
				{
					log('layer \'' + self.id + '\' repeating .. ');
					self.resetPlayList();
					self.playWidget();
				}
				if (self.playCallback) { self.playCallback(); }
			}			
		}
		
		TncLayer.prototype.resetPlayList = function ()
		{
			var self = this;		
			self.index = 0;				
		}
		
		TncLayer.prototype.unload = function()
		{
			var self = this;
			
			self.deactivate();
		
			for(var i=0;i<self.widgets.length;i+=1)
			{
				self.widgets[i].unload();
				self.widgets[i] = null;
			}			
			
			delete self.settings;
			delete self.widgets;
			delete self.playCallback;
		
			del(self, 'border');
			del(self, 'position');
			del(self, 'margin');		
			del(self, 'transition');
		
			self.$element.remove();
			delete self.$element;	
			self.$layer.remove();			
			delete self.$layer;								
			self.$overlay.remove();
			delete self.$overlay;								
			self.$perspective.remove();
			delete self.$perspective;											
			self.$container.remove();
			delete self.$container;							
			self.$panel.remove();
			delete self.$panel;		
			self.$content.remove();
			delete self.$content;			
			
			self.prevWidget = null;
			
			self.unloaded = true;
			
			log('Layer unloaded', self);
			self = null;
		}
		
		// =======
	    // Widget	
	    function TncWidget(settings) 
	    {
			this.init(settings);
		}
			 
	    TncWidget.prototype.init = function (settings) 
		{
			var self = this;
			self.settings = settings || {};
			
			self.backgroundColor = getColor(self.settings.backgroundColor, 'transparent');
			self.border = new TncBorder(self.settings.border);
			self.opacity = limitOpacity(getNum(self.settings.opacity, 1));
			self.transition = new TncTransition(self.settings.transition, self);
			self.data = new TncWidgetData(self.settings.data, self.settings);
			self.margin = new TncMargin(self.settings.margin);
			self.padding = new TncMargin(self.settings.padding);
			self.duration = getNum(self.settings.duration, 0);
			self.id = getStr(self.settings.id, '');
			self.name = self.settings.name;
			self.widgetType = self.settings.widgetType;
			self.sorting = getNum(self.settings.sorting, 0);		
		};
		
		TncWidget.prototype.instanceId = 0;
		
		TncWidget.prototype.getWidgetInstance = function() {
			
			var self = this;
			function TncWidgetInstance()
			{
				var instance = this;
				instance.instanceId = TncWidget.prototype.instanceId;				
				instance.data = self.data.getWidgetDataInstance();				
				TncWidget.prototype.instanceId++;
			}
			
			TncWidgetInstance.prototype = self;
						
		
			return new TncWidgetInstance();
		};
		
		
		TncWidget.prototype.load = function($parent)
		{
			var self = this;						
			
			// force reload whole widget
			if (self.$element)
			{	
				self.$element.remove();
				self.$element = null;
			}			
			
			self.$element = $new('div', $parent);
			self.$element.addClass('tncWidget');
			self.$element.attr('data-widget-name', self.name);
			self.$element.attr('data-widget-type', self.data.dataType);
			
			self.$margin = $new('div', self.$element);
			self.$margin.addClass('tncWidgetMargin');
			
			self.$panel = $new('div', self.$margin);
			self.$panel.addClass('tncWidgetPanel');
			
			self.$frame = $new('div', self.$panel);	
			self.$frame.addClass('tncWidgetFrame');			
			
			if (self.data.dataType != 'knockout' && self.data.dataType != 'image')
			{
				self.$frame.css('overflow', 'hidden'); // for borders;
			}

			/*
			setTimeout(function() {
				module.create3dpanel(self.$frame, 4, false);
			}, 50);*/
			
			self.$element.data('transitionType', self.transition.transitionType);
			self.$element.on('beforeTransition', function() {
				var $this = $(this);
				var vertical = false;
				switch($this.data('transitionType'))
				{
					case 'rotate3d-top':
					case 'rotate3d-bottom':
						vertical = true;
						break;
				}	
				module.create3dpanel($this, 4, vertical);									
			});		
			
			self.$margin.css({position:'absolute', width:'auto', height:'auto'});
			self.margin.applyMargin(self.$margin);			
			
			
			
			self.hide();
			 
			var dataType = getStr(self.data.dataType, '').toLowerCase();
			if (dataType != 'knockout' && dataType != 'image')
			{
				self.$panel.css({position:'absolute', width:'100%', height:'100%' });
				self.border.applyBorder(self.$panel);
				
				self.padding.applyMargin(self.$frame);							
				self.$frame.css({ position:'absolute', width:'auto', height:'auto', backgroundColor: self.backgroundColor, opacity: self.opacity, 'box-sizing': 'border-box' });
			}			
			else
			{
				self.$frame.css({ position:'absolute', width:'100%', height:'100%' });
			}
			
			self.data.load(self.$frame);			
		};
		
		TncWidget.prototype.show = function()
		{
			var self = this;
			self.$element.css({ opacity: self.opacity });			
		};
		
		TncWidget.prototype.hide = function()
		{
			var self = this;
			self.$element.css({opacity: 0.001 });			
		};
		
		TncWidget.prototype.getSettings = function()
		{
			var self = this;
			
			return { 
				backgroundColor : self.backgroundColor,
				border : self.border.getSettings(),				
				opacity : self.opacity,
				transition : self.transition.getSettings(),
				data : self.data.getSettings(),
				duration : self.duration,				
				id : self.id,
				name : self.name,
				widgetType : self.widgetType,
				sorting : self.sorting
			};				
		};
		
		TncWidget.prototype.getDuration = function()
		{
			var self = this;
			
			var duration = 0;
			//duration += self.transition.duration; // transition is included in widget duration			
			duration += self.duration;
			
			return duration;
		};
		
		
		TncWidget.prototype.beforePlay = function(callback)
		{
			var self = this;
			log('loading widget['+self.widgetIndex+']');
			
			if (self.data)
			{
				self.data.beforePlay(self.widgetIndex, callback);
			}
		};
		
		TncWidget.prototype.afterPlay = function()
		{
			var self = this;
			log('unloading widget['+self.widgetIndex+'] ('+self.data.dataType+')');
			
			if (self.data)
			{
				self.data.afterPlay(self.widgetIndex);
				
				// It's a widget instance, unload immediately
				self.unload();
			}
			
			if (self.$element)
			{
				self.$element.remove();
				self.$element = null;
			}			
		};
		
		
		TncWidget.prototype.play = function(prev)
		{
			var self = this;	
			self.playing = true;
						
			log('playing widget['+self.widgetIndex+'] type: '+self.data.dataType+', ' + self.transition.transitionType);			
						
			if (prev)
			{										
				self.transition.applyTransition(self, prev, self.$element, prev.$element);
				
				setTimeout(function() { prev.afterPlay(); }, self.transition.duration);
			}
			else
			{
				self.transition.applyTransition(self, null, self.$element, null);
			}
			
			if (self.data)
			{	
				self.data.widgetInstance = self;
				self.data.play(self.widgetIndex);
			}
			else
			{			
				setTimeout(function() { playCompleted(); }, self.transition.duration + self.duration);
			}
		};
		
		TncWidget.prototype.playCompleted = function()
		{
			var self = this;
			if (self.playing)
			{
				self.playing = false;
				
				if (self.onWidgetInstanceReady)
				{
					self.onWidgetInstanceReady();				
				}
			}			
		}
		
		TncWidget.prototype.unload = function()
		{
			var self = this;
			if (self.$element)
			{
				self.$element.remove();			
			}	
			
			delete self.$element;	
			if (self.$panel)
			{
				self.$panel.remove();
				delete self.$panel;
			}
			if (self.$frame)
			{
				self.$frame.remove();
				delete self.$frame;	
			}
			
			del(self, 'onWidgetInstanceReady');
			del(self, 'margin');
			del(self, 'padding');
			del(self, 'transition');
			del(self, 'border');
			del(self, 'data');
			
			self.unloaded = true;
						
			log('Widget unloaded', self);
			self = null;			
		};
		
		// =======
	    // WidgetData
	    function TncWidgetData(settings, parentSettings) 
	    {
			this.init(settings, parentSettings);
		}
			 
	    TncWidgetData.prototype.init = function (settings, parentSettings) 
		{
			var self = this;
			self.settings = settings || {};
			self.parentSettings = parentSettings || {};
			
			self.dataType = self.settings.dataType || 'html'; // html, image, video, knockout 
		};
		
		TncWidgetData.prototype.instanceId = 0;
		
		TncWidgetData.prototype.getWidgetDataInstance = function() {
			
			var self = this;
			function WidgetDataInstance()
			{
				this.instanceId = TncWidgetData.prototype.instanceId;
				TncWidgetData.prototype.instanceId++;
			}
			
			WidgetDataInstance.prototype = self;
						
		
			return new WidgetDataInstance();
		};
		
		TncWidgetData.prototype.load = function($parent)
		{
			var self = this;

			// create layer			
			if (!self.$element)
			{	
				self.$element = $new('div', $parent);
				self.$element.addClass('tncWidgetData');			
			}								
		};
		
		TncWidgetData.prototype.beforePlay = function(widgetIndex, callback)
		{
			var self = this;
			self.loadedCallback = callback;
			
			ko.cleanNode(self.$element[0]);
			self.$element.empty();
			
			switch(self.dataType.toLowerCase())
			{
				case 'image': self.loadImage(widgetIndex); break;
				case 'video': self.loadVideo(widgetIndex); break;
				case 'knockout': self.loadKnockout(widgetIndex); break;
				case 'maatwerk': self.loadMaatwerk(widgetIndex); break;
				case 'html':
				default: self.loadHtml(widgetIndex); break;
			}						
		}
		
		TncWidgetData.prototype.play = function(widgetIndex)
		{
			var self = this;

			var transition = new TncTransition(self.parentSettings.transition);
			var duration = getNum(self.parentSettings.duration, 0);		
			
			var isVideo = self.dataType.toLowerCase() == 'video';			
			if (duration === 0 && isVideo)
			{
				duration = 60000 * 5;	// Filmje als geen duration is opgegeven standaard max 5 minuten
			}
				
			switch(self.dataType.toLowerCase())
			{
				case 'image': self.playKnockout(widgetIndex); break;
				case 'knockout': self.playKnockout(widgetIndex); break;								
				default: setTimeout(function() { self.widgetInstance.playCompleted(); }, transition.duration + duration); break;
			}						
		}
		
		
		TncWidgetData.prototype.afterPlay = function(widgetIndex, callback)
		{
			var self = this;
			//self.$element.html('');			
			//self.$element.empty();	

			ko.cleanNode(self.$element[0]);
			
			log('DATA REMOVED');
		}
		
		TncWidgetData.prototype.getSettings = function()
		{
			var self = this;			
			
			return self.settings; // return de originele settings, er mag sowieso niets worden gewijzigd aan dit object
		};
		
		// Widget data specific
		
		TncWidgetData.prototype.loadMaatwerk = function(widgetIndex)
		{
			var self = this;

			var src = self.settings.src || '';			

			var srcUrl = resourceUrl(src);
										
			if (src != '')
			{			
				//var doc = self.$element[0].ownerDocument;
				//$newJs(srcUrl, $('head', $(doc)));
				//log(srcUrl);
				
				var $frame = $new('iframe', self.$element, { src: srcUrl });
				$frame.css(
					{ 
						border: 0, 
						margin: 0,
						'position':'absolute',
						'left':'0',
						'top':'0',
						'width':'100%',
						'height':'100%'				
					});				
			}
						
			setTimeout(function() { if (self.loadedCallback) self.loadedCallback(); }, 1000); // reserve a second for html content loading
		}
		
		
		TncWidgetData.prototype.loadHtml = function(widgetIndex)
		{
			var self = this;
					
			var html = self.settings.html || '';				
			self.$element.html(html);			
			setTimeout(function() { if (self.loadedCallback) self.loadedCallback(); }, 200); // reserve a second for html content loading
		}
		
		TncWidgetData.prototype.loadImage = function(widgetIndex, callback)
		{			
			var self = this;			
			
			var sources = getArray(self.settings.images, []);
			
			self.items = [];
			for(var i=0;i<sources.length;i+=1)
			{
        var imgSource = sources[i];
        if (imgSource && imgSource != '')
        {
            self.items.push({ src: imgSource});
        }
			}
			
			var size = getStr(self.settings.size, 'cover');			
			
			var folder = module.folder || '';
			
			if (folder != '')
			{
				folder = folder + '/'; 
			}
			
			var preloadArr = [];
			for(var i=0;i<self.items.length;i+=1)
			{
				var src = self.items[i].src;
				var srcUrl = resourceUrl(src);
				preloadArr.push(srcUrl);
				self.items[i].src = 'url(\''+srcUrl+'\')';
				self.items[i].size = size;
			}
			self.preloadArr = preloadArr;
			
			self.settings.amountOnPage = 1
			self.settings.layout = "<div><div data-bind=\"foreach: items\">"+
				"<div data-bind=\"attr { dataSrc: src() },style: { backgroundImage: src(), backgroundSize: size() } \" style=\"position:absolute;width:100%;height:100%;background-size:cover;background-repeat:no-repeat;background-position:50% 50%;\">"+
				"</div></div>";
			
			self.knockoutItemsLoaded();			
		}
		
		TncWidgetData.prototype.loadVideo = function(widgetIndex)
		{
			var self = this;					
			var videoType = getStr(self.settings.videoType, 'youtube').toLowerCase();
			var videoID = getStr(self.settings.videoID, '');			
			switch(videoType)
			{
				case 'vimeo': self.loadVimeo(widgetIndex, videoID); break;
				case 'youtube':
				default: self.loadYoutube(widgetIndex, videoID); break;
			}			
		}
		
		TncWidgetData.prototype.loadVimeo = function(widgetIndex, videoID)
		{
			var self = this;
			
			var src = "http://player.vimeo.com/video/" + videoID;
			var playerID = "vimeo_player"+widgetIndex;
			
			src = updateQueryString(src, "api", "1");
			src = updateQueryString(src, "player_id", playerID);
			src = updateQueryString(src, "html5", "1");
			
			//if (!module.enableEdit)
			//{
				src = updateQueryString(src, "autoplay", "1"); // TIJDELIJK UITGESCHAKELD
			//}
			src = updateQueryString(src, "loop", "1");
			src = updateQueryString(src, "badge", "0");		
		
			var $frame = $new('iframe', self.$element, { src: src, id: playerID });
			$frame.css(
				{ 
					border: 0, 
					margin: 0,
					'position':'absolute',
					'left':'0',
					'top':'0',
					'width':'100%',
					'height':'100%'				
				});
				
			var url = '';
			
			// Helper function for sending a message to the player
			function post(action, value) {
				var data = {
				  method: action
				};
				
				if (value) {
					data.value = value;
				}
				
				var message = JSON.stringify(data);
				try
				{
					$frame[0].contentWindow.postMessage(data, url);				
				} 
				catch(e)
				{
					// niets				
				}
			}
			
			function registerMessages()
			{
				url = $frame.attr('src').split('?')[0];								
				post('addEventListener', 'pause');
				post('addEventListener', 'finish');
				post('addEventListener', 'playProgress');
				
				var win = getWindowFromElement(self.$element);								
				$(win).on('message', function(e) {
					var data = JSON.parse(e.originalEvent.data);										
					if (data)
					{					
						//log(data);
						//var id = data.videoData.video_id;
						if (true) //id.toLowerCase() === videoID.toLowerCase())
						{
							switch (data.event) {
								case 'ready':
									debugger;
									log('vimeo ready');
									
									break;						   
								case 'playProgress':
									debugger;
									log('vimeo play progress');
									break;
									
								case 'pause':
									log('vimeo pause');
									break;
								   
								case 'finish':
									log('vimeo finished');
									break;
							}							
						}						
					}
				});
			}
			
			setTimeout(function() { registerMessages(); if (self.loadedCallback) self.loadedCallback(); }, 2000);
		}
		
		TncWidgetData.prototype.loadYoutube = function(widgetIndex, videoID)
		{
			var self = this;								
			
			var doc = self.$element[0].ownerDocument;
			var win = getWindowFromElement(self.$element);
			var $playerElement = $new('div', self.$element);

			
			// This function creates an <iframe> (and YouTube player) after the API code downloads.
			var player;
			function onYouTubeIframeAPIReady() {
				win.YTapiIsReady = true;				
			}
			
			
			function createPlayer()
			{
				var YT = win.YT;
				
				player = new YT.Player($playerElement[0], { // id
				  height: '100%',
				  width: '100%',
				  videoId: videoID,
				  events: {
					'onReady': onPlayerReady,
					'onStateChange': onPlayerStateChange
				  },
				  playerVars: { 
				         'autoplay': 1,
				         'controls': 0, 
				         'rel' : 0
				  }
				});
			}
			
			
			// The API will call this function when the video player is ready.
			function onPlayerReady(event) {
				event.target.playVideo();
			}
			
			// The API calls this function when the player's state changes.
			//    The function indicates that when playing a video (state=1),
			//    the player should play for six seconds and then stop.
			var done = false;
			var state = null;
			function onPlayerStateChange(event) {				
				if (state !== event.data)
				{
					switch(event.data)
					{
						//case -1:; break; // – unstarted
						case 0: videoEnded(); break; // – ended
						//case 1:; break; // – playing
						//case 2:; break;// – paused
						//case 3:; break;// – buffering
						//case 5:; break;// – video cued															
					}
					state = event.data;
				}
			}
			function stopVideo() {
				player.stopVideo();
			}
			
			function videoEnded() {
				self.widgetInstance.playCompleted();
			}
			
			win.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady;
		
			var condition = function () { return win.YTapiIsReady || false; };
			waitFor(condition, createPlayer);
			
			// This code loads the IFrame Player API code asynchronously.
			$newJs("https://www.youtube.com/iframe_api", $('head', $(doc)));			
			
			setTimeout(function() { if (self.loadedCallback) self.loadedCallback(); }, 2000);
		}
		
		
		TncWidgetData.prototype.knockoutItemsLoaded = function(widgetIndex)
		{
			var self = this;
			var items = self.items;
			var transition = new TncTransition(self.parentSettings.transition);
			var transitionType = transition.transitionType; 
			
			self.$container = $new('div', self.$element);
			self.$container.addClass("knockoutContainer");
			
			self.$subcontainer = $new('div', self.$container);
			self.$subcontainer.addClass("knockoutSubcontainer");
			
			self.$panelParent = $new('div', self.$subcontainer);
			self.$panelParent.addClass("knockoutPanelParent");
			
			var layout = getStr(self.settings.layout, '');							
			
			var pageSize = self.settings.amountOnPage || [];			
			var pageCount = Math.ceil(items.length / pageSize);
			var pageIndex = 0;			
			
			var backgroundColor = getColor(self.parentSettings.backgroundColor, 'transparent');
			var border = new TncBorder(self.parentSettings.border || {});
			var padding = new TncMargin(self.parentSettings.padding || {});		
						
			function createPage(pageItems, border)
			{
				var page = {};
				page.ready = false;
				page.readyStateChanged = false;
				
				var items = pageItems;
								
				var $panel = $new('div', self.$panelParent);
				$panel.addClass("knockoutPanel")
				
				
				var $panelContent = $new('div', $panel);
				$panelContent.addClass("knockoutPanelContent");
				$panelContent.css({ opacity: limitOpacity(0) });

				$panelContent.css({ 'background-color': backgroundColor/*, overflow:'hidden'*/ });				
				border.applyBorder($panelContent);	
				
				
				var $ko = $new('div', $panelContent);
				page.$ko = $ko;
				$ko.addClass("knockoutContent");
							
				$ko.css({ position:'absolute', width:'auto', height:'auto' });
				padding.applyMargin($ko);				
				
				$ko.attr('data-bind', 'template: { afterRender: onAfterRender }');
				
				var $inner = $new('div', $ko);
				$inner.attr('data-bind', 'fadeVisible: pageReady');
				$inner.html(layout);				
								
				setTimeout(function() {
					module.create3dpanel($panelContent, 4, false);
					switch(transitionType)
					{
						case 'rotate3d-top':
						case 'rotate3d-bottom':
							module.create3dpanel($panelContent, 4, true);
							break;
					};
				}, 50);				
							
				function ViewModel()
				{
					var self = this;
					self.items = ko.observableArray([]);
					self.pageReady = ko.observable(false);
				    // Ensure it notifies about changes no more than once per one second
					self.items.extend({ rateLimit: 20 });  // scheelt een hoop schokken in de animaties
					
					self.onAfterRender = function()
					{
						if (!page.ready)
						{											
							page.ready = true;
							self.pageReady(true);
							//self.items.valueHasMutated();
							
							if (page.readyStateChanged)
							{
								page.readyStateChanged();
							}
						}
					};								
					
					function ItemViewModel(itemToAdd)
					{
						var s = this;					
						for (var property in itemToAdd) 
						{
						    if (itemToAdd.hasOwnProperty(property)) 
						    {
						    	var v = itemToAdd[property];
						    	if (isUrl(v))
						    	{
						    		v = resourceUrl(v);
						    	}
						    	
						    	s[property] = ko.observable(v);
						    }
						}
						s = undefined;
					}					
					for(var j=0;j<items.length;j+=1)
	                {
	                    var itemToAdd = items[j];              
	                    var item = new ItemViewModel(itemToAdd);
	                    self.items.push(item);    
	                }
				}
				
				ko.applyBindings(new ViewModel(), $ko[0]);
								
				page.$panel = $panelContent;
				return page;
			}
			
			var pages = [];			
			for(var i=0;i<pageCount;i+=1)
			{
				var pageItems = [];
				for(var j=0;j<pageSize;j+=1)
				{
					var index = (i*pageSize)+j;
					if (items[index]) pageItems.push(items[index]);
					log('page:', i, 'item:', index)
				}				
				pages.push(createPage(pageItems, border));
			}		

			border = null;
			transition = undefined;
			
			self.pages = pages;		
			
			if (self.preloadArr)
			{
				preloadImages(self.preloadArr, function(){
					if (self.loadedCallback) self.loadedCallback();
				});
			}
			else
			{
				setTimeout(function() { 				
					if (self.loadedCallback) self.loadedCallback();				
				}, 1000);
			}
		};
		
		TncWidgetData.prototype.loadKnockout = function()
		{
			var self = this;										
			
			self.items = [];
			
			if (module.enableEdit || module.exampleItems)
			{
				self.items = getArray(self.settings.items, []);
				
				self.knockoutItemsLoaded();
			}
			else
			{			
				var dataUrl = module.folder + '/' + self.parentSettings.id + '.json';				
				log(dataUrl);
				var r = $.ajax({
						url: dataUrl, 
						cache: false 
					});					
				
				r.always(function() {					
					if (r.responseText)
					{
						try
						{
							self.items = JSON.parse(r.responseText);								
						} 
						catch(e)
						{
														
						}
					}
					self.knockoutItemsLoaded();
				});
			}
			
		}
		
		TncWidgetData.prototype.playKnockout = function()
		{
			var self = this;
			var transition = new TncTransition(self.parentSettings.transition);
			var duration = getNum(self.parentSettings.duration, 0);	
		
			var $prevPage = null; 
			
			var pages = self.pages;			
			var pageCount = pages.length;
			var pageIndex = 0;
			
			function playPage()
			{
				if (pageIndex < pageCount)
				{					
					log('playing page:', pageIndex, pages[pageIndex]);
					var prevPage = null;
					var page = pages[pageIndex];
					var $page = page.$panel;
					
					var pageLoaded = function()
					{
						function pageReady()
						{												
							// transition + waiting here					
							pageIndex += 1;
							setTimeout(function () { playPage(); }, duration);
						}
																		
						transition.transitionDisabled = (pageIndex == 0);
						transition.applyTransition(prevPage, page, $page, $prevPage, pageReady);					
						prevPage = page;
						$prevPage = $page;											
					}
					
					//pageLoaded();
					
					if (page.ready)
					{
						pageLoaded();
					}
					else
					{
						page.readyStateChanged = function () { pageLoaded(); };
					}
				}
				else
				{					
					log('all pages played');
					self.widgetInstance.playCompleted();
				}				
			}
			
			playPage();
		};
		
		TncWidgetData.prototype.unload = function()
		{
			var self = this;
			
			if (self.pages)
			{
				for(var i=0;i<self.pages.length;i+=1)
				{
					ko.unapplyBindings(self.pages[i].$ko, true);					
				}				
			}
			
			del(self, 'loadedCallback');			
			del(self, 'settings');
			del(self, 'transition');
			
			self.unloaded = true;
			log('Widget data unloaded', self);
			
			self = null;
		};
		
		
		// =======
	    // Border	
	    function TncBorder(settings) 
	    {
			this.init(settings);
		}
			 
	    TncBorder.prototype.init = function (settings) 
		{
			var self = this;
			self.settings = settings || {};
			
			self.color = getColor(self.settings.color, 'transparent');
			self.radius = getNum(self.settings.radius, 0);
			self.size = getNum(self.settings.size, 0);
			self.style = getStr(self.settings.style, 'solid');
		};	
		
		TncBorder.prototype.applyBorder = function ($e) 
		{
			var self = this;			
			$e.css({
				borderColor: self.color,
				borderRadius: self.radius + 'px',
				borderWidth: self.size + 'px',
				borderStyle: self.style						
			});
		};	
		
		TncBorder.prototype.getSettings = function()
		{
			var self = this;
						
			return {
				color: self.settings.color,
				radius: self.settings.radius,
				size: self.settings.size,
				style: self.settings.style
			};
		};
		
		TncBorder.prototype.unload = function()
		{
			var self = this;
			del(self, 'settings');
			
			self.unloaded = true;
			
			log('Border unloaded', self);
			
			self = null;
		};
		
		
		// =======
	    // Margin	
	    function TncMargin(settings) 
	    {
			this.init(settings);	
		}
			 
	    TncMargin.prototype.init = function (settings) 
		{
			var self = this;
			self.settings = settings || {};
			
			self.top = getNum(self.settings.top, 0);
			self.right = getNum(self.settings.right, 0);
			self.bottom = getNum(self.settings.bottom, 0);
			self.left = getNum(self.settings.left, 0);
		};
		
		TncMargin.prototype.getSettings = function()
		{
			var self = this;			
			
			return {
				top: self.top,
				right: self.right,
				bottom: self.bottom,
				left: self.left				
			};
		};	
		
		TncMargin.prototype.applyMargin = function ($e) 
		{
			var self = this;
			$e.css({
				top: self.top + '%',
				right: self.right  + '%',
				bottom: self.bottom  + '%',
				left: self.left	 + '%'					
			});
		};		
		
		TncMargin.prototype.unload = function()
		{
			var self = this;
			del(self, 'settings');
			
			self.unloaded = true;
			
			log('Margin unloaded', self);
			
			self = null;
		};
		
		// =======
	    // Transition	
	    function TncTransition(settings, obj) 
	    {
			this.init(settings);			
			this.obj = obj;			
		}
			 
	    TncTransition.prototype.init = function (settings) 
		{
			var self = this;
			self.settings = settings || {};					
			self.transitionType = getStr(self.settings.transitionType, 'fade').toLowerCase();
			self.duration = getNum(self.settings.duration, 2000);
			self.delay = getNum(self.settings.delay, 0);
			self.extraDelay = 0; // For custom situations
			self.transitionDisabled = false;
		};	
		
		TncTransition.prototype.applyTransition = function (obj_new, obj_old, $e_new, $e_old, callback) 
		{
			var self = this;											
			
			//if (self.delay > 0)
			//{
			var duration = self.duration;
			if (self.transitionDisabled)
			{
				duration = 0;
			}			
			
			if ($e_new)
			{
				$e_new.triggerHandler('beforeTransition');
				$e_new.css('z-index', 20);				
			}

			if ($e_old)
			{
				$e_old.triggerHandler('beforeTransition');
				$e_old.css('z-index', 0);				
			}
			
			switch(self.transitionType)
			{
				case 'rotate3d-top': self.rotate3d_top($e_new, $e_old, duration); break;
				case 'rotate3d-bottom': self.rotate3d_bottom($e_new, $e_old, duration); break;
				case 'rotate3d-right': self.rotate3d_right($e_new, $e_old, duration); break;
				case 'rotate3d-left': self.rotate3d_left($e_new, $e_old, duration); break;
				case 'scale': self.scale($e_new, $e_old, duration); break;
				case 'clips': self.clips($e_new, $e_old, duration); break;
				case 'none': { duration = 0;  self.fade_in($e_new, $e_old, duration); break; }
				case 'fadein':
				default: self.fade_in($e_new, $e_old, duration);					
			}										
												
			setTimeout(callback, self.duration+50);
		};
		
		TncTransition.prototype.getSettings = function()
		{
			var self = this;			
			
			return {
				transitionType: self.transitionType,
				duration: self.duration				
			};
		};
		
		TncTransition.prototype.unload = function()
		{
			var self = this;
			
			del(self, 'settings');
			self.obj = null;
			
			self.unloaded = true;
			
			log('Transition unloaded', self);
			self = null;
		};
		
		// TRANSITION TYPES
		
		TncTransition.prototype.rotate3Dhelper = function($e_new, $e_old, duration, property, reverse)
		{
			var $e_new = $e_new;
			var $e_old = $e_old;
			
			if ($e_new && $e_new.transition)
			{
			
				var duration = duration;
				var property = property;
				var reverse = reverse;
				var radius = $e_new.attr('data-radius');
				var $newParent = $e_new.parent();
				var $oldParent = null;
				if ($e_old && $e_old.transition)
				{
					$oldParent = $e_old.parent();
				}
				
				var degfrom = 90;
				var degTo = -90;
				var options;
				
				if (reverse) 
				{ 
					degfrom = degfrom * -1; 
					degTo = degTo * -1;
				};
				
				var ready = function()
				{					
					module.rotate3dtransform($e_new, radius, property, 0, duration);
					if ($oldParent)
					{
						module.rotate3dtransform($e_old, radius, property, degTo, duration);
					}
				}	

				$newParent.css({ opacity: limitOpacity(1), zIndex:30 });
				$e_new.css({ opacity: limitOpacity(1), zIndex:30 });	
				module.rotate3dtransform($e_new, radius, property, degfrom, 0);
				
				setTimeout(ready, 100);
			}
		}
		
		TncTransition.prototype.fade_in = function($e_new, $e_old, duration)
		{
			var self = this;
			
			$e_new.transition({ opacity: limitOpacity(0), queue: true }, 10);
			$e_new.transition({ opacity: limitOpacity(1), queue: true}, duration);
			if ($e_old)
			{
				$e_old.transition({ opacity: limitOpacity(1), queue: true }, 10);
				$e_old.transition({ opacity: limitOpacity(0), queue: true}, duration);
			}			
		};
		
		TncTransition.prototype.scale = function($e_new, $e_old, duration)
		{
			var self = this;
			
			$e_new.transition({ scale: 0.1, opacity: limitOpacity(0), queue: true}, 10);
			$e_new.transition({ scale: 1, opacity: limitOpacity(1), queue: true}, duration);
			if ($e_old)
			{
				$e_old.transition({ opacity: limitOpacity(1), queue: true }, 10);
				$e_old.transition({ opacity: limitOpacity(0), queue: true }, duration);
			}			
		};
		
		TncTransition.prototype.rotate3d_left = function($e_new, $e_old, duration)
		{	
			var self = this;	
			
			self.rotate3Dhelper($e_new, $e_old, duration, 'rotateY', false);
		};
		
		TncTransition.prototype.rotate3d_right = function($e_new, $e_old, duration)
		{	
			var self = this;
			
			self.rotate3Dhelper($e_new, $e_old, duration, 'rotateY', true);		
		};
		
		TncTransition.prototype.rotate3d_top = function($e_new, $e_old, duration)
		{	
			var self = this;			
			self.rotate3Dhelper($e_new, $e_old, duration, 'rotateX', false);
		};
		
		TncTransition.prototype.rotate3d_bottom = function($e_new, $e_old, duration)
		{	
			var self = this;							
			
			self.rotate3Dhelper($e_new, $e_old, duration, 'rotateX', true);										
		};
		
		TncTransition.prototype.clips = function($e_new, $e_old, duration)
		{	
			var self = this;
			
			var $e = $($e_new.children()[0])
			var $eContent = $($e.children()[0])
			
						
			$e_new.css({ opacity: 1 });
						
			
			var rows = Math.floor(Math.random() * 4) + 1;
			var cols = Math.floor(Math.random() * 4) + 1;			
			var clips = genClips($e, rows, cols);
			
			//$e.css({ display: 'none' });
			
			
			$eContent.css({ display: 'none' });
			/*
			function removeClips()
			{	
				$('div.clipParent', $e_new).remove();
			}
			
			setTimeout(removeClips, duration);	
			*/
			
			
			var doClip = function(clip)
			{														
				function showClip(clip){
					var $e = clip.$e;				
					var range = $e_new.width();
					var startTime =  ((duration / 2) / clips.length) * (clip.r * clip.c);
					
					var x = Math.floor(Math.random() * range + 1) - (range/2);
					var y = Math.floor(Math.random() * range + 1) - (range/2);	
					
					$e.transition({ 
						x: x, 
						y: y,
						opacity: 0							
					}, { 						
						duration: 0, 
					});
					
					$e.transition({ 
						x: 0, 
						y: 0,
						opacity: 1,					
						delay: startTime,
						queue: true,
						easing: 'cubic-bezier(0.255, 0.010, 0.375, 1.585)'
					}, { 						
						duration: (duration / 2) 
					});											
				}
				
				showClip(clip);
			}	
			
			for(var i=0;i<clips.length;i+=1)
			{						
				doClip(clips[i]);
			}
			
			if ($e_old)
			{
				$e_old.transition({ opacity: 1, queue: true }, 10);
				$e_old.transition({ opacity: 0.001, queue: true }, duration);
			}
		};
		
		
		// =======
	    // Position	
	    function TncPosition(settings) 
	    {
			this.init(settings);
		}
			 
	    TncPosition.prototype.init = function (settings) 
		{
			var self = this;
			self.settings = settings || {};
					
			self.position = getNum(settings.position, 1);
			self.xPos = getNum(settings.xPos, 0);
			self.yPos = getNum(settings.yPos, 0);
			self.height = getNum(settings.height, 0);
			self.width = getNum(settings.width, 0);		
			self.zIndex = getNum(settings.zIndex, 0);
		};
		
		TncPosition.prototype.load = function()
		{
			var self = this;
			
			// setup position and size limitations (?)			
			self.width = roundPerc( limitPerc(self.width) );
			self.height = roundPerc( limitPerc(self.height) );
			
			self.xPos = roundPerc( limitPerc(self.xPos + self.width) - self.width);
			self.yPos = roundPerc( limitPerc(self.yPos + self.height) - self.height);
			
			self.xPos = roundPerc(limitPerc(self.xPos));
			self.yPos = roundPerc(limitPerc(self.yPos));			
			
			switch(self.position)
			{
				case 2: // topleft
					self.left = 100 - (self.xPos + self.width);
					self.top = self.yPos;					
					break;
				case 3: // bottomleft
					self.left = self.xPos;
					self.top = 100 - (self.yPos + self.height);					
					break;
				case 4: // bottomright
					self.left = 100 - (self.xPos + self.width);
					self.top = 100 - (self.yPos + self.height);					
					break;
				case 1: // topright
				default:
					self.left = self.xPos;
					self.top = self.yPos;									
			}
		};
		
		TncPosition.prototype.unload = function()
		{
			var self = this;
			
			delete self.settings;
			
			self.unloaded = true;
			
			log('Position unloaded', self);
			self = null;
		};
		
		TncPosition.prototype.getSettings = function()
		{
			var self = this;
						
			return {				
				position: self.position,
				xPos: self.xPos,
				yPos: self.yPos,
				height: self.height,
				width: self.width,
				zIndex: self.zIndex				
			};
		};
		
		TncPosition.prototype.fromTopLeft = function(percentageLeft, percentageTop, percentageWidth, percentageHeight)
		{
			var self = this;
			self.width = percentageWidth;
			self.height = percentageHeight;			
			
			var percentageRight = 100 - (percentageLeft + percentageWidth);
			var percentageBottom = 100 - (percentageTop + percentageHeight);
			
			switch(self.position)
			{
				case 2:
					self.xPos = percentageRight;
					self.yPos = percentageTop;					
					break;
				case 3:
					self.xPos = percentageLeft;
					self.yPos = percentageBottom;
					break;
				case 4:
					self.xPos = percentageRight;
					self.yPos = percentageBottom;
					break;
				case 1:
				default:
					self.xPos = percentageLeft;
					self.yPos = percentageTop;		
			}							
		};
		
		TncPosition.prototype.applyPosition = function($e)
		{
			var self = this;
			
			self.load();
			
			var o = { 
					left: self.left + '%',
					top: self.top + '%', 
					width: self.width +'%',
					height: self.height +'%',
					zIndex: self.zIndex				
			};
			$e.css(o);
		}	
		/*
		
function wait(time, func)
{
  return setTimeout(func, time);
}

var PlayList = (function(items)
  {    
    
    var playList = this;
    playList.items = items || [];

    playList.index = 0;
    playList.currentItem = null;
    playList.nextItem = null;	

    playList.stateList = ['initial', 'init', 'loaded', 'show', 'finished', 'hide', 'unload'];
    playList.STATE_INITIAL = 0, 
    playList.STATE_INIT = 1, 
    playList.STATE_LOADED = 2, 
    playList.STATE_SHOW = 3,
    playList.STATE_FINISHED = 4,
    playList.STATE_HIDE = 5,
    playList.STATE_UNLOAD = 6;			
	
	function currentItemStateChanged(item)
	{			
	    if (item.state == playList.STATE_FINISHED)
	    {
	    	// Move to next state (STATE_HIDE) when next item is loaded
	    	if (playList.nextItem.state === playList.STATE_LOADED)
	    	{
	    		slide();
	    	}
	    }
	    else
	    {
	    	if (item.state == playList.STATE_SHOW)
		    {
		    	if (playList.nextItem)
		    	{
		    		playList.nextItem
		    	}	
			}
	    
	    	// Move seamlessly to next state
			nextState(item);
	    }
	}
	
	function nextItemStateChanged(item)
	{
	    if (item.state == playList.STATE_LOADED)
	    {
	    	// Move to next state (STATE_SHOW) when current item is finished
	    	if (playList.currentItem.state === playList.STATE_FINISHED)
	    	{
	    		slide();
	    	}		    	
	    }
	    else
	    {
	    	// Move seamlessly to next state
			nextState(item);
	    }
	}
	
    function stateChanged(item)
    {
    	item.state = item.state || 0;
    
    	if (playList.currentItem && item === playList.currentItem) { currentItemStateChanged(item) }
    	else if (playList.nextItem && item === playList.nextItem) { nextItemStateChanged(item) }
    	else
    	{    		    	
			// Move seamlessly to next state
			nextState(item);
    	}        
    }
    
    function nextState(item)
    {
    	// if not already unloaded
		if (state < playList.STATE_UNLOAD)
		{
			// Move seamlessly to next state
			item.state = validIndex(playList.stateList, state += 1);
			 			   
		    item.stateComplete = function() { wait(10, stateChanged(item)); };
		
		    if (item.stateChanged)
		    {							
		      item.stateChanged(item, playList, state);
		    }
		    else
		    {
		      wait(10, item.stateComplete);
		    }
		}
    }
    
    function slide()
    {
    	nextState(playList.currentItem);
    	//nextState(playList.nextItem);    	    
      	playList.index += 1;
      	playItem();
    }

    function validIndex(arr, index)
    {
      if (index < 0 || arr.length === 0)
      {
        index = 0;
      }
      else if (index >= arr.length)
      {
        index = index % arr.length;
      }
      return index;
    }

    function hasNext()
    {
      if (playList.index+1 < playList.items.length)
      {
        return true;
      }
      return false;
    }

    function playItem()
    {				
      var index = playList.index;
      index = validIndex(playList.items, index);

      playList.currentItem = playList.items[index];
      playList.currentItem.index = index;
      if (hasNext())
      {
        playList.nextItem = playList.items[index+1];
        playList.nextItem.index = index+1;
      }
      else
      {
        playList.nextItem = null;
      }
      
      stateChanged(playList.currentItem);
    }

    playList.play = function(resume)
    {
      playList.paused = false;
      if (!resume)
      {
        playList.index = 0;
      }
      if (playList.items.length > 0)
      {
        playItem();
      }
    }

    playList.pause = function ()
    {
      playList.paused = true;
    }
   
  });
			
		}
		*/
		$(document).ready(documentReady);  
		
		//3dTransform
		(function(module)
		{
			function rotate3dtransform($e, radius, property, angle, duration)
			{
				$e.css('transition', 'none');
				
				//setTimeout(function() {
					
					if (!radius || isNaN(radius))
					{
						radius = 0;
					}
					
					var rotate3d;
					if (property == 'rotateY')
					{
						rotate3d = '0,1,0,'+angle+'deg';
					}
					else
					{
						rotate3d = '1,0,0,'+angle+'deg';
					}
					
					var transforms = [];
					//transforms.push('perspective(1000px)');
					//transforms.push('translate3d(0,0,'+ roundPerc(radius * -1) +'px)');
					transforms.push('rotate3d('+rotate3d+')');
					
					var transform = transforms.join(' ');
					var origin = '50% 50% '+(-roundPerc(radius))+'px';
										
					$e.css({ 
						'transition': 'all '+duration+'ms',						
						'transform-origin': origin,
						'transform': transform
					});										
				//}, 100);
			}
			
			function create3dpanel($element, numFaces, vertical)
			{
				var $e = $element;
				var $p = $e.parent();
								
				var w = $p.width();
				var h = $p.height();
				
				var sideMargin = 0; 
				var maxWidth = w + (sideMargin * 2);
				var maxHeight = h;
				var radius = maxWidth / (2 * Math.tan(deg2rad( 180 / numFaces)));
				
				if (vertical)
				{
					radius = maxHeight / (2 * Math.tan(deg2rad( 180 / numFaces)));
				}
				
				$p.css({ 'transform-style': 'preserve-3d', 'transform': 'perspective(1000px)' });				
				$e.css({ 'backface-visibility': 'hidden' });
				$e.attr('data-radius', radius);
				rotate3dtransform($e, radius, 'rotateY', 0, 0);
			}
			
			function create3dpoly($parent)
			{
				var $e = $parent;				
				var $space3d = $new('div', $e);
				$space3d.addClass('space3d');			
				createPolygon($space3d, 4);	
				
				return $space3d;
			}
						
			function deg2rad(degrees) { return degrees * (Math.PI/180); }	
			function rad2deg(radians) { return radians * (180/Math.PI); }

			var curRotation = 0;
			function rotatePoly($poly, deg)
			{
				curRotation += deg;
				$poly.transition({ rotateY: curRotation+'deg' }, 0);				
			}
			
			function createPolygon($e, numFaces)
			{				
				var sideMargin = 0; 
				
				var w = $(window).width();
				var h = $(window).height();
			
				var maxWidth = w + (sideMargin * 2);
				var maxHeight = h;
				var radius = maxWidth / (2 * Math.tan(deg2rad( 180 / numFaces)));
				
				$('.space3d').css('perspective', (radius + w) + 'px')					
				var $poly = $new('div', $e);
				$poly.addClass('poly');		
				$poly.width(w);
				$poly.height(h);			
				$poly.css('transform', 'translateZ(-'+radius+'px)');

				var rotation = 0;			
				var faces = [];
				
				for(var i=0;i<numFaces;i+=1)
				{
					var $face = $new('div', $poly);
					$face.addClass('face');								
					$face.css( { position: 'absolute', left:0, top:0, opacity:1  } ); // padding: '0 0 0 100px'				
					$face.width(w);
					$face.height(h);		
					$face.css('transform', 'rotateY('+rotation+'deg) translateZ('+radius+'px)');				
					faces.push({ origRotation: rotation, $face: $face } )							
					rotation += 360 / numFaces; 								
				}
			}
			
			module.create3dpanel = create3dpanel;
			module.create3dpoly = create3dpoly;
			module.rotate3dtransform = rotate3dtransform;
		}(module));		
	} 	
	
	loadScriptsNoCache(['theme/jquery.min.js'], function() {
		// jquery loaded
		var scripts = [
		               'theme/jqDragAndMove.js',
		               'theme/jquery.transit.js',
		               'theme/sammy-0.7.4.min.js',
		               'theme/knockout-3.2.0.js'
		               ];
		
		loadScriptsNoCache(scripts, tncModule);
	});
	
	
}());
