
/* begin MiniStats */
(function($)
{	
	// constructor
	function MiniStats(root, conf)
	{	
		// Private fields ------------------------------------------------------------------
	
		var _root = $(root),
			_domElement = _root[0],
			_self = this,
			_opts = {
				debug: true
			}
			;
			$.extend(_opts, conf);
	
		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			debug: function(str)
			{
				if (_opts.debug == true) {
					try {
						console.log("str: " + str);
					} 
					catch (e) {
						alert("str: " + str);
					}
				}
			}
		});
	
		// Private methods -----------------------------------------------------------------
	
		function init()
		{	
			var rows = _root.find("table > tbody > tr");
			rows.hover(
				function () {
					$(this).addClass("on");
				}, 
				function () {
					$(this).removeClass("on");
				}

			)
		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};

	// jQuery plugin implementation
	$.fn.miniStats = function(conf)
	{
		var opts = {};
		$.extend(opts, conf);
		
		this.each(function()
		{
			var $instance = new MiniStats($(this), opts);
		});
		return this; 
	};
})(jQuery);
/* end MiniStats */
(function($)
{	
	// constructor
	function SportsnetPage(conf)
	{	
		// Private fields ------------------------------------------------------------------
		var _self = this,
			_initQueue = [],
			_initObjectUIDCount = 0,
			_tickIntervalID,
			_curInitObject,
			_curState = "paused",
			_rankMap = {
				CORE: 0, //page starters and core functions (e.g., cookie checker); keep in mind - making everything core defeats the purpose :)
				PAGE_MAJOR: 1, //page elements that need to be shown immediately (e.g., top story)
				PAGE_MINOR: 2, //page elements lower on page, or having initial state that shows content (e.g., scrollers, polls)
				NAV: 3, //site navigation (may be counter-intuitive to have under page content, but usually users are here to browse first)
				AJAX: 4,
				DISPLAY: 5 //less noticable display functions that add polish/details (e.g., ellipsis)
			},
			_opts = {
				debug: false
			}
			;
			$.extend(_opts, conf);
	
		// Public methods ------------------------------------------------------------------
		$.extend(_self, {
			registerInit: function(initObj)
			{
				//{_expression:"p", _contextExpression:"#pHolder", _functionRef:"testFunc", _data:{}, _rank:"PAGE_MAJOR"}
				if (initObj._rank == undefined || _rankMap[initObj._rank] == undefined) {
					initObj._rank = "DISPLAY";
				}
				addToQueue(initObj);
			},
			play: function()
			{	
				if (_curState == "paused") {
					_curState = "playing";
					startTicking();
				}
			},
			pause: function()
			{	
				_curState = "paused";
				stopTicking();
			},
			executeInit: function()
			{
				executeNextInit();
			},
			printQueue: function()
			{
				printQueue();
			}
		});
	
		// Private methods -----------------------------------------------------------------
		function initSubQueues()
		{
			//creates a nested array in _initQueue for each of the ranks, at the index associated with the rank
			//e.g., CORE objects array is index 0 in _initQueue
			for (var curRank in _rankMap) {
				_initQueue[_rankMap[curRank]] = [];
			}
		};
		
		function addToQueue(initObj)
		{
			if (objectIsInQueue(initObj) == false) {
				var targetArrayName = initObj._rank;
				var targetArray = _initQueue[_rankMap[targetArrayName]];
				var oldLen = targetArray.length;
				targetArray.push(initObj);
				debug("## addToQueue ADDED item (_expression: " + initObj._expression + "); items in " + targetArrayName + ": " + targetArray.length + " (was " + oldLen + ")");
			} else {
				debug("## addToQueue DID NOT ADD item with _expression: " + initObj._expression + "; item already exists");
			}
		};
		
		function executeInit()
		{
			//always acts on _curInitObject
			//printObjectProps(_curInitObject);
			var funcExists = $.fn[_curInitObject._functionRef] != undefined;
			if (funcExists == true) {
				//the timeout's to make fireFunction() start its own call stack (can't pass params to setTimeout in IE, so arbitrary function it is)
				setTimeout(function(){
					fireFunction(_curInitObject);
				}, 0);
			}
			else {
				debug("## executeInit; DID NOT FIRE; function not found (_functionRef: '" + _curInitObject._functionRef + "')");
				onExecuteInitComplete();
			}
		};
		
		function fireFunction(initObj)
		{
			var start = new Date().getTime();
			var jqParams = [initObj._expression];
			if (initObj._contextExpression != undefined) {
				var contextJQ = $(initObj._contextExpression);
				jqParams.push(contextJQ);
			}
			var jqResultSet = $.apply($, jqParams);
			jqResultSet[initObj._functionRef](initObj._data);
	
			var end = new Date().getTime();
			var timeToExecute = end - start;
			debug("## executeInit>fireFunction (" + _curInitObject._functionRef + "); timeToExecute: " + timeToExecute);
			
			onExecuteInitComplete();
		};
				
		function onExecuteInitComplete()
		{
			//debug("## onExecuteInitComplete; continuing...");
			setTimeout(executeNextInit, 0); //the timeout's to break out of the call stack
		};
		
		function executeNextInit()
		{
			_curInitObject = getNextInitObject();
			if (_curInitObject == undefined) {
				debug("## executeNextInit - no more initObjects available");
			
				postProcessPage();
			}
			else {
				//test to see if context or element is ready (just need to test one, if context exists, element must)
				var jqObj = _curInitObject._contextExpression != undefined ? $(_curInitObject._contextExpression) : $(_curInitObject._expression);
				var jqObj = jqObj != undefined;
				if (jqObj != undefined) {
					executeInit();
				}
				else {
					debug("## executeNextInit; element/context doesn't exist; not executing and continuing...");
					onExecuteInitComplete();
				}
			}
		};
		
		function getNextInitObject()
		{
			var curObj;
			for (var i=0; i<_initQueue.length; i++) {
				var curArray = _initQueue[i];
				curObj = curArray.shift();
				if (curObj != undefined) break;
			}
			return curObj;
		};
		
		function objectIsInQueue(testObj)
		{
			var isInQ = false;
			for (var curRank in _rankMap) {
				var curSubQueue = _initQueue[_rankMap[curRank]];
				for (var i = 0; i < curSubQueue.length; i++) {
					var curIObj = curSubQueue[i];
					var sameExpr = curIObj._expression == testObj._expression;
					var sameContext = curIObj._contextExpression == testObj._contextExpression;
					var sameFunc = curIObj._functionRef == testObj._functionRef;
					var sameData = haveSameData(curIObj, testObj);
					isInQ = sameExpr && sameContext && sameFunc && sameData;
					if (isInQ == true) break;
				}
				if (isInQ == true) break;
			}
			return isInQ;
		};
		
		function haveSameData(testObj1, testObj2){
			//NOTE: this does not recurse - meant for testing primitive values in options object passed to constructor
			var haveSameData = true;
			//if both are null or undefined, we're done
			var obj1HasData = testObj1._data != undefined && testObj1._data != null;
			var obj2HasData = testObj2._data != undefined && testObj2._data != null;
			var bothHaveData = obj1HasData && obj2HasData;
			if (bothHaveData) {
				for (var curProp in testObj1._data) {
					//debug("testing data " + curProp + "; testObj1: " + testObj1._data[curProp] + "; testObj2:" + testObj2._data[curProp]);
					var isMatch = testObj1._data[curProp] == testObj2._data[curProp];
					//debug("isMatch: " + isMatch);
					if (isMatch == false) {
						haveSameData = false;
						break;
					}
				}
			}
			else {
				haveSameData = obj1HasData == obj2HasData;
			}
			return haveSameData;
		};
		
		function printQueue()
		{
			debug("-------------------\nprintQueue...");
			for (var curRank in _rankMap) {
				var curSubQueue = _initQueue[_rankMap[curRank]];
				debug(curRank + " (" + curSubQueue.length + ")");
				for (var i = 0; i < curSubQueue.length; i++) {
					var curIObj = curSubQueue[i];
					debug(" - _expression: " + curIObj._expression + ", _contextExpression: " + curIObj._contextExpression + ", _functionRef: " + curIObj._functionRef + ", _data: " + curIObj._data);
				}
			}
			debug("done\n-------------------");
		};
		
		function debug(str)
		{
			if (_opts.debug == true) {
				try {
					trace(str);
				} 
				catch (e) {
					alert("debug: " + str);
				}
			}
		};
			
		// generic binding function
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
				
			$(_self).bind(name, fn);
		};
	
		function postProcessPage()
		{
			//run any functions we need to have happen on page load
			
			//reposition columnist image if necessary
			var copyHolder = $("div.column-page-heaader");
			if (copyHolder.height() > 80) { //magic number - any taller and the image shows space below, AND MUST BE MOVED!
				var titleHolder = $("div.opinion-page-header");
				var imageHolder = $("div.columnist-header-pic");
				var yOffset = $.browser.msie6 ? 0 : 3; //magic number (except for ie6?) - 2 for margin-top, 1 for extra pixel in image cropped by div
				var targetTop = copyHolder.offset().top + copyHolder.height() - imageHolder.height() - yOffset;
				imageHolder.css({
					top: targetTop
				});
			}
		};
	
		function init()
		{
			initSubQueues();
		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};
	
	// jQuery plugin implementation
	$.fn.sportsnetPage = function(opts)
	{
		return new SportsnetPage(opts);
	};
})(jQuery);

var sportsnetPage =  $.fn.sportsnetPage({});
(function($)
{	
	// constructor
	function TopNavMenu(root, conf)
	{	
		// Private fields ------------------------------------------------------------------
	
		var _root = $(root),
			_domElement = _root[0],
			_self = this,
			_hotspot = _root.parent(),
			_menu,
			_hideTimer,
			_showWait = 150,
			_hideWait = 250,
			_animDur = 150,
			_menuHeight,
			_positionLeft = "default", //hotspot-left 
			_positionTop = "default", //hotspot-top 
			_opts = {
				debug: false
			}
			;
			$.extend(_opts, conf);
	
		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			toggleMenu: function()
			{
				if (_root.hasClass("on")) {
					_self.debug("calling closeMenu");
					_self.closeMenu();
				}
				else {
					_self.debug("calling doMouseEnter");
					_self.doMouseEnter();
				}
			},
			positionMenu: function()
			{
				if (_menu.hasClass("body-flyout") && _menu.parent()[0].tagName != "BODY") {
					_self.debug("moving menu element to end of document body");
					$("body").append(_menu); //move in-body flyout to end of body (if it's not there already) to avoid messy cascading styles
				}
				
				if (_positionLeft != "default") {
					var targetX;
					switch (_positionLeft) {
						case "hotspot-left":{
							targetX = _hotspot.offset().left;
							break;
						}
						case "hotspot-right":{
							targetY = _hotspot.offset().left + _hotspot.outerWidth();
							break;
						}
					}
					_self.debug("setting left position: " + targetX);
					_menu.css({
						left: targetX
					});
				} else {
					_self.debug("using default _positionLeft");
				}
				if (_positionTop != "default") {
					var targetY;
					switch (_positionTop) {
						case "hotspot-top":{
							targetY = _hotspot.offset().top;
							break;
						}
						case "hotspot-bottom":{
							targetY = _hotspot.offset().top + _hotspot.outerHeight();
							break;
						}
					}
					_self.debug("setting top position: " + targetY);
					_menu.css({
						top: targetY
					});
				} else {
					_self.debug("using default _positionTop");
				}
			},
			openMenu: function()
			{
				if (_root.hasClass("on") == false) {
					_self.debug("openMenu");
					_self.positionMenu();
					_menu.show();
					_menu.animate({height:_menuHeight}, _animDur, "swing", function() { _self.onOpen(); });
				}
			},
			onOpen: function()
			{
				_self.debug("onOpen");
				_root.addClass("on");
			},
			closeMenu: function()
			{
				_self.debug("closeMenu");
				_menu.animate({height:"0px"}, _animDur, "swing", function() { _self.onClose(); });
			},
			onClose: function()
			{
				_self.debug("onClose");
				_menu.hide();
				_root.removeClass("on");
			},
			doMouseEnter: function()
			{
				_self.debug("doMouseEnter");
				clearTimeout(_hideTimer);
				_hideTimer = setTimeout(function(){
					_self.openMenu();
				}, _showWait);
			},
			doMouseLeave: function()
			{
				_self.debug("doMouseLeave");
				clearTimeout(_hideTimer);
				_hideTimer = setTimeout(function(){
					_self.closeMenu();
				}, _hideWait);
			},
			debug: function(str)
			{
				if (_opts.debug == true) {
					try {
						trace(str);
					} 
					catch (e) {
						alert("str: " + str);
					}
				}
			}
		});
	
		// Private methods -----------------------------------------------------------------
		
		// generic binding function
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
				
			$(_self).bind(name, fn);
		};
	
		function init()
		{	
			var hasMenuName = /\bmenu:\s*(\S+)\b/.test(_domElement.className);
			
			_self.debug("_domElement.className: " + _domElement.className);
			_self.debug("hasMenuName: " + hasMenuName);
			
			if (hasMenuName == true) {
				var menuElementName = /\bmenu:\s*(\S+)\b/.exec(_domElement.className)[1];
				_self.debug("menuElementName: " + menuElementName);
				_menu = $("#" + menuElementName);
				
				//by default, just expands the menu in-place; if over-ridden, it positions the menu relative to hotspot
				var hasPositionOverride = /\bposition:\s*(\S+)\b/.test(_domElement.className);
				if (hasPositionOverride == true) {
					var posString = /\bposition:\s*(\S+)\b/.exec(_domElement.className)[1];
					_self.debug("posString: " + posString);
					if (posString.indexOf("left") != -1) {
						_positionLeft = "hotspot-left";
					}
					if (posString.indexOf("top") != -1) {
						_positionTop = "hotspot-top";
					}
					if (posString.indexOf("bottom") != -1) {
						_positionTop = "hotspot-bottom";
					}
				}
			}
			
			if (_menu != undefined) {
				_menuHeight = _menu.height();
				_menu.height("0px");
				
				_hotspot.click(function(){
					_self.toggleMenu();
				});
				_hotspot.mouseenter(function(){
					_self.doMouseEnter();
				});
				_hotspot.mouseleave(function(){
					_self.doMouseLeave();
				});
				_menu.mouseenter(function(){
					_self.doMouseEnter();
				});
				_menu.mouseleave(function(){
					_self.doMouseLeave();
				});
				_menu.find('a').click(function(){
					_self.doMouseLeave();	
				});
			}

		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};

	// jQuery plugin implementation
	$.fn.topNavMenu = function(conf)
	{
		var opts = {};
		$.extend(opts, conf);
		
		this.each(function()
		{
			var $instance = new TopNavMenu($(this), opts);
		});
		return this; 
	};
})(jQuery);
/**
 * jquery.scrollable 1.0.4 - Scroll your HTML with eye candy.
 * 
 * ******** With modification for Sportsnet ********
 * 
 * Copyright (c) 2009 Tero Piirainen
 * http://flowplayer.org/tools/scrollable.html
 *
 * Dual licensed under MIT and GPL 2+ licenses
 * http://www.opensource.org/licenses
 *
 * Launch  : March 2008
 * Date: 2009-06-08 10:42:59 +0000 (Mon, 08 Jun 2009)
 * Revision: 1898 
 */
(function($) {
		
	// static constructs
	$.tools = $.tools || {version: {}};
	
	$.tools.version.scrollable = '1.0.4';
				
	var current = null;		

	
	// constructor
	function Scrollable(root, conf) {

		// current instance
		var self = this;  
		if (!current) { current = self; }		
		
		// generic binding function
		function bind(name, fn) {
			$(self).bind(name, function(e, args)  {
				if (fn && fn.call(this, args.index) === false && args) {
					args.proceed = false;	
				}	
			});	
			
			return self;
		}
		
		function debug(str) {
			var showDebug = false;
			if (showDebug == true) {
				try {
					console.log(str);
				} 
				catch (e) {
					alert("debug: " + str);
				}
			}
		}
		
		// bind all callbacks from configuration
		$.each(conf, function(name, fn) {
			if ($.isFunction(fn)) { bind(name, fn); }
		});   
		
		// horizontal flag
		var horizontal = !conf.vertical;				
		
		// wrap (root elements for items)
		var wrap = $(conf.items, root),
			items = wrap.children()
			;
		
		// current index
		var index = 0;		
		
		function find(query, ctx) {
			return query.indexOf("#") != -1 ? $(query).eq(0) : ctx.siblings(query).eq(0);	
		}		
		
		// get handle to navigational elements
		// AlexGorbatchev: replaced find(..., root) with $(root).find(...)
		var navi = $(root).find(conf.navi),
			prev = $(root).find(conf.prev),
			next = $(root).find(conf.next),
			prevPage = $(root).find(conf.prevPage),
			nextPage = $(root).find(conf.nextPage)
			;
		
		// methods
		$.extend(self, {
			
			getIndex: function() {
				return index;	
			},
	
			getConf: function() {
				return conf;	
			},
			
			getSize: function() {
				var size = self.getItems().size();
				
				return conf.fitPages
					? size
					: Math.ceil(size / conf.pageSize) * conf.pageSize
					;
			},
	
			getPageSize: function() {
				return conf.pageSize;
			},
			
			getPageAmount: function() {
				return Math.ceil(this.getSize() / conf.pageSize);
			},
			
			getPageIndex: function() {
				return Math.ceil(index / conf.pageSize);	
			},

			getRoot: function() {
				return root;	
			},
			
			getItemWrap: function() {
				return wrap;	
			},
			
			getItems: function() {
				return items;	
			},
			
			getVisibleItems: function() {
				return self.getItems().slice(index, index + conf.pageSize);	
			},
			
			/* all seeking functions depend on this */		
			seekTo: function(i, time, fn) {
				
				// default speed
				time = time || conf.speed;
				
				// function given as second argument
				if ($.isFunction(time)) {
					fn = time;
					time = conf.speed;
				}
								
				if (i < 0) { i = 0; }				
				if (i > self.getSize() - conf.pageSize) { return self; } 				

				var item = self.getItems().eq(i);					
				if (!item.length) { return self; }				
				
				
				// onBeforeSeek
				var p = {index: i, proceed: true};
				$(self).trigger("onBeforeSeek", p);				
				if (!p.proceed) { return self; }
									
				
				if (horizontal) {
 					var left = -item.position().left;					
					wrap.animate({left: left}, time, conf.easing, fn ? function() { fn.call(self); } : null);
					
				} else {
					var top = -item.position().top;										
					wrap.animate({top: top}, time, conf.easing, fn ? function() { fn.call(self); } : null);							
				}	
				
				
				// navi status update
				if (navi.length) {
					var page = Math.ceil(i / conf.pageSize);
					
					if (conf.maxItemsInNavi > self.getPageAmount()) {
						var klass = conf.activeClass;
						page = Math.min(page, navi.children().length - 1);
						navi.children().removeClass(klass).eq(page).addClass(klass);
					} else {
						$(navi).find(".current").html(page + 1);
					}
				} 
				
				// prev buttons disabled flag
				if (i === 0) {
					prev.add(prevPage).addClass(conf.disabledClass);					
				} else {
					prev.add(prevPage).removeClass(conf.disabledClass);
				}
								
				// next buttons disabled flag
				if (i >= self.getSize() - conf.pageSize) {
					next.add(nextPage).addClass(conf.disabledClass);
				} else {
					next.add(nextPage).removeClass(conf.disabledClass);
				}				
				
				current = self;
				index = i;				
				
				// onSeek after index being updated
				$(self).trigger("onSeek", {index: i});				
				return self; 
			},			
			
				
			move: function(offset, time, fn) {
				var to = index + offset;
				var max = self.getSize() - conf.pageSize;
				
				// AlexGorbatchev: fixed so that looping works in all directions for even and uneven pages
				if (conf.loop == true)
				{
					if (to == self.getSize())
						to = 0;
					else if (to > max)
						to = max;	
					else if (to < 0 && to > - conf.pageSize)
						to = 0;
					else if (to < 0)
						to = max;
				}
				
				return this.seekTo(to, time, fn);
			},
			
			next: function(time, fn) {
				return this.move(conf.scrollByPage ? conf.pageSize : 1, time, fn);	
			},
			
			prev: function(time, fn) {
				return this.move(conf.scrollByPage ? -conf.pageSize : -1, time, fn);	
			},
			
			movePage: function(offset, time, fn) {
				return this.move(conf.pageSize * offset, time, fn);		
			},
			
			setPage: function(page, time, fn) {
				var size = conf.pageSize;
				var index = size * page;
				var lastPage = index + size >= this.getSize(); 
				if (lastPage) {
					index = this.getSize() - conf.pageSize;
				}
				return this.seekTo(index, time, fn);
			},
			
			prevPage: function(time, fn) {
				return this.setPage(this.getPageIndex() - 1, time, fn);
			},  
	
			nextPage: function(time, fn) {
				return this.setPage(this.getPageIndex() + 1, time, fn);
			}, 
			
			begin: function(time, fn) {
				return this.seekTo(0, time, fn);	
			},
			
			end: function(time, fn) {
				return this.seekTo(this.getSize() - conf.pageSize, time, fn);	
			},
			
			reload: function() {
				return load();	
			},
			
			click: function(index, time, fn) {
				
				var item = self.getItems().eq(index);
				var klass = conf.activeClass;			
				
				// check that index is sane
				if (index < 0 || index >= this.getSize()) { return self; }
					
				
				// special case with two items
				if (conf.pageSize == 2) {
					if (index == self.getIndex()) { index--; }
					self.getItems().removeClass(klass);
					item.addClass(klass);					
					return this.seekTo(index, time, fn);
				}
				

				if (!item.hasClass(klass)) {				
					self.getItems().removeClass(klass);
					item.addClass(klass);
					var delta = Math.floor(conf.pageSize / 2);
					var to = index - delta;

					// next to last item must work
					if (to > self.getSize() - conf.pageSize) { 
						to = self.getSize() - conf.pageSize; 
					}
					
					if (to !== index) {
						return this.seekTo(to, time, fn);		
					}				 
				}
				
				return self;
			},

			// callback functions
			onBeforeSeek: function(fn) {
				return bind("onBeforeSeek", fn); 		
			},
			
			onSeek: function(fn) {
				return bind("onSeek", fn); 		
			}			
			
		});
		
		// mousewheel
//		if ($.isFunction($.fn.mousewheel)) { 
//			root.bind("mousewheel.scrollable", function(e, delta)  {
//				// opera goes to opposite direction
//				var step = $.browser.opera ? 1 : -1;
//				
//				self.move(delta > 0 ? step : -step, 50);
//				return false;
//			});
//		}  
		
		// prev button
		prev.addClass(conf.disabledClass).click(function() { 
			self.prev(); 
		});
		
		// next button
		next.click(function() { 
			self.next(); 
		});
		
		// next page button
		nextPage.click(function() { 
			self.nextPage(); 
		});

		// next page button
		prevPage.addClass(conf.disabledClass).click(function() { 
			self.prevPage(); 
		});		

		debug("items.length: " + items.length);
		debug("conf.pageSize: " + conf.pageSize);
		//added test to hide prev/next buttons if there is just one page - Chris Bennett 9/23/09
		var hasMultiplePages = items.length > conf.pageSize;
		if (hasMultiplePages == false) {
			prev.hide();
			next.hide();
			prevPage.hide();
			nextPage.hide();
		}
		
		// keyboard
		if (conf.keyboard) {			

			// keyboard works on one instance at the time. thus we need to unbind first
			$(document).unbind("keydown.scrollable").bind("keydown.scrollable", function(evt) {
				
				var el = current;	
				if (!el || evt.altKey || evt.ctrlKey) { return; }
					
				if (horizontal && (evt.keyCode == 37 || evt.keyCode == 39)) {					
					el.move(evt.keyCode == 37 ? -1 : 1);
					return evt.preventDefault();
				}	
				
				if (!horizontal && (evt.keyCode == 38 || evt.keyCode == 40)) {
					el.move(evt.keyCode == 38 ? -1 : 1);
					return evt.preventDefault();
				}
				
				return true;
				
			});	 
		}

		// navi 			
		function createDottedNavi() {			
	
			// generate new entries
			if (navi.is(":empty") || navi.data("me") == self) {
				
				navi.empty();
				navi.data("me", self);
				
				for (var i = 0; i < self.getPageAmount(); i++) {		
					
					var item = $("<" + conf.naviItem + "/>").attr("href", i).click(function(e) {							
						var el = $(this);
						el.parent().children().removeClass(conf.activeClass);
						el.addClass(conf.activeClass);
						self.setPage(el.attr("href"));
						return e.preventDefault();
					});
					
					if (i === 0) { item.addClass(conf.activeClass); }
					navi.append(item);					
				}
				
			// assign onClick events to existing entries
			} else {
				
				// find a entries first -> syntaxically correct
				var els = navi.children(); 
				
				els.each(function(i)  {
					var item = $(this);
					item.attr("href", i);
					if (i === 0) { item.addClass(conf.activeClass); }
					
					item.click(function() {
						navi.find("." + conf.activeClass).removeClass(conf.activeClass);
						item.addClass(conf.activeClass);
						self.setPage(item.attr("href"));
					});
					
				});
			}
			
			
			// item.click()
			if (conf.clickable) {
				self.getItems().each(function(index, arg) {
					var el = $(this);
					if (!el.data("set")) {
						el.bind("click.scrollable", function() {
							self.click(index);		
						});
						el.data("set", true);
					}
				});				
			}
			
			
			// hover
			if (conf.hoverClass) {
				self.getItems().hover(function()  {
					$(this).addClass(conf.hoverClass);		
				}, function() {
					$(this).removeClass(conf.hoverClass);	
				});
			}			
			
			return self;
		};
		
		function createNumeredNavi()
		{
			navi.append('<span class="current">1</span>');
			navi.append('<span class="separator">' + conf.numberedNaviSeparator + '</span>');
			navi.append('<span class="total">' + self.getPageAmount() + '</span>');
		};
		
		function createRows()
		{
			var pageSize = conf.size * conf.rows,
				pageCount = Math.ceil(self.getSize() / pageSize)
				;

			for (var i = 0; i < pageCount; i++)
			{
				wrap.find('li').slice(i * pageSize, (i + 1) * pageSize).wrapAll('<div class="scrolling-panel-page"></div>');
			}
		};
		
		if (conf.maxItemsInNavi > self.getPageAmount())
			createDottedNavi();
		else
			createNumeredNavi();
		
		if (conf.rows > 1)
			createRows();
			
		// interval stuff
		var timer = null;

		function setTimer() {
			timer = setInterval(function()  {
					
				// see if interval has been disabled at runtime
				if (conf.interval === 0) { 
					clearInterval(timer); 
				}
				
				self.next();
				
			}, conf.interval);
		}	
		
		if (conf.interval > 0) {			
			
			root.hover(function() {			
				clearInterval(timer);		
			}, function() {		
				setTimer();	
			});
			
			setTimer();	
		}
		
	} 

		
	// jQuery plugin implementation
	// AlexGorbatchev: renamed to scrollingPanel 
	$.fn.scrollingPanel = function(conf) { 

		// AlexGorbatchev: we actually want to be able to recreate the element when called
		// already constructed --> return API
		// var el = this.eq(typeof conf == 'number' ? conf : 0).data("scrollable");
		// if (el) { return el; }		
		
		var opts = {
			
			// basics
			size: 5,
			scrollByPage: false,			// AlexGorbatchev : allows to scroll in full page sizes
			maxItemsInNavi: 4,				// AlexGorbatchev : converts navi to X/NN style if more pages than this value
			numberedNaviSeparator: ' / ',	// AlexGorbatchev : separator that goes between X and NN when displayed
			vertical: false,			
			clickable: true,
			loop: false,
			interval: 0,			
			speed: 400,
			keyboard: true,			
			
			// other
			activeClass:'active',
			disabledClass: 'disabled',
			hoverClass: null,			
			easing: 'swing',
			
			// navigational elements
			items: '.items',
			prev: '.prev',
			next: '.next',
			prevPage: '.prevPage',
			nextPage: '.nextPage',			
			navi: '.navi',
			naviItem: 'a',
			api: false,
			
			// callbacks
			onBeforeSeek: null,
			onSeek: null
			
		};
		
		
		// AlexGorbatchev: default sportsnet values
		$.extend(opts, {
			scrollByPage: true,
			rows: 1,
			items: 'ul',
			hoverClass: 'hover',
			next: '.navigation .next', 
			prev: '.navigation .prev',
			navi: '.navigation .pager',
			fitPages: false,
			loop: false,
			keyboard: false
		});
		
		$.extend(opts, conf);	
		
		// allows specifying size like '3x2'
		if (typeof(opts.size) == 'string' && /^\d+x\d+$/.test(opts.size))
		{
			var $parts = opts.size.split('x');
			opts.size = parseInt($parts[0]);
			opts.rows = parseInt($parts[1]);
		}
		
		opts.pageSize = opts.size * opts.rows;
		
		this.each(function()
		{
			el = new Scrollable($(this), opts);
			$(this).data("scrollable", el);	
		});
		
		return opts.api ? el: this; 
		
	};
			
	
})(jQuery);
(function($)
{	
	// constructor
	function MediaAndPhotos(root, conf)
	{	
		// Private fields ------------------------------------------------------------------
	
		var _root = $(root),
			_self = this,
			_mediaPanel = _root.find(".scrolling-panel.media"),
			_mediaPanelHtml
			;
	
		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			activateMediaSection: function(index)
			{
				var $url = conf.mediaSections[index][1],
					$aTags = _root.find('.header .sections a')
					;
				
				$aTags.removeClass('active')
				$($aTags[index]).addClass('active');
				
				_self.loadMedia($url);
			},
			
			loadMedia: function(url)
			{	
				var $minTimePassed = false,
					$timer = getTimer(),
					$minTime = 500
					;

				_mediaPanel.find('.loading').show();
				
				function process(json)
				{
					_mediaPanel.html(_mediaPanelHtml);
					_mediaPanel.find('.loading').hide();
					
					var $items = $(_self).triggerHandler("mediaLoaded", [ json ]),
						$container = _mediaPanel.find(".scroll-container"),
						$ul
						;
					
					$container.html('<ul></ul>');
					$ul = $container.find("ul");
					
					for (var $i = 0; $i < $items.length; $i++) 
					{
						var $item = $items[$i];
						
						$(
						'<li>'
							+ '<div class="text"><div class="ellipsis">' + $item.label + '</div></div>'
							+ '<div class="img" style="background-image: url(' + $item.thumb + ')" />'
							+ '<span class="video"><img src="/assets/img/top_story/btn_play.png" /></span>'
						+ '</li>'
						).appendTo($ul);
						
						$ul.find('li:last')
							.data('url', $item.url)
							.click(function()
							{
								window.location = $(this).data('url');
							});
					}

					_mediaPanel.scrollingPanel($.extend({ size: '3x2' }, conf));
					
					$ul.find('.text > .ellipsis').ellipsis();
				};
				
				$.ajax({
					type: "GET",
					url: url,
					dataType: "json",
					success: function(json){
						var $time = getTimer() - $timer;
						
						if ($time > $minTime) 
							process(json);
						else 
							setTimeout(function(){
								process(json);
							}, $minTime - $time);
					}//,
					//error: function(XMLHttpRequest, textStatus, errorThrown){
						// typically only one of textStatus or errorThrown will have info
					//	trace("textStatus: " + textStatus);
					//	trace("errorThrown: " + errorThrown);
					//}
				});
			},
			
			createItem: function(url, thumb, label)
			{
				return { url : url, thumb : thumb, label : label };
			}
		});
	
		// Private methods -----------------------------------------------------------------
		
		function getTimer()
		{
			return new Date().getTime();
		};
		
		// generic binding function
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
				
			$(_self).bind(name, fn);	
		};
	
		function initLayout()
		{
			if ($.browser.msie6)
				_root.addClass('ie6');
			else if ($.browser.msie7)
				_root.addClass('ie7');
				
			var $photosPanel = _root.find(".scrolling-panel.photos");
			
			$photosPanel.scrollingPanel($.extend({ size: 1 }, conf));
			$photosPanel.find('li div p').addClass('ellipsis multiline').ellipsis();
			$photosPanel.find('li').click(function()
			{
				window.location = $(this).find('a')[0].href;
			})		
			
			var $sectionLinksContainer = _root.find(".header .sections"),
				$sections = []
				;
			
			//only show the section links if there's more than one section
			if (conf.mediaSections.length > 1) {
				for (var $i = 0; $i < conf.mediaSections.length; $i++) {
					var $item = conf.mediaSections[$i], $text = $item[0];
					
					$('<a href="#">' + $text + '</a>').appendTo($sectionLinksContainer);
					$('<span>|</span>').appendTo($sectionLinksContainer);
					
					$sectionLinksContainer.find('a:last').data('sectionIndex', $i).click(function(){
						_self.activateMediaSection($(this).data('sectionIndex'));
						return false;
					});
				}
			}
			
			// remove the last extra divider
			$sectionLinksContainer.find("span:last").remove();

			// add loader to the media panel
			_mediaPanel.append('<div class="loading">Loading</div>');

			// keep html those far
			_mediaPanelHtml = _mediaPanel.html();

			_mediaPanel.find('.loading').hide();
			
			// activate default first section
			_self.activateMediaSection(0);
		};
	
		// Initialization ------------------------------------------------------------------
		
		// bind all callbacks from configuration
		$.each(conf, bind);
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.mediaAndPhotos = function(conf)
	{ 
		var opts = {
			scrollByPage: true,
			items: 'ul',   
			hoverClass: 'hover',
			next: '.navigation .next', 
			prev: '.navigation .prev',
			navi: '.navigation .pager',
			keyboard: false
		}; 
		
		$.extend(opts, conf);		
		
		this.each(function() 
		{
			var $instance = new MediaAndPhotos($(this), opts);
			$(this).data("mediaAndPhotos", $instance);
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function TopModule(root, conf)
	{	
		// Private fields -------------------------------------------------------------------

		var _root = $(root),
			_ul = _root.find('> ul'),
			_thumbsHolder = _root.find('> ul.main'),
			_thumbs = _thumbsHolder.find('div.image-clip img'),
			_thumbBtns,
			_container = _root.find('.top-story-container'),
			_brightcovePlayer,
			_brightcovePlayerNameRoot = 'TopStoryPlayer',
			_curBrightcovePlayerName,  //_brightcovePlayerNameRoot appended with random number on each create to eliminate issues with IE (multiple audio streams were playing when user clicked a new thumbnail in the top story module)
			_closeBtnHtml = '<div id="videoClose">close</div>',
			
			_self = this,
			_timer = 0,
			_itemCount = _ul.children().length,
			_itemIndex = 0,
			
			_canToggleOverlay = true,
			_overlayDelay = 0,
			_expendClass = 'expanded';
			_opts = {
				debug: false
			}
			;
			
		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			next: function() 
			{
				_itemIndex ++;
				
				if (_itemIndex >= _itemCount)
					_itemIndex = 0;
					
				this.activate(_itemIndex);
			},
			
			activate: function(index)
			{
				var $curActive = _container.find('.content.active');

				if ($curActive.hasClass('video') == true)
				{
					$curActive.find('> a').show();
					removeBrightcove();
				}
				
				conf.hideFunc($curActive.removeClass('active'));
				conf.showFunc(_container.find('.content:eq(' + index + ')').addClass('active'));

				_ul.find('li').removeClass('active');
				
				_ul.find('li:eq(' + index + ')').addClass('active');
				
				$curActive = _container.find('.content.active');
				if ($curActive.hasClass('video') == false)
				{
					positionTextFields();
				}
				
				updateOverlay($curActive);
				
				return $curActive;
			},
			
			initCloseButton: function(){
				_container.find(".video.active").prepend(_closeBtnHtml);
				$("#videoClose").click(function(){
					_self.closeVideo();
				});
			},
			
			clearCloseButton: function(){
				$("#videoClose").remove();
			},
			
			closeVideo: function(){
				_container.find('.content.active a').show();
				removeBrightcove();
				setTimer();
			},
			
			playVideo: function(a)
			{	
				//_self.initCloseButton();
				_container.addClass("video-playing");
				_thumbs.hide();
				_thumbBtns.hide();

				var $videoId = $(a).attr('href').replace('#', ''),
					$container = $(a)[0].parentNode
					;

				// replace content of the parent node with SWF
				$(a).hide();
				createBrightcove($container, $videoId);

				// not forgetting to stop the timer
				stopTimer();
			}
		});

		// Private methods -----------------------------------------------------------------
		function debug(str)
		{
			if (_opts.debug == true) {
				try {
					console.log(str);
				} 
				catch (e) {
					alert("debug: " + str);
				}
			}
		};

		// generic binding function
		function bind(name, fn)
		{
			if ($.isFunction(fn))
				$(_self).bind(name, fn);
		};
				
		function setTimer()
		{
			clearInterval(_timer);
			_timer = setInterval(function()
			{
				// see if interval has been disabled at runtime
				if (conf.interval == 0) 
					stopTimer(); 

				_self.next();
			}, conf.interval);
		};
		
		function stopTimer()
		{
			if (_timer == 0)
				return;
				
			clearInterval(_timer);
			_timer = 0;
		};

		// positionTextFields added by Gord 24/06/09
		function positionTextFields()
		{
			var $curContent = _container.find('.content.active'),
				$totalHeight = $curContent.height(),
				$curSummary = $curContent.find('.top-story-summary'),
				$curColumn = $curSummary.parent(),
				$headerHeight;

				
			if ($curContent.hasClass('five-cols')) {
				$headerHeight = $curContent.find('h3').outerHeight();
			}	else {
				$headerHeight = 0;
			}

			var	$staticHeight = $curColumn.outerHeight() - $curSummary.outerHeight() + $headerHeight;
				$lineHeight = Math.ceil(($curSummary.css('line-height').split('p'))[0]),
				$numRows = Math.floor(($totalHeight - $staticHeight) / $lineHeight),
				$summaryHeight = $numRows * $lineHeight
				;

			if ($curSummary.height() > $summaryHeight) {
				$curSummary.height($summaryHeight);
			}
		};
		
		function animateOverlay(expandable, targetAttribute, targetValue)
		{
			var $animationArgs = {};
			
			_canToggleOverlay = false;
			
			$animationArgs[targetAttribute] = targetValue;
			
			expandable.animate($animationArgs, { queue : false, duration : 150, complete: function()
			{
				_canToggleOverlay = true;

			}});
			
			stopTimer();
		};
		
		function resetOverlay(expandable, targetAttribute, targetValue)
		{
			expandable.css(targetAttribute, targetValue);
		};

		function toggleOverlay(expandable, expand, callback)
		{
			if (_canToggleOverlay == false)
				return;
				
			var $overlay = expandable.find('.overlay'),
				$container = expandable.parent('.content.five-cols'),
				$position = /\bposition-(\w+)-(\w+)\b/.exec(expandable[0].className),
				$verticalPosition = $position[1],
				$horizontalPosition = $position[2],
				$targetValue = expand ? 0 : $container.height() - expandable.data('__height'),
				$targetAttribute = { 'bottom' : 'top', 'top' : 'bottom' }[$verticalPosition]
				;
			
			expandable[(expand ? 'add' : 'remove') + 'Class'](_expendClass);
			callback(expandable, $targetAttribute, $targetValue);
			
			// for IE6 we have to also expand hight because it seems that bottom:0
			// isn't really sticking.
			if ($.browser.msie6)
			{
				var $container = expandable.parent('.content.five-cols');
				callback(
					expandable, 
					'height', 
					(expand ? $container.height() : expandable.data('__height')) - 2 // 2 pixels extra in IE6... not sure why
					);
			}

		};
		
		function updateOverlay(content)
		{
			$(content).find('.overlay.expandable').each(function()
			{
				var $expandable = $(this);

				if ($expandable.data('__height') != null)
					return;

				$expandable
					.data('__height', $expandable.height())
					//.mouseleave(close).mouseenter(open)
					//.find('h3')
						//.click(toggle)
					;
				$expandable
					.find('a.expand').click(toggle);

				function open()
				{
					toggleOverlay($expandable, true, animateOverlay);
					$expandable.data('toggle', closeNow);

					clearTimeout(_overlayDelay);
					_overlayDelay = 0;
					return false;
				};

				function closeNow()
				{
					toggleOverlay($expandable, false, animateOverlay);
					$expandable.data('toggle', open);
					_overlayDelay = 0;
					return false;
				};

				function close()
				{
					clearTimeout(_overlayDelay);
					_overlayDelay = setTimeout(closeNow, 250);
					return false;
				};

				function toggle()
				{
					toggleOverlay($expandable, !$expandable.hasClass(_expendClass), animateOverlay);
					//$expandable.data('toggle')();
					return false;
				};
				
				// reset initial coordinates so that animation works fine in most browsers
				toggleOverlay($expandable, true /*false /* keep closed */, resetOverlay);
			});
		};
		
		function initLayout()
		{
			// generate layout -----------------------------------------

			
			_ul.find('li.thumb').each(function(index)
			{
				_this = $(this);
				
				if (_this.find('.content').hasClass('video')) {
					_this.find('span.video').show();
				}
				
				_this.click(function()
				{
					var $content = _self.activate(index);
					
//					if ($content.hasClass('video'))
//						_self.playVideo($content.find('> a'));
					
					stopTimer();
					return false;
				});
				_this.show();
			});
			
			// move all content to the container
			_ul.find('li .content').appendTo(_container);
			
			_container.find('.content').hide();
			
			// activate first container
			_self.activate(_itemIndex);
			
			// hide the list if there's just one item
			if (_itemCount <= 1)
				_ul.hide();
			
			// set up image to swf functionality ------------------------
			
			_container.find('.content.video a').click(function()
			{
				_self.playVideo(this);
				return false;
			});
			
			_ul.find('.top-story-caption').ellipsis();
			
			_root.bind('onMediaComplete', function(e){
				setTimer();
			});
			
			_thumbBtns = _thumbsHolder.find('span.video:visible');
		};

		function createBrightcove(container, videoId)
		{
			var $params = {
					playerID : "53059221001",
					videoId : videoId,
					autoStart : "true",
					bgcolor : "#000000",
					wmode : "opaque",
					width : "645",
					height : "416", /* 350px for 4:3; 416px for 16:9 */
					isVid : "true",
					isUI : "true",
					cacheAMFURL : "http://services.brightcove.com/services/messagebroker/amf"
				};
			
			_brightcovePlayer = brightcove.createElement("object");
			_curBrightcovePlayerName = _brightcovePlayerNameRoot + (Math.round(Math.random() * 10000));
			_brightcovePlayer.id = _curBrightcovePlayerName;

			for (var $key in $params)
			{
			     $parameter = brightcove.createElement("param");
			     $parameter.name = $key;
			     $parameter.value = $params[$key];
			     _brightcovePlayer.appendChild($parameter);
			}

			brightcove.createExperience(_brightcovePlayer, container, true);
			
			debug("created brightcove player: " + _curBrightcovePlayerName);
		};
		
		function removeBrightcove()
		{
			if (_brightcovePlayer != null)
			{
				debug("removing brightcove player: " + _curBrightcovePlayerName);
				//_self.clearCloseButton();
				var curExp = brightcove.getExperience(_curBrightcovePlayerName);
				var curPlayer = curExp.getModule(APIModules.VIDEO_PLAYER);
				curPlayer.mute(); //this is to address the "ghost multi-streams" that were playing in IE only
				
				brightcove.removeExperience(_curBrightcovePlayerName);
				_brightcovePlayer = null;
				_container.removeClass("video-playing");
				_thumbs.show();
				_thumbBtns.show();
			}
		};
		// Initialization ------------------------------------------------------------------
		
		// bind all callbacks from configuration
		$.each(conf, bind);
		
		initLayout();
		
		if (conf.interval > 0 && _itemCount > 1) 
		{
			setTimer();	
		}
	};

	// jQuery plugin implementation
	$.fn.topModule = function(conf)
	{ 
		var opts = {
			interval : 0,		// Number of miliseconds to for auto rotation. 0 - no auto rotation
			hideFunc : function(element) { element.hide(); },
			showFunc : function(element) { element.fadeIn(); }
		}; 
		
		$.extend(opts, conf);
		
		this.each(function() 
		{
			var $instance = new TopModule($(this), opts);
			$(this).data("topModule", $instance);
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function Poll(root, conf)
	{	
		// Private fields ------------------------------------------------------------------
		var _root = $(root),
			_domElement = _root[0],
			_self = this,
			_userHasVoted,
			_resultContainer = _root.find('#poll-result-container'),
			_answerElem = _root.find('.poll-answers'),
			_opts = {
				debug: false
			}
			;
			$.extend(_opts, conf);
	
		// Public methods ------------------------------------------------------------------
//		$.extend(_self, {
//		});
	
		// Private methods -----------------------------------------------------------------
		function debug(str)
		{
			if (_opts.debug == true) {
				try {
					console.log(str);
				} 
				catch (e) {
					alert("debug: " + str);
				}
			}
		};
			
		// generic binding function
		
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
				
			$(_self).bind(name, fn);
		};
		
		function showPollAnswers()
		{
			_resultContainer.hide();
			_answerElem.show();
		};
		
		function showPollResults(submitAnswer){
			//submitAnswer is false if the user clicks the "view results" link
			
            // make ajax request to vote and retrieve results
            var response_id = _answerElem.find('input[name=poll]:checked').val();
            if (!response_id || submitAnswer == false) response_id = '';
			debug("response_id: " + response_id);

            $.ajax({
                url: "/scrum/poll_vote.php",
                cache: false,
                data: "poll_id="+_opts.poll_id+"&response_id="+response_id,
                success: function(html) {
					_answerElem.hide();
                  // update poll-results
					_resultContainer.html(html);
					_resultContainer.fadeIn();
					_userHasVoted = getVoteStatus();
					debug("aft _userHasVoted: " + _userHasVoted);
					if (_userHasVoted == false) showAnswersLink();
                }
            });
			
			return false;
		};
		
		function showAnswersLink(){
			var linkHtml = '<div class="arrow" style="margin:7px 0 0 10px;"><a href="#" id="pollVoteNowLink">Vote Now</a></div>';
			_resultContainer.append(linkHtml);
			_root.find("#pollVoteNowLink").click(function(){
				showPollAnswers();
				return false;
			});
		};
		
		function enableButton(){
			$("#pollVoteButton", _root).removeClass("buttonInactive");
			$("#pollVoteButton", _root).click(showPollResults);
		};
		
		function getVoteStatus(){
			var hasVoted = false;
			
            var polls = $.cookie('polls');
            if (polls) {
				var pollsVoted = polls.split('|');
			
                $.each(pollsVoted, function(key, value) {
                    if (value == _opts.poll_id) {
						hasVoted = true;
					}
                });
            }
			return hasVoted;
		};
	
		function init()
		{
            // see if user has already voted
			_userHasVoted = getVoteStatus();
			
			debug("_userHasVoted: " + _userHasVoted);
			
            if (_userHasVoted == true) {
                showPollResults();
            } else {
                _answerElem.find("input").click(enableButton);
                $("#pollShowResultsLink", _root).click(function(){
					showPollResults(false);
					return false;
				});
                showPollAnswers();
            }
		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};
	
	// jQuery plugin implementation
	$.fn.poll = function(conf)
	{
		var opts = { };
		$.extend(opts, conf);
		
		this.each(function()
		{
			var $instance = new Poll($(this), opts);
		});
		
		return this;
	};
})(jQuery);
(function($)
{	
	// constructor
	function UserInfoModule(root)
	{	
		// Private fields -------------------------------------------------------------------

		var _root = $('body'),
			_signIn = $('.sign-in-link'),
			_signInCancel = $('#sign-in-cancel'),
			_login = $('#login-btn'),
			_channelSelector = $('#channel-selector'),
			_channelWrapper = $('.channel-wrapper'),
			_channelTip = $('.channel-tip'),
			_channelChoicesToggle = $('.channel-choices-toggle'),
			_channelOptions = $('.channel-option'),
			_logout = $('#logout-link'),
			_settings = $('#settings-link'),
			_userName = $('#user-name'),
			_passwordLabel = _root.find('#password-label'),
			_password = _root.find('#password'),
			_overlayBlocker = _root.find('.overlay-blocker'),
			//_overlayChannelSelector = _root.find('.overlay-channel-selector'),
			//_overlayChannelButtons = $(_root.find('#channel-buttons')).find('a'),
			//_overlayChannelInfo = _root.find('#channel-info'),
			_userChannel = "Ontario",
			_leftChannelBar = _root.find('.region'),
			_self = this,
			_opts = {
				debug: false
			}

			;  

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			
			//check for channel cookie
			doCookieCheck: function (whichCookie)
			{
				var $myCookie = whichCookie;
				var c = $.cookies.get($myCookie);
				_self.debug("doCookieCheck for '"+whichCookie+"'; value: " + c);
				
				if (c != null) {
					_userChannel = c;
					$.cookies.del($myCookie);
					_self.doCookieSet($myCookie, c);
					return true;
				} else {
					//alert("doCookieCheck: cookie: "+c);
					return false;
				}
			},	
			
			//set user's channel cookie
			doCookieSet: function (whichCookie, whichValue)
			{
				var $tmp = window.location.href,
					$url = $tmp.split('/'),
					$loc = $url[2].split('.'),
					$dom = '.'+$loc[1]+'.'+$loc[2];
				 var cookieOptions = {
				    path: '/',
					domain: $dom,//'.pairsite.com', //'.sportsnet.ca',
				    hoursToLive: 90000,
				    secure: false
				  }
				var $cookie = whichCookie;
				var $value = whichValue;
				$.cookies.set($cookie, $value, cookieOptions);

			},
			
			//open channel selection overlay
			doChannelSelectOverlay: function () 
			{
				var $blockerHeight = $('body').height();
				
				//var $topnavXOffset = 6; //distance from _overlayChannelSelector to edge of top nav.
				var $rightEdge = _channelWrapper.offset().left +  _channelWrapper.outerWidth(); //+ $topnavXOffset;
				var $targetX = $rightEdge - _overlayChannelSelector.outerWidth();
				var $logoYOffset = 37;
				var $targetY = _channelWrapper.offset().top - $logoYOffset;


				_overlayChannelSelector.css({left: $targetX, top: $targetY});
				_overlayChannelSelector.fadeIn('slow');

				if ($blockerHeight > _overlayBlocker.height()) {
					_overlayBlocker.css({
						height: $blockerHeight
					});
				}
				_overlayBlocker.toggleClass('hidden');

			},
			
			//close channel selection overlay and set cookie from user input
			closeOverlay: function () 
			{
				_self.doCookieSet('sn_region', _userChannel);
				var content = _overlayChannelSelector.find('#select-content');
				var header = _overlayChannelSelector.find('#select-header');
				header.fadeOut('fast');
				content.children().slideUp('fast');
				content.animate({
					height: '0px',
					width: '200px',
					left: '770px',
					right: '1080px'}, 400, function() { _self.reloadPage(); });
			},
			//refresh page - used when user changes the channel choice.
			reloadPage: function () 
			{
				_overlayBlocker.slideUp();
				location.reload();
			},
			
			//set the text for the current channel.
			initUserChannel: function ()
			{
				var $klass = _userChannel.toLowerCase();
				$('#cur-channel').removeClass();
				$('#cur-channel').addClass($klass);
				$($('#channel-options').find('.'+$klass)).addClass('on');
				$('#cur-channel').text(_userChannel);
				
				_leftChannelBar.find('.on').removeClass('on');
				_leftChannelBar.find('#reg-'+_userChannel).addClass('on');
				_leftChannelBar.slideDown();
			},
			
			//show the user sign in form.
			showLogin: function()
			{
				$('.welcome').fadeOut('fast');
				$('.channel-wrapper').fadeOut('fast');
				$('.sign-in').fadeIn();
			},
			
			//submit sign-in info - TODO validation.
			doSignIn: function() 
			{
				$('.sign-in').fadeOut('fast');
				$('#user-display-name').text($('#user-name').val());
				$('.logged-in').fadeIn();
				$('.channel-wrapper').fadeIn();
			},
			
			//toggles the channel selection drop down.
			doOpenChannelSelect: function() 
			{
				$('.channel-choices-toggle').addClass('close');
				_channelWrapper.addClass('active');
			},
			doCloseChannelSelect: function() 
			{
				$('.channel-choices-toggle').removeClass('close');
				_channelWrapper.removeClass('active');
			},
			
			//cancels login and takes user back to greeting message.
			cancelSignIn: function() 
			{
				$('.sign-in').fadeOut('fast');
				$('.welcome').fadeIn();
				$('.channel-wrapper').fadeIn();
			},
			
			//user logs out and is returned to greeting message.
			doLogout: function() 
			{
				$('.logged-in').fadeOut('fast');
				$('.welcome').fadeIn();
			},
			debug: function(str)
			{
				if (_opts.debug == true) {
					try {
						console.log("str: " + str);
					} 
					catch (e) {
						//alert("str: " + str);
					}
				}
			}
		});

		// Private methods ----------------------------------------------------------------
		function initLayout()
		{	
			//_root.append(_overlayBlocker);
			//_root.append(_overlayChannelSelector);
			
			var _cookieCheck = _self.doCookieCheck('sn_region');
			
			if (_cookieCheck == false){
				// remove the bottom lines to disable regional selection
				//_leftChannelBar.hide('fast');
				//_self.doChannelSelectOverlay();
			} else {
				_self.initUserChannel();
			};
			
			_signIn.click(function(){
				_self.showLogin();
			}),
			
			_login.click(function() {
				_self.doSignIn();
			}),
			
			_signInCancel.click(function() {
				_self.cancelSignIn();
			}),
			_userName.focus(function() {
				_userName.val('');
			}),	
			_userName.blur(function() {
				if(_userName.val() == ''){
					_userName.val('Username');
				}
			}),
			_passwordLabel.focus(function(){
				_passwordLabel.addClass('hidden');
				_password.removeClass('hidden');
				_password.focus();
			}),
			_password.blur(function(){
				if(_password.val() == "") {
					_password.addClass('hidden');
					_passwordLabel.removeClass('hidden');
				}
			}),

			//_channelTip.hoverIntent(_self.doShowTip, _self.doHideTip); 
			
			_channelOptions.click(function() {
				
				if ($(this).hasClass('on') == false) {
					var txt = this.innerHTML;
					var $color = $(this).css('color');
					
					$('#cur-channel').text(this.innerHTML);
					$('#cur-channel').css({
						color: $color
					});
					
					_userChannel = txt;
					_self.doCookieSet('sn_region', _userChannel);
					for (var i = 0; i < _channelOptions.length; i++) {
						var curOption = _channelOptions.eq(i);
						if (curOption.text() == this.innerHTML) {
							curOption.addClass('active');
						}
						else {
							curOption.removeClass('active');
						}
					}
					_self.doCloseChannelSelect();
					_self.reloadPage();
				}	
			}),
			
			_channelChoicesToggle.click(function() {
				if(_channelChoicesToggle.hasClass('close') == true) {
					_self.doCloseChannelSelect();
				} else {
					_self.doOpenChannelSelect();
				}
			}),

			_channelWrapper.hoverIntent(function()	{ 
  				//do nothing
 
 			}, function() {	
				if(_channelWrapper.hasClass('active') == true) {
					_self.doCloseChannelSelect();
				}
			}),	
			
			_logout.click(function() {
				_self.doLogout();
			}),
			/*
			_overlayChannelButtons.click(function(evt) {
				var id = $(this).attr('id');
				var str = id.split('-');
				var ch = str[1];
				_userChannel = ch;
				_self.closeOverlay()
			}),
			
			_overlayChannelButtons.hoverIntent(function () {
				var id = $(this).attr('id');
				var str = id.split('-');
				var ch = str[1];
				var info = _overlayChannelInfo.find('#info-'+ch);
				info.fadeIn('fast');
			}, function() {
				var id = $(this).attr('id');
				var str = id.split('-');
				var ch = str[1];
				var info = _overlayChannelInfo.find('#info-'+ch);
				info.fadeOut('fast');
			}),
			*/
			_leftChannelBar.find('a').each(function(){
				$(this).click(function() {
					if ($(this).hasClass('on') == false) {
						var id = $(this).attr('id');
						var str = id.split('-');
						var ch = str[1];
						_userChannel = ch;
						_self.doCookieSet('sn_region', _userChannel);
						_self.reloadPage();
					}
				});
			})
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
		

	};

	// jQuery plugin implementation
	$.fn.userInfoModule = function()
	{ 
		this.each(function() 
		{
			var $instance = new UserInfoModule($(this));
			$(this).data("userInfoModule", $instance);
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function Tabs(root, conf)
	{	
		// Private fields ------------------------------------------------------------------

		var _root = $(root),
		    _ul = $(root).find('> ul'),
			_container = _root.find('.tabs-container'),
			_self = this,
			_lastTabIndex,
			_opts = {
				defaultIndex: 0,
				colWidth: null 
			}
			;
		$.extend(_opts, conf);

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			activate: function(index)
			{
				var $active = 'active',
					$eq = ':eq(' + index + ')'
					;
					
				conf.hideFunc(_ul.find('> li.' + $active).removeClass($active));
				conf.showFunc(_ul.find('> li' + $eq).addClass($active));

				_container.find('div.' + $active).removeClass($active);
				_container.find('div.activeLast').removeClass("activeLast");
				_container.find('div' + $eq).addClass($active);
				
				if (index == _lastTabIndex) {
					_container.find('div' + $eq).addClass("activeLast");
				}
			}
		});

		// Private methods -----------------------------------------------------------------

		// generic binding function
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
				
			$(_self).bind(name, function(e, args)
			{
				if (fn && fn.call(this, args.index) === false && args)
				{
					args.proceed = false;	
				}	
			});	
		};
		
		function initLayout()
		{
			_ul.find('> li').hide();
			var cHeaders = _ul.find('> li > ' + conf.header);
			_lastTabIndex = cHeaders.length-1;
			
			var tabTable = $('<table class="tabTable" cellpadding="10" cellspacing="0" width="100%" border="1"></table>').appendTo(_container);
			var tabBody = $('<tbody></tbody>').appendTo(tabTable);
			var tabRow = $('<tr></tr>').appendTo(tabBody);
			var targetWidth = _opts.colWidth == null ? _container.outerWidth() / cHeaders.length : _opts.colWidth;
			// converts all H5 tags to the tab elements
			cHeaders.each(function(index)
			{
				$('<td width="'+targetWidth+'"><div><a href="#">' + $(this).text() + '</a></div></td>').appendTo(tabRow);
				$(this).remove();
				
				_container.find('a:eq(' + index + ')').click(function()
				{
					_self.activate(index);
					return false;
				})
			});
			
			_container.find('div:first').addClass('first');
			_container.find('div:last').addClass('last');
			
			_self.activate(_opts.defaultIndex);
			
			_root.show();
		};

		// Initialization ------------------------------------------------------------------
		
		// bind all callbacks from configuration
		$.each(conf, bind);
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.tabs = function(conf)
	{ 
		var opts = {
			header : 'h5',
			hideFunc : function(element) { element.hide(); },
			showFunc : function(element) { element.fadeIn(); }
		}; 
		
		$.extend(opts, conf);		
		
		this.each(function() 
		{
			var $instance = new Tabs($(this), opts);
			$(this).data("tab", $instance);
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function PlayerStats(root, values)
	{	
		// Private fields ------------------------------------------------------------------
	
		var _root = $(root),
			_self = this,
			_sport = /sport-(\w+)/.exec(_root[0].className)[1],
			_data = SPORTSNET_PLAYER_STATS[_sport],
			_comparePosition,
			_comparePlayerId,
			_positionsContainer,
			_measuresContainer,
			_dimensionsContainer,
			_statsContainer,
			_selectedPosition,
			_selectedMeasure,
			_compareContainer,
			
			_changeHtml = '<span class="change">(<a href="#">change</a>)</span>',
			_clearHtml = '<div style="clear:both"></div>'
			;
	
		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			selectPosition: function(sender)
			{
				if (_root.hasClass('position-selected'))
					return;
				
				var $radio = $(sender).parent().find('input:radio'),
					$position = $radio.val(),
					$data = findPosition($position)
					;

				if ($data == null)
					return false;

				_self.deselectPosition(null);
				_self.deselectMeasure(null);

				// deselectPosition will clear this, put it back
				$radio.attr('checked', 'true');
				
				_selectedPosition = $position;
				_root.addClass('position-selected');
				$(sender).parents('.radio').addClass('selected');
				
				// clear all dimensions
				_dimensionsContainer.html('');
				// create measures and stats
				createMeasure($data.measures);
				createStats($data.stats);

				bindEvents();
			},
			
			deselectPosition: function(sender)
			{
				_selectedPosition = null;
				_root.removeClass('position-selected');
				_root.removeClass('no-stats');
				_root.removeClass('no-dimensions');
				_positionsContainer
					.find('.radio').removeClass('selected')
					.find('input:radio').attr('checked', '')
					;
				_self.deselectMeasure(null);
				_self.activateButton(false);
				_measuresContainer.html('');
				_statsContainer.html('');
				_statsContainer.removeClass('opened');
			},
			
			selectMeasure: function(sender)
			{
				if (_root.hasClass('measure-selected'))
					return;
					
				var $radio = $(sender).parent().find('input:radio'),
					$measure = $radio.val(),
					$data = findPosition(_selectedPosition)
					;

				_self.deselectMeasure(null);
				
				// deselectMeasure clears all checks, so put it back
				$radio.attr('checked', 'true');
				
				_selectedMeasure = $measure;
				_root.addClass('measure-selected');
				$(sender).parents('.radio').addClass('selected');
				
				// only create dimension once
				if (_dimensionsContainer.html() == '')
					createDimensions($data.dimensions);
				
				bindEvents();
			},
			
			deselectMeasure: function(sender)
			{
				_selectedMeasure = null;
				_root.removeClass('measure-selected');
				_measuresContainer
					.find('.radio').removeClass('selected')
					.find('input:radio').attr('checked', '')
					;
				_measuresContainer.find('.radio').removeClass('selected');
				_dimensionsContainer.html('');
			},

			selectDimension: function(sender)
			{
				$(sender).parents('.items').find('.radio').each(function()
				{
					var $select = $(this).find('select'),
						$radio = $(this).find('input:radio')
						;
						
					if ($radio[0].checked == false && $select.length > 0)
						$select.attr('selectedIndex', 0);
				});
			},
			
			deselectDimension: function(sender)
			{
				_root.removeClass('prefilled-dimensions');
			},
			
			toggleStats: function(sender)
			{
				var $max = 5,
					$count = _statsContainer.find('input:checkbox[checked="true"]').length,
					$left = $max - Math.min($max, $count)
					;

				if ($count > $max && sender.checked == true)
					sender.checked = false;
					
				_statsContainer.find('.message .counter').html($left);
				
				_statsContainer
					.find('.radio input:checkbox[checked!="true"]')
					.parents('.radio')
						[($left == 0 ? 'add' : 'remove') + 'Class']('dim')
				;
			},
			
			toggleStatsSection: function(sender)
			{
				_statsContainer.toggleClass('opened');
				_root.removeClass('prefilled-stats');
			},
			
			setSelected: function(values)
			{
				function s(v) { return 'input[value="' + v + '"]'; };
				function check(sender) { sender.checked = true; };
				function open(container, callback, value)
				{
					if (value)
						return callback(container.find(s(value))[0]) != false;
					
					return false;
				};

				var $data,
					$position,
					$labels,
					$name
					;
				
				// handle compare values
				if (values.compare && values.compare.left != '' && values.compare.right != '')
				{
					var $players = SPORTSNET_PLAYERS[_sport],
						$leftData = findInArray($players, [0], values.compare.left),
						$rightData = findInArray($players, [0], values.compare.right)
						;
						
					_self.activateCompareTab();
					_self.setCompareValue(_compareContainer.find('input[name="left"]')[0], $leftData);
					_self.setCompareValue(_compareContainer.find('input[name="right"]')[0], $rightData);
					
					$position = $leftData[2];
				}
				else if (open(_positionsContainer, _self.selectPosition, values.position))
				{
					$position = values.position;
				}
				else
				{
					return;
				}

				$data = findPosition($position);
				open(_measuresContainer, _self.selectMeasure, values.measure);
				
				if (values.dimensions && values.dimensions.length > 0 && $data.dimensions)
				{
					var $dimensions = [];

					$labels = [];

					for (var $i = 0; $i < $data.dimensions.length; $i++)
						$dimensions = $dimensions.concat($data.dimensions[$i]);

					for (var $i = 0; $i < values.dimensions.length; $i++)
					{
						$name = values.dimensions[$i];
						
						if ($name == null || $name == '')
							continue;

						var $parts = $name.split(','),
							$label
							;
							
						$name = $parts[0];
						$label = findInArray($dimensions, 'name', $name).label;
						
						open(_dimensionsContainer, check, $name);
						
						if ($parts.length > 1)
						{
							var $team = $parts[1];
							
							_dimensionsContainer.find('select[name="' + field('dimension' + $i, $name) + '"]').val($team);
							
							// special case for 'Vs. TEAM NAME'
							if ($name == 'vs_team')
								$label = 'Vs. ' + findInArray(SPORTSNET_TEAMS[_sport], 0, $team)[2];
						}

						$labels.push($label);
					}

					if ($labels.length == 0)
						$labels.push('Defaults');
					
					_root.addClass('prefilled-dimensions');
					_dimensionsContainer.find('.title').after(''
						+ '<div class="prefilled">'
							+ $labels.join(', ')
							+ _changeHtml
						+ '</div>'
					);
				}

				// prefill 'Add stats' checkboxes
				if (values.stats && values.stats.length > 0)
				{
					$labels = [];
					
					for (var $i = 0; $i < values.stats.length; $i++)
					{
						$name = values.stats[$i];
						if ($name == null || $name == '')
							continue;
							
						$labels.push(findInArray($data.stats, 'name', $name).label);
						_statsContainer.find(s($name)).attr('checked', 'true');
						open(_statsContainer, _self.toggleStats, $name);
					}

					_root.addClass('prefilled-stats');
					_statsContainer.append(''
						+ '<div class="prefilled">'
							+ '<span class="label">Include:</span> '
							+ $labels.join(', ')
						+ '</div>'
					);
					
					// add a bit of extra space if more than 3 items were selected
					if ($labels.length > 3)
						_root.find('.buttonHolder').addClass('extra-space');
				}
				
				_self.activateButton(true);
			},
			
			getCompareHiddenField: function(name)
			{
				return _root.find('input[name="' + field('compare', name) + '"]');
			},
			
			setCompareValue: function(input, data)
			{
				if (data == null)
					return;
					
				var $player = data[1],
					$position = data[2],
					$id = data[0]
					;
				
				$(input).parent('.input').addClass('selected');
				$(input).siblings('.value').html($player);
				_self.getCompareHiddenField(input.name).val($id);
				
				// if left compare value was set, have to manipulate the right input field
				if (input.name == 'left')
				{
					_comparePlayerId = data[0];
					_comparePosition = $position;
					
					_compareContainer.find('.comment').html('Select another player');
					_compareContainer.find('.right input').attr('disabled', false);
				}
				
				if (input.name == 'right')
				{
					_self.activateButton(true);
				}
			},
			
			clearCompareValue: function(input)
			{
				var $parent = $(input).parent('.input');
				
				$parent.removeClass('selected');
				$parent.find('.value').html('');
				$(input).val('');
				$(input).hint();
				_root.find('.box').hide();
				_self.getCompareHiddenField(input.name).val('');
				_statsContainer.hide();
				_self.activateButton(false);
				
				if ($parent.hasClass('left'))
				{
					_comparePosition = null;
					_comparePlayerId = null;
					_compareContainer.find('.comment').html('');
					_compareContainer.find('.right input').attr('disabled', true);
					_self.clearCompareValue(_compareContainer.find('input[name="right"]'));
					_self.deselectPosition();
				}
				
				if ($parent.hasClass('right'))
				{
				}
			},
			
			activateCompareTab: function()
			{
				if (_self.activateTab(_root.find('.compare-tab a')) == false)
					return;

				createCompare();
				_compareContainer.show();
				_self.deselectPosition(null);
				_root.addClass('no-position no-measures');				
				_root.find('.box').hide();
				_root.data('no-position-by-compare', true);
				_root.data('no-measures-by-compare', true);
			},
			
			activateCustomTab: function()
			{
				if (_self.activateTab(_root.find('.custom-tab a')) == false)
					return;
				
				// removes no-position and no-measure from the _root if 
				// they were set by activating compare tab.
				function revertVisibility(name)
				{
					var $n = 'no-' + name + '-by-compare';
					
					if (_root.data($n) == true)
					{
						_root.removeClass('no-' + name);
						_root.data($n, null);
					}
				};
				
				revertVisibility('position');
				revertVisibility('measures');
				
				_root.find('.box').show();
				_root.find('input[name="' + field('compare', 'left') + '"]').val('');
				_root.find('input[name="' + field('compare', 'right') + '"]').val('');
				_self.deselectPosition(null);
				_compareContainer.html('');
				_compareContainer.hide();
				_comparePosition = null;
				_comparePlayerId = null;
				
				// have to select default if it's present
				if (findPosition('default') != null)
					_self.selectPosition(_positionsContainer.find('input[value="default"]'));
			},
			
			activateTab: function(a)
			{
				var $div = $(a).parent('div');
				
				if ($div.hasClass('on'))
					return false;
					
				$(a).parents('.tabs').find('.on').removeClass('on');
				$div.addClass('on');
				
				return true;
			},
			
			activateButton: function(activate)
			{
				_root.find('.buttonHolder .button')[(activate ? 'remove' : 'add') + 'Class']('buttonInactive');
			}
		});
	
		// Private methods -----------------------------------------------------------------
		
		function getWidth(target)
		{
			return target.width()
				+ parseInt(target.css('padding-left'))
				+ parseInt(target.css('padding-right'))
				+ parseInt(target.css('margin-left'))
				+ parseInt(target.css('margin-right'))
				;
		};
		
		function setSelected(target, selected)
		{
			$(target)[(selected ? 'add' : 'remove') + 'Class']('selected');
		};
		
		function findInArray(array, fieldname, value)
		{
			for (var $i = 0; $i < array.length; $i++)
				if (array[$i][fieldname] == value)
					return array[$i];
					
			return null;
		};
		
		function findPosition(name)
		{
			return findInArray(_data, 'name', name);
		};
		
		function field(name, param)
		{
			return 'player_stats[' + name + '][' + (param == null ? '' : param)+ ']';
		};
		
		function sortByField(array, field)
		{
			return array.sort(function(a, b)
			{
				a = a[field];
				b = b[field];
				
				if (a > b)
					return 1;
				else if (a < b)
					return -1;
				else
					return 0;
			});
		};
		
		/**
		 * Adds <div style='clear: both' /> after Nth .radio element in the container.
		 */
		function setColumns(container, columns)
		{
			columns =  columns || 3;
			$(container).find('.radio:nth-child(' + columns + 'n)').after('<div style="clear: both"></div>');
		};
		
		function createCompare()
		{
			if (_compareContainer.html() != '')
				return;
			
			var $html = ''
				+ '<div class="input left float sn-input">'
					+ '<input type="text" class="sn-input" name="left" title="Type player\'s name" />'
					+ '<div class="value float"></div>'
					+ _changeHtml
				+ '</div>'
				+ '<div class="vs float">vs.</div>'
				+ '<div class="input right float sn-input">'
					+ '<input type="text" class="sn-input" name="right" title="Player 2" disabled="true" />'
					+ '<div class="comment"></div>'
					+ '<div class="value float"></div>'
					+ _changeHtml
				+ '</div>'
				+ _clearHtml
			;
			
			_compareContainer.html($html);
				
			_compareContainer.find('.change a').click(function()
			{
				_self.clearCompareValue($(this).parents('.input').find('input:textbox'));
				return false;
			});
			
			_compareContainer.find('input:textbox')
				.hint()
				
				.autocomplete(SPORTSNET_PLAYERS[_sport], {
					minChars: 0,
					width: 200,
					matchContains: true,
					autoFill: false,
					formatItem: function(row, i, max) 
					{
						// make sure we show players of the same position in the drop down on the right
						if (_comparePosition != null && row[2] != _comparePosition)
							return false;

						// make sure we don't show the same player in the right drop down
						if (_comparePlayerId != null && row[0] == _comparePlayerId)
							return false;
							
						return row[1];
					},
					formatMatch: function(row, i, max) 
					{
						return row[1];
					},
					formatResult: function(row) 
					{
						return row[1];
					}
				})
				
				.bind('result', function(e, data)
				{
					_self.setCompareValue(this, data);
				})
			;
		};
		
		function createPositions()
		{
			var $html = ''
				+ '<div class="title">Position:</div>'
				+ '<div class="items">'
				;

			for (var $i = 0; $i < _data.length; $i++)
			{
				var $position = _data[$i],
					$value = $position.name,
					$id = 'sport-position-' + $value 
					;
				
				$html += ''
					+ '<div class="radio position">'
						+ '<input type="radio" id="' + $id + '" name="' + field('position') + '" value="' + $value + '" />'
						+ '<div class="label-box">'
							+ '<label for="' + $id + '">' + $position.label + '</label>'
						+ '</div>'
					+ '</div>'
					;
			}
			
			$html += ''
				+ '</div>'
				+ _changeHtml
				+ _clearHtml
				;

			_positionsContainer.html($html);
			
			// check if there's just one position in current sport and act accordingly
			if (_data.length == 1 && _data[0].name == 'default')
			{
				_positionsContainer.hide();
				_root.addClass('no-position');
				_self.selectPosition(_positionsContainer.find('input:radio')[0]);
			}
		};

		function vsTeam(name, selected)
		{
			var $teams = sortByField(SPORTSNET_TEAMS[_sport], 2),
				$html = ''
					+ '<select size="1" name="' + name + '">'
						+ '<option value=""></option>'
				;
				
			for (var $i = 0; $i < $teams.length; $i++)
				$html += '<option value="' + $teams[$i][0] + '">' + $teams[$i][2] + '</option>';
			
			$html += '</select>'
			
			return $html;
		};
		
		function createMeasure(measures)
		{
			var $html = ''
				+ '<div class="title">Sort by:</div>'
				+ '<div class="items">'
				;

			for (var $i = 0; $i < measures.length; $i++)
			{
				var $measure = measures[$i],
					$value = $measure.name,
					$id = 'sport-measure-' + $value
					;
				
				$html += ''
					+ '<div class="radio measure">'
						+ '<input type="radio" id="' + $id + '" name="' + field('measure') + '" value="' + $value + '" />'
						+ '<div class="label-box">'
							+ '<label for="' + $id + '">' + $measure.label + '</label>'
						+ '</div>'
					+ '</div>'
					;
			}
			
			$html += ''
				+ '</div>'
				+ _changeHtml
				+ _clearHtml
				;
			
			var $hasPositions = _positionsContainer.is(':visible');
			
			_root.removeClass('prefilled-measure');
			_measuresContainer.html($html);
			setColumns(_measuresContainer, $hasPositions ? 2 : 3);

			if ($hasPositions)
				_measuresContainer.find('.items').css('margin-left', getWidth(_positionsContainer));
		};
		
		function createStats(stats)
		{
			if (stats == null || stats.length == 0)
			{
				_root.addClass('no-stats');
				return;
			}
						
			var $html = ''
				+ '<a href="#" class="more">Add stats</a>'
				+ '<div class="message">Choose up to 5 additional stats (<span class="counter">5</span> left)</div>'
				+ '<div class="items">'
				;
				
			for (var $i = 0; $i < stats.length; $i++)
			{
				var $item = stats[$i],
					$value = $item.name,
					$id = 'sport-stats-' + $value 
					;
				
				$html += ''
					+ '<div class="radio stats">'
						+ '<input type="checkbox" id="' + $id + '" name="' + field('stats', '') + '" value="' + $value + '" />'
						+ '<div class="label-box">'
							+ '<label for="' + $id + '">' + $item.label + '</label>'
						+ '</div>'
					+ '</div>'
					;
			}
			
			$html += ''
				+ '</div>'
				+ '<div style="clear: both" class="toggled-clear"></div>'
				;
			
			_root.removeClass('prefilled-stats');
			_statsContainer.html($html);
			setColumns(_statsContainer);
		};
		
		function createDimensions(dimensions)
		{
			if (dimensions == null || dimensions.length == 0)
			{
				_root.addClass('no-dimensions');
				return;
			}
			
			var $html = ''
				+ '<div class="title">Only show:</div>'
				+ '<div class="items-group">'
				;
				
			for (var $dimensionIndex = 0; $dimensionIndex < dimensions.length; $dimensionIndex ++)
			{
				var $dimensions = dimensions[$dimensionIndex];
				
				$html += '<div class="items">';
				
				for (var $i = 0; $i < $dimensions.length; $i++)
				{
					var $dimension = $dimensions[$i],
						$value = $dimension.name,
						$id = 'sport-dimension-' + $dimensionIndex + '-' + $value,
						$vs = ($value == 'vs_team')
						;
				
					$html += ''
						+ '<div class="radio dimension ' + ($vs ? 'vs' : '') + '">'
							+ '<input type="radio" id="' + $id + '" name="' + field('dimension' + $dimensionIndex) + '" value="' + $value + '" />'
							+ '<div class="label-box">'
								+ '<label for="' + $id + '">' + $dimension.label + '</label>'
								+ ($vs ? vsTeam(field('dimension' + $dimensionIndex, 'vs_team')) : '')
							+ '</div>'
						+ '</div>'
						;
				}
				
				$html += ''
				 		+ _clearHtml
					+ '</div>'
					;
			}
			
			$html += '' 
					+ _clearHtml
				+ '</div>'
				;
			
			_root.removeClass('prefilled-dimensions');
			_dimensionsContainer.html($html);
			_dimensionsContainer.find('.items:first').addClass('first');
			_dimensionsContainer.find('.items:last').addClass('last');
			
			setColumns(_dimensionsContainer);
		};
		
		function initLayout()
		{
			if ($.browser.msie6)
				_root.addClass('ie6');
			else if ($.browser.msie7)
				_root.addClass('ie7');
							
			_root.html();
			_root.html(''
				+ '<div class="tabs">'
					+ (SPORTSNET_TEAM_STATS_TARGET ? '<div class="arrow" style="float:right;"><a href="'+SPORTSNET_TEAM_STATS_TARGET+'" class="today-link">Team Stats</a></div>' : '')
					+ '<div class="tab-section custom-tab on"><a href="#">Custom Stat Search</a></div>'
					+ '<div class="tab-section compare-tab"><a href="#">Compare With</a></div>'
				+ '</div>'
				+ _clearHtml
				+ '<div class="compare"></div>'
				+ '<form method="post" name="statsForm" id="statsForm" action="' + SPORTSNET_PLAYER_STATS_TARGET + '">'
					+ '<input type="hidden" name="' + field('compare', 'left') + '" />'
					+ '<input type="hidden" name="' + field('compare', 'right') + '" />'
					+ '<div class="box">'
						+ '<div class="section position"></div>'
						+ '<div class="section measures"></div>'
						+ '<div class="section dimensions"></div>'
						+ _clearHtml
					+ '</div>'
					+ '<div class="section stats"></div>'
					+ '<div class="buttonHolder">'
						+ '<a href="#" class="button buttonInactive"><span>Get Stats Now</span></a>'
					+ '</div>'
				+ '</form>'
				);
				
			// add non-functional bits for tab visuals
			_root.find('.tabs > div > a')
				.before('<span class="left"></span>')
				.after('<span class="right"></span>')
				;

			_compareContainer = _root.find('.compare');
			_positionsContainer = _root.find('.position');
			_measuresContainer = _root.find('.measures');
			_dimensionsContainer = _root.find('.dimensions');
			_statsContainer = _root.find('.stats');
			
			_compareContainer.hide();
			createPositions();
			_self.setSelected(values);
			
			bindEvents();
		};
		
		// rebinds all radio buttons, checkboxes and '(change)' links
		function bindEvents()
		{
			_positionsContainer.find('input:radio').unbind().click(function()
			{
				_self.selectPosition(this);
				_self.activateButton(true);
			});
			
			_positionsContainer.find('.change a').unbind().click(function()
			{
				_self.deselectPosition(this);
				return false;
			});

			_measuresContainer.find('input:radio').unbind().click(function()
			{
				_self.selectMeasure(this);
			});

			_measuresContainer.find('.change a').unbind().click(function()
			{
				_self.deselectMeasure(this);
				return false;
			});
			
			_dimensionsContainer.find('input:radio').unbind().click(function()
			{
				_self.selectDimension(this);
			});

			_dimensionsContainer.find('.prefilled .change a').unbind().click(function()
			{
				_self.deselectDimension(this);
				return false;
			});
						
			_statsContainer.find('input:checkbox').unbind().click(function()
			{
				_self.toggleStats(this);
			});
			
			_statsContainer.find('a').unbind().click(function()
			{
				_self.toggleStatsSection(this);
				return false;
			});
			
			_root.find('.compare-tab a').unbind().click(function()
			{
				_self.activateCompareTab();
				return false;
			});

			_root.find('.custom-tab a').unbind().click(function()
			{
				_self.activateCustomTab();
				return false;
			});

			_root.find('a.button').click(function()
			{
				if ($(this).hasClass('buttonInactive') == false)
					$("#statsForm").submit();
					
				return false;
			});
			
			// make sure that all selects activate their respective radios
			_dimensionsContainer.find('select').unbind().change(function()
			{
				$(this).parents('.radio').find('input:radio').attr('checked', true);
			});
		};
		
		// Initialization ------------------------------------------------------------------

		initLayout();
	};

	// jQuery plugin implementation
	$.fn.playerStats = function(values)
	{ 
		this.each(function() 
		{
			var $instance = new PlayerStats($(this), values);
			$(this).data('playerStats', $instance);
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function DateManager(root, conf)
	{	
		// Private fields -------------------------------------------------------------------

		var _root = $(root),
			_next = _root.find('.next'),
			_prev = _root.find('.prev'),
			_input = _root.find('input'),
			_curDate = _input.val(),
			_changeDateButtons = [],
			_cookieName = "sn_date",
			_cookieValue = null;
			_self = this
			;
			
		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			changeDate: function (_date, _increment) {
				var newDateObj = new Date(_date.getFullYear(), _date.getMonth(), (_date.getDate() + _increment));
				return newDateObj;
			},
			onOpenDatePick: function () {
				_changeDateButtons.each(function(){
					this.style.display = "none";
				});
			},
			onCloseDatePick: function () {
				_changeDateButtons.each(function(){
					this.style.display = "block";
				});
				//_changeDateButtons.show();
			}
		});

		// Private methods -----------------------------------------------------------------

		// generic binding function
		function bind(name, fn)
		{
			if ($.isFunction(fn))
				$(_self).bind(name, fn);
		};
				
		function initLayout(){
			if (conf.highlightWeek == true)
				$('#datepicker-wrapper').addClass('weeks');

			// generate layout -----------------------------------------
			//console.log("curDate: "+curDate)
			$('#embedded-datepicker').datepick($.extend({
				mandatory: true,
				showDefault: true,
				numberOfMonths: 3,
				showCurrentAtPos: 2,
				stepMonths: 3,
				showOtherMonths: true,
				showOn: 'both',
				buttonImageOnly: true,
				buttonImage: '/assets/img/date_picker/calendar.gif',
				dateFormat: 'MM d, yy',
				changeMonth: false,
				changeYear: false,
				showAnim: 'slideDown',
				duration: 100,
				paddingTop: 5,
				paddingLeft: 0,
				onClose: function(){
					_self.onCloseDatePick();
				},
				onOpen: function(){
					_self.onOpenDatePick();
				}
			}, conf));
			
			_changeDateButtons = $('.change-date');
			_changeDateButtons.click(function(){
				var input = $.datepick._getInst($("#embedded-datepicker")[0]);
				var curDisplayDate = $.datepick._getDate(input);
				var newDate;
				if ($(this).hasClass('next')) {
					newDate = _self.changeDate(curDisplayDate, (conf.highlightWeek == true ? 7 : 1));
				}
				else {
					newDate = _self.changeDate(curDisplayDate, (conf.highlightWeek == true ? -7 : -1));
				}
				$.datepick._setDate(input, newDate);
				$('#embedded-datepicker').trigger('change');
				return false;
			});
			_changeDateButtons.hover(
				function(){
					$(this).addClass("hover");
				},
				function(){
					$(this).removeClass("hover");
				}
			);
			
			$('#embedded-datepicker').change(function(){
				$('#dateInput').val($.datepick.formatDate('yymmdd', $.datepick._getDate($.datepick._getInst($("#embedded-datepicker")[0]))));
				$('#datepickerForm').submit();
			});
			
			$(window).resize(function(){
				$.datepick._hideDatepick();
			});
		}

		// Initialization ------------------------------------------------------------------
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.dateManager = function(conf)
	{
		// update dates from array to match what we expect
		if ($.isArray(conf.selectedDates))
		{
			var $dates = conf.selectedDates;
			
			for (var $i = 0; $i < $dates.length; $i++)
			{
				var $date = new Date($dates[$i] * 1000);
				$date.setHours(0);
				$dates[$i] = $date.getTime(); 
			}
		}
		
		this.each(function()
		{
			var $instance = new DateManager($(this), conf);
			$(this).data("dateManager", $instance);
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function TeamFeatureStory(root)
	{	
		// Private fields ------------------------------------------------------------------
		var _root = $(root),
			_domElement = _root[0],
			_self = this,
			_opts = {
				debug: false
			}
			;
	
		// Public methods ------------------------------------------------------------------
//		$.extend(_self, {
//		});
	
		// Private methods -----------------------------------------------------------------
		function debug(str)
		{
			if (_opts.debug == true) {
				try {
					trace(str);
				} 
				catch (e) {
					alert("debug: " + str);
				}
			}
		};
		
		function scaleSummary()
		{
			var availHeight = $(_root.parent()).parent().outerHeight();
			availHeight -= _root.outerHeight();
			debug("availHeight: " + availHeight);
			var summaryElem = _root.find('.summary:first');
			
			summaryElem.css({height:"auto"});
			debug("summaryElem.height(): " + summaryElem.height());
			if (summaryElem.height() > availHeight) {
				summaryElem.css({height:"0px"});
			
				var lineHeight = Math.ceil((summaryElem.css('line-height').split('p'))[0]);
				var numRows = Math.floor(availHeight / lineHeight);
				var summaryHeight = numRows * lineHeight;
				
				summaryElem.height(summaryHeight);
			}
			
			try {
				sportsnetPage.registerInit({
					_expression: ".ellipsis",
					_functionRef: "ellipsis",
					_rank: "DISPLAY"
				});
			}
			catch (e) {
				summaryElem.ellipsis();
			}
		};
	
		function init()
		{	
			scaleSummary();
		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};
	
	// jQuery plugin implementation
	$.fn.teamFeatureStory = function()
	{
		this.each(function()
		{
			var $instance = new TeamFeatureStory($(this));
		});
		
		return this;
	};
})(jQuery);
(function($)
{	
	// constructor
	function Gallery(root, conf)
	{	
		// Private fields ------------------------------------------------------------------

		var _root = $(root),
			_self = this,
			_thumbs,
			_featureContainer,
			_selectedIndex,
			_playIntervalId = 0,
			_playButton,
			_scroller
			;

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			showImage: function(index, keepAutoPlaying)
			{
				var $thumb = $(_thumbs[index]),
					$a = $thumb.find('a'),
					$src = $a.data('src')
					;
				
				_featureContainer.html('<img src="' + $src + '" class="feature img">');
				
				// fade feature image in if not in IE6
				if ($.browser.msie6 == false)
					conf.showFunc(_featureContainer.find('.feature').hide());
				
				// update 'N of X' label
				_root.find('.controls .position').html((index + 1) + ' of ' + _thumbs.length);
				
				// update text labels
				_root.find('.middle > .text').html($thumb.find('.text').html());
				_root.find('.bottom > .meta').html($thumb.find('.meta').html());
				
				// place selection on the correct thumb
				_thumbs.find('.selection').remove();
				$(_thumbs[index]).append('<div class="selection"></div>');
				
				var $expectedPageIndex = Math.floor(index / _scroller.getPageSize());
				
				if (_scroller.getPageIndex() != $expectedPageIndex)
					_scroller.setPage($expectedPageIndex)
				
				_selectedIndex = index;
				
				if (keepAutoPlaying != true && _playIntervalId > 0)
					_self.pause();
			},
			
			prevImage: function()
			{
				if (--_selectedIndex < 0)
					_selectedIndex = _thumbs.length - 1;
					
				_self.showImage(_selectedIndex)
//				_self.updateAds();
			},
			
			nextImage: function(keepAutoPlaying)
			{
				// if next image would be looped and we are autoplaying, then pause
				if (keepAutoPlaying == true && _selectedIndex >= _thumbs.length)
				{
					_self.pause();
					++_selectedIndex;
				}
				else if (++_selectedIndex >= _thumbs.length)
				{
					_selectedIndex = 0;
					keepAutoPlaying = false;
				}
				
				_self.showImage(_selectedIndex, keepAutoPlaying);
//				_self.updateAds();
			},
			
			play: function()
			{
				_self.pause();
				_playIntervalId = setInterval(function() { _self.nextImage(true /* keep autoplaying */) }, conf.playbackDelay);
				_playButton.addClass('pause');
				_self.nextImage(true);
			},
			
			pause: function()
			{
				_playButton.removeClass('pause');
				clearInterval(_playIntervalId);
				_playIntervalId = 0;
			},
			
			playPause: function()
			{
				if (_playIntervalId > 0)
					_self.pause();
				else
					_self.play();
			},
			updateAds: function()
			{
				var $bbAds = $('.right-col-ads').find('.ad'),
					$curBbAd = $bbAds.not('.hidden'),
					$hidBbAds = $bbAds.filter('.hidden'),
					$nextBbAd = $hidBbAds[Math.floor(Math.random() * $hidBbAds.length)];				
					$lbAds = $('.ads-topnav').find('.ad'),
					$curLbAd = $lbAds.not('.hidden'),
					$hidLbAds = $lbAds.filter('.hidden'),
					$nextLbAd = $hidLbAds[Math.floor(Math.random() * $hidLbAds.length)];	
					$ftAds = $('.ads').find('.ad'),
					$curFtAd = $ftAds.not('.hidden'),
					$hidFtAds = $ftAds.filter('.hidden'),
					$nextFtAd = $hidFtAds[Math.floor(Math.random() * $hidFtAds.length)];	
				
				$($curBbAd).addClass('hidden');				
				$($nextBbAd).removeClass('hidden');
				$($curLbAd).addClass('hidden');
				$($nextLbAd).removeClass('hidden');				
				$($curFtAd).addClass('hidden');
				$($nextFtAd).removeClass('hidden');				
			}
		});

		// Private methods -----------------------------------------------------------------
		
		function initLayout()
		{
			if ($.browser.msie6)
				_root.addClass('ie6');
			else if ($.browser.msie7)
				_root.addClass('ie7');
			
			_featureContainer = _root.find('.feature-container');
			
			// set up scrolling panel
			_scroller = _root.find('.scrolling-panel').scrollingPanel({ size: '4x3' }).data('scrollable');
			
			_thumbs = _root.find('.scroll-container ul li');
			
			_thumbs.find('a')
				.each(function(index)
				{
					var $img = $(this).find('img')[0];
					$(this)
						.wrap('<div class="thumb img" style="background-image: url(' + $img.alt + ')">')
						.data('src', $img.src)
						.data('index', index)
						.html('')
					;
				})
				.unbind()
				.click(function()
				{
					_self.showImage($(this).data('index'));
//					_self.updateAds();
					return false;
				})
			;
			
			var $controls = _root.find('.top .controls');
			
			$controls.find('.prev').click(function()
			{
				_self.prevImage();
				return false;
			});

			$controls.find('.next').click(function()
			{
				_self.nextImage();
				return false;
			});
			
			_playButton = $controls.find('.play').click(function()
			{
				_self.playPause();
				return false;
			});
			
			// calculate height of the middle section
			_root.find('.middle').css('height',
				_root.find('.sidebar').height() - 
				_root.find('.bottom').height() - 
				_root.find('.top').height()
				- 10 // some spacing
			);
			
			// show first image
			_self.showImage(conf.index || 0);
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.gallery = function(conf)
	{ 
		var opts = {
			hideFunc : function(element) { element.hide(); },
			showFunc : function(element) { element.fadeIn(); },
			playbackDelay : 5000
		}; 
		
		$.extend(opts, conf);		
		
		this.each(function() 
		{
			var $instance = new Gallery($(this), opts);
			$(this).data("gallery", $instance);	
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function GalleryFeature(root, conf)
	{	
		// Private fields ------------------------------------------------------------------

		var _root = $(root),
			_self = this
			;

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
		});

		// Private methods -----------------------------------------------------------------
		
		function initLayout()
		{
			if ($.browser.msie6)
				_root.addClass('ie6');
			else if ($.browser.msie7)
				_root.addClass('ie7');
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.galleryFeature = function(conf)
	{ 
		var opts = {}; 
		$.extend(opts, conf);		
		
		this.each(function() 
		{
			var $instance = new GalleryFeature($(this), opts);
			$(this).data("galleryFeature", $instance);	
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function GalleryRecent(root, conf)
	{	
		// Private fields ------------------------------------------------------------------

		var _root = $(root),
			_self = this
			;

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
		});

		// Private methods -----------------------------------------------------------------
		
		function initLayout()
		{
			if ($.browser.msie6)
				_root.addClass('ie6');
			else if ($.browser.msie7)
				_root.addClass('ie7');
				
			_root.find('.scrolling-panel').scrollingPanel({ size: 2 });
			
			_root.find('.thumb').each(function()
			{
				$(this).hover(function() {											// Hover function added 
									   		$(this).addClass('hover');				// to override IE6 behaviour.
									   }, function() {								//	
										   	$(this).removeClass('hover');			//	Gord 09/08/2009
									   });
				
				var $url = $(this).find('a').attr('href');
				$(this).find('.img').click(function()
				{
					window.location = $url;
					return false;
				});
			});
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.galleryRecent = function(conf)
	{ 
		var opts = {}; 
		$.extend(opts, conf);		
		
		this.each(function() 
		{
			var $instance = new GalleryRecent($(this), opts);
			$(this).data("galleryRecent", $instance);	
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function GalleryList(root, conf)
	{	
		// Private fields ------------------------------------------------------------------

		var _root = $(root),
			_self = this
			;

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
		});

		// Private methods -----------------------------------------------------------------
		
		function initLayout()
		{
			if ($.browser.msie6)
				_root.addClass('ie6');
			else if ($.browser.msie7)
				_root.addClass('ie7');
							
			_root.find('.thumb').each(function()
			{
				var $url = $(this).find('.meta a').attr('href');
				$(this).find('.img').click(function()
				{
					window.location = $url;
					return false;
				});
				$(this).hover(
					function(){
						$(this).addClass("hover");
					},
					function(){
						$(this).removeClass("hover");
					}
				);
			});
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.galleryList = function(conf)
	{ 
		var opts = {}; 
		$.extend(opts, conf);		
		
		this.each(function() 
		{
			var $instance = new GalleryList($(this), opts);
			$(this).data("galleryRecent", $instance);	
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function Scores(root, conf)
	{	
		// Private fields ------------------------------------------------------------------

		var _root = $(root),
			_self = this,
			_index = 0,
			_gameId = -1,
			_intervalId = 0
			;

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
		});

		// Private methods -----------------------------------------------------------------
		
		function initLayout()
		{
			if ($.browser.msie6)
				_root.addClass('ie6');
			else if ($.browser.msie7)
				_root.addClass('ie7');
			
			_gameId = /gameid-(\d+)/.exec(_root[0].className)[1];
			
			_intervalId = setInterval(function()
			{
				$.get(
					conf.updateUrl + '?id=' + _gameId + '&s=' + Math.random() + '&i=' + _index,
					{ i : _index },
					function(html)
					{
						_root.html(html);
						
						// clear querying interval when the game is marked final
						if (_root.find('.mini-scoreboard-header .periods').hasClass('final'))
						{
							clearInterval(_intervalId);
						}
						
						_index++;
					}
				);
			},
			opts.refresh
			);
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.scores = function(conf)
	{ 
		var opts = {
			refresh : 5000,
			updateUrl : '/ui_components/stats_modules/scores_render.php'
		};
		
		$.extend(opts, conf);		
		
		this.each(function() 
		{
			var $instance = new Scores($(this), opts);
			$(this).data("scores", $instance);	
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function ShowHideDetails(root, conf)
	{	
		// Private fields -------------------------------------------------------------------
		var _root = $(root),
			_trigger = _root.find('.details-link'),
			_target = _root.parent().find(".showhide-details-content"),
			_self = this,
			_opts = {
				debug: false,
				openString: _trigger.text(), //defaults
				closeString: _trigger.attr('name'),
				onOpen: null, //callbacks - passed directly to animation functions
				onClose: null
			};
		$.extend(_opts, conf);
			
		// Public methods ------------------------------------------------------------------


		// Private methods ----------------------------------------------------------------
		function initLayout()
		{	
			_trigger.click(function() {
				if ($(this).hasClass('opened')) {
					_target.slideUp(200, _opts.onClose);
					$(this).toggleClass('opened');
					
						$(this).text(_opts.openString);
					
						
					
				}
				else {
					_target.slideDown(200, _opts.onOpen);
					$(this).toggleClass('opened');
					if(_trigger.attr('name') != undefined && _trigger.attr('name') != "") {
						$(this).text(_opts.closeString);
					} else {
						$(this).text(_opts.openString);
					}
				}
				return false;
			});
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
		

	};

	// jQuery plugin implementation
	$.fn.showHideDetails = function(conf)
	{ 
		var opts = {};
		$.extend(opts, conf);
		
		this.each(function() 
		{
			var $instance = new ShowHideDetails($(this), opts);
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function TrackerPoll(root, conf)
	{	
		// Private fields -------------------------------------------------------------------
		var _root = $(root),
			_optionTrigger = _root.find('.poll-option'),
			_viewTrigger = _root.find('.poll-view-results'),
			_voteTrigger = _root.find('.poll-vote-now'),
			_choicesTarget = _root.find('.poll-choices'),
			_resultsTarget = _root.find(".poll-results"), 
			_resultsTargetTextNew = _root.find(".poll-results div"), 
			_resultsTargetTextOld = _root.find(".poll-results span"), 
			_self = this;
        var _mode = _resultsTargetTextNew.length > 0 ? 'tracker2' : 'tracker';

	
		// Public methods ------------------------------------------------------------------


		// Private methods ----------------------------------------------------------------
		function initLayout()
		{	
			var fadeSpeed = 50;

			_optionTrigger.click(function() {
                $.ajax({
                    url: "/scrum/poll_vote.php",
                    cache: false,
                    data: "mode=" + _mode + "&response_id="+$(this).attr('href').substr(1),
                    success: function(html) {
                        _resultsTarget.html(html);

                        if ($.browser.msie6) { //fade messes up in ie6
                            _choicesTarget.hide();
                            _resultsTarget.show();
                        } else {
                            _choicesTarget.fadeOut(fadeSpeed, function(){
                                _resultsTarget.fadeIn(fadeSpeed);
                            });
                        }
                    }
                });

				return false;
			});
			
			_viewTrigger.click(function() {
                $.ajax({
                    url: "/scrum/poll_vote.php",
                    cache: false,
                    data: "mode=" + _mode + "&poll_id="+$(this).attr('href').substr(1),
                    success: function(html) {
                        if (_resultsTargetTextNew) {
                            _resultsTargetTextNew.html(html);
                        } else if (_resultsTargetTextOld) {
                            _resultsTargetTextOld.html(html);
                        }

                        if ($.browser.msie6) { //fade messes up in ie6
                            _choicesTarget.hide();
                            _resultsTarget.show();
                        } else {
                            _choicesTarget.fadeOut(fadeSpeed, function(){
                                _resultsTarget.fadeIn(fadeSpeed);
                            });
                        }
                    }
                });
				return false;
			});
			
			_voteTrigger.click(function() {
				if ($.browser.msie6) { //fade messes up in ie6
					_resultsTarget.hide();
					_choicesTarget.show();
				}
				else {
					_resultsTarget.fadeOut(fadeSpeed, function(){
						_choicesTarget.fadeIn(fadeSpeed);
					});
				}
				return false;
			});
			
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
		

	};

	// jQuery plugin implementation
	$.fn.trackerPoll = function()
	{ 
		var opts = {};
		$.extend(opts);
		
		this.each(function() 
		{
			var $instance = new TrackerPoll($(this));
		});
		
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function TabNav(root)
	{	
		// Private fields ------------------------------------------------------------------
		var _root = $(root),
			_self = this,
			_navTabs,
			_tabBottoms,
			_tabContentElems,
			_idx = 0,
			_old
			;
			
		// Public methods ------------------------------------------------------------------
		$.extend(_self, {
			onTabClick: function(tabElem) {
				var $tabElem = $(tabElem),
					_old = _idx;
					
				_idx = _navTabs.index($tabElem);
				
				_navTabs.eq(_old).toggleClass('on');
				_navTabs.eq(_idx).toggleClass('on');
				
				_tabBottoms.eq(_old).toggleClass('on');
				_tabBottoms.eq(_idx).toggleClass('on');
				
				_tabContentElems.eq(_old).toggleClass('hidden');
				_tabContentElems.eq(_idx).toggleClass('hidden');
				
				return false;
			}
		});
	
		// Private methods ------------------------------------------------------------------
		function init()
		{	
			//var $idx = 0;
			_navTabs = _root.find('td.tab-nav-option').click(function(){
				_self.onTabClick(this);
			});
			_navTabs.find("a").click(function(){
				_self.onTabClick($(this).closest("td.tab-nav-option"));
				return false;
			});
			_tabBottoms = _root.find('td.bottom-row');
			_tabContentElems = $('ul.tabs-featured');
		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};
	
	// jQuery plugin implementation
	$.fn.tabNav = function()
	{
		
		this.each(function()
		{
			var $instance = new TabNav($(this));
		});
		
		return this;
	};
})(jQuery);
(function($)
{	
	// constructor
	function FollowTeamLink(root, conf)
	{	
		// Private fields -------------------------------------------------------------------
		var _root = $(root),
			_element = _root[0],
			_label = _root.find(".follow-team-label"),
			_self = this,
			_addLabel = "Follow this team",
			_addHelp = "Click to add this team to \"My Headlines\" line-up.",
			_removeLabel = "Stop following this team",
			_removeHelp = "Click to remove this team from \"My Headlines\" line-up.",
			_editLabel = "Edit \"My Headlines\" teams",
			_editHelp = "You are already following 6 teams. Click to go to \"My Headlines\" page and edit your line-up.",
			_myHeadlinesURL = "/myheadlines/",
			_cookieOptions = {
				    path: '/',
				    hoursToLive: 90000,
				    secure: false
				  },
			_opts = {
				debug: false,
				lineupId: null
			};
			$.extend(_opts, conf);

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			reloadPage: function () 
			{
				location.reload();
			},
			getMyLineUp: function () 
			{
				var cookieVal = $.cookie("sn_myHeadlinesLineUp");
				var lineUp = cookieVal != null ? cookieVal : window.sn_HeadlinesLineUp;
				lineUp = lineUp.split(",");
				return lineUp;
			},
			updateDisplay: function ()
			{
				//if the my headlines module is on the page, reload - otherwise, just update the link
				var moduleIsPresent = $("div.my-teams-headlines").length > 0;
				_self.debug("moduleIsPresent: " + moduleIsPresent);
				if (moduleIsPresent) {
					_self.reloadPage();
				}
				else {
					_self.updateLink();
				}
			},
			removeTeam: function () 
			{
				_self.debug("removeTeam---");
				_self.debug("b4: " + $.cookie("sn_myHeadlinesLineUp"));
				_myLineUp = $.grep(_myLineUp, function(value){
					return value != _opts.lineupId;
				});
				$.cookies.set("sn_myHeadlinesLineUp", _myLineUp, _cookieOptions);
				_self.debug("aft: " + $.cookie("sn_myHeadlinesLineUp"));
				
				_self.updateDisplay();
			},
			addTeam: function () 
			{
				_self.debug("addTeam---");
				_self.debug("b4: " + $.cookie("sn_myHeadlinesLineUp"));
				_myLineUp.push(_opts.lineupId);
				
				$.cookies.set("sn_myHeadlinesLineUp", _myLineUp, _cookieOptions);
				
				_self.debug("aft: " + $.cookie("sn_myHeadlinesLineUp"));
				
				_self.updateDisplay();
			},
			updateLink: function () 
			{
				_root.unbind(); //remove any existing event handlers
				
				var teamIsInLineup = $.inArray(_opts.lineupId, _myLineUp) != -1;
				if (teamIsInLineup) {
					_label.html(_removeLabel);
					_root.attr('title', _removeHelp);
					_root.click(function(){
						_self.removeTeam();
						return false;
					});
				}
				else {
					var hasSpace = _myLineUp.length < 6;
					if (hasSpace) {
						_label.html(_addLabel);
						_root.attr('title', _addHelp);
						_root.click(function(){
							_self.addTeam();
							return false;
						});
					}
					else {
						_label.html(_editLabel);
						_root.attr('title', _editHelp);
						_root.attr('href', _myHeadlinesURL);
					}
				}
			},
			debug: function(str)
			{
				if (_opts.debug == true) {
					try {
						trace(str);
					} 
					catch (e) {
						alert(str);
					}
				}
			}
		});

		// Private methods ----------------------------------------------------------------
		function initLayout()
		{
			_myLineUp = _self.getMyLineUp();
			_self.updateLink();
			_root.show();
			
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
		

	};

	// jQuery plugin implementation
	$.fn.followTeamLink = function(opts)
	{ 
		this.each(function() 
		{
			new FollowTeamLink($(this), opts);
		});
	};
})(jQuery);
(function($)
{	
	// constructor
	function NewsArchiveFilter(root, conf)
	{	

		// Private fields -------------------------------------------------------------------

		var _page = $('body'),
			_root = $(root),
			_feedOptions = _root.find('a'),
			_contentHolder = _page.find('.content-listing'),
			$tmp = $(_root.find('.news-archive-sports a')).attr("id"),
			$t = $tmp.split("|"),
			_leagueName = $t[1],
			_curStories = new Array(),
			_filteredStories = new Array(),
			_self = this,
			_opts = {
				debug: false
			}; 
			 
		$.extend(_opts, conf);
		
		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			
			getFeed: function(type, feed)
			{
				var $feed = feed + "/xml/index.xml",
					$sport = _self.getSport(_leagueName);

				if (type == "team")
				{
					$feed = "news_"+feed + "/xml/index.xml";
				};
				if ($sport == "football") {
					var $path = $sport + "/" + _leagueName + "/"; 
				}
				else {
					var $path = $sport + "/";
				};	
				$.ajax({
					type: "GET",
					url: $path + $feed,
					dataType: "xml",
					success: function(xml){
						_self.killStories();
						_self.buildStories(xml);
					},
					error: function(){
						_self.doXMLError();
					}
				});
			},

			getSport: function(league) 
			{
					var sport = null;
					
					switch(league){
						case "nhl":
							sport = "hockey";
							break;
						case "mlb":
							sport = "baseball";
							break;
						case "nfl": 
							sport = "football";
							break;
						case "cfl":
							sport = "football";
							break;
						default:
							sport = "basketball";		
							break;
					}
					return sport;
			},
			
			buildStories: function(xml)
			{
				var $xmlObj = $(xml),
					_storyArray = $xmlObj.find('item'),
					_sectionDate = null;

				for(var i=0; i < _storyArray.length; i++)
				{
					var curStory = _storyArray[i],
						curDate = $($(curStory).find('citation')).attr('date'),
						curURL = $(curStory).attr('url'),
						curComment = $(curStory).attr('comments'),
						curCommentURL = $(curStory).attr('commentsURL'),
						curVideo = $(curStory).attr('hasVideo'),
						curHeadline = $($(curStory).find('headline')).text(),
						curSummary = $($(curStory).find('summary')).text()
						id = i;

					if(curDate == _sectionDate)
					{
						_self.createArticle(curURL, curComment, curCommentURL, curVideo, curHeadline, curSummary, id);
					} else {
						_sectionDate = curDate;
						_self.createDateSection(_sectionDate);
						_self.createArticle(curURL, curComment, curCommentURL, curVideo, curHeadline, curSummary, id);
					};
				}
				_contentHolder.children('li:last').addClass('last');
				_self.showStories();	
			},
			
			createDateSection: function(date)
			{
				var $tmp = date.split(" "),
					$month = $tmp[0],
					d = $tmp[1],
					dt = d.split(","),
					$day = dt[0],
					$year = $tmp[2],
					$section = "<li class='date-section'><div class='press-date'><span class='month'>"+$month+"</span><span class='day'>"+$day+"</span><span class='year'>"+$year+"</span></div><div class='press-items'><ul class='press-link-list'></ul></div></li>";
				
				_contentHolder.append($section); 
			},
			
			createArticle: function(url, comments, commentsURL, hasVideo, headline, summary, id)
			{
				var $section = _contentHolder.find('li.date-section:last');
					$articleWrapper = $section.find('ul.press-link-list'),
					$url = url,
					$comments = comments,
					$commentsURL = commentsURL,
					$hasVideo = hasVideo,
					$head = headline,
					$sum = summary,
					$id = id,
					$content = "<li id='"+$id+"'><a href='"+$url+"'>"+$head+"</a><span class='comment-balloon'><a href='"+$commentsURL+"'>"+$comments+"</a></span><span class='comment-video'><a href='"+$url+"'><img src='/assets/img/comment_video.gif'></a></span><p class='list-desc ellipsis multiline'>"+$sum+"</p></li>";
					
				$articleWrapper.append($content);
				if($comments <= 0 || $comments == undefined){
					$("li#"+$id+" span.comment-balloon").hide();
				}	
				if($hasVideo == "false" || $hasVideo == undefined){
					$("li#"+$id+" span.comment-video").hide();
				}
			},
			
			killStories: function()
			{
				_contentHolder.empty();
			},
			
			hideStories: function()
			{
				_contentHolder.hide();
				_contentHolder.parent().find('.error-content').hide();
				_contentHolder.parent().find('.updating-content').show();
			},
			
			showStories: function()
			{
				_contentHolder.parent().find('.updating-content').hide();
				_contentHolder.fadeIn();
			},
			
			doXMLError: function()
			{
				_contentHolder.parent().find('.updating-content').hide();
				_contentHolder.parent().find('.error-content').show();
			},
			
			debug: function(str)
			{
				if (_opts.debug == true) {
					try {
						trace(str);
					} 
					catch (e) {
						alert(str);
					}
				}
			}
		});

		// Private methods ----------------------------------------------------------------
		function initLayout()
		{	
			_contentHolder.parent().prepend('<div class="updating-content">Updating Content</div>');		
			_contentHolder.parent().prepend('<div class="error-content">The news feed you have chosen is currently unavailable.  Please select a different feed, or try again later.</div>');		
			_contentHolder.parent().find('.updating-content').hide();
			_contentHolder.parent().find('.error-content').hide();
			_self.hideStories();
			_self.getFeed("league", _leagueName);
			
			_feedOptions.click(function() 
			{
				var $this = $(this),
					$id = $this.attr('id'),
					tmp = $id.split('|'),
					$league = tmp[0],
					$feed = tmp[1];

				setTimeout(function(){
					_page.find('.default').text($this.text());	
					_self.hideStories();
					_self.getFeed($league, $feed);}, 750);
													
				return false;
			})
		};

		// Initialization ------------------------------------------------------------------
		
		initLayout();
	};

	// jQuery plugin implementation
	$.fn.newsArchiveFilter = function(conf)
	{ 
		var opts = {};
		$.extend(opts, conf);
		
		this.each(function() 
		{
			var $instance = new NewsArchiveFilter($(this), opts);
		});
		return this; 
	};
})(jQuery);
(function($)
{	
	// constructor
	function DataLayoutManager(root)
	{	
		// Private fields ------------------------------------------------------------------
		var _root = $(root),
			_domElement = _root[0],
			_containerDiv,
			_contentDiv,
			_bodyDiv,
			_rightColDiv,
			_self = this,
			_opts = {
				debug: false
			}
			;
	
		// Public methods ------------------------------------------------------------------
		$.extend(_self, {
			contentIsTooWide: function()
			{
				var myWidth = _root.width();
				var tableWidth = _root.find("table.stats").width();
				debug("myWidth: " + myWidth);
				debug("tableWidth: " + tableWidth);
				
				var isTooWide = tableWidth > myWidth;
				debug("isTooWide: " + isTooWide);
				return isTooWide;
			},
			initRightColumn: function()
			{
				_rightColDiv = _contentDiv.find("div.column.span-5.last");
				_rightColDiv.absolutize();
			},
			placeRightColumn: function()
			{
				var targetLeft = _containerDiv.offset().left + _containerDiv.width() + 1; //1 accounts for the border (not 2 since we want to overlap the right edge border)
				debug(" targetLeft: " + targetLeft);
				_rightColDiv.css({
					'left': targetLeft+"px",
					'border-top': "1px solid #999999",
					'border-right': "1px solid #999999",
					'border-bottom': "1px solid #999999",
					'background-color': "#FFFFFF",
					'padding-top': "15px"
				});
			},
			expandBody: function()
			{
				_bodyDiv = _contentDiv.find("div.column.span-10:first");
				//make the main div span all columns
				_bodyDiv.removeClass("span-10");
				_bodyDiv.addClass("span-15 last");
				//make all the children span all columns
				_bodyDiv.find("div.column.span-10").each(function(){
					_this = $(this);
					_this.removeClass("span-10");
					_this.addClass("span-15");
				});
			}
		});
	
		// Private methods -----------------------------------------------------------------
		function debug(str)
		{
			if (_opts.debug == true) {
				try {
					trace(str);
				} 
				catch (e) {
					alert(str);
				}
			}
		};
			
		// generic binding function
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
				
			$(_self).bind(name, fn);
		};
	
		function init()
		{	
			if (_self.contentIsTooWide()) {
				_containerDiv = $("body > div.container:first");
				_contentDiv = $("div.container > #content");
				
				_self.initRightColumn();
				_self.placeRightColumn();
				_self.expandBody();
					
				$(window).resize(function(){
					_self.placeRightColumn();
				});
			}
		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};
	
	// jQuery plugin implementation
	$.fn.dataLayoutManager = function()
	{
		this.each(function()
		{
			var $instance = new DataLayoutManager($(this));
		});
		
		return this;
	};
	
    jQuery.fn.absolutize = function(rebase){
		return this.each(function() {
			var el = $(this);
			var pos = el.position();
			el.css({ position: "absolute",
			marginLeft: 0, marginTop: 0,
			top: pos.top, left: pos.left });
			if (rebase)
				el.remove().appendTo("body");
		});
    };
	
})(jQuery);
//strings
function stripTags(string){
	return string.replace(/<\/?[^>]+>/gi, '');
}

//twitter module
function retweetThis() {
	var tweetElem = $("ul.twitter");
	var twitterUrl = tweetElem.find("li.feeds p.name a").attr("href");
	var src = "@" + (twitterUrl.substr(twitterUrl.indexOf("twitter.com/") + 12).split("/")[0]) + " ";
	//var src = "@" + stripTags(tweetElem.find("span#twitterSource").html()) + " ";
	var tweet = stripTags(tweetElem.find("p#twitterTweet").html());
	var tURL = "http://twitter.com/home?status=" + encodeURIComponent(src + tweet);
	var w = window.open(tURL, "twitterWindow");
	return false;
}
/** Drag and Drop **/
var DEFAULT_RANKING_FIELD = '<span class="instructions">Drag and Drop</span>';
$(document).ready(function(){
	$(".rank .block").each( function (i) {
		$(this).html($(this).html().toString().replace(/<a /gi, '<a target="_blank" '));
	});
	$(".rank .block").draggable({
		cursorAt: { top: '1', left:  '2' }, 
		cursor: 'none', 
		helper: function() { return $(this).find(".meta").clone().removeClass("meta").addClass("label").css("font-weight", "bold").css("font-size", "12px").css("padding", "5px").css("width", $(this).css("width")).css("z-index", "10000").css("white-space", "nowrap"); }
	}); 
	$(".rank .drop").draggable({
		cursorAt: { top: '1', left:  '2' }, 
		cursor: 'none', 
		helper: function() { return $(this).find(".label").clone().css("font-weight", "bold").css("font-size", "12px").css("padding", "5px").css("width", $(this).css("width")).css("z-index", "10000").css("white-space", "nowrap"); }
	}); 
	$(".drop").droppable({
		accept: function(draggable) { return (($(draggable).is('.block')) && ($(this).closest(".rank").attr("id") == $(draggable).closest(".rank").attr("id"))) || (($(draggable).is('.drop')) && ($(this).closest(".rank").attr("id") == $(draggable).closest(".rank").attr("id"))); }, 
		activeClass: 'dropactive',
		hoverClass: 'drophover',
		drop: function(ev, ui) {
			if ($(ui.draggable).find(".meta").length > 0) {
				/* drag from .rank .block (insert into) */
				$(this).closest(".rank-answers").each(
						function (i) { 
								$(this).find(".drop").each( function (i) {
										if ($(this).find(".label").length > 0 && $(this).find(".label").attr("id") == $(ui.draggable).find(".meta").attr("id")) {
											$(this).html(DEFAULT_RANKING_FIELD);
										}
								});
						});
				
				$(this).empty();
				$(this).append($(ui.draggable).find(".meta").clone().removeClass("meta").addClass("label").css("cursor", "pointer"));
			} else {
				/* drag from .rank .drop (move up and down) */
				var mouse_item = $(ui.draggable).find(".label").clone();
				var current_item = $(this).find(".label").clone();
				$(this).closest(".rank-answers").each(
						function (i) { 
								$(this).find(".drop").each( function (i) {
										if ($(this).find(".label").length > 0 && $(this).find(".label").attr("id") == $(mouse_item).attr("id")) {
											if ($(current_item).length == 0) {
												$(this).html(DEFAULT_RANKING_FIELD);
											} else {
												$(this).empty();
												$(this).append(current_item);
											}
										}
								});
						});
				
				$(this).empty();
				$(this).append(mouse_item);
			}
		}
	});
	$(".rank .block").dblclick(function () {
		var url = $(this).find(".img").attr("value");
		var nameTag = $(this).find(".meta").html();
		
		if (url) {
			var imgTag = "<img src='" + url + "' alt='" + name + "' />";
			$("#screener").remove();
			$("<div id='screener' onclick='$(this).html(\"\"); $(this).remove();'><div class='sidead leftad'></div><div class='screenshot'>" + imgTag + "<div class='screenname'>" + nameTag + "</div></div><div class='sidead rightad'></div><div class='screenbg'/></div>").appendTo("body");
			$("#screener .screenbg").css("z-index", "99").css("opacity", "0.75").css("background-color", "#000000");
			$("#screener").css("overflow", "auto");
			$("#screener .screenshot").css("z-index", "100").css("position", "relative").css("overflow", "hidden").css("display", "inline");
			$("#screener .sidead").css("z-index", "100").css("position", "relative").css("overflow", "hidden").css("display", "inline");
			
			var imgPreloader = new Image();
			imgPreloader.onload = function() {
				var margin = 4;
				var nameheight = 20;
				var w = window.innerWidth || self.innerWidth || (document.documentElement && document.documentElement.clientWidth) || document.body.clientWidth;
				var h = window.innerHeight || self.innerHeight || (document.documentElement && document.documentElement.clientHeight) || document.body.clientHeight;
				var imageWidth = (imgPreloader.width < w?(w - imgPreloader.width) / 2:0);
				var imageHeight = (imgPreloader.height < h?(h - imgPreloader.height) / 2:0);
				var bestwidthpx = Math.max(w, imgPreloader.width + margin * 2);
				var bestheightpx = Math.max(h, imgPreloader.height + margin * 2 + nameheight + margin * 2);
				var adwidthpx = '160';
				var adheightpx = '600';
				
				fixPositionFixed(fixWidthHeight($("#screener"), bestwidthpx, bestheightpx), true);
				fixPositionFixed(fixWidthHeight($("#screener .screenbg"), bestwidthpx , bestheightpx), false);
				$("#screener .screenshot").css("margin", imageHeight + "px" + " 0px 0px " + imageWidth + "px").css("display", "block");
				$("#screener .screenname").css("height", nameheight + "px").css("background-color", "#FFFFFF").css("padding", margin + "px").css("font-weight", "bold").css("width", imgPreloader.width + "px").css("text-align", "center");
				$("#screener .screenshot img").css("border", margin + "px solid #FFFFFF");
				
				fixPositionFixed($("#screener .sidead").css("width", adwidthpx + "px").css("height", adheightpx + "px").css("float", "left"), false);
				$("#screener .leftad").css("top", imageHeight + "px").css("left", (imageWidth - adwidthpx - margin) + "px").css("text-align", "right");
				$("#screener .rightad").css("top", imageHeight + "px").css("left", (imageWidth + (imgPreloader.width + margin * 2) + margin) + "px").css("text-align", "left");
				
				function fixWidthHeight(obj, w, h) {
					if ($.browser.msie && parseInt(jQuery.browser.version) < 7) {
						return $(obj).css("width", w + "px").css("height", h + "px");
					} else {
						return $(obj).css("width", "100%").css("height", "100%");
					}
				}
				function fixPositionFixed(obj, addClass) {
					if ($.browser.msie && parseInt(jQuery.browser.version) < 7) {
						if (addClass) {
							return $(obj).css("position", "absolute").addClass("positionfixed");
						} else {
							return $(obj).css("position", "absolute").css("top", "0");
						}
					} else {
						return $(obj).css("position", "fixed").css("top", "0");
					}
				}
				
				imgPreloader.onload=function(){};	//	clear onLoad, as IE will flip out w/animated gifs
				return false;
			};
			
			imgPreloader.src = url;
			
			var adcreative = $(this).closest(".rank").find(".adcreative");
			if ($(adcreative)) {
				$("#screener .sidead").html($(adcreative).html());
			}
        }
	});
	$(".rank_vote_button").click(function() { $(this).parent().children('.drop').html(DEFAULT_RANKING_FIELD); });
});


/** Rank AJAX **/
(function($) {
	// constructor
	function Rank(root, conf) {	
		// Private fields ------------------------------------------------------------------
		var RANK_ACTIVATED = "rank_activated";
		var _root = $(root),
			_domElement = _root[0],
			_self = this,
			_userHasVoted,
			_resultContainer = _root.find('.rank-result-container'),
			_answerElem = _root.find('.rank-answers'),
			_opts = {
				debug: false
			}
			;
			$.extend(_opts, conf);
		
		// Private methods -----------------------------------------------------------------
		function debug(str)
		{
			if (_opts.debug == true) {
				try {
					console.log(str);
				} 
				catch (e) {
					alert("debug: " + str);
				}
			}
		};
		
		// generic binding function
		
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
			$(_self).bind(name, fn);
		};
		
		function showRankAnswers()
		{
			_resultContainer.hide();
			_answerElem.show();
		};
		
		function showRankResults(submitAnswer){
			backendsumbit(new Array());
			
			return false;
		};
		
		function enableButton(){
			$(".rankVoteButton", _root).removeClass("buttonInactive");
			$(".rankVoteButton", _root).click(function(){
					var must_filled_bit = (1<<0 | 1<<1 | 1<<2);
					var check_filled_bit = 0x0;
					var response_data = new Array();
					var data_string = new Array();
					$(this).closest(".rank-answers").each(
							function (i) { 
									$(this).find(".drop").each( function (i) {
											response_data[i] = (isNaN($(this).children(".label").attr("id"))?0:$(this).children(".label").attr("id"));
											if (response_data[i] > 0) {
												check_filled_bit |= 1<<i;
											}
									});
							});
							
					if ((check_filled_bit & must_filled_bit) == must_filled_bit) {
						data_string.push("response_id=" + response_data.join(","));
						backendsumbit(data_string);
					} else {
						// update poll-results
						_resultContainer.html("Please fill in the Top 3");
						_resultContainer.fadeIn();
					}
					
					return false;
			});
			
			$(".rankClearButton", _root).click(function() { 
					$(this).closest(".rank-answers").each(
							function (i) { 
									$(this).find(".drop").each( function (i) {
											$(this).html(DEFAULT_RANKING_FIELD);
									});
							});
					return false;
			});
		};
		
		function backendsumbit(data_string) {
            $.ajax({
				url: "/scrum/rank_vote.php",
				type: "POST",
				cache: false,
				data: "rank_id=" + _opts.rank_id + "&" + data_string.join("&") + "&rand=" + Math.floor(Math.random()*10000000),
                success: function(html) {
					// update poll-results
					_resultContainer.html(html);
					_resultContainer.fadeIn();
					_userHasVoted = getVoteStatus();
					debug("aft _userHasVoted: " + _userHasVoted);
					if (_userHasVoted == false) showAnswersLink();
                }
            });
		}
		
		function getVoteStatus(){
			var ranks = $.cookie('ranks');
			if (ranks) {
				$(ranks.split('|')).each(function(i) {
					if ($(this) == _opts.rank_id) {
						return true;
					}
				});
			}
			return false;
		};
		
		function init()
		{
			if (!$(_root).hasClass(RANK_ACTIVATED) && ('rank_' + _opts.rank_id) == $(_root).attr("id")) {
				$(_root).addClass(RANK_ACTIVATED);
				// see if user has already voted
				_userHasVoted = getVoteStatus();
				
				debug("_userHasVoted: " + _userHasVoted);
				
				if (_userHasVoted == true) {
					showRankResults();
				} else {
					enableButton();
					showRankAnswers();
				}
			}
		};
		
		// Initialization ------------------------------------------------------------------
		init();
	};
	
	// jQuery plugin implementation
	$.fn.rank = function(conf)
	{
		var opts = { };
		$.extend(opts, conf);
		
		this.each(function()
		{
			var $instance = new Rank($(this), opts);
		});
		
		return this;
	};
})(jQuery);
/**
 * @preserve Galleria v 1.2.2 2011-02-25
 * http://galleria.aino.se
 *
 * Copyright (c) 2011, Aino
 * Licensed under the MIT license.
 */

/*global jQuery, navigator, Galleria, Image */

(function( $ ) {

// some references
var undef,
    window = this,
    doc    = window.document,
    $doc   = $( doc ),

// internal constants
    DEBUG = false,
    NAV   = navigator.userAgent.toLowerCase(),
    HASH  = window.location.hash.replace(/#\//, ''),
    CLICK = function() {
        // use this to make touch devices snappier
        return Galleria.TOUCH ? 'touchstart' : 'click';
    },
    IE    = (function() {

        var v = 3,
            div = doc.createElement( 'div' ),
            all = div.getElementsByTagName( 'i' );

        do {
            div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->';
        } while ( all[0] );

        return v > 4 ? v : undef;

    }() ),
    DOM   = function() {
        return {
            html:  doc.documentElement,
            body:  doc.body,
            head:  doc.getElementsByTagName('head')[0],
            title: doc.title
        };
    },

    // list of Galleria events
    _eventlist = 'data ready thumbnail loadstart loadfinish image play pause progress ' + 
              'fullscreen_enter fullscreen_exit idle_enter idle_exit rescale ' +
              'lightbox_open lightbox_close lightbox_image',

    _events = (function() {

        var evs = [];

        $.each( _eventlist.split(' '), function( i, ev ) {
            evs.push( ev );

            // legacy events
            if ( /_/.test( ev ) ) {
                evs.push( ev.replace( /_/g, '' ) );
            }
        });

        return evs;

    }()),

    // legacy options
    // allows the old my_setting syntax and converts it to camel case

    _legacyOptions = function( options ) {

        var n;

        if ( typeof options !== 'object' ) {

            // return whatever it was...
            return options;
        }

        $.each( options, function( key, value ) {
            if ( /^[a-z]+_/.test( key ) ) {
                n = '';
                $.each( key.split('_'), function( i, k ) {
                    n += i > 0 ? k.substr( 0, 1 ).toUpperCase() + k.substr( 1 ) : k;
                });
                options[ n ] = value;
                delete options[ key ];
            }
        });

        return options;
    },

    _patchEvent = function( type ) {

        // allow 'image' instead of Galleria.IMAGE
        if ( $.inArray( type, _events ) > -1 ) {
            return Galleria[ type.toUpperCase() ];
        }

        return type;
    },

    // the internal timeouts object
    // provides helper methods for controlling timeouts
    _timeouts = {

        trunk: {},

        add: function( id, fn, delay, loop ) {
            loop = loop || false;
            this.clear( id );
            if ( loop ) {
                var old = fn;
                fn = function() {
                    old();
                    _timeouts.add( id, fn, delay );
                };
            }
            this.trunk[ id ] = window.setTimeout( fn, delay );
        },

        clear: function( id ) {

            var del = function( i ) {
                window.clearTimeout( this.trunk[ i ] );
                delete this.trunk[ i ];
            }, i;

            if ( !!id && id in this.trunk ) {
                del.call( _timeouts, id );

            } else if ( typeof id === 'undefined' ) {
                for ( i in this.trunk ) {
                    if ( this.trunk.hasOwnProperty( i ) ) {
                        del.call( _timeouts, i );
                    }
                }
            }
        }
    },

    // the internal gallery holder
    _galleries = [],

    // the Utils singleton
    Utils = (function() {

        return {

            array : function( obj ) {
                return Array.prototype.slice.call(obj);
            },

            create : function( className, nodeName ) {
                nodeName = nodeName || 'div';
                var elem = doc.createElement( nodeName );
                elem.className = className;
                return elem;
            },

            forceStyles : function( elem, styles ) {
                elem = $(elem);
                if ( elem.attr( 'style' ) ) {
                    elem.data( 'styles', elem.attr( 'style' ) ).removeAttr( 'style' );
                }
                elem.css( styles );
            },

            revertStyles : function() {
                $.each( Utils.array( arguments ), function( i, elem ) {

                    elem = $( elem ).removeAttr( 'style' );

                    if ( elem.data( 'styles' ) ) {
                        elem.attr( 'style', elem.data('styles') ).data( 'styles', null );
                    }
                });
            },

            moveOut : function( elem ) {
                Utils.forceStyles( elem, {
                    position: 'absolute',
                    left: -10000
                });
            },

            moveIn : function() {
                Utils.revertStyles.apply( Utils, Utils.array( arguments ) );
            },

            hide : function( elem, speed, callback ) {
                elem = $(elem);

                // save the value if not exist
                if (! elem.data('opacity') ) {
                    elem.data('opacity', elem.css('opacity') );
                }

                // always hide
                var style = { opacity: 0 };

                if (speed) {
                    elem.stop().animate( style, speed, callback );
                } else {
                    elem.css( style );
                }
            },

            show : function( elem, speed, callback ) {
                elem = $(elem);

                // bring back saved opacity
                var saved = parseFloat( elem.data('opacity') ) || 1,
                    style = { opacity: saved };

                // reset save if opacity === 1
                if (saved === 1) {
                    elem.data('opacity', null);
                }

                // animate or toggle
                if (speed) {
                    elem.stop().animate( style, speed, callback );
                } else {
                    elem.css( style );
                }
            },

            addTimer : function() {
                _timeouts.add.apply( _timeouts, Utils.array( arguments ) );
                return this;
            },

            clearTimer : function() {
                _timeouts.clear.apply( _timeouts, Utils.array( arguments ) );
                return this;
            },

            wait : function(options) {
                options = $.extend({
                    until : function() { return false; },
                    success : function() {},
                    error : function() { Galleria.raise('Could not complete wait function.'); },
                    timeout: 3000
                }, options);

                var start = Utils.timestamp(),
                    elapsed,
                    now,
                    fn = function() {
                        now = Utils.timestamp();
                        elapsed = now - start;
                        if ( options.until( elapsed ) ) {
                            options.success();
                            return false;
                        }

                        if (now >= start + options.timeout) {
                            options.error();
                            return false;
                        }
                        window.setTimeout(fn, 2);
                    };

                window.setTimeout(fn, 2);
            },

            toggleQuality : function( img, force ) {

                if ( ( IE !== 7 && IE !== 8 ) || !img ) {
                    return;
                }

                if ( typeof force === 'undefined' ) {
                    force = img.style.msInterpolationMode === 'nearest-neighbor';
                }

                img.style.msInterpolationMode = force ? 'bicubic' : 'nearest-neighbor';
            },

            insertStyleTag : function( styles ) {
                var style = doc.createElement( 'style' );
                DOM().head.appendChild( style );

                if ( style.styleSheet ) { // IE
                    style.styleSheet.cssText = styles;
                } else {
                    var cssText = doc.createTextNode( styles );
                    style.appendChild( cssText );
                }
            },

            // a loadscript method that works for local scripts
            loadScript: function( url, callback ) {
                var done = false,
                    script = $('<scr'+'ipt>').attr({
                        src: url,
                        async: true
                    }).get(0);

               // Attach handlers for all browsers
               script.onload = script.onreadystatechange = function() {
                   if ( !done && (!this.readyState ||
                       this.readyState === 'loaded' || this.readyState === 'complete') ) {

                       done = true;

                       // Handle memory leak in IE
                       script.onload = script.onreadystatechange = null;

                       if (typeof callback === 'function') {
                           callback.call( this, this );
                       }
                   }
               };

               DOM().head.appendChild( script );
            },

            // parse anything into a number
            parseValue: function( val ) {
                if (typeof val === 'number') {
                    return val;
                } else if (typeof val === 'string') {
                    var arr = val.match(/\-?\d/g);
                    return arr && arr.constructor === Array ? parseInt( arr.join(''), 10 ) : 0;
                } else {
                    return 0;
                }
            },

            // timestamp abstraction
            timestamp: function() {
                return new Date().getTime();
            },

            // this is pretty crap, but works for now
            // it will add a callback, but it can't guarantee that the styles can be fetched
            // using getComputedStyle further checking needed, possibly a dummy element
            loadCSS : function( href, id, callback ) {

                var link,
                    ready = false,
                    length;

                // look for manual css
                $('link[rel=stylesheet]').each(function() {
                    if ( new RegExp( href ).test( this.href ) ) {
                        link = this;
                        return false;
                    }
                });

                if ( typeof id === 'function' ) {
                    callback = id;
                    id = undef;
                }

                callback = callback || function() {}; // dirty

                // if already present, return
                if ( link ) {
                    callback.call( link, link );
                    return link;
                }

                // save the length of stylesheets to check against
                length = doc.styleSheets.length;

                // add timestamp if DEBUG is true
                if ( DEBUG ) {
                    href += '?' + Utils.timestamp();
                }

                // check for existing id
                if( $('#'+id).length ) {
                    $('#'+id).attr('href', href);
                    length--;
                    ready = true;
                } else {
                    link = $( '<link>' ).attr({
                        rel: 'stylesheet',
                        href: href,
                        id: id
                    }).get(0);

                    window.setTimeout(function() {
                        var styles = $('link[rel="stylesheet"], style');
                        if ( styles.length ) {
                            styles.get(0).parentNode.insertBefore( link, styles[0] );
                        } else {
                            DOM().head.appendChild( link );
                        }

                        if ( IE ) {
                            link.attachEvent( 'onreadystatechange', function(e) {
                                if( link.readyState === 'complete' ) {
                                    ready = true;
                                }
                            });
                        } else {
                            // what to do here? returning for now.
                            ready = true;
                        }
                    }, 10);
                }

                if ( typeof callback === 'function' ) {

                    Utils.wait({
                        until: function() {
                            return ready && doc.styleSheets.length > length;
                        },
                        success: function() {
                            Utils.addTimer( 'css', function() {
                                callback.call( link, link );
                            }, 100);
                        },
                        error: function() {
                            Galleria.raise( 'Theme CSS could not load' );
                        },
                        timeout: 1000
                    });
                }
                return link;
            }
        };
    }()),

    // the transitions holder
    _transitions = {

        fade: function(params, complete) {
            $(params.next).css('opacity', 0).show().animate({
                opacity: 1
            }, params.speed, complete);

            if (params.prev) {
                $(params.prev).css('opacity', 1).show().animate({
                    opacity: 0
                }, params.speed);
            }
        },

        flash: function(params, complete) {
            $(params.next).css('opacity', 0);
            if (params.prev) {
                $(params.prev).animate({
                    opacity: 0
                }, (params.speed / 2), function() {
                    $(params.next).animate({
                        opacity: 1
                    }, params.speed, complete);
                });
            } else {
                $(params.next).animate({
                    opacity: 1
                }, params.speed, complete);
            }
        },

        pulse: function(params, complete) {
            if (params.prev) {
                $(params.prev).hide();
            }
            $(params.next).css('opacity', 0).animate({
                opacity:1
            }, params.speed, complete);
        },

        slide: function(params, complete) {
            var image  = $(params.next).parent(),
                images = this.$('images'), // ??
                width  = this._stageWidth,
                easing = this.getOptions( 'easing' );

            image.css({
                left: width * ( params.rewind ? -1 : 1 )
            });
            images.animate({
                left: width * ( params.rewind ? 1 : -1 )
            }, {
                duration: params.speed,
                queue: false,
                easing: easing,
                complete: function() {
                    images.css('left', 0);
                    image.css('left', 0);
                    complete();
                }
            });
        },

        fadeslide: function(params, complete) {

            var x = 0,
                easing = this.getOptions('easing'),
                distance = this.getStageWidth();

            if (params.prev) {
                x = Utils.parseValue( $(params.prev).css('left') );
                $(params.prev).css({
                    opacity: 1,
                    left: x
                }).animate({
                    opacity: 0,
                    left: x + ( distance * ( params.rewind ? 1 : -1 ) )
                },{
                    duration: params.speed,
                    queue: false,
                    easing: easing
                });
            }

            x = Utils.parseValue( $(params.next).css('left') );

            $(params.next).css({
                left: x + ( distance * ( params.rewind ? -1 : 1 ) ),
                opacity: 0
            }).animate({
                opacity: 1,
                left: x
            }, {
                duration: params.speed,
                complete: complete,
                queue: false,
                easing: easing
            });
        }
    };

/**
    The main Galleria class

    @class
    @constructor

    @example var gallery = new Galleria();

    @author http://aino.se

    @requires jQuery

*/

var Galleria = function() {

    var self = this;

    // the theme used
    this._theme = undef;

    // internal options
    this._options = {};

    // flag for controlling play/pause
    this._playing = false;

    // internal interval for slideshow
    this._playtime = 5000;

    // internal variable for the currently active image
    this._active = null;

    // the internal queue, arrayified
    this._queue = { length: 0 };

    // the internal data array
    this._data = [];

    // the internal dom collection
    this._dom = {};

    // the internal thumbnails array
    this._thumbnails = [];

    // internal init flag
    this._initialized = false;

    // global stagewidth/height
    this._stageWidth = 0;
    this._stageHeight = 0;

    // target holder
    this._target = undef;

    // instance id
    this._id = Math.random();

    // add some elements
    var divs =  'container stage images image-nav image-nav-left image-nav-right ' +
                'info info-text info-title info-description info-author ' +
                'thumbnails thumbnails-list thumbnails-container thumb-nav-left thumb-nav-right ' +
                'loader counter tooltip',
        spans = 'current total';

    $.each( divs.split(' '), function( i, elemId ) {
        self._dom[ elemId ] = Utils.create( 'galleria-' + elemId );
    });

    $.each( spans.split(' '), function( i, elemId ) {
        self._dom[ elemId ] = Utils.create( 'galleria-' + elemId, 'span' );
    });

    // the internal keyboard object
    // keeps reference of the keybinds and provides helper methods for binding keys
    var keyboard = this._keyboard = {

        keys : {
            'UP': 38,
            'DOWN': 40,
            'LEFT': 37,
            'RIGHT': 39,
            'RETURN': 13,
            'ESCAPE': 27,
            'BACKSPACE': 8,
            'SPACE': 32
        },

        map : {},

        bound: false,

        press: function(e) {
            var key = e.keyCode || e.which;
            if ( key in keyboard.map && typeof keyboard.map[key] === 'function' ) {
                keyboard.map[key].call(self, e);
            }
        },

        attach: function(map) {

            var key, up;

            for( key in map ) {
                if ( map.hasOwnProperty( key ) ) {
                    up = key.toUpperCase();
                    if ( up in keyboard.keys ) {
                        keyboard.map[ keyboard.keys[up] ] = map[key];
                    }
                }
            }
            if ( !keyboard.bound ) {
                keyboard.bound = true;
                $doc.bind('keydown', keyboard.press);
            }
        },

        detach: function() {
            keyboard.bound = false;
            $doc.unbind('keydown', keyboard.press);
        }
    };

    // internal controls for keeping track of active / inactive images
    var controls = this._controls = {

        0: undef,

        1: undef,

        active : 0,

        swap : function() {
            controls.active = controls.active ? 0 : 1;
        },

        getActive : function() {
            return controls[ controls.active ];
        },

        getNext : function() {
            return controls[ 1 - controls.active ];
        }
    };

    // internal carousel object
    var carousel = this._carousel = {

        // shortcuts
        next: self.$('thumb-nav-right'),
        prev: self.$('thumb-nav-left'),

        // cache the width
        width: 0,

        // track the current position
        current: 0,

        // cache max value
        max: 0,

        // save all hooks for each width in an array
        hooks: [],

        // update the carousel
        // you can run this method anytime, f.ex on window.resize
        update: function() {
            var w = 316,
                h = 315,
                hooks = [0];

            $.each( self._thumbnails, function( i, thumb ) {
                if ( thumb.ready ) {
                    //w += thumb.outerWidth || $( thumb.container ).outerWidth( true );
                    hooks[ i+1 ] = w;
                    //h = Math.max( h, thumb.outerHeight || $( thumb.container).outerHeight( true ) );
                }
            });

            self.$( 'thumbnails' ).css({
                width: w,
                height: h
            });

            carousel.max = w;
            carousel.hooks = hooks;
            carousel.width = self.$( 'thumbnails-list' ).width();
            carousel.setClasses();

            self.$( 'thumbnails-container' ).toggleClass( 'galleria-carousel', w > carousel.width );

            // todo: fix so the carousel moves to the left
        },

        bindControls: function() {

            var i;

            carousel.next.bind( CLICK(), function(e) {
                e.preventDefault();

                if ( self._options.carouselSteps === 'auto' ) {

                    for ( i = carousel.current; i < carousel.hooks.length; i++ ) {
                        if ( carousel.hooks[i] - carousel.hooks[ carousel.current ] > carousel.width ) {
                            carousel.set(i - 2);
                            break;
                        }
                    }

                } else {
                    carousel.set( carousel.current + self._options.carouselSteps);
                }
            });

            carousel.prev.bind( CLICK(), function(e) {
                e.preventDefault();

                if ( self._options.carouselSteps === 'auto' ) {

                    for ( i = carousel.current; i >= 0; i-- ) {
                        if ( carousel.hooks[ carousel.current ] - carousel.hooks[i] > carousel.width ) {
                            carousel.set( i + 2 );
                            break;
                        } else if ( i === 0 ) {
                            carousel.set( 0 );
                            break;
                        }
                    }
                } else {
                    carousel.set( carousel.current - self._options.carouselSteps );
                }
            });
        },

        // calculate and set positions
        set: function( i ) {
            i = Math.max( i, 0 );
            while ( carousel.hooks[i - 1] + carousel.width >= carousel.max && i >= 0 ) {
                i--;
            }
            carousel.current = i;
            carousel.animate();
        },

        // get the last position
        getLast: function(i) {
            return ( i || carousel.current ) - 1;
        },

        // follow the active image
        follow: function(i) {

            //don't follow if position fits
            if ( i === 0 || i === carousel.hooks.length - 2 ) {
                carousel.set( i );
                return;
            }

            // calculate last position
            var last = carousel.current;
            while( carousel.hooks[last] - carousel.hooks[ carousel.current ] <
                   carousel.width && last <= carousel.hooks.length ) {
                last ++;
            }

            // set position
            if ( i - 1 < carousel.current ) {
                carousel.set( i - 1 );
            } else if ( i + 2 > last) {
                carousel.set( i - last + carousel.current + 2 );
            }
        },

        // helper for setting disabled classes
        setClasses: function() {
            carousel.prev.toggleClass( 'disabled', !carousel.current );
            carousel.next.toggleClass( 'disabled', carousel.hooks[ carousel.current ] + carousel.width >= carousel.max );
        },

        // the animation method
        animate: function(to) {
            carousel.setClasses();
            var num = carousel.hooks[ carousel.current ] * -1;

            if ( isNaN( num ) ) {
                return;
            }

            self.$( 'thumbnails' ).animate({
                left: num
            },{
                duration: self._options.carouselSpeed,
                easing: self._options.easing,
                queue: false
            });
        }
    };

    // tooltip control
    // added in 1.2
    var tooltip = this._tooltip = {

        initialized : false,

        open: false,

        init: function() {

            tooltip.initialized = true;

            var css = '.galleria-tooltip{padding:3px 8px;max-width:50%;background:#ffe;color:#000;z-index:3;position:absolute;font-size:11px;line-height:1.3' +
                      'opacity:0;box-shadow:0 0 2px rgba(0,0,0,.4);-moz-box-shadow:0 0 2px rgba(0,0,0,.4);-webkit-box-shadow:0 0 2px rgba(0,0,0,.4);}';

            Utils.insertStyleTag(css);

            self.$( 'tooltip' ).css('opacity', 0.8);
            Utils.hide( self.get('tooltip') );

        },

        // move handler
        move: function( e ) {
            var mouseX = self.getMousePosition(e).x,
                mouseY = self.getMousePosition(e).y,
                $elem = self.$( 'tooltip' ),
                x = mouseX,
                y = mouseY,
                height = $elem.outerHeight( true ) + 1,
                width = $elem.outerWidth( true ),
                limitY = height + 15;

            var maxX = self.$( 'container').width() - width - 2,
                maxY = self.$( 'container').height() - height - 2;

            if ( !isNaN(x) && !isNaN(y) ) {

                x += 10;
                y -= 30;

                x = Math.max( 0, Math.min( maxX, x ) );
                y = Math.max( 0, Math.min( maxY, y ) );

                if( mouseY < limitY ) {
                    y = limitY;
                }

                $elem.css({ left: x, top: y });
            }
        },

        // bind elements to the tooltip
        // you can bind multiple elementIDs using { elemID : function } or { elemID : string }
        // you can also bind single DOM elements using bind(elem, string)
        bind: function( elem, value ) {

            if (! tooltip.initialized ) {
                tooltip.init();
            }

            var hover = function( elem, value) {

                tooltip.define( elem, value );

                $( elem ).hover(function() {

                    Utils.clearTimer('switch_tooltip');
                    self.$('container').unbind( 'mousemove', tooltip.move ).bind( 'mousemove', tooltip.move ).trigger( 'mousemove' );
                    tooltip.show( elem );

                    Galleria.utils.addTimer( 'tooltip', function() {
                        self.$( 'tooltip' ).stop().show();
                        Utils.show( self.get( 'tooltip' ), 400 );
                        tooltip.open = true;

                    }, tooltip.open ? 0 : 500);

                }, function() {

                    self.$( 'container' ).unbind( 'mousemove', tooltip.move );
                    Utils.clearTimer( 'tooltip' );

                    self.$( 'tooltip' ).stop();

                    Utils.hide( self.get( 'tooltip' ), 200, function() {

                        self.$( 'tooltip' ).hide();

                        Utils.addTimer('switch_tooltip', function() {
                            tooltip.open = false;
                        }, 1000);
                    });
                });
            };

            if ( typeof value === 'string' ) {
                hover( ( elem in self._dom ? self.get( elem ) : elem ), value );
            } else {
                // asume elemID here
                $.each( elem, function( elemID, val ) {
                    hover( self.get(elemID), val );
                });
            }
        },

        show: function( elem ) {

            elem = $( elem in self._dom ? self.get(elem) : elem );

            var text = elem.data( 'tt' ),
                mouseup = function( e ) {

                    // attach a tiny settimeout to make sure the new tooltip is filled
                    window.setTimeout( (function( ev ) {
                        return function() {
                            tooltip.move( ev );
                        };
                    }( e )), 10);

                    elem.unbind( 'mouseup', mouseup );

                };

            text = typeof text === 'function' ? text() : text;

            if ( ! text ) {
                return;
            }

            self.$( 'tooltip' ).html( text.replace(/\s/, '&nbsp;') );

            // trigger mousemove on mouseup in case of click
            elem.bind( 'mouseup', mouseup );
        },

        define: function( elem, value ) {

            // we store functions, not strings
            if (typeof value !== 'function') {
                var s = value;
                value = function() {
                    return s;
                };
            }

            elem = $( elem in self._dom ? self.get(elem) : elem ).data('tt', value);

            tooltip.show( elem );

        }
    };

    // internal fullscreen control
    // added in 1.195
    // still kind of experimental
    var fullscreen = this._fullscreen = {
        scrolled: 0,
        active: false,
        enter: function(callback) {

            fullscreen.active = true;

            // hide the image until rescale is complete
            Utils.hide( self.getActiveImage() );

            self.$( 'container' ).addClass( 'fullscreen' );

            fullscreen.scrolled = $(window).scrollTop();

            // begin styleforce
            Utils.forceStyles(self.get('container'), {
                position: 'fixed',
                top: 0,
                left: 0,
                width: '100%',
                height: '100%',
                zIndex: 10000
            });

            var htmlbody = {
                height: '100%',
                overflow: 'hidden',
                margin:0,
                padding:0
            };

            Utils.forceStyles( DOM().html, htmlbody );
            Utils.forceStyles( DOM().body, htmlbody );

            // attach some keys
            self.attachKeyboard({
                escape: self.exitFullscreen,
                right: self.next,
                left: self.prev
            });

            // init the first rescale and attach callbacks
            self.rescale(function() {

                Utils.addTimer('fullscreen_enter', function() {
                    // show the image after 50 ms
                    Utils.show( self.getActiveImage() );

                    if (typeof callback === 'function') {
                        callback.call( self );
                    }

                }, 100);

                self.trigger( Galleria.FULLSCREEN_ENTER );
            });

            // bind the scaling to the resize event
            $(window).resize( function() {
                fullscreen.scale();
            } );
        },

        scale : function() {
            self.rescale();
        },

        exit: function(callback) {

            fullscreen.active = false;

            Utils.hide( self.getActiveImage() );

            self.$('container').removeClass( 'fullscreen' );

            // revert all styles
            Utils.revertStyles( self.get('container'), DOM().html, DOM().body );

            // scroll back
            window.scrollTo(0, fullscreen.scrolled);

            // detach all keyboard events (is this good?)
            self.detachKeyboard();

            self.rescale(function() {
                Utils.addTimer('fullscreen_exit', function() {

                    // show the image after 50 ms
                    Utils.show( self.getActiveImage() );

                    if ( typeof callback === 'function' ) {
                        callback.call( self );
                    }

                }, 50);

                self.trigger( Galleria.FULLSCREEN_EXIT );
            });

            $(window).unbind('resize', fullscreen.scale);
        }
    };

    // the internal idle object for controlling idle states
    var idle = this._idle = {

        trunk: [],

        bound: false,

        add: function(elem, to) {
            if (!elem) {
                return;
            }
            if (!idle.bound) {
                idle.addEvent();
            }
            elem = $(elem);

            var from = {},
                style;

            for ( style in to ) {
                if ( to.hasOwnProperty( style ) ) {
                    from[ style ] = elem.css( style );
                }
            }
            elem.data('idle', {
                from: from,
                to: to,
                complete: true,
                busy: false
            });
            idle.addTimer();
            idle.trunk.push(elem);
        },

        remove: function(elem) {

            elem = jQuery(elem);

            $.each(idle.trunk, function(i, el) {
                if ( el.length && !el.not(elem).length ) {
                    self._idle.show(elem);
                    self._idle.trunk.splice(i, 1);
                }
            });

            if (!idle.trunk.length) {
                idle.removeEvent();
                Utils.clearTimer('idle');
            }
        },

        addEvent : function() {
            idle.bound = true;
            self.$('container').bind('mousemove click', idle.showAll );
        },

        removeEvent : function() {
            idle.bound = false;
            self.$('container').unbind('mousemove click', idle.showAll );
        },

        addTimer : function() {
            Utils.addTimer('idle', function() {
                self._idle.hide();
            }, self._options.idleTime );
        },

        hide : function() {
            self.trigger( Galleria.IDLE_ENTER );

            $.each( idle.trunk, function(i, elem) {

                var data = elem.data('idle');

                if (! data) {
                    return;
                }

                elem.data('idle').complete = false;

                elem.stop().animate(data.to, {
                    duration: self._options.idleSpeed,
                    queue: false,
                    easing: 'swing'
                });
            });
        },

        showAll : function() {

            Utils.clearTimer('idle');

            $.each(self._idle.trunk, function( i, elem ) {
                self._idle.show( elem );
            });
        },

        show: function(elem) {

            var data = elem.data('idle');

            if (!data.busy && !data.complete) {

                data.busy = true;

                self.trigger( Galleria.IDLE_EXIT );

                Utils.clearTimer( 'idle' );

                elem.stop().animate(data.from, {
                    duration: self._options.idleSpeed/2,
                    queue: false,
                    easing: 'swing',
                    complete: function() {
                        $(this).data('idle').busy = false;
                        $(this).data('idle').complete = true;
                    }
                });
            }
            idle.addTimer();
        }
    };

    // internal lightbox object
    // creates a predesigned lightbox for simple popups of images in galleria
    var lightbox = this._lightbox = {

        width : 0,

        height : 0,

        initialized : false,

        active : null,

        image : null,

        elems : {},

        init : function() {

            // trigger the event
            self.trigger( Galleria.LIGHTBOX_OPEN );

            if ( lightbox.initialized ) {
                return;
            }
            lightbox.initialized = true;

            // create some elements to work with
            var elems = 'overlay box content shadow title info close prevholder prev nextholder next counter image',
                el = {},
                op = self._options,
                css = '',
                abs = 'position:absolute;',
                prefix = 'lightbox-',
                cssMap = {
                    overlay:    'position:fixed;display:none;opacity:'+op.overlayOpacity+';filter:alpha(opacity='+(op.overlayOpacity*100)+
                                ');top:0;left:0;width:100%;height:100%;background:'+op.overlayBackground+';z-index:99990',
                    box:        'position:fixed;display:none;width:400px;height:400px;top:50%;left:50%;margin-top:-200px;margin-left:-200px;z-index:99991',
                    shadow:     abs+'background:#000;width:100%;height:100%;',
                    content:    abs+'background-color:#fff;top:10px;left:10px;right:10px;bottom:10px;overflow:hidden',
                    info:       abs+'bottom:10px;left:10px;right:10px;color:#444;font:11px/13px arial,sans-serif;height:13px',
                    close:      abs+'top:10px;right:10px;height:20px;width:20px;background:#fff;text-align:center;cursor:pointer;color:#444;font:16px/22px arial,sans-serif;z-index:99999',
                    image:      abs+'top:10px;left:10px;right:10px;bottom:30px;overflow:hidden;display:block;',
                    prevholder: abs+'width:50%;top:0;bottom:40px;cursor:pointer;',
                    nextholder: abs+'width:50%;top:0;bottom:40px;right:-1px;cursor:pointer;',
                    prev:       abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;left:20px;display:none;line-height:40px;text-align:center;color:#000',
                    next:       abs+'top:50%;margin-top:-20px;height:40px;width:30px;background:#fff;right:20px;left:auto;display:none;line-height:40px;text-align:center;color:#000',
                    title:      'float:left',
                    counter:    'float:right;margin-left:8px;'
                },
                hover = function(elem) {
                    return elem.hover(
                        function() { $(this).css( 'color', '#bbb' ); },
                        function() { $(this).css( 'color', '#444' ); }
                    );
                },
                appends = {};

            // IE8 fix for IE's transparent background event "feature"
            if ( IE === 8 ) {
                cssMap.nextholder += 'background:#000;filter:alpha(opacity=0);';
                cssMap.prevholder += 'background:#000;filter:alpha(opacity=0);';
            }

            // create and insert CSS
            $.each(cssMap, function( key, value ) {
                css += '.galleria-'+prefix+key+'{'+value+'}';
            });

            Utils.insertStyleTag( css );

            // create the elements
            $.each(elems.split(' '), function( i, elemId ) {
                self.addElement( 'lightbox-' + elemId );
                el[ elemId ] = lightbox.elems[ elemId ] = self.get( 'lightbox-' + elemId );
            });

            // initiate the image
            lightbox.image = new Galleria.Picture();

            // append the elements
            $.each({
                    box: 'shadow content close prevholder nextholder',
                    info: 'title counter',
                    content: 'info image',
                    prevholder: 'prev',
                    nextholder: 'next'
                }, function( key, val ) {
                    var arr = [];
                    $.each( val.split(' '), function( i, prop ) {
                        arr.push( prefix + prop );
                    });
                    appends[ prefix+key ] = arr;
            });

            self.append( appends );

            $( el.image ).append( lightbox.image.container );

            $( DOM().body ).append( el.overlay, el.box );

            // add the prev/next nav and bind some controls

            hover( $( el.close ).bind( CLICK(), lightbox.hide ).html('&#215;') );

            $.each( ['Prev','Next'], function(i, dir) {

                var $d = $( el[ dir.toLowerCase() ] ).html( /v/.test( dir ) ? '&#8249;&nbsp;' : '&nbsp;&#8250;' ),
                    $e = $( el[ dir.toLowerCase()+'holder'] );

                $e.bind( CLICK(), function() {
                    lightbox[ 'show' + dir ]();
                });

                // IE7 will simply show the nav
                if ( IE < 8 ) {
                    $d.show();
                    return;
                }

                $e.hover( function() {
                    $d.show();
                }, function(e) {
                    $d.stop().fadeOut( 200 );
                });

            });
            $( el.overlay ).bind( CLICK(), lightbox.hide );

        },

        rescale: function(event) {

            // calculate
             var width = Math.min( $(window).width()-40, lightbox.width ),
                height = Math.min( $(window).height()-60, lightbox.height ),
                ratio = Math.min( width / lightbox.width, height / lightbox.height ),
                destWidth = ( lightbox.width * ratio ) + 40,
                destHeight = ( lightbox.height * ratio ) + 60,
                to = {
                    width: destWidth,
                    height: destHeight,
                    marginTop: Math.ceil( destHeight / 2 ) *- 1,
                    marginLeft: Math.ceil( destWidth / 2 ) *- 1
                };

            // if rescale event, don't animate
            if ( event ) {
                $( lightbox.elems.box ).css( to );
            } else {
                $( lightbox.elems.box ).animate(
                    to,
                    self._options.lightboxTransitionSpeed,
                    self._options.easing,
                    function() {
                        var image = lightbox.image,
                            speed = self._options.lightboxFadeSpeed;

                        self.trigger({
                            type: Galleria.LIGHTBOX_IMAGE,
                            imageTarget: image.image
                        });

                        image.show();
                        Utils.show( image.image, speed );
                        Utils.show( lightbox.elems.info, speed );
                    }
                );
            }
        },

        hide: function() {

            // remove the image
            lightbox.image.image = null;

            $(window).unbind('resize', lightbox.rescale);

            $( lightbox.elems.box ).hide();

            Utils.hide( lightbox.elems.info );

            Utils.hide( lightbox.elems.overlay, 200, function() {
                $( this ).hide().css( 'opacity', self._options.overlayOpacity );
                self.trigger( Galleria.LIGHTBOX_CLOSE );
            });
        },

        showNext: function() {
            lightbox.show( self.getNext( lightbox.active ) );
        },

        showPrev: function() {
            lightbox.show( self.getPrev( lightbox.active ) );
        },

        show: function(index) {

            lightbox.active = index = typeof index === 'number' ? index : self.getIndex();

            if ( !lightbox.initialized ) {
                lightbox.init();
            }

            $(window).unbind('resize', lightbox.rescale );

            var data = self.getData(index),
                total = self.getDataLength();

            Utils.hide( lightbox.elems.info );

            lightbox.image.load( data.image, function( image ) {

                lightbox.width = image.original.width;
                lightbox.height = image.original.height;

                $( image.image ).css({
                    width: '100.5%',
                    height: '100.5%',
                    top: 0,
                    zIndex: 99998,
                    opacity: 0
                });

                lightbox.elems.title.innerHTML = data.title;
                lightbox.elems.counter.innerHTML = (index + 1) + ' / ' + total;
                $(window).resize( lightbox.rescale );
                lightbox.rescale();
            });

            $( lightbox.elems.overlay ).show();
            $( lightbox.elems.box ).show();
        }
    };

    return this;
};

// end Galleria constructor

Galleria.prototype = {

    // bring back the constructor reference

    constructor: Galleria,

    /**
        Use this function to initialize the gallery and start loading.
        Should only be called once per instance.

        @param {HTMLElement} target The target element
        @param {Object} options The gallery options

        @returns Instance
    */

    init: function( target, options ) {

        var self = this;

        options = _legacyOptions( options );

        // save the instance
        _galleries.push( this );

        // save the original ingredients
        this._original = {
            target: target,
            options: options,
            data: null
        };

        // save the target here
        this._target = this._dom.target = target.nodeName ? target : $( target ).get(0);

        // raise error if no target is detected
        if ( !this._target ) {
             Galleria.raise('Target not found.');
             return;
        }

        // apply options
        this._options = {
            name: "",
            ATHLETE_DETAIL: {}, //passed in by Athlete instance when galleria is created
            autoplay: false,
            carousel: true,
            carouselFollow: true,
            carouselSpeed: 400,
            carouselSteps: 'auto',
            clicknext: false,
            dataConfig : function( elem ) { trace('elem: ' + elem); return {}; },
            dataSelector: 'img',
           // dataSource: {}, //this._target,
            debug: undef,
            easing: 'galleria',
            extend: function(options) {},
            height: 'auto',
            idleTime: 3000,
            idleSpeed: 200,
            imageCrop: false,
            imageMargin: 0,
            imagePan: false,
            imagePanSmoothness: 12,
            imagePosition: '50%',
            keepSource: false,
            lightboxFadeSpeed: 200,
            lightboxTransition_speed: 500,
            linkSourceTmages: true,
            maxScaleRatio: undef,
            minScaleRatio: undef,
            overlayOpacity: 0.85,
            overlayBackground: '#0b0b0b',
            pauseOnInteraction: true,
            popupLinks: false,
            preload: 2,
            queue: true,
            show: 0,
            showInfo: true,
            showCounter: true,
            showImagenav: true,
            thumbCrop: true,
            thumbEventType: CLICK(),
            thumbFit: true,
            thumbMargin: 0,
            thumbQuality: 'auto',
            thumbnails: true,
            transition: 'fade',
            transitionInitial: undef,
            transitionSpeed: 400,
            width: 'auto'
        };

        // apply debug
        if ( options && options.debug === true ) {
            DEBUG = true;
        }

        // hide all content
        $( this._target ).children().hide();

        // now we just have to wait for the theme...
        // is 5 seconds enough?
        if ( typeof Galleria.theme === 'object' ) {
            this._init();
        } else {
            Utils.wait({
                until: function() {
                    return typeof Galleria.theme === 'object';
                },
                success: function() {
                    self._init.call( self );
                },
                error: function() {
                    Galleria.raise( 'No theme found.', true );
                },
                timeout: 5000
            });
        }
    },

    // this method should only be called once per instance
    // for manipulation of data, use the .load method

    _init: function() {
        var self = this;
        if ( this._initialized ) {
            Galleria.raise( 'Init failed: Gallery instance already initialized.' );
            return this;
        }

        this._initialized = true;

        if ( !Galleria.theme ) {
            Galleria.raise( 'Init failed: No theme found.' );
            return this;
        }

        // merge the theme & caller options
        $.extend( true, this._options, Galleria.theme.defaults, this._original.options );
		
        // bind the gallery to run when data is ready
        this.bind( Galleria.DATA, function() {

            // save the new data
            this._original.data = this._data;

            // lets show the counter here
            this.get('total').innerHTML = this.getDataLength();

            // cache the container
            var $container = this.$( 'container' );

            // the gallery is ready, let's just wait for the css
            var num = { width: 0, height: 0 };
            var testElem =  Utils.create('galleria-image');

            // check container and thumbnail height
            Utils.wait({
                until: function() {

                    // keep trying to get the value
                    $.each(['width', 'height'], function( i, m ) {

                        // first check if options is set

                        if ( self._options[ m ] && typeof self._options[ m ] === 'number' ) {
                            num[ m ] = self._options[ m ];
                        } else {

                            // else extract the measures from different sources and grab the highest value
                            num[m] = Math.max(
                                Utils.parseValue( $container.css( m ) ),         // 1. the container css
                                Utils.parseValue( self.$( 'target' ).css( m ) ), // 2. the target css
                                $container[ m ](),                               // 3. the container jQuery method
                                self.$( 'target' )[ m ]()                        // 4. the container jQuery method
                            );
                        }
                    });

                    var thumbHeight = function() {
                        return true;
                    };

                    // make sure thumbnails have a height as well
                    if ( self._options.thumbnails ) {
                        self.$('thumbnails').append( testElem );
                        thumbHeight = function() {
                            return !!$( testElem ).height();
                        };
                    }
                    return thumbHeight() && num.width && num.height > 10;

                },
                success: function() {

                    // remove the testElem
                    $( testElem ).remove();

                    // apply the new measures
                    $container.width( num.width );
                    $container.height( num.height );

                    // for some strange reason, webkit needs a single setTimeout to play ball
                    if ( Galleria.WEBKIT ) {
                        window.setTimeout( function() {
                            self._run();
                        }, 1);
                    } else {
                        
                        self._run();
                    }
                },
                error: function() {
                    // Height was probably not set, raise a hard error
                    Galleria.raise('Width & Height not found.', true);
                },
                timeout: 2000
            });
        });

        // postrun some stuff after the gallery is ready
        // make sure it only runs once
        var one = false;

        this.bind( Galleria.READY, (function(one) {

            return function() {

                // show counter
                Utils.show( this.get('counter') );

                // bind carousel nav
                if ( this._options.carousel ) {
                    this._carousel.bindControls();
                }

                // start autoplay
                if ( this._options.autoplay ) {

                    this.pause();

                    if ( typeof this._options.autoplay === 'number' ) {
                        this._playtime = this._options.autoplay;
                    }

                    this.trigger( Galleria.PLAY );
                    this._playing = true;
                }

                // if second load, just do the show and return
                if ( one ) {
                    if ( typeof this._options.show === 'number' ) {
                        this.show( this._options.show );
                    }
                    return;
                }

                one = true;

                // bind clicknext
                if ( this._options.clicknext ) {
                    $.each( this._data, function( i, data ) {
                        delete data.link;
                    });
                    this.$( 'stage' ).css({ cursor : 'pointer' }).bind( CLICK(), function(e) {
                        self.next();
                    });
                }

                // initialize the History plugin
                if ( Galleria.History ) {

                    // bind the show method
                    Galleria.History.change(function(e) {

                        // grab history ID
                        var val = parseInt( e.value.replace( /\//, '' ), 10 );

                        // if ID is NaN, the user pressed back from the first image
                        // return to previous address
                        if (isNaN(val)) {
                            window.history.go(-1);

                        // else show the image
                        } else {
                            self.show( val, undef, true );
                        }
                    });
                }

                // call the theme init method
                Galleria.theme.init.call( this, this._options );

                // call the extend option
                this._options.extend.call( this, this._options );

                // show the initial image
                // first test for permalinks in history
                if ( /^[0-9]{1,4}$/.test( HASH ) && Galleria.History ) {
                    this.show( HASH, undef, true );

                } else {
                    this.show( this._options.show );
                }
            };
        }( one )));

        // build the gallery frame
        this.append({
            'info-text' :
                ['info-title', 'info-description', 'info-author'],
            'info' :
                ['info-text'],
            'image-nav' :
                ['image-nav-right', 'image-nav-left'],
            'stage' :
                ['images', 'loader', 'counter', 'image-nav'],
            'thumbnails-list' :
                ['thumbnails'],
            'thumbnails-container' :
                ['thumb-nav-left', 'thumbnails-list', 'thumb-nav-right'],
            'container' :
                ['stage', 'thumbnails-container', 'info', 'tooltip']
        });

        Utils.hide( this.$( 'counter' ).append(
            this.get( 'current' ),
            ' / ',
            this.get( 'total' )
        ) );

        this.setCounter('&#8211;');
        
        Utils.hide( self.get('tooltip') );

        // add images to the controls
        $.each( new Array(2), function(i) {

            // create a new Picture instance
            var image = new Galleria.Picture();

            // apply some styles
            $( image.container ).css({
                position: 'absolute',
                top: 0,
                left: 0
            });

            // append the image
            self.$( 'images' ).append( image.container );

            // reload the controls
            self._controls[i] = image;

        });

        // some forced generic styling
        this.$( 'images' ).css({
            position: 'relative',
            top: 0,
            left: 0,
            width: '100%',
            height: '100%'
        });

        this.$( 'thumbnails, thumbnails-list' ).css({
            overflow: 'hidden',
            position: 'relative'
        });

        // bind image navigation arrows
        this.$( 'image-nav-right, image-nav-left' ).bind( CLICK(), function(e) {

            // tune the clicknext option
            if ( self._options.clicknext ) {
                e.stopPropagation();
            }

            // pause if options is set
            if ( self._options.pause_on_interaction ) {
                self.pause();
            }

            // navigate
            var fn = /right/.test( this.className ) ? 'next' : 'prev';
            self[ fn ]();

        });

        // hide controls if chosen to
        $.each( ['info','counter','image-nav'], function( i, el ) {
            if ( self._options[ 'show' + el.substr(0,1).toUpperCase() + el.substr(1).replace(/-/,'') ] === false ) {
                Utils.moveOut( self.get( el.toLowerCase() ) );
            }
        });

        // load up target content
        this.load();

        // now it's usually safe to remove the content
        // IE will never stop loading if we remove it, so let's keep it hidden for IE (it's usually fast enough anyway)
        if ( !this._options.keep_source && !IE ) {
            this._target.innerHTML = '';
        }

        // append the gallery frame
        this.$( 'target' ).append( this.get( 'container' ) );

        // parse the carousel on each thumb load
        if ( this._options.carousel ) {
            this.bind( Galleria.THUMBNAIL, function() {
                this.updateCarousel();
            });
        }

        return this;
    },

    // Creates the thumbnails and carousel
    // can be used at any time, f.ex when the data object is manipulated

    _createThumbnails : function() {

        var i,
            src,
            thumb,
            data,

            $container,

            self = this,
            o = this._options,

            // get previously active thumbnail, if exists
            active = (function() {
                var a = self.$('thumbnails').find('.active');
                if ( !a.length ) {
                    return false;
                }
                return a.find('img').attr('src');
            }()),

            // cache the thumbnail option
            optval = typeof o.thumbnails === 'string' ? o.thumbnails.toLowerCase() : null,

            // move some data into the instance
            // for some reason, jQuery cant handle css(property) when zooming in FF, breaking the gallery
            // so we resort to getComputedStyle for browsers who support it
            getStyle = function( prop ) {
                return doc.defaultView && doc.defaultView.getComputedStyle ?
                    doc.defaultView.getComputedStyle( thumb.container, null )[ prop ] :
                    $container.css( prop );
            },

            fake = function(image, index, container) {
                return function() {
                    $( container ).append( image );
                    self.trigger({
                        type: Galleria.THUMBNAIL,
                        thumbTarget: image,
                        index: index
                    });
                };
            },

            onThumbEvent = function( e ) {

                // pause if option is set
                if ( o.pauseOnInteraction ) {
                    self.pause();
                }

                // extract the index from the data
                var index = $( e.currentTarget ).data( 'index' );
                if ( self.getIndex() !== index ) {
                    self.show( index );
                }

                e.preventDefault();
            },

            onThumbLoad = function( thumb ) {

                // scale when ready
                thumb.scale({
                    width:    thumb.data.width,
                    height:   thumb.data.height,
                    crop:     o.thumbCrop,
                    margin:   o.thumbMargin,
                    complete: function( thumb ) {

                        // shrink thumbnails to fit
                        var top = ['left', 'top'],
                            arr = ['Width', 'Height'],
                            m,
                            css;

                        // calculate shrinked positions
                        $.each(arr, function( i, measure ) {
                            m = measure.toLowerCase();
                            if ( (o.thumbCrop !== true || o.thumbCrop === m ) && o.thumbFit ) {
                                css = {};
                                css[ m ] = thumb[ m ];
                                $( thumb.container ).css( css );
                                css = {};
                                css[ top[ i ] ] = 0;
                                $( thumb.image ).css( css );
                            }

                            // cache outer measures
                            thumb[ 'outer' + measure ] = $( thumb.container )[ 'outer' + measure ]( true );
                        });

                        // set high quality if downscale is moderate
                        Utils.toggleQuality( thumb.image,
                            o.thumbQuality === true ||
                            ( o.thumbQuality === 'auto' && thumb.original.width < thumb.width * 3 )
                        );

                        // trigger the THUMBNAIL event
                        self.trigger({
                            type: Galleria.THUMBNAIL,
                            thumbTarget: thumb.image,
                            index: thumb.data.order
                        });
                    }
                });
            };

        this._thumbnails = [];

        this.$( 'thumbnails' ).empty();

        // loop through data and create thumbnails
        for( i = 0; this._data[ i ]; i++ ) {

            data = this._data[ i ];

            if ( o.thumbnails === true ) {

                // add a new Picture instance
                thumb = new Galleria.Picture(i);

                // get source from thumb or image
                src = data.thumb || data.image;

                // append the thumbnail
                this.$( 'thumbnails' ).append( thumb.container );

                // cache the container
                $container = $( thumb.container );

                thumb.data = {
                    width  : Utils.parseValue( getStyle( 'width' ) ),
                    height : Utils.parseValue( getStyle( 'height' ) ),
                    order  : i
                };

                // grab & reset size for smoother thumbnail loads
                if ( o.thumbFit && o.thumbCrop !== true ) {
                    $container.css( { width: 0, height: 0 } );
                } else {
                    $container.css( { width: thumb.data.width, height: thumb.data.height } );
                }


                // load the thumbnail
                thumb.load( src, onThumbLoad );
				
				/* BEGIN ADDITION */
				$container.wrapInner('<div class="img-frame" />');
				/* END ADDITION */

                // preload all images here
                if ( o.preload === 'all' ) {
                    thumb.add( data.image );
                }

            // create empty spans if thumbnails is set to 'empty'
            } else if ( optval === 'empty' || optval === 'numbers' ) {

                thumb = {
                    container:  Utils.create( 'galleria-image' ),
                    image: Utils.create( 'img', 'span' ),
                    ready: true
                };

                // create numbered thumbnails
                if ( optval === 'numbers' ) {
                    $( thumb.image ).text( i + 1 );
                }

                this.$( 'thumbnails' ).append( thumb.container );

                // we need to "fake" a loading delay before we append and trigger
                // 50+ should be enough

                window.setTimeout( ( fake )( thumb.image, i, thumb.container ), 50 + ( i*20 ) );

            // create null object to silent errors
            } else {
                thumb = {
                    container: null,
                    image: null
                };
            }

            // add events for thumbnails
            // you can control the event type using thumb_event_type
            // we'll add the same event to the source if it's kept

            $( thumb.container ).add( o.keepSource && o.linkSourceImages ? data.original : null )
                .data('index', i).bind( o.thumbEventType, onThumbEvent );

            if (active === src) {
                $( thumb.container ).addClass( 'active' );
            }

            this._thumbnails.push( thumb );
        }
	},

    // the internal _run method should be called after loading data into galleria
    // makes sure the gallery has proper measurements before triggering ready
    _run : function() {

        var self = this;

		self._createThumbnails();

        // make sure we have a stageHeight && stageWidth

        Utils.wait({

            until: function() {
                
                // Opera crap
                if ( Galleria.OPERA ) {
                    self.$( 'stage' ).css( 'display', 'inline-block' );
                }
                
                self._stageWidth  = self.$( 'stage' ).width();
                self._stageHeight = self.$( 'stage' ).height();
                
                return( self._stageWidth && 
                        self._stageHeight > 50 ); // what is an acceptable height?
            },

            success: function() {
                self.trigger( Galleria.READY );
            },

            error: function() {
                Galleria.raise('Stage measures not found', true);
            }

        });
    },

    /**
        Loads data into the gallery.
        You can call this method on an existing gallery to reload the gallery with new data.

        @param {Array|string} source Optional JSON array of data or selector of where to find data in the document.
        Defaults to the Galleria target or dataSource option.

        @param {string} selector Optional element selector of what elements to parse.
        Defaults to 'img'.

        @param {Function} [config] Optional function to modify the data extraction proceedure from the selector.
        See the data_config option for more information.

        @returns Instance
    */

    load : function( source, selector, config ) {

        var self = this;

        // empty the data array
        this._data = [];

        // empty the thumbnails
        this._thumbnails = [];
        this.$('thumbnails').empty();

        // shorten the arguments
        if ( typeof selector === 'function' ) {
            config = selector;
            selector = null;
        }

        // use the source set by target
        source = source || this._options.dataSource;

        // use selector set by option
        selector = selector || this._options.dataSelector;

        // use the data_config set by option
        config = config || this._options.dataConfig;

        // check if the data is an array already
        if ( source.constructor === Array ) {
            if ( this.validate( source ) ) {

                this._data = source;
                this._parseData().trigger( Galleria.DATA );

            } else {
                Galleria.raise( 'Load failed: JSON Array not valid.' );
            }
            return this;
        }
        // loop through images and set data
        $( source ).find( selector ).each( function( i, img ) {
            img = $( img );
            var data = {},
                parent = img.parent(),
                href = parent.attr( 'href' );

            // check if it's a link to another image
            if ( /\.(png|gif|jpg|jpeg)(\?.*)?$/i.test(href) ) {
                data.image = href;

            // else assign the href as a link if it exists
            } else if ( href ) {
                data.link = href;
            }

            // mix default extractions with the hrefs and config
            // and push it into the data array
            self._data.push( $.extend({

                title:       img.attr('title'),
                thumb:       img.attr('src'),
                image:       img.attr('src'),
                description: img.attr('alt'),
                link:        img.attr('longdesc'),
                original:    img.get(0) // saved as a reference

            }, data, config( img ) ) );

        });
        // trigger the DATA event and return
        if ( this.getDataLength() ) {
            this.trigger( Galleria.DATA );
        } else {
            Galleria.raise('Load failed: no data found.');
        }
        return this;

    },

    // make sure the data works properly
    _parseData : function() {

        var self = this;

        // copy image as thumb if no thumb exists
        $.each( this._data, function( i, data ) {
            if ( 'thumb' in data === false ) {
                self._data[ i ].thumb = data.image;
            }
        });

        return this;
    },

    /**
        Adds and/or removes images from the gallery
		Works just like Array.splice
		https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice

        @example this.splice( 2, 4 ); // removes 4 images after the second image

        @returns Instance
    */

	splice: function() {
		Array.prototype.splice.apply( this._data, Utils.array( arguments ) );
		return this._parseData()._createThumbnails();
	},

	/**
        Append images to the gallery
		Works just like Array.push
		https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push

        @example this.push({
            image: 'image1.jpg'
        }); // appends the image to the gallery

        @returns Instance
    */

	push: function() {
		Array.prototype.push.apply( this._data, Utils.array( arguments ) );
		return this._parseData()._createThumbnails();
	},

    _getActive: function() {
        return this._controls.getActive();
    },

    validate : function( data ) {
        // todo: validate a custom data array
        return true;
    },

    /**
        Bind any event to Galleria

        @param {string} type The Event type to listen for
        @param {Function} fn The function to execute when the event is triggered

        @example this.bind( 'image', function() { Galleria.log('image shown') });

        @returns Instance
    */

    bind : function(type, fn) {
        // allow 'image' instead of Galleria.IMAGE
        type = _patchEvent( type );

        this.$( 'container' ).bind( type, this.proxy(fn) );
        return this;
    },

    /**
        Unbind any event to Galleria

        @param {string} type The Event type to forget

        @returns Instance
    */

    unbind : function(type) {

        type = _patchEvent( type );

        this.$( 'container' ).unbind( type );
        return this;
    },

    /**
        Manually trigger a Galleria event

        @param {string} type The Event to trigger

        @returns Instance
    */

    trigger : function( type ) {

        type = typeof type === 'object' ?
            $.extend( type, { scope: this } ) :
            { type: _patchEvent( type ), scope: this };

        this.$( 'container' ).trigger( type );

        return this;
    },

    /**
        Assign an "idle state" to any element.
        The idle state will be applied after a certain amount of idle time
        Useful to hide f.ex navigation when the gallery is inactive

        @param {HTMLElement|string} elem The Dom node or selector to apply the idle state to
        @param {Object} styles the CSS styles to apply

        @example addIdleState( this.get('image-nav'), { opacity: 0 });
        @example addIdleState( '.galleria-image-nav', { top: -200 });

        @returns Instance
    */

    addIdleState: function( elem, styles ) {
        this._idle.add.apply( this._idle, Utils.array( arguments ) );
        return this;
    },

    /**
        Removes any idle state previously set using addIdleState()

        @param {HTMLElement|string} elem The Dom node or selector to remove the idle state from.

        @returns Instance
    */

    removeIdleState: function( elem ) {
        this._idle.remove.apply( this._idle, Utils.array( arguments ) );
        return this;
    },

    /**
        Force Galleria to enter idle mode.

        @returns Instance
    */

    enterIdleMode: function() {
        this._idle.hide();
        return this;
    },

    /**
        Force Galleria to exit idle mode.

        @returns Instance
    */

    exitIdleMode: function() {
        this._idle.showAll();
        return this;
    },

    /**
        Enter FullScreen mode

        @param {Function} callback the function to be executed when the fullscreen mode is fully applied.

        @returns Instance
    */

    enterFullscreen: function( callback ) {
        this._fullscreen.enter.apply( this, Utils.array( arguments ) );
        return this;
    },

    /**
        Exits FullScreen mode

        @param {Function} callback the function to be executed when the fullscreen mode is fully applied.

        @returns Instance
    */

    exitFullscreen: function( callback ) {
        this._fullscreen.exit.apply( this, Utils.array( arguments ) );
        return this;
    },

    /**
        Toggle FullScreen mode

        @param {Function} callback the function to be executed when the fullscreen mode is fully applied or removed.

        @returns Instance
    */

    toggleFullscreen: function( callback ) {
        this._fullscreen[ this.isFullscreen() ? 'exit' : 'enter'].apply( this, Utils.array( arguments ) );
        return this;
    },

    /**
        Adds a tooltip to any element.
        You can also call this method with an object as argument with elemID:value pairs to apply tooltips to (see examples)

        @param {HTMLElement} elem The DOM Node to attach the event to
        @param {string|Function} value The tooltip message. Can also be a function that returns a string.

        @example this.bindTooltip( this.get('thumbnails'), 'My thumbnails');
        @example this.bindTooltip( this.get('thumbnails'), function() { return 'My thumbs' });
        @example this.bindTooltip( { image_nav: 'Navigation' });

        @returns Instance
    */

    bindTooltip: function( elem, value ) {
        this._tooltip.bind.apply( this._tooltip, Utils.array(arguments) );
        return this;
    },

    /**
        Note: this method is deprecated. Use refreshTooltip() instead.

        Redefine a tooltip.
        Use this if you want to re-apply a tooltip value to an already bound tooltip element.

        @param {HTMLElement} elem The DOM Node to attach the event to
        @param {string|Function} value The tooltip message. Can also be a function that returns a string.

        @returns Instance
    */

    defineTooltip: function( elem, value ) {
        this._tooltip.define.apply( this._tooltip, Utils.array(arguments) );
        return this;
    },

    /**
        Refresh a tooltip value.
        Use this if you want to change the tooltip value at runtime, f.ex if you have a play/pause toggle.

        @param {HTMLElement} elem The DOM Node that has a tooltip that should be refreshed

        @returns Instance
    */

    refreshTooltip: function( elem ) {
        this._tooltip.show.apply( this._tooltip, Utils.array(arguments) );
        return this;
    },

    /**
        Open a pre-designed lightbox with the currently active image.
        You can control some visuals using gallery options.

        @returns Instance
    */

    openLightbox: function() {
        this._lightbox.show.apply( this._lightbox, Utils.array( arguments ) );
        return this;
    },

    /**
        Close the lightbox.

        @returns Instance
    */

    closeLightbox: function() {
        this._lightbox.hide.apply( this._lightbox, Utils.array( arguments ) );
        return this;
    },

    /**
        Get the currently active image element.

        @returns {HTMLElement} The image element
    */

    getActiveImage: function() {
        return this._getActive().image || undef;
    },

    /**
        Get the currently active thumbnail element.

        @returns {HTMLElement} The thumbnail element
    */

    getActiveThumb: function() {
        return this._thumbnails[ this._active ].image || undef;
    },

    /**
        Get the mouse position relative to the gallery container

        @param e The mouse event

        @example

var gallery = this;
$(document).mousemove(function(e) {
    console.log( gallery.getMousePosition(e).x );
});

        @returns {Object} Object with x & y of the relative mouse postion
    */

    getMousePosition : function(e) {
        return {
            x: e.pageX - this.$( 'container' ).offset().left,
            y: e.pageY - this.$( 'container' ).offset().top
        };
    },

    /**
        Adds a panning effect to the image

        @param img The optional image element. If not specified it takes the currently active image

        @returns Instance
    */

    addPan : function( img ) {

        if ( this._options.imageCrop === false ) {
            return;
        }

        img = $( img || this.getActiveImage() );

        // define some variables and methods
        var self   = this,
            x      = img.width() / 2,
            y      = img.height() / 2,
            destX  = parseInt( img.css( 'left' ), 10 ),
            destY  = parseInt( img.css( 'top' ), 10 ),
            curX   = destX || 0,
            curY   = destY || 0,
            distX  = 0,
            distY  = 0,
            active = false,
            ts     = Utils.timestamp(),
            cache  = 0,
            move   = 0,

            // positions the image
            position = function( dist, cur, pos ) {
                if ( dist > 0 ) {
                    move = Math.round( Math.max( dist * -1, Math.min( 0, cur ) ) );
                    if ( cache !== move ) {

                        cache = move;

                        if ( IE === 8 ) { // scroll is faster for IE
                            img.parent()[ 'scroll' + pos ]( move * -1 );
                        } else {
                            var css = {};
                            css[ pos.toLowerCase() ] = move;
                            img.css(css);
                        }
                    }
                }
            },

            // calculates mouse position after 50ms
            calculate = function(e) {
                if (Utils.timestamp() - ts < 50) {
                    return;
                }
                active = true;
                x = self.getMousePosition(e).x;
                y = self.getMousePosition(e).y;
            },

            // the main loop to check
            loop = function(e) {

                if (!active) {
                    return;
                }

                distX = img.width() - self._stageWidth;
                distY = img.height() - self._stageHeight;
                destX = x / self._stageWidth * distX * -1;
                destY = y / self._stageHeight * distY * -1;
                curX += ( destX - curX ) / self._options.imagePanSmoothness;
                curY += ( destY - curY ) / self._options.imagePanSmoothness;

                position( distY, curY, 'Top' );
                position( distX, curX, 'Left' );

            };

        // we need to use scroll in IE8 to speed things up
        if ( IE === 8 ) {

            img.parent().scrollTop( curY * -1 ).scrollLeft( curX * -1 );
            img.css({
                top: 0,
                left: 0
            });

        }

        // unbind and bind event
        this.$( 'stage' ).unbind( 'mousemove', calculate ).bind( 'mousemove', calculate );

        // loop the loop
        Utils.addTimer('pan', loop, 50, true);

        return this;
    },

    /**
        Brings the scope into any callback

        @param fn The callback to bring the scope into
        @param scope Optional scope to bring

        @example $('#fullscreen').click( this.proxy(function() { this.enterFullscreen(); }) )

        @returns {Function} Return the callback with the gallery scope
    */

    proxy : function( fn, scope ) {
        if ( typeof fn !== 'function' ) {
            return function() {};
        }
        scope = scope || this;
        return function() {
            return fn.apply( scope, Utils.array( arguments ) );
        };
    },

    /**
        Removes the panning effect set by addPan()

        @returns Instance
    */

    removePan: function() {

        // todo: doublecheck IE8

        this.$( 'stage' ).unbind( 'mousemove' );

        Utils.clearTimer( 'pan' );

        return this;
    },

    /**
        Adds an element to the Galleria DOM array.
        When you add an element here, you can access it using element ID in many API calls

        @param {string} id The element ID you wish to use. You can add many elements by adding more arguments.

        @example addElement('mybutton');
        @example addElement('mybutton','mylink');

        @returns Instance
    */

    addElement : function( id ) {

        var dom = this._dom;

        $.each( Utils.array(arguments), function( i, blueprint ) {
           dom[ blueprint ] = Utils.create( 'galleria-' + blueprint );
        });

        return this;
    },

    /**
        Attach keyboard events to Galleria

        @param {Object} map The map object of events.
        Possible keys are 'UP', 'DOWN', 'LEFT', 'RIGHT', 'RETURN', 'ESCAPE', 'BACKSPACE', and 'SPACE'.

        @example

this.attachKeyboard({
    right: this.next,
    left: this.prev,
    up: function() {
        console.log( 'up key pressed' )
    }
});

        @returns Instance
    */

    attachKeyboard : function( map ) {
        this._keyboard.attach.apply( this._keyboard, Utils.array( arguments ) );
        return this;
    },

    /**
        Detach all keyboard events to Galleria

        @returns Instance
    */

    detachKeyboard : function() {
        this._keyboard.detach.apply( this._keyboard, Utils.array( arguments ) );
        return this;
    },

    /**
        Fast helper for appending galleria elements that you added using addElement()

        @param {string} parentID The parent element ID where the element will be appended
        @param {string} childID the element ID that should be appended

        @example this.addElement('myElement');
        this.appendChild( 'info', 'myElement' );

        @returns Instance
    */

    appendChild : function( parentID, childID ) {
        this.$( parentID ).append( this.get( childID ) || childID );
        return this;
    },

    /**
        Fast helper for prepending galleria elements that you added using addElement()

        @param {string} parentID The parent element ID where the element will be prepended
        @param {string} childID the element ID that should be prepended

        @example

this.addElement('myElement');
this.prependChild( 'info', 'myElement' );

        @returns Instance
    */

    prependChild : function( parentID, childID ) {
        this.$( parentID ).prepend( this.get( childID ) || childID );
        return this;
    },

    /**
        Remove an element by blueprint

        @param {string} elemID The element to be removed.
        You can remove multiple elements by adding arguments.

        @returns Instance
    */

    remove : function( elemID ) {
        this.$( Utils.array( arguments ).join(',') ).remove();
        return this;
    },

    // a fast helper for building dom structures
    // leave this out of the API for now

    append : function( data ) {
        var i, j;
        for( i in data ) {
            if ( data.hasOwnProperty( i ) ) {
                if ( data[i].constructor === Array ) {
                    for( j = 0; data[i][j]; j++ ) {
                        this.appendChild( i, data[i][j] );
                    }
                } else {
                    this.appendChild( i, data[i] );
                }
            }
        }
        return this;
    },

    // an internal helper for scaling according to options
    _scaleImage : function( image, options ) {

        options = $.extend({
            width:    this._stageWidth,
            height:   this._stageHeight,
            crop:     this._options.imageCrop,
            max:      this._options.maxScaleRatio,
            min:      this._options.minScaleRatio,
            margin:   this._options.imageMargin,
            position: this._options.imagePosition
        }, options );

       ( image || this._controls.getActive() ).scale( options );

        return this;
    },

    /**
        Updates the carousel,
        useful if you resize the gallery and want to re-check if the carousel nav is needed.

        @returns Instance
    */

    updateCarousel : function() {
        this._carousel.update();
        return this;
    },

    /**
        Rescales the gallery

        @param {number} width The target width
        @param {number} height The target height
        @param {Function} complete The callback to be called when the scaling is complete

        @returns Instance
    */

    rescale : function( width, height, complete ) {

        var self = this;

        // allow rescale(fn)
        if ( typeof width === 'function' ) {
            complete = width;
            width = undef;
        }

        var scale = function() {

            // set stagewidth
            self._stageWidth = width || self.$( 'stage' ).width();
            self._stageHeight = height || self.$( 'stage' ).height();

            // scale the active image
            self._scaleImage();

            if ( self._options.carousel ) {
                self.updateCarousel();
            }

            self.trigger( Galleria.RESCALE );

            if ( typeof complete === 'function' ) {
                complete.call( self );
            }
        };

        if ( Galleria.WEBKIT && !width && !height ) {
            Utils.addTimer( 'scale', scale, 5 );// webkit is too fast
        } else {
            scale.call( self );
        }

        return this;
    },

    /**
        Refreshes the gallery.
        Useful if you change image options at runtime and want to apply the changes to the active image.

        @returns Instance
    */

    refreshImage : function() {
        this._scaleImage();
        if ( this._options.imagePan ) {
            this.addPan();
        }
        return this;
    },

	
	track: function(evt, name) {
		var s=s_gi(s_account);
		s.linkTrackVars="eVar34,campaign,events"; 
		s.linkTrackEvents="event38";
		s.eVar34=name; 
		s.events="event38";
		void(s.tl(evt,'o',"HottestImage||"+name));
	},

    /**
        Shows an image by index

        @param {number|boolean} index The index to show
        @param {Boolean} rewind A boolean that should be true if you want the transition to go back

        @returns Instance
    */

    show : function( index, rewind, _history ) {
        // do nothing if index is false or queue is false and transition is in progress
        if ( index === false || ( !this._options.queue && this._queue.stalled ) ) {
            return;
        }
        this.track(true, this._original.options.name);

        index = Math.max( 0, Math.min( parseInt( index, 10 ), this.getDataLength() - 1 ) );

        rewind = typeof rewind !== 'undefined' ? !!rewind : index < this.getIndex();

        _history = _history || false;

        // do the history thing and return
        if ( !_history && Galleria.History ) {
            Galleria.History.value( index.toString() );
            return;
        }

        this._active = index;

        Array.prototype.push.call( this._queue, {
            index : index,
            rewind : rewind
        });
        if ( !this._queue.stalled ) {
            this._show();
        }

        return this;
    },

    // the internal _show method does the actual showing
    _show : function() {// shortcuts
        var self   = this,
            queue  = this._queue[ 0 ],
            data   = this.getData( queue.index );

        if ( !data ) {
            return;
        }
		
        var src    = data.image,
            active = this._controls.getActive(),
            next   = this._controls.getNext(),
            cached = next.isCached( src ),
            thumb  = this._thumbnails[ queue.index ];

        // to be fired when loading & transition is complete:
        var complete = function() {

            var win;

            // remove stalled
            self._queue.stalled = false;

            // optimize quality
            Utils.toggleQuality( next.image, self._options.imageQuality );

            // swap
            $( active.container ).css({
                zIndex: 0,
                opacity: 0
            });
            $( next.container ).css({
                zIndex: 1,
                opacity: 1
            });
            self._controls.swap();

            // add pan according to option
            if ( self._options.imagePan ) {
                self.addPan( next.image );
            }

            // make the image link
            if ( data.link ) {

                $( next.image ).css({
                    cursor: 'pointer'
                }).bind( CLICK(), function() {

                    // popup link
                    if ( self._options.popupLinks ) {
                        win = window.open( data.link, '_blank' );
                    } else {
                        window.location.href = data.link;
                    }
                });
            }

            // remove the queued image
            Array.prototype.shift.call( self._queue );

            // if we still have images in the queue, show it
            if ( self._queue.length ) {
                self._show();
            }

            // check if we are playing
            self._playCheck();

            // trigger IMAGE event
            self.trigger({
                type:        Galleria.IMAGE,
                index:       queue.index,
                imageTarget: next.image,
                thumbTarget: thumb.image
            });
        };

        // let the carousel follow
        if ( this._options.carousel && this._options.carouselFollow ) {
            this._carousel.follow( queue.index );
        }

        // preload images
        if ( this._options.preload ) {

            var p, i,
                n = this.getNext();

            try {
                for ( i = this._options.preload; i > 0; i-- ) {
                    p = new Galleria.Picture();
                    p.add( self.getData( n ).image );
                    n = self.getNext( n );
                }
            } catch(e) {}
        }

        // show the next image, just in case
        //Utils.show( next.container ); //commented out to avoid flickering of image on initial load in IE - chris 2011/03/15

        // add active classes
        $( self._thumbnails[ queue.index ].container )
            .addClass( 'active' )
            .siblings( '.active' )
            .removeClass( 'active' );

        // trigger the LOADSTART event
        self.trigger( {
            type: Galleria.LOADSTART,
            cached: cached,
            index: queue.index,
            imageTarget: next.image,
            thumbTarget: thumb.image
        });
        // begin loading the next image
        next.load( src, function( next ) {
            self._scaleImage( next, {

                complete: function( next ) {

                    Utils.show( next.container );

                    // toggle low quality for IE
                    if ( 'image' in active ) {
                        Utils.toggleQuality( active.image, false );
                    }
                    Utils.toggleQuality( next.image, false );

                    // stall the queue
                    self._queue.stalled = true;

                    // remove the image panning, if applied
                    // TODO: rethink if this is necessary
                    self.removePan();

                    // set the captions and counter
                    self.setInfo( queue.index );
                    self.setCounter( queue.index );

                    // trigger the LOADFINISH event
                    self.trigger({
                        type: Galleria.LOADFINISH,
                        cached: cached,
                        index: queue.index,
                        imageTarget: next.image,
                        thumbTarget: self._thumbnails[ queue.index ].image
                    });

                    var transition = active.image === null && self._options.transitionInitial ?
                        self._options.transition_Initial : self._options.transition;

                    // validate the transition
                    if ( transition in _transitions === false ) {

                        complete();

                    } else {
                        var params = {
                            prev:   active.image,
                            next:   next.image,
                            rewind: queue.rewind,
                            speed:  self._options.transitionSpeed || 400
                        };

                        // call the transition function and send some stuff
                        _transitions[ transition ].call(self, params, complete );

                    }
                }
            });
        });
    },

    /**
        Gets the next index

        @param {number} base Optional starting point

        @returns {number} the next index, or the first if you are at the first (looping)
    */

    getNext : function( base ) {
        base = typeof base === 'number' ? base : this.getIndex();
        return base === this.getDataLength() - 1 ? 0 : base + 1;
    },

    /**
        Gets the previous index

        @param {number} base Optional starting point

        @returns {number} the previous index, or the last if you are at the first (looping)
    */

    getPrev : function( base ) {
        base = typeof base === 'number' ? base : this.getIndex();
        return base === 0 ? this.getDataLength() - 1 : base - 1;
    },

    /**
        Shows the next image in line

        @returns Instance
    */

    next : function() {
        if ( this.getDataLength() > 1 ) {
            this.show( this.getNext(), false );
        }
        return this;
    },

    /**
        Shows the previous image in line

        @returns Instance
    */

    prev : function() {
        if ( this.getDataLength() > 1 ) {
            this.show( this.getPrev(), true );
        }
        return this;
    },

    /**
        Retrieve a DOM element by element ID

        @param {string} elemId The delement ID to fetch

        @returns {HTMLElement} The elements DOM node or null if not found.
    */

    get : function( elemId ) {
        return elemId in this._dom ? this._dom[ elemId ] : null;
    },

    /**
        Retrieve a data object

        @param {number} index The data index to retrieve.
        If no index specified it will take the currently active image

        @returns {Object} The data object
    */

    getData : function( index ) {
        return index in this._data ?
            this._data[ index ] : this._data[ this._active ];
    },

    /**
        Retrieve the number of data items

        @returns {number} The data length
    */
    getDataLength : function() {
        return this._data.length;
    },

    /**
        Retrieve the currently active index

        @returns {number|boolean} The active index or false if none found
    */

    getIndex : function() {
        return typeof this._active === 'number' ? this._active : false;
    },

    /**
        Retrieve the stage height

        @returns {number} The stage height
    */

    getStageHeight : function() {
        return this._stageHeight;
    },

    /**
        Retrieve the stage width

        @returns {number} The stage width
    */

    getStageWidth : function() {
        return this._stageWidth;
    },

    /**
        Retrieve the option

        @param {string} key The option key to retrieve. If no key specified it will return all options in an object.

        @returns option or options
    */

    getOptions : function( key ) {
        return typeof key === 'undefined' ? this._options : this._options[ key ];
    },

    /**
        Set options to the instance.
        You can set options using a key & value argument or a single object argument (see examples)

        @param {string} key The option key
        @param {string} value the the options value

        @example setOptions( 'autoplay', true )
        @example setOptions({ autoplay: true });

        @returns Instance
    */

    setOptions : function( key, value ) {
        if ( typeof key === 'object' ) {
            $.extend( this._options, key );
        } else {
            this._options[ key ] = value;
        }
        return this;
    },

    /**
        Starts playing the slideshow

        @param {number} delay Sets the slideshow interval in milliseconds.
        If you set it once, you can just call play() and get the same interval the next time.

        @returns Instance
    */

    play : function( delay ) {

        this._playing = true;

        this._playtime = delay || this._playtime;

        this._playCheck();

        this.trigger( Galleria.PLAY );

        return this;
    },

    /**
        Stops the slideshow if currently playing

        @returns Instance
    */

    pause : function() {

        this._playing = false;

        this.trigger( Galleria.PAUSE );

        return this;
    },

    /**
        Toggle between play and pause events.

        @param {number} delay Sets the slideshow interval in milliseconds.

        @returns Instance
    */

    playToggle : function( delay ) {
        return ( this._playing ) ? this.pause() : this.play( delay );
    },

    /**
        Checks if the gallery is currently playing

        @returns {Boolean}
    */

    isPlaying : function() {
        return this._playing;
    },

    /**
        Checks if the gallery is currently in fullscreen mode

        @returns {Boolean}
    */

    isFullscreen : function() {
        return this._fullscreen.active;
    },

    _playCheck : function() {
        var self = this,
            played = 0,
            interval = 20,
            now = Utils.timestamp(),
			timer_id = 'play' + this._id;

        if ( this._playing ) {

			Utils.clearTimer( timer_id );

            var fn = function() {

                played = Utils.timestamp() - now;
                if ( played >= self._playtime && self._playing ) {
                    Utils.clearTimer( timer_id );
                    self.next();
                    return;
                }
                if ( self._playing ) {

                    // trigger the PROGRESS event
                    self.trigger({
                        type:         Galleria.PROGRESS,
                        percent:      Math.ceil( played / self._playtime * 100 ),
                        seconds:      Math.floor( played / 1000 ),
                        milliseconds: played
                    });

                    Utils.addTimer( timer_id, fn, interval );
                }
            };
            Utils.addTimer( timer_id, fn, interval );
        }
    },

    setIndex: function( val ) {
        this._active = val;
        return this;
    },

    /**
        Manually modify the counter

        @param {number} index Optional data index to fectch,
        if no index found it assumes the currently active index

        @returns Instance
    */

    setCounter: function( index ) {

        if ( typeof index === 'number' ) {
            index++;
        } else if ( typeof index === 'undefined' ) {
            index = this.getIndex()+1;
        }

        this.get( 'current' ).innerHTML = index;

        if ( IE ) { // weird IE bug

            var count = this.$( 'counter' ),
                opacity = count.css( 'opacity' ),
                style = count.attr('style');
                
            if ( style && parseInt( opacity, 10 ) === 1) {
                count.attr('style', style.replace(/filter[^\;]+\;/i,''));
            } else {
                this.$( 'counter' ).css( 'opacity', opacity );
            }

        }

        return this;
    },

    /**
        Manually set captions

        @param {number} index Optional data index to fectch and apply as caption,
        if no index found it assumes the currently active index

        @returns Instance
    */

    setInfo : function( index ) {

        var self = this,
            data = this.getData( index );

        $.each( ['title','description','author'], function( i, type ) {

            var elem = self.$( 'info-' + type );

            if ( !!data[type] ) {
                elem[ data[ type ].length ? 'show' : 'hide' ]().html( data[ type ] );
            } else {
               elem.empty().hide();
            }
        });

        return this;
    },

    /**
        Checks if the data contains any captions

        @param {number} index Optional data index to fectch,
        if no index found it assumes the currently active index.

        @returns {boolean}
    */

    hasInfo : function( index ) {

        var check = 'title description'.split(' '),
            i;

        for ( i = 0; check[i]; i++ ) {
            if ( !!this.getData( index )[ check[i] ] ) {
                return true;
            }
        }
        return false;

    },

    jQuery : function( str ) {

        var self = this,
            ret = [];

        $.each( str.split(','), function( i, elemId ) {
            elemId = $.trim( elemId );

            if ( self.get( elemId ) ) {
                ret.push( elemId );
            }
        });

        var jQ = $( self.get( ret.shift() ) );

        $.each( ret, function( i, elemId ) {
            jQ = jQ.add( self.get( elemId ) );
        });

        return jQ;

    },

    /**
        Converts element IDs into a jQuery collection
        You can call for multiple IDs separated with commas.

        @param {string} str One or more element IDs (comma-separated)

        @returns jQuery

        @example this.$('info,container').hide();
    */

    $ : function( str ) {
        return this.jQuery.apply( this, Utils.array( arguments ) );
    }

};

// End of Galleria prototype

// Add events as static variables
$.each( _events, function( i, ev ) {

    // legacy events
    var type = /_/.test( ev ) ? ev.replace( /_/g, '' ) : ev;

    Galleria[ ev.toUpperCase() ] = 'galleria.'+type;

} );

$.extend( Galleria, {

    // Browser helpers
    IE9:     IE === 9,
    IE8:     IE === 8,
    IE7:     IE === 7,
    IE6:     IE === 6,
    IE:      !!IE,
    WEBKIT:  /webkit/.test( NAV ),
    SAFARI:  /safari/.test( NAV ),
    CHROME:  /chrome/.test( NAV ),
    QUIRK:   ( IE && doc.compatMode && doc.compatMode === "BackCompat" ),
    MAC:     /mac/.test( navigator.platform.toLowerCase() ),
    OPERA:   !!window.opera,
    IPHONE:  /iphone/.test( NAV ),
    IPAD:    /ipad/.test( NAV ),
    ANDROID: /android/.test( NAV ),

    // Todo detect touch devices in a better way, possibly using event detection
    TOUCH:   !!( /iphone/.test( NAV ) || /ipad/.test( NAV ) || /android/.test( NAV ) )

});

// Galleria static methods

/**
    Adds a theme that you can use for your Gallery

    @param {Object} theme Object that should contain all your theme settings.
    <ul>
        <li>name – name of the theme</li>
        <li>author - name of the author</li>
        <li>css - css file name (not path)</li>
        <li>defaults - default options to apply, including theme-specific options</li>
        <li>init - the init function</li>
    </ul>

    @returns {Object} theme
*/

Galleria.addTheme = function( theme ) {

    // make sure we have a name
    if ( !theme.name ) {
        Galleria.raise('No theme name specified');
    }

    if ( typeof theme.defaults !== 'object' ) {
        theme.defaults = {};
    } else {
        theme.defaults = _legacyOptions( theme.defaults );
    }

    var css = false,
        reg;
    
    if ( typeof theme.css === 'string' ) {
        
        // look for manually added CSS
        $('link').each(function( i, link ) {
            reg = new RegExp( theme.css );
            if ( reg.test( link.href ) ) {
                
                // we found the css
                css = true;
                Galleria.theme = theme;
                
                return false;
            }
        });
        
        // else look for the absolute path and load the CSS dynamic
        if ( !css ) {

            $('script').each(function( i, script ) {

                // look for the theme script
                reg = new RegExp( 'galleria\\.' + theme.name.toLowerCase() + '\\.' );
                if( reg.test( script.src )) {

                    // we have a match
                    css = script.src.replace(/[^\/]*$/, '') + theme.css;

                    Utils.addTimer( "css", function() {
                        Utils.loadCSS( css, 'galleria-theme', function() {
                            Galleria.theme = theme;
                        });
                    }, 1);

                }
            });
        }

        if ( !css ) {
            Galleria.raise('No theme CSS loaded');
        }
    } else {
        
        // pass
        Galleria.theme = theme;
    }
    return theme;
};

/**
    loadTheme loads a theme js file and attaches a load event to Galleria

    @param {string} src The relative path to the theme source file

    @param {Object} [options] Optional options you want to apply
*/

Galleria.loadTheme = function( src, options ) {

    var loaded = false,
        length = _galleries.length;

    // first clear the current theme, if exists
    Galleria.theme = undef;

    // load the theme
    Utils.loadScript( src, function() {
        loaded = true;
    } );

    // set a 1 sec timeout, then display a hard error if no theme is loaded
    Utils.wait({
        until: function() {
            return loaded;
        },
        error: function() {
            Galleria.raise( "Theme at " + src + " could not load, check theme path.", true );
        },
        success: function() {

            // check for existing galleries and reload them with the new theme
            if ( length ) {

                // temporary save the new galleries
                var refreshed = [];

                // refresh all instances
                // when adding a new theme to an existing gallery, all options will be resetted but the data will be kept
                // you can apply new options as a second argument
                $.each( Galleria.get(), function(i, instance) {

                    // mix the old data and options into the new instance
                    var op = $.extend( instance._original.options, {
                        data_source: instance._data
                    }, options);

                    // remove the old container
                    instance.$('container').remove();

                    // create a new instance
                    var g = new Galleria();

                    // move the id
                    g._id = instance._id;

                    // initialize the new instance
                    g.init( instance._original.target, op );

                    // push the new instance
                    refreshed.push( g );
                });

                // now overwrite the old holder with the new instances
                _galleries = refreshed;
            }
        },
        timeout: 2000
    });
};

/**
    Retrieves a Galleria instance.

    @param {number} [index] Optional index to retrieve.
    If no index is supplied, the method will return all instances in an array.

    @returns Instance or Array of instances
*/

Galleria.get = function( index ) {
    if ( !!_galleries[ index ] ) {
        return _galleries[ index ];
    } else if ( typeof index !== 'number' ) {
        return _galleries;
    } else {
        Galleria.raise('Gallery index ' + index + ' not found');
    }
};

/**
    Creates a transition to be used in your gallery

    @param {string} name The name of the transition that you will use as an option

    @param {Function} fn The function to be executed in the transition.
    The function contains two arguments, params and complete.
    Use the params Object to integrate the transition, and then call complete when you are done.

*/

Galleria.addTransition = function( name, fn ) {
    _transitions[name] = fn;
};

Galleria.utils = Utils;

/**
    A helper metod for cross-browser logging.
    It uses the console log if available otherwise it falls back to the opera
    debugger and finally <code>alert()</code>

    @example Galleria.log("hello", document.body, [1,2,3]);
*/

Galleria.log = function() {
    try {
        window.console.log.apply( window.console, Utils.array( arguments ) );
    } catch( e ) {
        try {
            window.opera.postError.apply( window.opera, arguments );
        } catch( er ) {
              window.alert( Utils.array( arguments ).split(', ') );
        }
    }
};

/**
    Method for raising errors

    @param {string} msg The message to throw

    @param {boolean} [fatal] Set this to true to override debug settings and display a fatal error
*/

Galleria.raise = function( msg, fatal ) {

    if ( DEBUG || fatal ) {
        var type = fatal ? 'Fatal error' : 'Error';
        throw new Error(type + ': ' + msg);
    }

};

/**
    Adds preload, cache, scale and crop functionality

    @constructor

    @requires jQuery

    @param {number} [id] Optional id to keep track of instances
*/

Galleria.Picture = function( id ) {

    // save the id
    this.id = id || null;

    // the image should be null until loaded
    this.image = null;

    // Create a new container
    this.container = Utils.create('galleria-image');

    // add container styles
    $( this.container ).css({
        overflow: 'hidden',
        position: 'relative' // for IE Standards mode
    });

    // saves the original measurements
    this.original = {
        width: 0,
        height: 0
    };

    // flag when the image is ready
    this.ready = false;

    // flag when the image is loaded
    this.loaded = false;

};

Galleria.Picture.prototype = {

    // the inherited cache object
    cache: {},

    // creates a new image and adds it to cache when loaded
    add: function( src ) {

        var i = 0,
            self = this,

            // create the image
            image = new Image(),
            
            onload = function() {
                
                // force chrome to reload the image in case of cache bug
                // set a limit just in case
                if ( ( !this.width || !this.height ) && i < 1000 ) {
                    i++;
                    $( image ).load( onload ).attr( 'src', src+'?'+new Date().getTime() );
                }

                self.original = {
                    height: this.height,
                    width: this.width
                };

                self.cache[ src ] = src; // will override old cache
                self.loaded = true;
            };

        // force a block display
        $( image ).css( 'display', 'block');
        
        if ( self.cache[ src ] ) {
            // no need to onload if the image is cached
            image.src = src;
            onload.call( image );
            return image;
        }

        // begin preload and insert in cache when done
        $( image ).load( onload ).attr( 'src', src );

        return image;

    },

    // show the image on stage
    show: function() {
        Utils.show( this.image );
    },

    // hide the image
    hide: function() {
        Utils.moveOut( this.image );
    },

    clear: function() {
        this.image = null;
    },

    /**
        Checks if an image is in cache

        @param {string} src The image source path, ex '/path/to/img.jpg'

        @returns {boolean}
    */

    isCached: function( src ) {
        return !!this.cache[src];
    },

    /**
        Loads an image and call the callback when ready.
        Will also add the image to cache.

        @param {string} src The image source path, ex '/path/to/img.jpg'
        @param {Function} callback The function to be executed when the image is loaded & scaled

        @returns The image container (jQuery object)
    */

    load: function(src, callback) {

        // save the instance
        var self = this;

        $( this.container ).empty(true);

        // add the image to cache and hide it
        this.image = this.add( src );
        Utils.hide( this.image );

        // append the image into the container
        $( this.container ).append( this.image );

        // check for loaded image using a timeout
        Utils.wait({
            until: function() {
                // TODO this should be properly tested in Opera
                return self.loaded && self.image.complete && self.original.width && self.image.width;
            },
            success: function() {
                // call success
                window.setTimeout(function() { callback.call( self, self ); }, 50 );
            },
            error: function() {
                window.setTimeout(function() { callback.call( self, self ); }, 50 );
                Galleria.raise('image not loaded in 10 seconds: '+ src);
            },
            timeout: 10000
        });

        // return the container
        return this.container;
    },

    /**
        Scales and crops the image

        @param {Object} options The method takes an object with a number of options:

        <ul>
            <li>width - width of the container</li>
            <li>height - height of the container</li>
            <li>min - minimum scale ratio</li>
            <li>max - maximum scale ratio</li>
            <li>margin - distance in pixels from the image border to the container</li>
            <li>complete - a callback that fires when scaling is complete</li>
            <li>position - positions the image, works like the css background-image property.</li>
            <li>crop - defines how to crop. Can be true, false, 'width' or 'height'</li>
        </ul>

        @returns The image container object (jQuery)
    */

    scale: function( options ) {

        // extend some defaults
        options = $.extend({
            width: 0,
            height: 0,
            min: undef,
            max: undef,
            margin: 0,
            complete: function() {},
            position: 'center',
            crop: false
        }, options);

        // return the element if no image found
        if (!this.image) {
            return this.container;
        }

        // store locale variables of width & height
        var width,
            height,
            self = this,
            $container = $( self.container );

        // wait for the width/height
        Utils.wait({
            until: function() {

                width  = options.width
                    || $container.width()
                    || Utils.parseValue( $container.css('width') );

                height = options.height
                    || $container.height()
                    || Utils.parseValue( $container.css('height') );

                return width && height;
            },
            success: function() {
                // calculate some cropping
                var newWidth = ( width - options.margin * 2 ) / self.original.width,
                    newHeight = ( height - options.margin * 2 ) / self.original.height,
                    cropMap = {
                        'true'  : Math.max( newWidth, newHeight ),
                        'width' : newWidth,
                        'height': newHeight,
                        'false' : Math.min( newWidth, newHeight )
                    },
                    ratio = cropMap[ options.crop.toString() ];

                // allow max_scale_ratio
                if ( options.max ) {
                    ratio = Math.min( options.max, ratio );
                }

                // allow min_scale_ratio
                if ( options.min ) {
                    ratio = Math.max( options.min, ratio );
                }

                $( self.container ).width( width ).height( height );

                // round up the width / height
                $.each( ['width','height'], function( i, m ) {
                    $( self.image )[ m ]( self.image[m] = self[ m ] = Math.round( self.original[ m ] * ratio ) );
                });

                // calculate image_position
                var pos = {},
                    mix = {},
                    getPosition = function(value, measure, margin) {
                        var result = 0;
                        if (/\%/.test(value)) {
                            var flt = parseInt( value, 10 ) / 100,
                                m = self.image[ measure ] || $( self.image )[ measure ]();

                            result = Math.ceil( m * -1 * flt + margin * flt );
                        } else {
                            result = Utils.parseValue( value );
                        }
                        return result;
                    },
                    positionMap = {
                        'top': { top: 0 },
                        'left': { left: 0 },
                        'right': { left: '100%' },
                        'bottom': { top: '100%' }
                    };

                $.each( options.position.toLowerCase().split(' '), function( i, value ) {
                    if ( value === 'center' ) {
                        value = '50%';
                    }
                    pos[i ? 'top' : 'left'] = value;
                });

                $.each( pos, function( i, value ) {
                    if ( positionMap.hasOwnProperty( value ) ) {
                        $.extend( mix, positionMap[ value ] );
                    }
                });

                pos = pos.top ? $.extend( pos, mix ) : mix;

                pos = $.extend({
                    top: '50%',
                    left: '50%'
                }, pos);

                // apply position
                $( self.image ).css({
                    position : 'relative',
                    top :  getPosition(pos.top, 'height', height),
                    left : getPosition(pos.left, 'width', width)
                });

                // show the image
                self.show();

                // flag ready and call the callback
                self.ready = true;
                options.complete.call( self, self );
            },
            error: function() {
                Galleria.raise('Could not scale image: '+self.image.src);
            },
            timeout: 1000
        });
        return this;
    }
};

// our own easings 
$.extend( $.easing, {

    galleria: function (_, t, b, c, d) {
        if ((t/=d/2) < 1) {
            return c/2*t*t*t*t + b;
        }
        return -c/2 * ((t-=2)*t*t*t - 2) + b;
    },

    galleriaIn: function (_, t, b, c, d) {
        return c*(t/=d)*t*t*t + b;
    },

    galleriaOut: function (_, t, b, c, d) {
        return -c * ((t=t/d-1)*t*t*t - 1) + b;
    }

});

// the plugin initializer
$.fn.galleria = function( options ) {

    return this.each(function() {

        var gallery = new Galleria();
        gallery.init( this, options );

    });
};

// Expose
window.Galleria = Galleria;

// phew

}( jQuery ) );(function ($) {
	// constructor
	function InstagramGallery(root, conf)
	{	
		// Private fields ------------------------------------------------------------------

		var $root = $(root),
			$domElem = $root[0],
			_self = this,
			
			$btnBack,
			$btnPlayPause,
			$btnMore,
			$instaInfo,
			$instaPhoto,
			$instaUser,
			$instaCaption,
			$instaFilter,
			$timerBar,
			timerMaxWidth,
			
			curStartIndex = 0,
			numThumbs = 16,
			_imgData,
			
			GALLERIA,
			$galleryStage,
			SLIDESHOW_WAIT = 4000, //milliseconds to show each image
			isPlaying = false,
			isPlayingOverride = false,
			loadMoreTimeout = -1,
			
			$opts = {
				imgData: null,
				eventName: 'instagramDefault',
				showLocation: true,
				defaultMapAddress: '',
				mainImgWidth: 612,
				mainImgHeight: 612
			};
			
		$.extend($opts, conf);

		// Public methods ------------------------------------------------------------------
		
		$.extend(_self, {
			onProgressEvent: function(evt) {
				updateTimerPercent(evt.percent);
			},
			onPlayEvent: function(e) {
				isPlaying = true;
				
				startCountdown();
				setPauseState();
			},
			onPauseEvent: function(e) {
				isPlaying = false;
				clearTimeout(loadMoreTimeout);
				
				stopCountdown();
				setPlayState();
			},
			onImageLoaded: function(e) {
				setInfo();
			},
			onImageDisplayed: function(e) {
				GALLERIA.$('loader').hide();
				if (isPlaying == true) {
					testLoadMore();
				}
			},
			onSlideshowMoreLoaded: function() {
				GALLERIA.unbind('loadfinish');
				GALLERIA.bind('loadfinish', GALLERIA._options.galleriaMgr.onImageLoaded);
				clearTimeout(loadMoreTimeout);
				loadMoreTimeout = setTimeout(resumeSlideshow, 100);
			}
		});

		// Private methods -----------------------------------------------------------------
		function initGalleria() {
			Galleria.addTheme({
			    name: 'classic',
			    init: function(options) {
			        this.$('loader').show().css('opacity', 0.4);
					
					this.bind('play', this._options.galleriaMgr.onPlayEvent);
					this.bind('pause', this._options.galleriaMgr.onPauseEvent);
					this.bind('progress', this._options.galleriaMgr.onProgressEvent);
					this.bind('loadfinish', this._options.galleriaMgr.onImageLoaded);
					this.bind('image', this._options.galleriaMgr.onImageDisplayed);
			        
			        this.bind('loadstart', function(e) {
			            if (!e.cached) {
			                this.$('loader').show();
			            }
			        });
			        
			        this.bind('loadfinish', function(e) {
			            this.$('loader').hide();
			        });
			    }
			});

			var curData = _imgData.slice(curStartIndex,numThumbs);
			$('#galleria').galleria({
				dataSource: curData,
				name: $opts.eventName,
				galleriaMgr: _self,
				preload: 5,
				transition: 'fade',
				width: 960,
				height: $opts.mainImgHeight,
				showInfo: false,
				showCounter: false,
				thumbCrop: true,
				transition: 'none',
				thumbFit: false,
				imageCrop: false //true means images will be scaled to fill the stage, centered and cropped. # false will scale down so the entire image fits.
			});
			
			GALLERIA = Galleria.get(0);
			$galleryStage = $('.galleria-stage');
			
			$('.instagram-galleria-nav').appendTo('.galleria-container').show();
			if ($opts.showLocation == true) {
				$('.instagram-galleria-map').appendTo('.galleria-container').show();
			}
		}
				
		function showGalleria(){
			$galleryStage.show();
		}		
		function testLoadMore() {
			var doLoadMore = GALLERIA.getNext() == 0 && _imgData.length > numThumbs;
//			trace('GALLERIA.getNext(): ' + GALLERIA.getNext());
//			trace('doLoadMore: ' + doLoadMore);
			if (doLoadMore) {
				loadMoreTimeout = setTimeout(loadMoreSlideshow, SLIDESHOW_WAIT);
			}
		}
		function setInfo() {
			var data = GALLERIA.getData(GALLERIA.getIndex());
			$instaPhoto.attr('src', data.profile_picture);
			$instaUser.html(data.username);
			$instaCaption.html(data.caption);
			$instaFilter.html(data.filter);
			$instaInfo.show();
			
			if ($opts.showLocation == true) {
				var mapUrl = "http://maps.googleapis.com/maps/api/staticmap?center="+$opts.defaultMapAddress+"&zoom=14&size=313x208&maptype=roadmap&sensor=false";
				if (data.location && data.location.latitude) {
					var latLong = data.location.latitude + "," + data.location.longitude;
					mapUrl = "http://maps.googleapis.com/maps/api/staticmap?center="+latLong+"&zoom=14&size=313x208&maptype=roadmap&sensor=false&markers=color:blue|"+latLong;
					
					$instaMapImg.attr("src", mapUrl);
					$instaMapImg.css('opacity', 1);
					$instaMapMsg.hide();
				} else {
					$instaMapImg.attr("src", mapUrl);
					$instaMapImg.css('opacity', .5);
					$instaMapMsg.show();
				}
			}
		}
		function resumeSlideshow() {
			GALLERIA.play(SLIDESHOW_WAIT);
		}
		function doPlay(evt) {
			GALLERIA.play(SLIDESHOW_WAIT);
			testLoadMore();
			trackClick(evt, 'Play SlideShow ' + $opts.eventName);
			
			return false;
		}
		function doPause() {
			GALLERIA.pause();
			
			return false;
		}
		function setPauseState() {
			$btnPlayPause.unbind();
			$btnPlayPause.addClass("show-paused")
				.find('.slideshow-caption').html("STOP");
			$btnPlayPause.click(doPause);
		}
		function setPlayState() {
			if (isPlayingOverride) {
				isPlayingOverride = false;
			} else {
				$btnPlayPause.unbind();
				$btnPlayPause.removeClass("show-paused").find('.slideshow-caption').html("SLIDESHOW");
				$btnPlayPause.click(doPlay);
			}
		}
		function loadPrev(evt) {
			GALLERIA.pause();
			curStartIndex = Math.max(curStartIndex-numThumbs, 0);
			loadCurrentSet();
			updateNavStates();
			
			return false;
		}
		function loadMoreSlideshow() {
			isPlayingOverride = true;
			GALLERIA.pause();
			GALLERIA.unbind('loadfinish');
			GALLERIA.bind('loadfinish', GALLERIA._options.galleriaMgr.onSlideshowMoreLoaded);
			
			loadMore();
		}
		function loadMore(evt) {
			GALLERIA.pause();
			curStartIndex = curStartIndex + numThumbs;
			if (curStartIndex >= _imgData.length) {
				curStartIndex = 0;
			}
			loadCurrentSet();
			updateNavStates();
			
			return false;
		}
		function loadCurrentSet() {
			var curData = _imgData.slice(curStartIndex,curStartIndex+numThumbs);
			GALLERIA.load(curData);
		}
		function updateNavStates() {
			$btnBack.unbind();
			if (curStartIndex > 0) {
	        	$btnBack.bind("click", loadPrev);
				$btnBack.removeClass('inactive');
			} else {
				$btnBack.addClass('inactive');
			}
			$btnMore.unbind();
			var hasMore = (curStartIndex + numThumbs) < _imgData.length;
			if (hasMore) {
	        	$btnMore.bind("click", loadMore);
				$btnMore.removeClass('inactive');
			} else {
				$btnMore.addClass('inactive');
			}
		}
		
		function startCountdown(){
			$timerBar.show();
		}
		
		function updateTimerPercent(percent){
			var maxWidth = timerMaxWidth || initTimerMaxWidth(); 
			var displayPercent = (100-percent) / 100;
			$timerBar.width(maxWidth * displayPercent);
		}
		
		function stopCountdown(){
			$timerBar.hide();
		}
		
		function initTimerMaxWidth(){
			timerMaxWidth = parseInt($timerBar.css("width"));
			return timerMaxWidth;
		}
		
		function trackClick(evt, name) {
			/*
			try {
				var s=s_gi(s_account);
				s.linkTrackVars="eVar34,campaign,events"; 
				s.linkTrackEvents="event38";
				s.eVar34=name; 
				s.events="event38";
				void(s.tl(evt,'o',"InstagramClick||"+name));
			} catch (e) {}
			*/
		}
		
		function init() {
			_imgData = $opts.imgData;
			
			//trace('_imgData.length: ' + _imgData.length);
			
			if (_imgData == null || _imgData.length == 0) {
				//trace('no images');
			} else {
				$instaInfo = $('.instagram-info');
				$instaPhoto = $(".instagram-profile-pic img");
				$instaUser = $(".instagram-username");
				$instaCaption = $(".instagram-caption");
				$instaFilter = $(".instagram-filter");
				
				$instaMap = $('.instagram-galleria-map');
				$instaMapImg = $('.instagram-galleria-map img');
				$instaMapMsg = $('.instagram-galleria-map .map-no-data-message');
				
	        	$btnBack = $(".instagram-back");//.bind("click", loadPrev);
	        	$btnPlayPause = $(".instagram-slideshow").bind("click", doPlay);
	        	$btnMore = $(".instagram-more");//.bind("click", loadMore);
				updateNavStates();
			
				$timerWrapper = $('div.timer-wrapper');
				$timerBar = $('div.timer-bar');
				
				initGalleria();
			}
		}

		// Initialization ------------------------------------------------------------------
		init();
	};

	// jQuery plugin implementation
	$.fn.instagramGallery = function(conf)
	{ 
		this.each(function() {
			var $instance = new InstagramGallery(this, conf);
			$(this).data("instagramGallery", $instance);
		});
		
		return this;
	};
})(jQuery);
(function($)
{	
	// constructor
	function v1Poll(root, conf)
	{	
		// Private fields ------------------------------------------------------------------
		var _root = $(root),
			_domElement = _root[0],
			_self = this,
			_resultContainer = _root.find('.v1-poll-result-container'),
			_questionElem = _root.find('.v1-poll-question'),
			_answerElem = _root.find('.v1-poll-answers'),
			_requestUrl = "/scrum/poll_vote.php",
			_opts = {
				poll_id: 0,
				debug: false
			}
			;
			$.extend(_opts, conf);
	
		// Public methods ------------------------------------------------------------------
//		$.extend(_self, {
//		});
	
		// Private methods -----------------------------------------------------------------
		function debug(str)
		{
			if (_opts.debug == true) {
				try {
					alert(str);
				} 
				catch (e) {
					alert("debug: " + str);
				}
			}
		};
			
		// generic binding function
		
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
				
			$(_self).bind(name, fn);
		};
		
		function showPollAnswers()
		{
			_resultContainer.hide();
			_answerElem.show();
		};
		
		function answerPoll(){
            var response_id = _answerElem.find('input[name=poll]:checked').val();
			if (typeof(response_id) == "undefined") {
				alert("Please select an option.");
			}
			else {
				showPollResults(response_id);
			}
			
			return false;
		};
		
		function showPollResults(response_id){
			var submitData = "poll_id=" + _opts.poll_id;
			
			if (typeof(response_id) != "undefined") {
				submitData += "&response_id="+response_id;
			}
			
            $.ajax({
            	url: _requestUrl,
                cache: false,
                data: submitData,
                error: function(xhr, status, exception) {
            		doRequestError(status, exception);
            	},
                success: function(responseHtml) {
            		var isValidResponse = responseHtml.indexOf("poll-results") != -1;
					if(isValidResponse == true) {
	            		_answerElem.hide();
						_resultContainer.html(responseHtml);
						_resultContainer.fadeIn();
						if (userHasVoted() == true) {
							showThanks();
						} else {
							showAnswersLink();
						}
					} else {
						doRequestError("error", "invalid response");
					}
                }
            });
			
			return false;
		};
		
		function showPollResultsOld(submitAnswer){
			//submitAnswer is false if the user clicks the "view results" link
			
            // make ajax request to vote and retrieve results
            var response_id = _answerElem.find('input[name=poll]:checked').val();
            if (!response_id || submitAnswer == false) response_id = '';
			debug("response_id: " + response_id);


            $.ajax({
            	url: requestUrl,
                cache: false,
                data: "poll_id="+_opts.poll_id+"&response_id="+response_id,
                error: function(xhr, status, exception) {
            		doRequestError(status, exception);
            	},
                success: function(responseHtml) {
            		var isValidResponse = responseHtml.indexOf("poll-results") != -1;
					//alert("isValidResponse: " + isValidResponse);
					if(isValidResponse == true) {
	            		_answerElem.hide();
						_resultContainer.html(responseHtml);
						_resultContainer.fadeIn();
						if (userHasVoted() == true) {
							showThanks();
						} else {
							showAnswersLink();
						}
					} else {
						doRequestError("error", "invalid response");
					}
                }
            });
			
			return false;
		};
		
		function doRequestError(status, exception) {
			var errorHtml = "<div class='v1-poll-error'><p><b>Sorry...</b><br/>We're experiencing technical difficulties. Please try again later.</p></div>";
			errorHtml += '<span style="display:none;">status: ' + status + '; exception: ' + exception + '</span>';
			_questionElem.hide();
			_answerElem.hide();
    		_resultContainer.html(errorHtml);
			_resultContainer.fadeIn();
		};
		
		function showAnswersLink(){
			var linkHtml = '<div class="arrow" style="margin:7px 0 0 10px;"><a href="#" class="pollVoteNowLink">Vote Now</a></div>';
			_resultContainer.append(linkHtml);
			_root.find(".pollVoteNowLink").click(function(){
				showPollAnswers();
				return false;
			});
		};
		
		function showThanks(){
			var thanksHtml= '<p class="thanks" style="margin:7px 0 0 10px;">Thanks for voting!</p>';
			_resultContainer.append(thanksHtml);
		};
		
//		function enableButton(){
//			$(".v1-pollVoteButton", _root).removeClass("buttonInactive");
//			$(".v1-pollVoteButton", _root).click(showPollResults);
//		};
		
		function userHasVoted(){
			var hasVoted = false;
			
            var polls = $.cookie('polls');
            if (polls) {
				var pollsVoted = polls.split('|');
			
                $.each(pollsVoted, function(key, value) {
                    if (value == _opts.poll_id) {
						hasVoted = true;
					}
                });
            }
			return hasVoted;
		};
	
		function init()
		{
			if (_opts.poll_id == -1) _requestUrl = "/assets/js/v1/fake_poll_response.html";
			
            // see if user has already voted	
            if (userHasVoted() == true) {
                showPollResults();
            } else {
                //_answerElem.find("input").click(enableButton);
				
                _root.find(".v1-pollVoteButton").click(answerPoll);
			
                _root.find(".v1-pollShowResultsLink").click(showPollResults);
				
                showPollAnswers();
            }
		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};
	
	// jQuery plugin implementation
	$.fn.v1Poll = function(conf)
	{
		var opts = { };
		$.extend(opts, conf);
		
		this.each(function()
		{
			var $instance = new v1Poll($(this), opts);
		});
		
		return this;
	};
})(jQuery);
var v1TopNav = {
	defaultSearchFieldValue: "Search sportsnet.ca",
	
	onSearchFocus: function() {
		if ($.trim($("#v1topnavSearchField").val()) == v1TopNav.defaultSearchFieldValue) {
			$("#v1topnavSearchField").val("");
		}
	},
	
	onSearchBlur: function() {
		if ($.trim($("#v1topnavSearchField").val()) == "") {
			$("#v1topnavSearchField").val(v1TopNav.defaultSearchFieldValue);
		}
	},
	
	sponsorExpand: function(_height) {
		var status = "success";
		try {
			$("div#expandoAd").height(_height);
			if ($("div#expandoAd").height() != _height) {
				status = "failure (height: " + $("div#expandoAd").height() + ")";
			}
		} catch(e) {
			status = "failure - height not set";
		}
		return status;
	},
	
	sponsorCollapse: function() {
		var status = "success";
		try {
			$("div#expandoAd").height(47);
			if ($("div#expandoAd").height() != 47) {
				status = "failure (height: " + $("div#expandoAd").height() + ")";
			}
		} catch(e) {
			status = "failure - height not set";
		}
		return status;
	},
	
	categoryExpand: function(_height) {
		var status = "success";
		try {
			$("div.catgory_bar_ad").height(_height);
			if ($("div.catgory_bar_ad").height() != _height) {
				status = "failure (height: " + $("div.catgory_bar_ad").height() + ")";
			}
		} catch(e) {
			status = "failure - height not set";
		}
		return status;
	},
	
	categoryCollapse: function() {
		var status = "success";
		try {
			$("div.catgory_bar_ad").height(47);
			if ($("div.catgory_bar_ad").height() != 47) {
				status = "failure (height: " + $("div.catgory_bar_ad").height() + ")";
			}
		} catch(e) {
			status = "failure - height not set";
		}
		return status;
	},
	
	sliverExpand: function(_height) {
		var status = "success";
		try {
			$("div.hidden_sliver_ad").height(_height);
			if ($("div.hidden_sliver_ad").height() != _height) {
				status = "failure (height: " + $("div.hidden_sliver_ad").height() + ")";
			}
		} catch(e) {
			status = "failure - height not set";
		}
		return status;
	},
	
	sliverCollapse: function() {
		var status = "success";
		try {
			$("div.hidden_sliver_ad").height(47);
			if ($("div.hidden_sliver_ad").height() != 47) {
				status = "failure (height: " + $("div.hidden_sliver_ad").height() + ")";
			}
		} catch(e) {
			status = "failure - height not set";
		}
		return status;
	}
};

var v1Tabs = {
	oldHtml: '', //container for the current content - reverts to this on error
	loadingHtml: '<div class="v1-ajax-loading" style="height:{height}px;"></div>',
	errorMessage: 'Sorry...\nWe\'re experiencing technical difficulties. Please try again later.',
	
	loadContent: function(contentUrl) {
		//valid contentId's are the property names of contentUrls
		v1Tabs.showLoading();
		$.ajax({
			url: contentUrl,
			cache: false,
			success: v1Tabs.onLoad,
			error: v1Tabs.onLoadError
		});
	},
	
	showLoading: function(){
		var curContentDiv = $("div.v1TabsWidget div.contentHolder");
		v1Tabs.oldHtml = curContentDiv.html();
		
		var contentHeight = curContentDiv.height();
		curContentDiv.attr("style", "height:"+contentHeight+"px");
		var curLoadingHtml = v1Tabs.loadingHtml.replace("{height}", contentHeight);
		curContentDiv.html(curLoadingHtml);
	},
	
	onLoad: function(responseHtml){
		v1Tabs.curThumb = -1;
		
		var curContentDiv = $("div.v1TabsWidget div.contentHolder");
		curContentDiv.html(responseHtml);
		curContentDiv.attr("style", "");
	},
	
	onLoadError: function(xhr, status, exception){
		//alert("error; status: " + status + "; exception: " + exception);
		alert(v1Tabs.errorMessage);
		$("div.v1TabsWidget div.contentHolder").html(v1Tabs.oldHtml);
	}
};

var v1TabsBox = function (div) {
	this.$div = div;
	this.$curMenuLists = $(this.$div).find("li.v1TabsMenu");
	
	this.$curMenuLists.css("cursor", "pointer");
	$(this.$curMenuLists).click(function(evt) {
									var curContentDiv = $(this).closest(".v1TabsWidget").find("div.v1TabsHolder");
									var index = $(this).parent().children().index($(this));
									var contentLists = $(this).closest(".v1TabsWidget").find("div.v1TabsContent");
									
									var content = $(contentLists[index]).html();
									$(curContentDiv[0]).html(content.replace("<!-- //", "").replace("// -->", ""));
								});
	
};

var v1ToggleBox = function (div) {
	this.$div = div;
	this.$curMenuLists = $(this.$div).find("li.v1TabsMenu");
	
	this.$curMenuLists.css("cursor", "pointer");
	$(this.$curMenuLists).click(function(evt) {
									var curContentDiv = $(this).closest(".v1TabsWidget").find("div.v1TabsHolder");
									var index = $(this).parent().children().index($(this));
									var contentLists = $(this).closest(".v1TabsWidget").find("div.v1TabsContent");
									
									$(contentLists).each( function(index) { this.style.display = 'none'; } );
									$(contentLists[index]).css('display', 'inline');
								});
	
};
var v1TopStories = {
	//location of HTML files to be loaded into the content div
	contentUrls: {
		topStories: "/assets/includes/topstories_videos/topstories.php",
		mostPopular: "/assets/includes/topstories_videos/mostpopular.php",
		latestVideos: "/assets/includes/topstories_videos/latestvideos.php"
	},
	//location of RSS files associated with each view (used to update href in the RSS link)
	//if there is no RSS feed for a view, set the value to an empty string
	rssUrls: {
		topStories: "/index-pics-{region}.xml",
		mostPopular: "",
		latestVideos: "/video/videos.xml"
	},
	oldHtml: '', //container for the current content - reverts to this on error
	loadingHtml: '<div class="v1-ajax-loading" style="height:{height}px;"></div>',
	errorMessage: 'Sorry...\nWe\'re experiencing technical difficulties. Please try again later.',
	
	//for video player/thumbnail behaviours
	curThumb: -1,
	
	loadContent: function(contentId,bricCategory,videoString,storyType) {
		v1TopStories.newContentId = contentId; //store this for RSS update
		
		//valid contentId's are the property names of contentUrls
		v1TopStories.showLoading();
		$.ajax({
			url: v1TopStories.contentUrls[contentId]+"?cat_uri="+bricCategory+"&vid_path="+videoString+"&story_type="+storyType,
			cache: false,
			success: v1TopStories.onLoad,
			error: v1TopStories.onLoadError
		});
	},
	
	showLoading: function(){
		var curContentDiv = $("div.v1TopStoriesWidget div.contentHolder");
		v1TopStories.oldHtml = curContentDiv.html();
		
		var contentHeight = curContentDiv.height();
		curContentDiv.attr("style", "height:"+contentHeight+"px");
		var curLoadingHtml = v1TopStories.loadingHtml.replace("{height}", contentHeight);
		curContentDiv.html(curLoadingHtml);
	},
	
	onLoad: function(responseHtml){
		v1TopStories.curThumb = -1;
		
		var curContentDiv = $("div.v1TopStoriesWidget div.contentHolder");
		curContentDiv.html(responseHtml);
		curContentDiv.attr("style", "");
		
		v1TopStories.updateRssLink();
	},
	
	insertRegion: function(_str){
		var userRegion = $.cookies.get("sn_region");
		//alert('userRegion: ' + userRegion);
		userRegion = (userRegion != null) ? userRegion.toLowerCase() : "ontario";
		
		_str = _str.replace("{region}", userRegion);
		return _str;
	},
	
	updateRssLink: function(){
		var rssLink = $("div.v1TopStoriesWidget a.rssLink");
		var newRssHref = v1TopStories.rssUrls[v1TopStories.newContentId];
		
		if (newRssHref == "" || typeof(newRssHref) == "undefined") {
			rssLink.hide();
		} else {
			newRssHref = v1TopStories.insertRegion(newRssHref);
			rssLink.attr('href', newRssHref);
			rssLink.show();
		}
	},
	
	onLoadError: function(xhr, status, exception){
		//alert("error; status: " + status + "; exception: " + exception);
		alert(v1TopStories.errorMessage);
		$("div.v1TopStoriesWidget div.contentHolder").html(v1TopStories.oldHtml);
	},
	
	updateCurThumb: function(thumbnail){
		if (v1TopStories.curThumb == -1) {
			v1TopStories.curThumb = $($("ul.v1-video-thumbnails a")[0]); //first() is in v1.4
		}
		
		v1TopStories.curThumb.find("div.available").show();
		v1TopStories.curThumb.find("div.current").hide();
		
		v1TopStories.curThumb = $(thumbnail);
		v1TopStories.curThumb.find("div.available").hide();
		v1TopStories.curThumb.find("div.current").show();
	},
	
	onThumbnailClick: function(thumbnail, playerId) {
		//have to use a try/catch since IE's typeof returns "object" instead of "undefined"
		try {
			var player = window["bc-" + playerId];
			var playerModule = player.getModule(APIModules.VIDEO_PLAYER);
			
			var curVideoId = playerModule.getCurrentVideo().id;
			
			if (curVideoId != thumbnail.id) {
				v1TopStories.updateCurThumb(thumbnail);
				playerModule.loadVideo(thumbnail.id);
			}
		} catch (e) {
			alert("Sorry - the video player is still loading. Please try again in a few seconds.");
		}
	}
};
var socialMedia = {
	//location of HTML files to be loaded into the content div
	contentUrls: {
		twitter: "/ui_components/v1/social_media/twitter_inc.php",
		twittertalent: "/assets/includes/twitter/talent_v1.html",
		twitterhockey: "/assets/includes/twitter/hockey_v1.html",
		twitternba: "/assets/includes/twitter/nba_v1.html",
		twittersoccer: "/assets/includes/twitter/soccer_v1.html",
		twittermma: "/assets/includes/twitter/mma_v1.html",
		facebook: "/assets/includes/facebook.php"
	},		
	oldHtml: '', //container for the current content - reverts to this on error
	loadingHtml: '<div class="v1-ajax-loading" style="height:{height}px;"></div>',
	errorMessage: 'Sorry...\nWe\'re experiencing technical difficulties. Please try again later.',

    retweet: function(id) {
        var aTweet = $("li#tweet_"+id+" span.tweet").html().replace(/<\/?[^>]+>/gi, '');
        var aTwit = $("li#tweet_"+id+" span.twit").html();

        var tURL = "http://twitter.com/home?status=" + encodeURIComponent('RT @'+aTwit+' '+aTweet);
        var w = window.open(tURL, "twitterWindow");
        return false;
    },

	loadContent: function(siteId){
		//valid siteId's are the propery names of contentUrls
		socialMedia.showLoading();
		$.ajax({
			url: socialMedia.contentUrls[siteId],
			cache: false,
			success: socialMedia.onLoad,
			error: socialMedia.onLoadError
		});
	},
	
	showLoading: function(){
		var curContentDiv = $("div.socialMediaWidget div.contentHolder");
		socialMedia.oldHtml = curContentDiv.html();
		
		var contentHeight = curContentDiv.height();
		var curLoadingHtml = socialMedia.loadingHtml.replace("{height}", contentHeight);
		curContentDiv.html(curLoadingHtml);
	},
	
	onLoad: function(responseHtml){
		$("div.socialMediaWidget div.contentHolder").html(responseHtml);
	},
	
	onLoadError: function(xhr, status, exception){
		//alert("error; status: " + status + "; exception: " + exception);
		alert(socialMedia.errorMessage);
		$("div.socialMediaWidget div.contentHolder").html(socialMedia.oldHtml);
	}
};
var v1Rotation = {
	curPageIndex: 0,
	numPages: -1,
	curMediaElem: {},
	curVideoId: -1,
	curTabElem: {},
	curRelatedLinkElem: {},
	autoDelay: 0, //milliseconds between auto-rotation (0 = no auto-rotation)
	autoTimer: -1, //reference to interval id
	fadeDuration: 500, //milliseconds to fade up new content - not currently used due to IE issues with jquery fade
	
	init: function(_autoDelay) {
		v1Rotation.mediaElems = $("div.v1-rotation div.mediaWrapper > div[class*='-container']");
		for (var i=0; i<v1Rotation.mediaElems.length; i++) {
			v1Rotation.mediaElems[i] = $(v1Rotation.mediaElems[i]);
		}
		v1Rotation.tabElems = $("div.v1-rotation div.tabWrapper a");
		for (var i=0; i<v1Rotation.tabElems.length; i++) {
			v1Rotation.tabElems[i] = $(v1Rotation.tabElems[i]);
		}
		v1Rotation.relatedLinkElems = $("div.v1-rotation div.relatedLinksWrapper div");
		for (var i=0; i<v1Rotation.relatedLinkElems.length; i++) {
			v1Rotation.relatedLinkElems[i] = $(v1Rotation.relatedLinkElems[i]);
		}
		
		if (typeof(_autoDelay) != "undefined") {
			if (parseInt(_autoDelay) > 0) {
				v1Rotation.numPages = v1Rotation.mediaElems.length;
				v1Rotation.autoDelay = parseInt(_autoDelay);
				v1Rotation.startTimer();
			}
		}
	},
		
	startTimer: function(){
		clearInterval(v1Rotation.autoTimer);
		v1Rotation.autoTimer = setInterval(v1Rotation.goNext, v1Rotation.autoDelay);		
	},
		
	stopTimer: function(){
		clearInterval(v1Rotation.autoTimer);
	},
		
	goNext: function(){
		var newIndex = v1Rotation.curPageIndex + 1;
		if (newIndex > v1Rotation.numPages - 1) {
			newIndex = 0;
		}
		v1Rotation.setPage(newIndex, true);
	},
		
	setPage: function(index, doContinue) {
		if (v1Rotation.curPageIndex != index) {
			v1Rotation.hideElems(v1Rotation.curPageIndex);
			v1Rotation.curPageIndex = index;
			v1Rotation.showElems(v1Rotation.curPageIndex);
			
			var continueAuto = (typeof(doContinue) != "undefined") ? doContinue : false;
			if (continueAuto == false) {
				v1Rotation.stopTimer();
			}
		}
	},
		
	hideElems: function(index) {
		var hiddenVideoHolder = v1Rotation.mediaElems[index].find("a.video-image-holder:hidden");
		if (hiddenVideoHolder.length != 0) {
			//have to use a try/catch since IE's typeof returns "object" instead of "undefined"
			try {
				//need to halt video so we don't get phantom audio in IE
				var player = window["v1RotationPlayer-" + v1Rotation.curVideoId];
				var playerModule = player.getModule(APIModules.VIDEO_PLAYER);
				playerModule.cueVideo(v1Rotation.curVideoId); //incredibly, calling stop() thru javascript API doesn't work even though the function exists ???
			} catch (e) {
				//nothing to do
			}
			v1Rotation.mediaElems[index].find("div[id*='v1RotationVideoContainer']").hide();
			hiddenVideoHolder.show();
		}
		v1Rotation.mediaElems[index].hide();
		v1Rotation.tabElems[index].addClass("active");
		v1Rotation.tabElems[index].removeClass("on");
		v1Rotation.relatedLinkElems[index].hide();
	},
		
	showElems: function(index) {
		v1Rotation.mediaElems[index].show();
		v1Rotation.tabElems[index].addClass("on");
		v1Rotation.tabElems[index].removeClass("active");
		v1Rotation.relatedLinkElems[index].show();
	},
		
	showVideo: function(anchor, videoId){
		var _anchor = $(anchor);
		var videoContainer = _anchor.parent().find("div#v1RotationVideoContainer-" + videoId);
		_anchor.hide();
		videoContainer.show();
		v1Rotation.curVideoId = videoId;
		
		//have to use a try/catch since IE's typeof returns "object" instead of "undefined"
		try {
			var player = window["v1RotationPlayer-" + v1Rotation.curVideoId];
			var playerModule = player.getModule(APIModules.VIDEO_PLAYER);
			playerModule.play();
		} catch (e) {
			//nothing to do
		}
	}
};

/*########################################################*/
/*-------------- top stories/videos rotation -------------*/

var v2Rotation = {
	curSectionId: 'Articles',
	curPageIndexArticles: 0,
	numPagesArticles: -1,
	curPageIndexVideos: 0,
	numPagesVideos: -1,
	curVideoId: -1,
	isPlaying: false,
	
	init: function() {
		//tabs
		$('.v2-rotation .top-story-tabs a.stories').click(function() {
				v2Rotation.setSection('Articles');
				return false;
			}
		);
		$('.v2-rotation .top-story-tabs a.videos').click(function() {
				v2Rotation.setSection('Videos');
				return false;
			}
		);
		
		//article thumbs
		this.mediaElemsArticles = $('.v2-articles-body .v2-rotation-media > div');
		for (var i=0; i<this.mediaElemsArticles.length; i++) {
			this.mediaElemsArticles[i] = $(this.mediaElemsArticles[i]);
			var curAnchor = this.mediaElemsArticles[i].find('.v2-top-video-still-container');
			if (curAnchor.length == 0) {
				curAnchor = null;
			}
			this.mediaElemsArticles[i].data({
					'index': i,
					'anchor': curAnchor
				});
		}		
		this.tabElemsArticles = $(".v2-articles-body .v2-rotation-thumbs .thumb > a");
		for (var i=0; i<this.tabElemsArticles.length; i++) {
			this.tabElemsArticles[i] = $(this.tabElemsArticles[i]);
			this.tabElemsArticles[i]
				.data('index', i)
				.mouseenter(function(){
					$this = $(this);
					
					v2Rotation.setPageOnMouseover("Articles", $(this).data('index'));
					
					if (v2Rotation.curPageIndexArticles != $this.data('index')) {
						$this.parent().addClass('active');
					}
					
					return false;
				})
				.mouseleave(function(){
					$this = $(this);
					if (v2Rotation.curPageIndexArticles != $this.data('index')) {
						$this.parent().removeClass('active');
					}
					return false;
				})
				.click(function(){
					v2Rotation.setPageOrPlay('Articles', $(this));
					return false;
				});
		}
		$(".v2-articles-body .v2-rotation-media .v2-top-story-caption h1 a")
			.each(function(index){
				$(this).data('index', index);
			})
			.click(function(){
				v2Rotation.setPageOrPlay('Articles', $(this));
				return false;
			});
		
		//video thumbs
		var selectorString1 = '.v2-videos-body .v2-rotation-media > div'; //videos tab
		var selectorString2 = '.v2-video-story'; //videos in main rotation
//		v2Rotation.mediaElemsVideos = $(selectorString1 + ', ' + selectorString2);
		this.mediaElemsVideos = $(selectorString1);
		for (var i=0; i<this.mediaElemsVideos.length; i++) {
			this.mediaElemsVideos[i] = $(this.mediaElemsVideos[i]);
			var curAnchor = this.mediaElemsVideos[i].find('.v2-top-video-still-container');
			this.mediaElemsVideos[i].data({
					'index': i,
					'anchor': curAnchor
				});
		}
		var thumbSelectorString1 = '.v2-videos-body .v2-rotation-thumbs .thumb > a'; //videos tab
		var thumbSelectorString2 = '.video-thumb'; //videos in main rotation
//		v2Rotation.tabElemsVideos = $(thumbSelectorString1 + ', ' + thumbSelectorString2);
		this.tabElemsVideos = $(thumbSelectorString1);
		for (var i=0; i<this.tabElemsVideos.length; i++) {
			this.tabElemsVideos[i] = $(this.tabElemsVideos[i]);
			this.tabElemsVideos[i]
				.data({
					'index': i
				})
				.mouseenter(function(){
					$this = $(this);
					v2Rotation.setPageOnMouseover("Videos", $this.data('index'));
					
					if (v2Rotation.curPageIndexVideos != $this.data('index')) {
						$this.parent().addClass('active');
					}
					return false;
				})
				.mouseleave(function(){
					$this = $(this);
					if (v2Rotation.curPageIndexVideos != $this.data('index')) {
						$this.parent().removeClass('active');
					}
					return false;
				})
				.click(function(){
					v2Rotation.setPageOrPlay('Videos', $(this));
					return false;
				});
		}
		$(".v2-videos-body .v2-rotation-media .v2-top-video-caption h1 a")
			.each(function(index){
				$(this).data('index', index);
			})
			.click(function(){
				v2Rotation.setPageOrPlay('Videos', $(this));
				return false;
			});
	},
		
	setSection: function(secId) {
		if (this.curSectionId != secId) {
			this.stopVideo();

			$('.v2-'+this.curSectionId.toLowerCase()+'-body').hide();
			this.curSectionId = secId;
			$('.v2-'+this.curSectionId.toLowerCase()+'-body').show();
		}
	},
	
	setPageOnMouseover: function(type, index) {
		if (this.isPlaying == false) {
			this.setPage(type, index);			
		}
	},
	
	setPageOrPlay: function(type, $elem) {
		var index = $elem.data('index');
		if (this["curPageIndex"+type] == index) {
			if (this.isPlaying == false) {
				var anchorElem = this["mediaElems"+type][index].data('anchor');
				if (anchorElem != null) {
					this.showVideo(anchorElem[0], anchorElem.attr('href'));
				} else {
					window.location.href = $elem.attr('href');
				}
			}
		} else {
			this.setPage(type, index);
		}
	},
		
	setPage: function(type, index) {
		if (this["curPageIndex"+type] != index) {
			this.hideElems(type, this["curPageIndex"+type]);
			this["curPageIndex"+type] = index;
			this.showElems(type, this["curPageIndex"+type]);
		}
	},
		
	hideElems: function(type, index) {
		this.stopVideo();
		
		this["mediaElems"+type][index].hide();
		this["tabElems"+type][index].parent().removeClass("active");
	},
	
	stopVideo: function() {
		var type = this.curSectionId;
		var index = this['curPageIndex'+type];
		
		if (this.isPlaying == true) {
			var hiddenVideoHolder = this["mediaElems" + type][index].find("a.v2-top-video-still-container:hidden");
			
			if (hiddenVideoHolder.length != 0) {
				//have to use a try/catch since IE's typeof returns "object" instead of "undefined"
				try {
					//need to halt video so we don't get phantom audio in IE
					var player = window["v2RotationPlayer-" + this.curVideoId];
					var playerModule = player.getModule(APIModules.VIDEO_PLAYER);
					playerModule.cueVideo(this.curVideoId); //incredibly, calling stop() thru javascript API doesn't work even though the function exists ???
				} 
				catch (e) {
				//nothing to do
				}
				this["mediaElems" + type][index].find("div[id*='v2RotationVideoContainer']").hide();
				hiddenVideoHolder.show();
				this.isPlaying = false;
			}
		}
	},
		
	showElems: function(type, index) {
		this["mediaElems"+type][index].show();
		this["tabElems"+type][index].parent().addClass("active");
	},
		
	showVideo: function(anchor, videoId){
		var _anchor = $(anchor);
		var videoContainer = _anchor.parent().find("div#v2RotationVideoContainer-" + videoId);
		_anchor.hide();
		videoContainer.show();
		this.curVideoId = videoId;
		
		if (this.isPlaying == false) {
			this.isPlaying = true;
		}
	}
};
/*########################################################*/
/*-------------- listen live radio widget ----------------*/
/*
requires:
-jquery.urldecoder.min.js
 */

var v2RadioPromo = {
	//milliseconds to wait before assuming the call has failed (getJSON error handler is defined, but we do this just in case)
	timeoutWait: 3000,
	timeoutId: -1,
	stationCallSign: "cjcl",
	//show this on AJAX errors
	defaultData: {
		name: "Open the Sportsnet Radio Web Player",
		show_image_url: "/assets/img/v2/logo_fan590.png"
	},
	
	init: function() {
		var uRegion = $.cookie("sn_region") || 'ontario';
		uRegion = uRegion.toLowerCase();
		if (uRegion == 'west' || uRegion == 'pacific') {
			this.defaultData.show_image_url = "/assets/img/v2/logo_fan960.png";
			this.stationCallSign = 'cfac';
			$('.v2-radio-promo-wrap a').attr('href', 'http://player.rogersradio.ca/' + this.stationCallSign + '/');
		}
		
		this.timeoutId = setTimeout(function() {
				v2RadioPromo.onTimeout();
			}, this.timeoutWait);
		
		this.loadData();
	},
	
	loadData: function(station_call_name) {
        $.ajax({
            url: "http://player.rogersradio.ca/" + this.stationCallSign + "/widget/on_air",
            dataType: 'jsonp',
            jsonp: 'jsoncallback',
            jsonpCallback: 'll_onair',
            success: function(data) {
                v2RadioPromo.onDataLoaded(data);
            }
        })
		.error(function() {
			v2RadioPromo.onDataLoadError();
		});

		return false;
	},
	
	onDataLoaded: function(dataObj){
		clearTimeout(this.timeoutId);
		var displayData = dataObj;
		if (!(displayData.name && displayData.show_image_url)) {
			displayData = this.defaultData;
		}
		this.updateDisplay(displayData);
	},
	
	onTimeout: function() {
		this.onDataLoadError();
	},
	
	onDataLoadError: function() {
		clearTimeout(this.timeoutId);
		this.updateDisplay(this.defaultData);
	},
	
	updateDisplay: function(dataObj) {
		$('.promo-text').html(dataObj.name);
		var imgSrc = dataObj.show_image_url ? $.url.decode(dataObj.show_image_url) : '';
		var $img = $('.promo-image-wrapper img');
		if (imgSrc != '') {
			$img.attr({
					"src": imgSrc,
					"title": dataObj.name
					}
				)
				.show();
		} else {
			$img.hide();
		}
	},
	
	openPlayer: function() {
		var WEBPLAYER_URL = 'http://player.rogersradio.ca/' + this.stationCallSign + '/';
		var WEBPLAYER_WIDTH = 1000;
		var WEBPLAYER_HEIGHT = 725;
		var win = window.open(WEBPLAYER_URL,'ListenLiveWindow','width='+WEBPLAYER_WIDTH+',height='+WEBPLAYER_HEIGHT+',status=0,scrollbars=0,resizable=0');
		win.focus();
	}
};
/*########################################################*/
/*-------------- tabbed content groups -------------------*/
/*

requirements:
-tab/content groups are associated by a unique identifier in their wrapper class names
	-unique id is case sensitive
	-tab/content items must appear in the same order in the DOM (i.e., the first tab hides/shows the first item, etc.)
-tab container
	-has class "v2-tabbed-content-manager-[uid]"
-content container
	-has class "v2-tabbed-content-group-[uid]"
	-child elements to be hidden/shown have class "v2-tabbed-content-item" (this allows for cases where items to be hidden/shown aren't children of the group element)
	-all elements but the first one are hidden by default
-initialization call (v2TabbedContent.init([uid])) must appear after the tab container
	-it may appear before the content container UNLESS A DEFAULT INDEX IS PASSED (i.e., the content needs to exist so it can be shown) 
-a default tab other than the first one can be set, and will be switched to during initialization
	-to do this, pass a second parameter for the 0-based index of the tab you want to show
	-example: v2TabbedContent.init('some-uid', 3);

# example HTML with unique id "SAMPLE" (shows items as children of the group element, but this is not required - see above):
	<ul class="v2-tabbed-content-manager-SAMPLE">
		<li class="current"><a href="#">LATEST NEWS</a></li>
		<li><a href="#">POPULAR</a></li>
	</ul>
	<script type="text/javascript">
		v2TabbedContent.init('SAMPLE');
	</script>
	<div class="v2-tabbed-content-group-SAMPLE">
		<div class="v2-tabbed-content-item">
			[content]
		</div>
		<div class="v2-tabbed-content-item" style="display:none;">
			[content]
		</div>
	</div>
*/

var v2TabbedContent = {
	init: function(groupId, defaultIndex) {
		var $tabs = $('.v2-tabbed-content-manager-' + groupId + ' a');
		$tabs.each(function(index) {
				$(this).data({
					'groupId': groupId,
					'index': index
				});
			})
			.click(function(){
				v2TabbedContent.setSelected(this);
				return false;
			});
		
		if (defaultIndex != -1) {
			this.setSelected($tabs[defaultIndex]);
		}
	},
	
	setSelected: function(elem) {
		var $elem = $(elem);
		var groupId = $elem.data('groupId');
		var selectedIndex = $elem.data('index');
		
		var tabGroup = $('.v2-tabbed-content-manager-' + groupId + ' li');
		var contentItems = $('.v2-tabbed-content-group-' + groupId + ' .v2-tabbed-content-item');
		//test in case the content doesn't actually exist (will do nothing in this case - just a failsafe in case a default index is passed and content doesn't exist in the DOM yet)
		if (contentItems.length > 0) {
			if (!(tabGroup.eq(selectedIndex).hasClass('current'))) {
				tabGroup.removeClass('current').eq(selectedIndex).addClass('current');
				contentItems.hide().eq(selectedIndex).show();
			}
		}
	}
};

var v2GameZoneTabbedContent = {
	init: function(groupId, defaultIndex) {
		defaultIndex = defaultIndex || -1;
		
		var $tabs = $('.v2-tabbed-content-manager-' + groupId + ' a');
		$tabs.each(function(index) {
				$(this).data({
					'groupId': groupId,
					'index': index
				});
			})
			.click(function(){
				v2GameZoneTabbedContent.setSelected(this);
				return false;
			});
		
		if (defaultIndex != -1) {
			this.setSelected($tabs[defaultIndex]);
		}
	},
	
	setSelected: function(elem) {
		var $elem = $(elem);
		var groupId = $elem.data('groupId');
		var selectedIndex = $elem.data('index');
		
		var tabGroup = $('.v2-tabbed-content-manager-' + groupId + ' a');
		var contentItems = $('.v2-tabbed-content-group-' + groupId + ' .v2-tabbed-content-item');
		//test in case the content doesn't actually exist (will do nothing in this case - just a failsafe in case a default index is passed and content doesn't exist in the DOM yet)
		
		if (contentItems.length > 0) {
			if (!(tabGroup.eq(selectedIndex).hasClass('gzActiveTab'))) {
				tabGroup.removeClass('gzActiveTab').eq(selectedIndex).addClass('gzActiveTab');
				contentItems.hide().eq(selectedIndex).show();
			}
		}
	}
};
/*
 DEV NOTE:
 -this doesn't use AJAX anymore (direction was to just hide/show content, so we use an instance of v2TabbedContent)
 -also, no twitter IDs - retweet() finds the parent "v2-tweet-src" element
 */
var v2SocialMedia = {
	retweet: function(elem) {
		var $curSrc = $(elem).closest('.v2-tweet-src');
        var aTweet = $curSrc.find(".tweetContent").text();
        var aTwit = $curSrc.find(".twitterSource").text();

        var tURL = "http://twitter.com/home?status=" + encodeURIComponent('RT @'+aTwit+' '+aTweet);
        var w = window.open(tURL, "twitterWindow");
        return false;
    }
};
/*########################################################*/
/*-------------- team news -------------------*/
/*
-the tab changing is managed by v2TabbedContent
-these methods just manage the logo swap and getting/setting default tab index
*/

var v2TeamNews = {
	imgPath: '/assets/img/team_logos/scores/38x38/hockey/nhl/',
	savedIndex: -1, //based on the cookie set when user clicks "set default"
	
	init: function() {
		var $saveLinks = $('.v2-team-tab-saver'); 
		$saveLinks.each(function(index) {
			$(this).data({
				'index': index
			});
		}).click(function(){
			if ($(this).is('a')) {
				v2TeamNews.setDefault(this);
			}
			return false;
		});
		
		this.savedIndex = $.cookie('v2TeamNewsIndex') || this.savedIndex;
		if (this.savedIndex != -1) {
			this.setDefault($saveLinks[this.savedIndex]);
		}
		
		this.initTeamTabs();
	},
	
	setDefault: function(elem) {
		//clear any existing default
		$('.v2-team-clear-default-link').remove();
		$('.v2-team-tab-saver').show();
		var $elem = $(elem).hide().after('<a href="#" class="v2-team-clear-default-link" onclick="v2TeamNews.clearDefault(this); return false">Clear default</a>');
		var sIndex = $elem.data('index');
		$.cookie('v2TeamNewsIndex', sIndex, { expires: 1000, path: '/' });
	},
	
	clearDefault: function(elem) {
		$(elem).hide().siblings().show();
		$(elem).remove();
		$.cookie('v2TeamNewsIndex', null);
	},
	
	initTeamTabs: function(){
		$('a.[class*=v2-team-tab-mgr-]').each(function(index){
			//determine id for the clicked tab - defaults to "latest"
			var regex = /v2-team-tab-mgr-(\S+)/;
			var tabId = regex.exec(this.className)[1] || "latest";
			$(this).data({
				'tabId': tabId,
				'index': index
			});
		}).click(function(evt){
			var tabId = $(this).data('tabId');
			if (tabId == 'latest') {
				$('.v2-team-tab-logo img').hide();
			}
			else {
				$('.v2-team-tab-logo img').attr('src', v2TeamNews.imgPath + tabId + '.png').show();
				var s=s_gi(s_account);
				s.linkTrackVars="eVar9,campaign,events";
				s.linkTrackEvents="event2";
				s.eVar9=tabId; 
				s.events="event2";
				void(s.tl(evt,'o',"TeamNewsClick||"+tabId));
			}
		});
	}
};var v2SportLandingVideos = {
	//for video player/thumbnail behaviours
	curPage: 0,
	numPages: -1,
	curThumb: -1,
	
	$pages: null,
	$curPageLabel: null,
	
	init: function() {
		this.$pages = $('.v2-video-thumbnails');
		this.numPages = this.$pages.length;
		this.$curPageLabel = $('.v2-video-page-current');
	},
	
	updateCurThumb: function(thumbnail){
		if (this.curThumb == -1) {
			this.curThumb = $($("ul.v2-video-thumbnails a")[0]); //first() is in v2.4
		}
		
		this.curThumb.find("div.available").show();
		this.curThumb.find("div.current").hide();
		
		this.curThumb = $(thumbnail);
		this.curThumb.find("div.available").hide();
		this.curThumb.find("div.current").show();
	},
	
	prev: function() {
		var newPage = this.curPage - 1;
		if (newPage < 0) {
			newPage = this.numPages - 1;	
		}
		this.gotoPage(newPage);
	},
	
	next: function() {
		var newPage = this.curPage + 1;
		if (newPage >= this.numPages) {
			newPage = 0;	
		}
		this.gotoPage(newPage);
	},
	
	gotoPage: function(index) {
		this.$pages.eq(this.curPage).hide();
		this.curPage = index;
		this.$pages.eq(this.curPage).show();
		this.$curPageLabel.html((this.curPage+1));
	},
	
	onThumbnailClick: function(thumbnail, playerId) {
		//have to use a try/catch since IE's typeof returns "object" instead of "undefined"
		try {
			var player = window["bc-" + playerId];
			var playerModule = player.getModule(APIModules.VIDEO_PLAYER);
			
			var curVideoId = playerModule.getCurrentVideo().id;

			if (curVideoId != thumbnail.id) {
				this.updateCurThumb(thumbnail);
				playerModule.loadVideo(thumbnail.id);
			}
		} catch (e) {
			alert("Sorry - the video player is still loading. Please try again in a few seconds.");
		}
	}
};
(function($)
{	
	// constructor
	function v2Poll(root, conf)
	{	
		// Private fields ------------------------------------------------------------------
		var _root = $(root),
			_domElement = _root[0],
			_self = this,
			_resultContainer = _root.find('.v2-poll-result-container'),
			_questionElem = _root.find('.v2-poll-question'),
			_answerElem = _root.find('.v2-poll-answers'),
			_requestUrl = "/scrum/poll_vote.php",
			_opts = {
				poll_id: 0,
				debug: false
			}
			;
			$.extend(_opts, conf);
	
		// Private methods -----------------------------------------------------------------
		function debug(str)
		{
			if (_opts.debug == true) {
				try {
					alert(str);
				} 
				catch (e) {
					alert("debug: " + str);
				}
			}
		};
			
		// generic binding function
		
		function bind(name, fn)
		{
			if ($.isFunction(fn) == false)
				return;
				
			$(_self).bind(name, fn);
		};
		
		function showPollAnswers()
		{
			_resultContainer.hide();
			_answerElem.show();
		};
		
		function answerPoll(){
            var response_id = _answerElem.find('input[name=poll]:checked').val();
			if (typeof(response_id) == "undefined") {
				alert("Please select an option.");
			}
			else {
				showPollResults(response_id);
			}
			
			return false;
		};
		
		function showPollResults(response_id){
			var submitData = "poll_id=" + _opts.poll_id;
			
			if (typeof(response_id) != "undefined") {
				submitData += "&response_id="+response_id;
			}
			
            $.ajax({
            	url: _requestUrl,
                cache: false,
                data: submitData,
                error: function(xhr, status, exception) {
            		doRequestError(status, exception);
            	},
                success: function(responseHtml) {
            		var isValidResponse = responseHtml.indexOf("poll-results") != -1;
					if(isValidResponse == true) {
	            		_answerElem.hide();
						_resultContainer.html(responseHtml);
						_resultContainer.fadeIn();
						if (userHasVoted() == true) {
							showThanks();
						} else {
							showAnswersLink();
						}
					} else {
						doRequestError("error", "invalid response");
					}
                }
            });
			
			return false;
		};
		
		function showPollResultsOld(submitAnswer){
			//submitAnswer is false if the user clicks the "view results" link
			
            // make ajax request to vote and retrieve results
            var response_id = _answerElem.find('input[name=poll]:checked').val();
            if (!response_id || submitAnswer == false) response_id = '';
			debug("response_id: " + response_id);


            $.ajax({
            	url: requestUrl,
                cache: false,
                data: "poll_id="+_opts.poll_id+"&response_id="+response_id,
                error: function(xhr, status, exception) {
            		doRequestError(status, exception);
            	},
                success: function(responseHtml) {
            		var isValidResponse = responseHtml.indexOf("poll-results") != -1;
					//alert("isValidResponse: " + isValidResponse);
					if(isValidResponse == true) {
	            		_answerElem.hide();
						_resultContainer.html(responseHtml);
						_resultContainer.fadeIn();
						if (userHasVoted() == true) {
							showThanks();
						} else {
							showAnswersLink();
						}
					} else {
						doRequestError("error", "invalid response");
					}
                }
            });
			
			return false;
		};
		
		function doRequestError(status, exception) {
			var errorHtml = "<div class='v2-poll-error'><p><b>Sorry...</b><br/>We're experiencing technical difficulties. Please try again later.</p></div>";
			errorHtml += '<span style="display:none;">status: ' + status + '; exception: ' + exception + '</span>';
			_questionElem.hide();
			_answerElem.hide();
    		_resultContainer.html(errorHtml);
			_resultContainer.fadeIn();
		};
		
		function showAnswersLink(){
			var linkHtml = '<ul class="v2-link-list margin-t5"><li><a href="#" class="pollVoteNowLink">Vote Now</a></li></ul>';
			_resultContainer.append(linkHtml);
			_root.find(".pollVoteNowLink").click(function(){
				showPollAnswers();
				return false;
			});
		};
		
		function showThanks(){
			var thanksHtml= '<p style="margin:7px 0 0 0;"><strong>Thanks for voting!</strong></p>';
			_resultContainer.append(thanksHtml);
		};
		
		function userHasVoted(){
			var hasVoted = false;
			
            var polls = $.cookie('polls');
            if (polls) {
				var pollsVoted = polls.split('|');
			
                $.each(pollsVoted, function(key, value) {
                    if (value == _opts.poll_id) {
						hasVoted = true;
					}
                });
            }
			return hasVoted;
		};
	
		function init() {
			if (_opts.poll_id == -1) _requestUrl = "/ui_components/v2/fake_poll_response.php";
			
            // see if user has already voted	
            if (userHasVoted() == true) {
                showPollResults();
            } else {
                _root.find(".v2-pollVoteButton").click(answerPoll);
                _root.find(".v2-pollShowResultsLink").click(showPollResults);
				
                showPollAnswers();
            }
		};
	
		// Initialization ------------------------------------------------------------------
		init();
	};
	
	// jQuery plugin implementation
	$.fn.v2Poll = function(conf)
	{
		var opts = { };
		$.extend(opts, conf);
		
		this.each(function()
		{
			var $instance = new v2Poll($(this), opts);
		});
		
		return this;
	};
})(jQuery);

/*########################################################*/
/*-------------- v2BoxscorePageManager --------------------*/

(function($)
{
	// constructor
	function v2BoxscorePageManager(root, conf)
	{
		// Private vars ------------------------------------------------------------------
		var $root = $(root),
			_rootElem = $root[0],
			_self = this, 
			
			$boxScoreElem,
			
			checkedPastPeriods = false,
			revealDuration = 500, //milliseconds over which new content is "wiped" in
						
			_errMessage, //set to cs string 001
			
			_opts = {
				dataUrl: null, //URL for HTML response to insert into container
				//optional
				refreshInterval: 30000, //milliseconds between reload/requests
				viewOrder: 'asc' //asc (oldest to newest; period 1 at the bottom) / desc (newest to oldest; period 1 at the top)
			};
		$.extend(_opts, conf);

		// Public methods ------------------------------------------------------------------		
		$.extend(_self, {
			
		});

		// Private methods -----------------------------------------------------------------
		function init() {
			if (_opts.dataUrl) {
				logMsg('initializing...');
				
				$boxScoreElem = $('.v2-ajax-boxscore');
				$boxScoreElem.parent().css('position', 'relative');
				
				initTimer();
			} else {
				logMsg('v2BoxscorePageManager called without required parameters; can\'t proceed.');
			}
		};
	
		function initTimer() {
			logMsg('timer set for next AJAX request in ' + (_opts.refreshInterval/1000) + ' seconds.');
			setTimeout($.proxy(ajaxLoad, this), _opts.refreshInterval);
		};
	
		function updateDisplay(jsonObj) {
			var fadeDur = 750; //milliseconds
			updateBoxScore(jsonObj, fadeDur);
			
			updateGeneral(jsonObj);
			
			var proxyUpdatePeriods = $.proxy(updatePeriods, this);
			setTimeout(function() {
				proxyUpdatePeriods(jsonObj);
			},
			fadeDur);
		};
	
		function updateGeneral(jsonObj) {
			//replaces HTML content of any elements defined in the "html_updates" JSON object
			if (jsonObj.html_updates) {
				for (var i=0; i<jsonObj.html_updates.length; i++) {
					var curData = jsonObj.html_updates[i];
					var $curElem = $('#' + curData.id);
					logMsg('#updateGeneral; attempting to replace content of element with ID: ' + curData.id);
					if ($curElem.length > 0) {
						logMsg('content replaced.');
						$curElem.html(curData.html);
					} else {
						logMsg('no element found with ID: ' + curData.id);
					}
				}
			} else {
				logMsg('#updateGeneral; no general updates ("html_updates" property not in JSON)');
			}
		};
		
		function logMsg(str) {
			try {
				console.log('v2BoxscorePageManager; ' + str);
			} catch (e) {
				//no log, no messages
			}
		};
	
		function updatePeriods(jsonObj) {
			logMsg("#updatePeriods");
			if (!checkedPastPeriods) {
				logMsg("updating past periods...");
				checkedPastPeriods = true;
				var testPeriod = 1;
				while(testPeriod < jsonObj.game_period) {
					logMsg("updating past period " + testPeriod);
					updatePeriod(testPeriod, jsonObj);
					testPeriod++;
				}
			}
			updatePeriod(jsonObj.game_period, jsonObj);
		};
	
		function updatePeriod(periodNum, jsonObj) {
			logMsg("#updatePeriod");
			
			var $curRootElem = getPeriodRootElem(periodNum, jsonObj);
			logMsg("period: " + periodNum + "; $curRootElem selector: " + ('.v2-game-segment-' + periodNum));
			
			updatePeriodHeader($curRootElem);
			updateLog(periodNum, jsonObj);
			updateNoScoreElem($curRootElem, jsonObj);
			
		};
		
		function getPeriodRootElem(periodNum, jsonObj) {
			var $rootElem = $('.v2-game-segment-' + periodNum);
			if ($rootElem.length == 0) {
				var segmentData = getPeriodData(periodNum, jsonObj);
				var segmentType = segmentData.type;
				
				logMsg('creating game segment element for period ' + periodNum + '; type: ' + segmentType);
				
				var segmentTypeClass = 'v2-game-segment-template-' + segmentType;
				$rootElem = $('.' + segmentTypeClass).clone()
					.removeClass(segmentTypeClass)
					.addClass('v2-game-segment-' + periodNum);
				$rootElem.find('.module-header h4').text(segmentData.label);
					
				if (_opts.viewOrder == 'asc') {
					$rootElem.prependTo($root).show();
				} else {
					$rootElem.appendTo($root).show();
				}
			}
			return $rootElem;
		};
		
		function getPeriodData(periodNum, jsonObj) {
			var sData;
			var periodsData = jsonObj.periods;
			for (var i=0; i<periodsData.length; i++) {
				if (periodsData[i].number == periodNum) {
					sData = periodsData[i];
				}
			}
			return sData;
		};
		
		function updatePeriodHeader($curRootElem) {
			logMsg('#updatePeriodHeader');
			var $header = $curRootElem.find('.module-header');
			if ($header.is(':hidden')){
				logMsg('-showing header');
				initReveal($header);
				doReveal($header);
			} else {
				logMsg('-header already showing');
			}
		};
		
		function updateNoScoreElem($curRootElem) {
			logMsg('#updateNoScoreElem');
			var $noScoreElem = $curRootElem.find('.v2-game-segment-default');
			
			var periodHasGoals = $curRootElem.find('.v2-game-segment-goals tbody tr').length > 0;
			logMsg('-periodHasGoals: ' + periodHasGoals);
			if (periodHasGoals){
				if ($noScoreElem.is(':visible')) {
					logMsg('-hiding no score elem');
					initReveal($noScoreElem);
					doHide($noScoreElem);
				} else {
					logMsg('-no score elem already hidden');
				}
			} else {
				if ($noScoreElem.is(':hidden')) {
					logMsg('-showing no score elem');
					initReveal($noScoreElem);
					doReveal($noScoreElem);
				} else {
					logMsg('-no score elem already showing');
				}
			}
		};
		
		function updateLog(curPeriod, jsonObj) {
			var types = [
				'goals',
				'penalties'
			];
			
			var $curRootElem = $('.v2-game-segment-' + curPeriod);
				
			for (var x=0; x<types.length; x++) {
				var type = types[x];
				logMsg("#updateLog; updating: " + type + "; viewOrder: " + _opts.viewOrder);
				
				var $existingRows = $curRootElem.find('.v2-game-segment-' + type + ' tbody tr');
				var existingIds = [];
				$existingRows.each(function() {
					existingIds.push(this.id);
				});
				logMsg("-$existingRows: " + $existingRows.length + "; IDs: [" + existingIds + "]");
				
				var $ajaxRows = getRowsFromJson(jsonObj, type, curPeriod);
				var ajaxIds = [];
				$ajaxRows.each(function() {
					ajaxIds.push(this.id);
				});
				logMsg("-$ajaxRows: " + $ajaxRows.length + "; IDs: [" + ajaxIds + "]");
				
				var $newRows = $ajaxRows.filter(function() {
					return ($.inArray(this.id, existingIds) == -1);
				});
				logMsg("-$newRows: " + $newRows.length);
				
				var $targetTable = $curRootElem.find('.v2-game-segment-' + type);
				if ($newRows.length > 0) {
					initReveal($targetTable);
					if (_opts.viewOrder == 'asc') {
						var _newRows = $.makeArray($newRows);
						_newRows.reverse();
						$newRows = $(_newRows);
						$newRows.prependTo($targetTable);
					} else {
						$newRows.appendTo($targetTable);
					}
					prepForDisplay($targetTable);
					doReveal($targetTable);
				}
			}
		};
	
		function prepForDisplay($table) {
			$table.find('tr')
				.removeClass('first-row alt-bg')
				.each(function(index){
					var realIndex = index-1;
					if (realIndex == 0) {
						$(this).addClass('first-row');	
					}
					if (realIndex % 2 == 0) {
						$(this).addClass('alt-bg');	
					}
				});
		};
	
		function initReveal($elem) {
			if (!($elem.parent().hasClass('revealWrapper'))) {
				$elem.wrap('<div class="revealWrapper" style="overflow:hidden;"></div>');
			}
			
			var startHeight;
			if ($elem.is(':visible')) {
				startHeight = $elem.outerHeight(true);
			} else {
				startHeight = 0;
				$elem.show();
			}
			
			$elem.parent().height(startHeight);
		};
	
		function doReveal($elem) {
			$elem.parent().animate({
					height: $elem.outerHeight(true)
				},
				revealDuration);
		}
	
		function doHide($elem) {
			$elem.parent().animate({
					height: 0
				},
				revealDuration,
				function(){
					$elem.hide();
				});
		}
		
		function getRowsFromJson(jsonObj, type, period) {
			var objs = getObjsFromJson(jsonObj, type, period);
			
			var html = ''; 
			for (var i=0; i<objs.length; i++) {
				html += objs[i].html;
			}
			$rows =  $(html);
			return $rows;
		};
	
		function getObjsFromJson(jsonObj, type, period) {
			var rows = [];
			for (var i=0; i<jsonObj[type].length; i++) {
				if (jsonObj[type][i].period == period) {
					rows.push(jsonObj[type][i]);
				}
			}
			return rows;
		};
		
		function updateBoxScore(jsonObj, fadeDur) {
			logMsg('#updateBoxScore');
			try {
				var isOldIe = $.browser.msie && (parseInt($.browser.version) < 9);
				if (isOldIe) {
					$boxScoreElem.html(jsonObj.score_table_html);
				} else {
					var $copyElem = $boxScoreElem.clone();
					$copyElem.css({
						'position': 'absolute',
						'top': '0px',
						'left': '0px'
					});
					$boxScoreElem.after($copyElem);
					
					$boxScoreElem.html(jsonObj.score_table_html);
					
					$copyElem.animate({
							opacity: 0
						},
						fadeDur,
						function(){
							$copyElem.remove();
						});
				}
			} catch (e) {
				$boxScoreElem.html(jsonObj.score_table_html);
			}
		};
		
		function gameIsComplete(jsonObj) {
			return $.trim(jsonObj.game_status.toLowerCase()) == "final";
		};
		
		function onAjaxSuccess(jsonObj) {
			logMsg('-----------------------------');
			logMsg('AJAX request successful. updating display...');
			updateDisplay(jsonObj);
			
			if (!gameIsComplete(jsonObj)) {
				initTimer();
			} else {
				logMsg('GAME COMPLETE - ending automatic updates.');
			}
		};
		
		function onAjaxError(XMLHttpRequest, textStatus, errorThrown) {
			alert("Sorry. We're experiencing technical difficulties. Please try reloading the page.");
			logMsg('onAjaxError; textStatus: ' + textStatus);
			logMsg('onAjaxError; errorThrown: ' + errorThrown);
		};

		function ajaxLoad() {
			logMsg('sending AJAX request to "' + _opts.dataUrl + '"...');
			$.ajax({
				url: _opts.dataUrl,
				global: false,
				type: "GET",
				dataType: "json",
				cache: false,
				success: $.proxy(onAjaxSuccess, this),
				error: $.proxy(onAjaxError, this)
			});
		};
		
		// Initialization ------------------------------------------------------------------
		init();
	};

	// jQuery plugin implementation
	$.fn.v2boxscorePageManager = function(conf) {
		var opts = {}; //defaults
		$.extend(opts, conf);
		
		this.each(function() {
			var $instance = new v2BoxscorePageManager(this, opts);
			$(this).data("v2boxscorePageManager", $instance);
		});
		
		return this;
	};
})(jQuery);var dataURL = "/hockey/nhl/live";

var psRefreshInterval = 30000; //milliseconds between requests/updates

var overlayContent = {
	pointShare: {
		title: 'Point Share (PS)',
		desc: 'The value of a player to his team based on how many points in the standings his play has contributed to his the team. Eg.: Of the Flames\' 38 points in the standings, Jarome Iginla\'s play contributed nine of those points.'
	},
	mvp: {
		title: 'MVP',
		desc: 'MVP tells us how much better in percentages a player makes his team in the standings for his role and position.'
	},
	psMvp: {
		title: 'Definitions',
		desc: '<strong>Point Share</strong> = a player\'s value based on how many points his play has contributed to the team\'s standings.<br/>'
			+ '<strong>MVP</strong> = % better a player makes his team in the standings, for his role and position.'
	},
	mvp: {
		title: 'MVP',
		desc: 'MVP tells us how much better in percentages a player makes his team in the standings for his role and position.'
	},
	momentum: {
		title: 'Momentum Meter',
		desc: 'Charts on a timeline the effort and momentum swings of a team based on shots on goal.'
	},
	domCurve: {
		title: 'Dominance Curves',
		desc: 'Based on the momentum meter, dominance curves show which team is dominating the play at any given time in the game.'
	},
	powerRanking: {
		title: 'NHL Power Rankings',
		desc: 'Eleven different metrics are measured over the team&apos;s last five games to determine a team&apos;s power rankings.'
	}
};

function hsOverlay(elem, contentId) {
	var $elem = $(elem);
	var elemOffset = $elem.offset();
	var tX = elemOffset.left + ($elem.width()/2);
	var tY = elemOffset.top + ($elem.height()/2);
	hs.dimmingOpacity = .000000001;
	hs.dimmingDuration = 0;
	hs.htmlExpand(null, {
		pageOrigin: {
			x: tX,
			y: tY
		},
		headingText: overlayContent[contentId].title,
		maincontentText: overlayContent[contentId].desc,
		width: overlayContent[contentId].width || 300
	});
};

function loadOneGame(gameId, mode) {
	var $root = $('#'+gameId+'-'+mode);
	$root.find('.v2-ps-graph-holder').hide();
	$root.find('.v2-ps-loader').show();
	
	ajaxLoad($root, gameId, mode);
}

function ajaxLoad($root, gameId, mode) {
	logMsg('sending AJAX request to "' + dataURL+"/"+mode+"/"+gameId + '"...');
	$.ajax({
			url: dataURL+"/"+mode+"/"+gameId,
			global: false,
			type: "GET",
			cache: false,
			success: function(msg){
				logMsg('-----------------------------');
				logMsg('AJAX request successful. updating display...');
				$root.find('.v2-ps-graph-holder').html(msg).show();
				$root.find('.v2-ps-loader').hide();
				
				if ($root.find('.v2-game-state').hasClass('v2-game-state-final') == false) {
					logMsg('timer set for next AJAX request in ' + (psRefreshInterval/1000) + ' seconds.');
					setTimeout(function() {
						loadOneGame(gameId, mode);
					},
					psRefreshInterval);
				} else {
					logMsg('GAME COMPLETE - ending automatic updates.');
				}
			},
			error: function(XMLHttpRequest, textStatus, errorThrown){
				ajaxErrorHandler(XMLHttpRequest, textStatus, errorThrown);
				printObjectProps(XMLHttpRequest);
			}
		});
}
		
function logMsg(str) {
	try {
		console.log('sportsnet.powerscout.js; ' + str);
	} catch (e) {
		//no log, no messages
	}
}
		
function ajaxErrorHandler(XMLHttpRequest, textStatus, errorThrown) {
	alert("Sorry. We're experiencing technical difficulties. Please try reloading the page.");
	logMsg('onAjaxError; textStatus: ' + textStatus);
	logMsg('onAjaxError; errorThrown: ' + errorThrown);
}
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS

/**
 * @license Highcharts JS v2.1.9 (2011-11-11)
 *
 * (c) 2009-2011 Torstein Hønsi
 *
 * License: www.highcharts.com/license
 */

// JSLint options:
/*global document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */

(function () {
// encapsulated variables
var doc = document,
	win = window,
	math = Math,
	mathRound = math.round,
	mathFloor = math.floor,
	mathCeil = math.ceil,
	mathMax = math.max,
	mathMin = math.min,
	mathAbs = math.abs,
	mathCos = math.cos,
	mathSin = math.sin,
	mathPI = math.PI,
	deg2rad = mathPI * 2 / 360,


	// some variables
	userAgent = navigator.userAgent,
	isIE = /msie/i.test(userAgent) && !win.opera,
	docMode8 = doc.documentMode === 8,
	isWebKit = /AppleWebKit/.test(userAgent),
	isFirefox = /Firefox/.test(userAgent),
	SVG_NS = 'http://www.w3.org/2000/svg',
	hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
	hasRtlBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
	Renderer,
	hasTouch = doc.documentElement.ontouchstart !== undefined,
	symbolSizes = {},
	idCounter = 0,
	timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
	garbageBin,
	defaultOptions,
	dateFormat, // function
	globalAnimation,
	pathAnim,


	// some constants for frequently used strings
	UNDEFINED,
	DIV = 'div',
	ABSOLUTE = 'absolute',
	RELATIVE = 'relative',
	HIDDEN = 'hidden',
	PREFIX = 'highcharts-',
	VISIBLE = 'visible',
	PX = 'px',
	NONE = 'none',
	M = 'M',
	L = 'L',
	/*
	 * Empirical lowest possible opacities for TRACKER_FILL
	 * IE6: 0.002
	 * IE7: 0.002
	 * IE8: 0.002
	 * IE9: 0.00000000001 (unlimited)
	 * FF: 0.00000000001 (unlimited)
	 * Chrome: 0.000001
	 * Safari: 0.000001
	 * Opera: 0.00000000001 (unlimited)
	 */
	TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable
	NORMAL_STATE = '',
	HOVER_STATE = 'hover',
	SELECT_STATE = 'select',

	// time methods, changed based on whether or not UTC is used
	makeTime,
	getMinutes,
	getHours,
	getDay,
	getDate,
	getMonth,
	getFullYear,
	setMinutes,
	setHours,
	setDate,
	setMonth,
	setFullYear,

	// check for a custom HighchartsAdapter defined prior to this file
	globalAdapter = win.HighchartsAdapter,
	adapter = globalAdapter || {},

	// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
	// and all the utility functions will be null. In that case they are populated by the
	// default adapters below.
	each = adapter.each,
	grep = adapter.grep,
	map = adapter.map,
	merge = adapter.merge,
	addEvent = adapter.addEvent,
	removeEvent = adapter.removeEvent,
	fireEvent = adapter.fireEvent,
	animate = adapter.animate,
	stop = adapter.stop,

	// lookup over the types and the associated classes
	seriesTypes = {};

/**
 * Extend an object with the members of another
 * @param {Object} a The object to be extended
 * @param {Object} b The object to add to the first one
 */
function extend(a, b) {
	var n;
	if (!a) {
		a = {};
	}
	for (n in b) {
		a[n] = b[n];
	}
	return a;
}

/**
 * Shortcut for parseInt
 * @param {Object} s
 */
function pInt(s, mag) {
	return parseInt(s, mag || 10);
}

/**
 * Check for string
 * @param {Object} s
 */
function isString(s) {
	return typeof s === 'string';
}

/**
 * Check for object
 * @param {Object} obj
 */
function isObject(obj) {
	return typeof obj === 'object';
}

/**
 * Check for array
 * @param {Object} obj
 */
function isArray(obj) {
	return Object.prototype.toString.call(obj) === '[object Array]';
}

/**
 * Check for number
 * @param {Object} n
 */
function isNumber(n) {
	return typeof n === 'number';
}

function log2lin(num) {
	return math.log(num) / math.LN10;
}
function lin2log(num) {
	return math.pow(10, num);
}

/**
 * Remove last occurence of an item from an array
 * @param {Array} arr
 * @param {Mixed} item
 */
function erase(arr, item) {
	var i = arr.length;
	while (i--) {
		if (arr[i] === item) {
			arr.splice(i, 1);
			break;
		}
	}
	//return arr;
}

/**
 * Returns true if the object is not null or undefined. Like MooTools' $.defined.
 * @param {Object} obj
 */
function defined(obj) {
	return obj !== UNDEFINED && obj !== null;
}

/**
 * Set or get an attribute or an object of attributes. Can't use jQuery attr because
 * it attempts to set expando properties on the SVG element, which is not allowed.
 *
 * @param {Object} elem The DOM element to receive the attribute(s)
 * @param {String|Object} prop The property or an abject of key-value pairs
 * @param {String} value The value if a single property is set
 */
function attr(elem, prop, value) {
	var key,
		setAttribute = 'setAttribute',
		ret;

	// if the prop is a string
	if (isString(prop)) {
		// set the value
		if (defined(value)) {

			elem[setAttribute](prop, value);

		// get the value
		} else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
			ret = elem.getAttribute(prop);
		}

	// else if prop is defined, it is a hash of key/value pairs
	} else if (defined(prop) && isObject(prop)) {
		for (key in prop) {
			elem[setAttribute](key, prop[key]);
		}
	}
	return ret;
}
/**
 * Check if an element is an array, and if not, make it into an array. Like
 * MooTools' $.splat.
 */
function splat(obj) {
	return isArray(obj) ? obj : [obj];
}


/**
 * Return the first value that is defined. Like MooTools' $.pick.
 */
function pick() {
	var args = arguments,
		i,
		arg,
		length = args.length;
	for (i = 0; i < length; i++) {
		arg = args[i];
		if (typeof arg !== 'undefined' && arg !== null) {
			return arg;
		}
	}
}

/**
 * Set CSS on a given element
 * @param {Object} el
 * @param {Object} styles Style object with camel case property names
 */
function css(el, styles) {
	if (isIE) {
		if (styles && styles.opacity !== UNDEFINED) {
			styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
		}
	}
	extend(el.style, styles);
}

/**
 * Utility function to create element with attributes and styles
 * @param {Object} tag
 * @param {Object} attribs
 * @param {Object} styles
 * @param {Object} parent
 * @param {Object} nopad
 */
function createElement(tag, attribs, styles, parent, nopad) {
	var el = doc.createElement(tag);
	if (attribs) {
		extend(el, attribs);
	}
	if (nopad) {
		css(el, {padding: 0, border: NONE, margin: 0});
	}
	if (styles) {
		css(el, styles);
	}
	if (parent) {
		parent.appendChild(el);
	}
	return el;
}

/**
 * Extend a prototyped class by new members
 * @param {Object} parent
 * @param {Object} members
 */
function extendClass(parent, members) {
	var object = function () {};
	object.prototype = new parent();
	extend(object.prototype, members);
	return object;
}

/**
 * Format a number and return a string based on input settings
 * @param {Number} number The input number to format
 * @param {Number} decimals The amount of decimals
 * @param {String} decPoint The decimal point, defaults to the one given in the lang options
 * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
 */
function numberFormat(number, decimals, decPoint, thousandsSep) {
	var lang = defaultOptions.lang,
		// http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
		n = number,
		c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
		d = decPoint === undefined ? lang.decimalPoint : decPoint,
		t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
		s = n < 0 ? "-" : "",
		i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
		j = i.length > 3 ? i.length % 3 : 0;

	return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
		(c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
}

/**
 * Based on http://www.php.net/manual/en/function.strftime.php
 * @param {String} format
 * @param {Number} timestamp
 * @param {Boolean} capitalize
 */
dateFormat = function (format, timestamp, capitalize) {
	function pad(number) {
		return number.toString().replace(/^([0-9])$/, '0$1');
	}

	if (!defined(timestamp) || isNaN(timestamp)) {
		return 'Invalid date';
	}
	format = pick(format, '%Y-%m-%d %H:%M:%S');

	var date = new Date(timestamp * timeFactor),
		key, // used in for constuct below
		// get the basic time values
		hours = date[getHours](),
		day = date[getDay](),
		dayOfMonth = date[getDate](),
		month = date[getMonth](),
		fullYear = date[getFullYear](),
		lang = defaultOptions.lang,
		langWeekdays = lang.weekdays,
		/* // uncomment this and the 'W' format key below to enable week numbers
		weekNumber = function() {
			var clone = new Date(date.valueOf()),
				day = clone[getDay]() == 0 ? 7 : clone[getDay](),
				dayNumber;
			clone.setDate(clone[getDate]() + 4 - day);
			dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
			return 1 + mathFloor(dayNumber / 7);
		},
		*/

		// list all format keys
		replacements = {

			// Day
			'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
			'A': langWeekdays[day], // Long weekday, like 'Monday'
			'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
			'e': dayOfMonth, // Day of the month, 1 through 31

			// Week (none implemented)
			//'W': weekNumber(),

			// Month
			'b': lang.shortMonths[month], // Short month, like 'Jan'
			'B': lang.months[month], // Long month, like 'January'
			'm': pad(month + 1), // Two digit month number, 01 through 12

			// Year
			'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
			'Y': fullYear, // Four digits year, like 2009

			// Time
			'H': pad(hours), // Two digits hours in 24h format, 00 through 23
			'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
			'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
			'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
			'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
			'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
			'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59
			
			'z': (hours * 60) + date[getMinutes](),
			'Z': (((hours * 60) + date[getMinutes]()) == '10' ? '1st' : (((hours * 60) + date[getMinutes]()) == '30' ? '2nd' : (((hours * 60) + date[getMinutes]()) == '50' ? '3rd' : (((hours * 60) + date[getMinutes]()) == '65' ? 'OT' : ''/*(((hours * 60) + date[getMinutes]()))*/))))
		};


	// do the replaces
	for (key in replacements) {
		format = format.replace('%' + key, replacements[key]);
	}

	// Optionally capitalize the string and return
	return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
};

/**
 * Loop up the node tree and add offsetWidth and offsetHeight to get the
 * total page offset for a given element. Used by Opera and iOS on hover and
 * all browsers on point click.
 *
 * @param {Object} el
 *
 */
function getPosition(el) {
	var p = { left: el.offsetLeft, top: el.offsetTop };
	el = el.offsetParent;
	while (el) {
		p.left += el.offsetLeft;
		p.top += el.offsetTop;
		if (el !== doc.body && el !== doc.documentElement) {
			p.left -= el.scrollLeft;
			p.top -= el.scrollTop;
		}
		el = el.offsetParent;
	}
	return p;
}

/**
 * Helper class that contains variuos counters that are local to the chart.
 */
function ChartCounters() {
	this.color = 0;
	this.symbol = 0;
}

ChartCounters.prototype = {
	/**
	 * Wraps the color counter if it reaches the specified length.
	 */
	wrapColor: function (length) {
		if (this.color >= length) {
			this.color = 0;
		}
	},

	/**
	 * Wraps the symbol counter if it reaches the specified length.
	 */
	wrapSymbol: function (length) {
		if (this.symbol >= length) {
			this.symbol = 0;
		}
	}
};

/**
 * Utility method extracted from Tooltip code that places a tooltip in a chart without spilling over
 * and not covering the point it self.
 */
function placeBox(boxWidth, boxHeight, outerLeft, outerTop, outerWidth, outerHeight, point) {
	// keep the box within the chart area
	var pointX = point.x,
		pointY = point.y,
		x = pointX - boxWidth + outerLeft - 25,
		y = pointY - boxHeight + outerTop + 10,
		alignedRight;

	// it is too far to the left, adjust it
	if (x < 7) {
		x = outerLeft + pointX + 15;
	}

	// Test to see if the tooltip is to far to the right,
	// if it is, move it back to be inside and then up to not cover the point.
	if ((x + boxWidth) > (outerLeft + outerWidth)) {
		x -= (x + boxWidth) - (outerLeft + outerWidth);
		y -= boxHeight;
		alignedRight = true;
	}

	if (y < 5) {
		y = 5; // above

		// If the tooltip is still covering the point, move it below instead
		if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
			y = pointY + boxHeight - 5; // below
		}
	} else if (y + boxHeight > outerTop + outerHeight) {
		y = outerTop + outerHeight - boxHeight - 5; // below
	}

	return {x: x, y: y};
}

/**
 * Utility method that sorts an object array and keeping the order of equal items.
 * ECMA script standard does not specify the behaviour when items are equal.
 */
function stableSort(arr, sortFunction) {
	var length = arr.length,
		i;

	// Add index to each item
	for (i = 0; i < length; i++) {
		arr[i].ss_i = i; // stable sort index
	}

	arr.sort(function (a, b) {
		var sortValue = sortFunction(a, b);
		return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
	});

	// Remove index from items
	for (i = 0; i < length; i++) {
		delete arr[i].ss_i; // stable sort index
	}
}

/**
 * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
 * It loops all properties and invokes destroy if there is a destroy method. The property is
 * then delete'ed.
 */
function destroyObjectProperties(obj) {
	var n;
	for (n in obj) {
		// If the object is non-null and destroy is defined
		if (obj[n] && obj[n].destroy) {
			// Invoke the destroy
			obj[n].destroy();
		}

		// Delete the property from the object.
		delete obj[n];
	}
}

/**
 * Path interpolation algorithm used across adapters
 */
pathAnim = {
	/**
	 * Prepare start and end values so that the path can be animated one to one
	 */
	init: function (elem, fromD, toD) {
		fromD = fromD || '';
		var shift = elem.shift,
			bezier = fromD.indexOf('C') > -1,
			numParams = bezier ? 7 : 3,
			endLength,
			slice,
			i,
			start = fromD.split(' '),
			end = [].concat(toD), // copy
			startBaseLine,
			endBaseLine,
			sixify = function (arr) { // in splines make move points have six parameters like bezier curves
				i = arr.length;
				while (i--) {
					if (arr[i] === M) {
						arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
					}
				}
			};

		if (bezier) {
			sixify(start);
			sixify(end);
		}

		// pull out the base lines before padding
		if (elem.isArea) {
			startBaseLine = start.splice(start.length - 6, 6);
			endBaseLine = end.splice(end.length - 6, 6);
		}

		// if shifting points, prepend a dummy point to the end path
		if (shift) {

			end = [].concat(end).splice(0, numParams).concat(end);
			elem.shift = false; // reset for following animations
		}

		// copy and append last point until the length matches the end length
		if (start.length) {
			endLength = end.length;
			while (start.length < endLength) {

				//bezier && sixify(start);
				slice = [].concat(start).splice(start.length - numParams, numParams);
				if (bezier) { // disable first control point
					slice[numParams - 6] = slice[numParams - 2];
					slice[numParams - 5] = slice[numParams - 1];
				}
				start = start.concat(slice);
			}
		}

		if (startBaseLine) { // append the base lines for areas
			start = start.concat(startBaseLine);
			end = end.concat(endBaseLine);
		}
		return [start, end];
	},

	/**
	 * Interpolate each value of the path and return the array
	 */
	step: function (start, end, pos, complete) {
		var ret = [],
			i = start.length,
			startVal;

		if (pos === 1) { // land on the final path without adjustment points appended in the ends
			ret = complete;

		} else if (i === end.length && pos < 1) {
			while (i--) {
				startVal = parseFloat(start[i]);
				ret[i] =
					isNaN(startVal) ? // a letter instruction like M or L
						start[i] :
						pos * (parseFloat(end[i] - startVal)) + startVal;

			}
		} else { // if animation is finished or length not matching, land on right value
			ret = end;
		}
		return ret;
	}
};


/**
 * Set the global animation to either a given value, or fall back to the
 * given chart's animation option
 * @param {Object} animation
 * @param {Object} chart
 */
function setAnimation(animation, chart) {
	globalAnimation = pick(animation, chart.animation);
}

/*
 * Define the adapter for frameworks. If an external adapter is not defined,
 * Highcharts reverts to the built-in jQuery adapter.
 */
if (globalAdapter && globalAdapter.init) {
	// Initialize the adapter with the pathAnim object that takes care
	// of path animations.
	globalAdapter.init(pathAnim);
}
if (!globalAdapter && win.jQuery) {
	var jQ = jQuery;

	/**
	 * Utility for iterating over an array. Parameters are reversed compared to jQuery.
	 * @param {Array} arr
	 * @param {Function} fn
	 */
	each = function (arr, fn) {
		var i = 0,
			len = arr.length;
		for (; i < len; i++) {
			if (fn.call(arr[i], arr[i], i, arr) === false) {
				return i;
			}
		}
	};

	/**
	 * Filter an array
	 */
	grep = jQ.grep;

	/**
	 * Map an array
	 * @param {Array} arr
	 * @param {Function} fn
	 */
	map = function (arr, fn) {
		//return jQuery.map(arr, fn);
		var results = [],
			i = 0,
			len = arr.length;
		for (; i < len; i++) {
			results[i] = fn.call(arr[i], arr[i], i, arr);
		}
		return results;

	};

	/**
	 * Deep merge two objects and return a third object
	 */
	merge = function () {
		var args = arguments;
		return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
	};

	/**
	 * Add an event listener
	 * @param {Object} el A HTML element or custom object
	 * @param {String} event The event type
	 * @param {Function} fn The event handler
	 */
	addEvent = function (el, event, fn) {
		jQ(el).bind(event, fn);
	};

	/**
	 * Remove event added with addEvent
	 * @param {Object} el The object
	 * @param {String} eventType The event type. Leave blank to remove all events.
	 * @param {Function} handler The function to remove
	 */
	removeEvent = function (el, eventType, handler) {
		// workaround for jQuery issue with unbinding custom events:
		// http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
		var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
		if (doc[func] && !el[func]) {
			el[func] = function () {};
		}

		jQ(el).unbind(eventType, handler);
	};

	/**
	 * Fire an event on a custom object
	 * @param {Object} el
	 * @param {String} type
	 * @param {Object} eventArguments
	 * @param {Function} defaultFunction
	 */
	fireEvent = function (el, type, eventArguments, defaultFunction) {
		var event = jQ.Event(type),
			detachedType = 'detached' + type;
		extend(event, eventArguments);

		// Prevent jQuery from triggering the object method that is named the
		// same as the event. For example, if the event is 'select', jQuery
		// attempts calling el.select and it goes into a loop.
		if (el[type]) {
			el[detachedType] = el[type];
			el[type] = null;
		}

		// trigger it
		jQ(el).trigger(event);

		// attach the method
		if (el[detachedType]) {
			el[type] = el[detachedType];
			el[detachedType] = null;
		}

		if (defaultFunction && !event.isDefaultPrevented()) {
			defaultFunction(event);
		}
	};

	/**
	 * Animate a HTML element or SVG element wrapper
	 * @param {Object} el
	 * @param {Object} params
	 * @param {Object} options jQuery-like animation options: duration, easing, callback
	 */
	animate = function (el, params, options) {
		var $el = jQ(el);
		if (params.d) {
			el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
			params.d = 1; // because in jQuery, animating to an array has a different meaning
		}

		$el.stop();
		$el.animate(params, options);

	};
	/**
	 * Stop running animation
	 */
	stop = function (el) {
		jQ(el).stop();
	};


	//=== Extend jQuery on init
	
	/*jslint unparam: true*//* allow unused param x in this function */
	jQ.extend(jQ.easing, {
		easeOutQuad: function (x, t, b, c, d) {
			return -c * (t /= d) * (t - 2) + b;
		}
	});
	/*jslint unparam: false*/

	// extend the animate function to allow SVG animations
	var jFx = jQuery.fx,
		jStep = jFx.step;
		
	// extend some methods to check for elem.attr, which means it is a Highcharts SVG object
	each(['cur', '_default', 'width', 'height'], function (fn, i) {
		var obj = i ? jStep : jFx.prototype, // 'cur', the getter' relates to jFx.prototype
			base = obj[fn],
			elem;
		
		if (base) { // step.width and step.height don't exist in jQuery < 1.7
		
			// create the extended function replacement
			obj[fn] = function (fx) {
				
				// jFx.prototype.cur does not use fx argument
				fx = i ? fx : this;
				
				// shortcut
				elem = fx.elem;
				
				// jFX.prototype.cur returns the current value. The other ones are setters 
				// and returning a value has no effect.
				return elem.attr ? // is SVG element wrapper
					elem.attr(fx.prop, fx.now) : // apply the SVG wrapper's method
					base.apply(this, arguments); // use jQuery's built-in method
			};
		}
	});
	
	// animate paths
	jStep.d = function (fx) {
		var elem = fx.elem;


		// Normally start and end should be set in state == 0, but sometimes,
		// for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
		// in these cases
		if (!fx.started) {
			var ends = pathAnim.init(elem, elem.d, elem.toD);
			fx.start = ends[0];
			fx.end = ends[1];
			fx.started = true;
		}


		// interpolate each value of the path
		elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));

	};
}


/**
 * Add a global listener for mousemove events
 */
/*addEvent(doc, 'mousemove', function(e) {
	if (globalMouseMove) {
		globalMouseMove(e);
	}
});*/
/**
 * Set the time methods globally based on the useUTC option. Time method can be either
 * local time or UTC (default).
 */
function setTimeMethods() {
	var useUTC = defaultOptions.global.useUTC;

	makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
		return new Date(
			year,
			month,
			pick(date, 1),
			pick(hours, 0),
			pick(minutes, 0),
			pick(seconds, 0)
		).getTime();
	};
	getMinutes = useUTC ? 'getUTCMinutes' : 'getMinutes';
	getHours = useUTC ? 'getUTCHours' : 'getHours';
	getDay = useUTC ? 'getUTCDay' : 'getDay';
	getDate = useUTC ? 'getUTCDate' : 'getDate';
	getMonth = useUTC ? 'getUTCMonth' : 'getMonth';
	getFullYear = useUTC ? 'getUTCFullYear' : 'getFullYear';
	setMinutes = useUTC ? 'setUTCMinutes' : 'setMinutes';
	setHours = useUTC ? 'setUTCHours' : 'setHours';
	setDate = useUTC ? 'setUTCDate' : 'setDate';
	setMonth = useUTC ? 'setUTCMonth' : 'setMonth';
	setFullYear = useUTC ? 'setUTCFullYear' : 'setFullYear';

}

/**
 * Merge the default options with custom options and return the new options structure
 * @param {Object} options The new custom options
 */
function setOptions(options) {
	defaultOptions = merge(defaultOptions, options);

	// apply UTC
	setTimeMethods();

	return defaultOptions;
}

/**
 * Get the updated default options. Merely exposing defaultOptions for outside modules
 * isn't enough because the setOptions method creates a new object.
 */
function getOptions() {
	return defaultOptions;
}

/**
 * Discard an element by moving it to the bin and delete
 * @param {Object} The HTML node to discard
 */
function discardElement(element) {
	// create a garbage bin element, not part of the DOM
	if (!garbageBin) {
		garbageBin = createElement(DIV);
	}

	// move the node and empty bin
	if (element) {
		garbageBin.appendChild(element);
	}
	garbageBin.innerHTML = '';
}

/* ****************************************************************************
 * Handle the options                                                         *
 *****************************************************************************/
var

defaultLabelOptions = {
	enabled: true,
	// rotation: 0,
	align: 'center',
	x: 0,
	y: 15,
	/*formatter: function() {
		return this.value;
	},*/
	style: {
		color: '#666',
		fontSize: '11px',
		lineHeight: '14px'
	}
};

defaultOptions = {
	colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
		'#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
	symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
	lang: {
		loading: 'Loading...',
		months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
				'August', 'September', 'October', 'November', 'December'],
		shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
		weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
		decimalPoint: '.',
		resetZoom: 'Reset zoom',
		resetZoomTitle: 'Reset zoom level 1:1',
		thousandsSep: ','
	},
	global: {
		useUTC: true
	},
	chart: {
		//animation: true,
		//alignTicks: false,
		//reflow: true,
		//className: null,
		//events: { load, selection },
		//margin: [null],
		//marginTop: null,
		//marginRight: null,
		//marginBottom: null,
		//marginLeft: null,
		borderColor: '#4572A7',
		//borderWidth: 0,
		borderRadius: 5,
		defaultSeriesType: 'line',
		ignoreHiddenSeries: true,
		//inverted: false,
		//shadow: false,
		spacingTop: 10,
		spacingRight: 10,
		spacingBottom: 15,
		spacingLeft: 10,
		style: {
			fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
			fontSize: '12px'
		},
		backgroundColor: '#FFFFFF',
		//plotBackgroundColor: null,
		plotBorderColor: '#C0C0C0'
		//plotBorderWidth: 0,
		//plotShadow: false,
		//zoomType: ''
	},
	title: {
		text: 'Chart title',
		align: 'center',
		// floating: false,
		// margin: 15,
		// x: 0,
		// verticalAlign: 'top',
		y: 15,
		style: {
			color: '#3E576F',
			fontSize: '16px'
		}

	},
	subtitle: {
		text: '',
		align: 'center',
		// floating: false
		// x: 0,
		// verticalAlign: 'top',
		y: 30,
		style: {
			color: '#6D869F'
		}
	},

	plotOptions: {
		line: { // base series options
			allowPointSelect: false,
			showCheckbox: false,
			animation: {
				duration: 1000
			},
			//connectNulls: false,
			//cursor: 'default',
			//dashStyle: null,
			//enableMouseTracking: true,
			events: {},
			//legendIndex: 0,
			lineWidth: 2,
			shadow: true,
			// stacking: null,
			marker: {
				enabled: true,
				//symbol: null,
				lineWidth: 0,
				radius: 4,
				lineColor: '#FFFFFF',
				//fillColor: null,
				states: { // states for a single point
					hover: {
						//radius: base + 2
					},
					select: {
						fillColor: '#FFFFFF',
						lineColor: '#000000',
						lineWidth: 2
					}
				}
			},
			point: {
				events: {}
			},
			dataLabels: merge(defaultLabelOptions, {
				enabled: false,
				y: -6,
				formatter: function () {
					return this.y;
				}
			}),

			//pointStart: 0,
			//pointInterval: 1,
			showInLegend: true,
			states: { // states for the entire series
				hover: {
					//enabled: false,
					//lineWidth: base + 1,
					marker: {
						// lineWidth: base + 1,
						// radius: base + 1
					}
				},
				select: {
					marker: {}
				}
			},
			stickyTracking: true
			//zIndex: null
		}
	},
	labels: {
		//items: [],
		style: {
			//font: defaultFont,
			position: ABSOLUTE,
			color: '#3E576F'
		}
	},
	legend: {
		enabled: true,
		align: 'center',
		//floating: false,
		layout: 'horizontal',
		labelFormatter: function () {
			return this.name;
		},
		borderWidth: 1,
		borderColor: '#909090',
		borderRadius: 5,
		// margin: 10,
		// reversed: false,
		shadow: false,
		// backgroundColor: null,
		style: {
			padding: '5px'
		},
		itemStyle: {
			cursor: 'pointer',
			color: '#3E576F'
		},
		itemHoverStyle: {
			cursor: 'pointer',
			color: '#000000'
		},
		itemHiddenStyle: {
			color: '#C0C0C0'
		},
		itemCheckboxStyle: {
			position: ABSOLUTE,
			width: '13px', // for IE precision
			height: '13px'
		},
		// itemWidth: undefined,
		symbolWidth: 16,
		symbolPadding: 5,
		verticalAlign: 'bottom',
		// width: undefined,
		x: 0,
		y: 0
	},

	loading: {
		hideDuration: 100,
		labelStyle: {
			fontWeight: 'bold',
			position: RELATIVE,
			top: '1em'
		},
		showDuration: 100,
		style: {
			position: ABSOLUTE,
			backgroundColor: 'white',
			opacity: 0.5,
			textAlign: 'center'
		}
	},

	tooltip: {
		enabled: true,
		//crosshairs: null,
		backgroundColor: 'rgba(255, 255, 255, .85)',
		borderWidth: 2,
		borderRadius: 5,
		//formatter: defaultFormatter,
		shadow: true,
		//shared: false,
		snap: hasTouch ? 25 : 10,
		style: {
			color: '#333333',
			fontSize: '12px',
			padding: '5px',
			whiteSpace: 'nowrap'
		}
	},

	toolbar: {
		itemStyle: {
			color: '#4572A7',
			cursor: 'pointer'
		}
	},

	credits: {
		enabled: true,
		text: 'Highcharts.com',
		href: 'http://www.highcharts.com',
		position: {
			align: 'right',
			x: -10,
			verticalAlign: 'bottom',
			y: -5
		},
		style: {
			cursor: 'pointer',
			color: '#909090',
			fontSize: '10px'
		}
	}
};

// Axis defaults
var defaultXAxisOptions =  {
	// allowDecimals: null,
	// alternateGridColor: null,
	// categories: [],
	absoluteLabels: false,
	dateTimeLabelFormats: {
		second: '%H:%M:%S',
		minute: '%H:%M',
		hour: '%H:%M',
		day: '%e. %b',
		week: '%e. %b',
		month: '%b \'%y',
		year: '%Y'
	},
	endOnTick: false,
	gridLineColor: '#C0C0C0',
	// gridLineDashStyle: 'solid', // docs
	// gridLineWidth: 0,
	// reversed: false,

	labels: defaultLabelOptions,
		// { step: null },
	lineColor: '#C0D0E0',
	lineWidth: 1,
	//linkedTo: null,
	max: null,
	min: null,
	minPadding: 0.01,
	maxPadding: 0.01,
	//maxZoom: null,
	minorGridLineColor: '#E0E0E0',
	// minorGridLineDashStyle: null,
	minorGridLineWidth: 1,
	minorTickColor: '#A0A0A0',
	//minorTickInterval: null,
	minorTickLength: 2,
	minorTickPosition: 'outside', // inside or outside
	//minorTickWidth: 0,
	//opposite: false,
	//offset: 0,
	//plotBands: [{
	//	events: {},
	//	zIndex: 1,
	//	labels: { align, x, verticalAlign, y, style, rotation, textAlign }
	//}],
	//plotLines: [{
	//	events: {}
	//  dashStyle: {}
	//	zIndex:
	//	labels: { align, x, verticalAlign, y, style, rotation, textAlign }
	//}],
	//reversed: false,
	// showFirstLabel: true,
	// showLastLabel: false,
	startOfWeek: 1,
	startOnTick: false,
	tickColor: '#C0D0E0',
	//tickInterval: null,
	tickLength: 5,
	tickmarkPlacement: 'between', // on or between
	tickPixelInterval: 100,
	tickPosition: 'outside',
	tickWidth: 1,
	title: {
		//text: null,
		align: 'middle', // low, middle or high
		//margin: 0 for horizontal, 10 for vertical axes,
		//rotation: 0,
		//side: 'outside',
		style: {
			color: '#6D869F',
			//font: defaultFont.replace('normal', 'bold')
			fontWeight: 'bold'
		}
		//x: 0,
		//y: 0
	},
	type: 'linear' // linear, logarithmic or datetime
},

defaultYAxisOptions = merge(defaultXAxisOptions, {
	absoluteLabels: false,
	endOnTick: true,
	gridLineWidth: 1,
	tickPixelInterval: 72,
	showLastLabel: true,
	labels: {
		align: 'right',
		x: -8,
		y: 3
	},
	lineWidth: 0,
	maxPadding: 0.05,
	minPadding: 0.05,
	startOnTick: true,
	tickWidth: 0,
	title: {
		rotation: 270,
		text: 'Y-values'
	},
	stackLabels: {
		enabled: false,
		//align: dynamic,
		//y: dynamic,
		//x: dynamic,
		//verticalAlign: dynamic,
		//textAlign: dynamic,
		//rotation: 0,
		formatter: function () {
			return this.total;
		},
		style: defaultLabelOptions.style
	}
}),

defaultLeftAxisOptions = {
	labels: {
		align: 'right',
		x: -8,
		y: null
	},
	title: {
		rotation: 270
	}
},
defaultRightAxisOptions = {
	labels: {
		align: 'left',
		x: 8,
		y: null
	},
	title: {
		rotation: 90
	}
},
defaultBottomAxisOptions = { // horizontal axis
	labels: {
		align: 'center',
		x: 0,
		y: 14
		// staggerLines: null
	},
	title: {
		rotation: 0
	}
},
defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
	labels: {
		y: -5
		// staggerLines: null
	}
});




// Series defaults
var defaultPlotOptions = defaultOptions.plotOptions,
	defaultSeriesOptions = defaultPlotOptions.line;
//defaultPlotOptions.line = merge(defaultSeriesOptions);
defaultPlotOptions.spline = merge(defaultSeriesOptions);
defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
	lineWidth: 0,
	states: {
		hover: {
			lineWidth: 0
		}
	}
});
defaultPlotOptions.area = merge(defaultSeriesOptions, {
	// threshold: 0,
	// lineColor: null, // overrides color, but lets fillColor be unaltered
	// fillOpacity: 0.75,
	// fillColor: null

});
defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
defaultPlotOptions.column = merge(defaultSeriesOptions, {
	borderColor: '#FFFFFF',
	borderWidth: 1,
	borderRadius: 0,
	//colorByPoint: undefined,
	groupPadding: 0.2,
	marker: null, // point options are specified in the base options
	pointPadding: 0.1,
	//pointWidth: null,
	minPointLength: 0,
	equalWidths: false,
	minPointWidth: 0,
	states: {
		hover: {
			brightness: 0.1,
			shadow: false
		},
		select: {
			color: '#C0C0C0',
			borderColor: '#000000',
			shadow: false
		}
	},
	dataLabels: {
		y: null,
		verticalAlign: null
	}
});
defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
	dataLabels: {
		align: 'left',
		x: 5,
		y: 0
	}
});
defaultPlotOptions.pie = merge(defaultSeriesOptions, {
	//dragType: '', // n/a
	borderColor: '#FFFFFF',
	borderWidth: 1,
	center: ['50%', '50%'],
	colorByPoint: true, // always true for pies
	dataLabels: {
		// align: null,
		// connectorWidth: 1,
		// connectorColor: point.color,
		// connectorPadding: 5,
		distance: 30,
		enabled: true,
		formatter: function () {
			return this.point.name;
		},
		// softConnector: true,
		y: 5
	},
	//innerSize: 0,
	legendType: 'point',
	marker: null, // point options are specified in the base options
	size: '75%',
	showInLegend: false,
	slicedOffset: 10,
	states: {
		hover: {
			brightness: 0.1,
			shadow: false
		}
	}

});

// set the default time methods
setTimeMethods();


/**
 * Handle color operations. The object methods are chainable.
 * @param {String} input The input color in either rbga or hex format
 */
var Color = function (input) {
	// declare variables
	var rgba = [], result;

	/**
	 * Parse the input color to rgba array
	 * @param {String} input
	 */
	function init(input) {

		// rgba
		result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input);
		if (result) {
			rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
		} else { // hex
			result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
			if (result) {
				rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
			}
		}

	}
	/**
	 * Return the color a specified format
	 * @param {String} format
	 */
	function get(format) {
		var ret;

		// it's NaN if gradient colors on a column chart
		if (rgba && !isNaN(rgba[0])) {
			if (format === 'rgb') {
				ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
			} else if (format === 'a') {
				ret = rgba[3];
			} else {
				ret = 'rgba(' + rgba.join(',') + ')';
			}
		} else {
			ret = input;
		}
		return ret;
	}

	/**
	 * Brighten the color
	 * @param {Number} alpha
	 */
	function brighten(alpha) {
		if (isNumber(alpha) && alpha !== 0) {
			var i;
			for (i = 0; i < 3; i++) {
				rgba[i] += pInt(alpha * 255);

				if (rgba[i] < 0) {
					rgba[i] = 0;
				}
				if (rgba[i] > 255) {
					rgba[i] = 255;
				}
			}
		}
		return this;
	}
	/**
	 * Set the color's opacity to a given alpha value
	 * @param {Number} alpha
	 */
	function setOpacity(alpha) {
		rgba[3] = alpha;
		return this;
	}

	// initialize: parse the input
	init(input);

	// public methods
	return {
		get: get,
		brighten: brighten,
		setOpacity: setOpacity
	};
};

/**
 * A wrapper object for SVG elements
 */
function SVGElement() {}

SVGElement.prototype = {
	/**
	 * Initialize the SVG renderer
	 * @param {Object} renderer
	 * @param {String} nodeName
	 */
	init: function (renderer, nodeName) {
		this.element = doc.createElementNS(SVG_NS, nodeName);
		this.renderer = renderer;
	},
	/**
	 * Animate a given attribute
	 * @param {Object} params
	 * @param {Number} options The same options as in jQuery animation
	 * @param {Function} complete Function to perform at the end of animation
	 */
	animate: function (params, options, complete) {
		var animOptions = pick(options, globalAnimation, true);
		if (animOptions) {
			animOptions = merge(animOptions);
			if (complete) { // allows using a callback with the global animation without overwriting it
				animOptions.complete = complete;
			}
			animate(this, params, animOptions);
		} else {
			this.attr(params);
			if (complete) {
				complete();
			}
		}
	},
	/**
	 * Set or get a given attribute
	 * @param {Object|String} hash
	 * @param {Mixed|Undefined} val
	 */
	attr: function (hash, val) {
		var key,
			value,
			i,
			child,
			element = this.element,
			nodeName = element.nodeName,
			renderer = this.renderer,
			skipAttr,
			shadows = this.shadows,
			htmlNode = this.htmlNode,
			hasSetSymbolSize,
			ret = this;

		// single key-value pair
		if (isString(hash) && defined(val)) {
			key = hash;
			hash = {};
			hash[key] = val;
		}

		// used as a getter: first argument is a string, second is undefined
		if (isString(hash)) {
			key = hash;
			if (nodeName === 'circle') {
				key = { x: 'cx', y: 'cy' }[key] || key;
			} else if (key === 'strokeWidth') {
				key = 'stroke-width';
			}
			ret = attr(element, key) || this[key] || 0;

			if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
				ret = parseFloat(ret);
			}

		// setter
		} else {

			for (key in hash) {
				skipAttr = false; // reset
				value = hash[key];

				// paths
				if (key === 'd') {
					if (value && value.join) { // join path
						value = value.join(' ');
					}
					if (/(NaN| {2}|^$)/.test(value)) {
						value = 'M 0 0';
					}
					this.d = value; // shortcut for animations

				// update child tspans x values
				} else if (key === 'x' && nodeName === 'text') {
					for (i = 0; i < element.childNodes.length; i++) {
						child = element.childNodes[i];
						// if the x values are equal, the tspan represents a linebreak
						if (attr(child, 'x') === attr(element, 'x')) {
							//child.setAttribute('x', value);
							attr(child, 'x', value);
						}
					}

					if (this.rotation) {
						attr(element, 'transform', 'rotate(' + this.rotation + ' ' + value + ' ' +
							pInt(hash.y || attr(element, 'y')) + ')');
					}

				// apply gradients
				} else if (key === 'fill') {
					value = renderer.color(value, element, key);

				// circle x and y
				} else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
					key = { x: 'cx', y: 'cy' }[key] || key;

				// translation and text rotation
				} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
					this[key] = value;
					this.updateTransform();
					skipAttr = true;

				// apply opacity as subnode (required by legacy WebKit and Batik)
				} else if (key === 'stroke') {
					value = renderer.color(value, element, key);

				// emulate VML's dashstyle implementation
				} else if (key === 'dashstyle') {
					key = 'stroke-dasharray';
					value = value && value.toLowerCase();
					if (value === 'solid') {
						value = NONE;
					} else if (value) {
						value = value
							.replace('shortdashdotdot', '3,1,1,1,1,1,')
							.replace('shortdashdot', '3,1,1,1')
							.replace('shortdot', '1,1,')
							.replace('shortdash', '3,1,')
							.replace('longdash', '8,3,')
							.replace(/dot/g, '1,3,')
							.replace('dash', '4,3,')
							.replace(/,$/, '')
							.split(','); // ending comma

						i = value.length;
						while (i--) {
							value[i] = pInt(value[i]) * hash['stroke-width'];
						}

						value = value.join(',');
					}

				// special
				} else if (key === 'isTracker') {
					this[key] = value;

				// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
				// is unable to cast them. Test again with final IE9.
				} else if (key === 'width') {
					value = pInt(value);

				// Text alignment
				} else if (key === 'align') {
					key = 'text-anchor';
					value = { left: 'start', center: 'middle', right: 'end' }[value];


				// Title requires a subnode, #431
				} else if (key === 'title') {
					var title = doc.createElementNS(SVG_NS, 'title');
					title.appendChild(doc.createTextNode(value));
					element.appendChild(title);
				}



				// jQuery animate changes case
				if (key === 'strokeWidth') {
					key = 'stroke-width';
				}

				// Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
				if (isWebKit && key === 'stroke-width' && value === 0) {
					value = 0.000001;
				}

				// symbols
				if (this.symbolName && /^(x|y|r|start|end|innerR)/.test(key)) {


					if (!hasSetSymbolSize) {
						this.symbolAttr(hash);
						hasSetSymbolSize = true;
					}
					skipAttr = true;
				}

				// let the shadow follow the main element
				if (shadows && /^(width|height|visibility|x|y|d)$/.test(key)) {
					i = shadows.length;
					while (i--) {
						attr(shadows[i], key, value);
					}
				}

				// validate heights
				if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
					value = 0;
				}

				if (key === 'text') {
					// only one node allowed
					this.textStr = value;
					if (this.added) {
						renderer.buildText(this);
					}
				} else if (!skipAttr) {
					//element.setAttribute(key, value);
					attr(element, key, value);
				}

				// Issue #38
				if (htmlNode && (key === 'x' || key === 'y' ||
						key === 'translateX' || key === 'translateY' || key === 'visibility')) {
					var wrapper = this,
						bBox,
						arr = htmlNode.length ? htmlNode : [this],
						length = arr.length,
						itemWrapper,
						j;

					for (j = 0; j < length; j++) {
						itemWrapper = arr[j];
						bBox = itemWrapper.getBBox();
						htmlNode = itemWrapper.htmlNode; // reassign to child item
						css(htmlNode, extend(wrapper.styles, {
							left: (bBox.x + (wrapper.translateX || 0)) + PX,
							top: (bBox.y + (wrapper.translateY || 0)) + PX
						}));

						if (key === 'visibility') {
							css(htmlNode, {
								visibility: value
							});
						}
					}
				}

			}

		}
		return ret;
	},

	/**
	 * If one of the symbol size affecting parameters are changed,
	 * check all the others only once for each call to an element's
	 * .attr() method
	 * @param {Object} hash
	 */
	symbolAttr: function (hash) {
		var wrapper = this;

		each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR'], function (key) {
			wrapper[key] = pick(hash[key], wrapper[key]);
		});

		wrapper.attr({
			d: wrapper.renderer.symbols[wrapper.symbolName](
					mathRound(wrapper.x * 2) / 2, // Round to halves. Issue #274.
					mathRound(wrapper.y * 2) / 2,
					wrapper.r,
					{
						start: wrapper.start,
						end: wrapper.end,
						width: wrapper.width,
						height: wrapper.height,
						innerR: wrapper.innerR
					}
			)
		});
	},

	/**
	 * Apply a clipping path to this object
	 * @param {String} id
	 */
	clip: function (clipRect) {
		return this.attr('clip-path', 'url(' + this.renderer.url + '#' + clipRect.id + ')');
	},

	/**
	 * Calculate the coordinates needed for drawing a rectangle crisply and return the
	 * calculated attributes
	 * @param {Number} strokeWidth
	 * @param {Number} x
	 * @param {Number} y
	 * @param {Number} width
	 * @param {Number} height
	 */
	crisp: function (strokeWidth, x, y, width, height) {

		var wrapper = this,
			key,
			attr = {},
			values = {},
			normalizer;

		strokeWidth = strokeWidth || wrapper.strokeWidth || 0;
		normalizer = strokeWidth % 2 / 2;

		// normalize for crisp edges
		values.x = mathFloor(x || wrapper.x || 0) + normalizer;
		values.y = mathFloor(y || wrapper.y || 0) + normalizer;
		values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
		values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
		values.strokeWidth = strokeWidth;

		for (key in values) {
			if (wrapper[key] !== values[key]) { // only set attribute if changed
				wrapper[key] = attr[key] = values[key];
			}
		}

		return attr;
	},

	/**
	 * Set styles for the element
	 * @param {Object} styles
	 */
	css: function (styles) {
		/*jslint unparam: true*//* allow unused param a in the regexp function below */
		var elemWrapper = this,
			elem = elemWrapper.element,
			textWidth = styles && styles.width && elem.nodeName === 'text',
			n,
			serializedCss = '',
			hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
		/*jslint unparam: false*/

		// convert legacy
		if (styles && styles.color) {
			styles.fill = styles.color;
		}

		// Merge the new styles with the old ones
		styles = extend(
			elemWrapper.styles,
			styles
		);


		// store object
		elemWrapper.styles = styles;


		// serialize and set style attribute
		if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
			if (textWidth) {
				delete styles.width;
			}
			css(elemWrapper.element, styles);
		} else {
			for (n in styles) {
				serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
			}
			elemWrapper.attr({
				style: serializedCss
			});
		}


		// re-build text
		if (textWidth && elemWrapper.added) {
			elemWrapper.renderer.buildText(elemWrapper);
		}

		return elemWrapper;
	},

	/**
	 * Add an event listener
	 * @param {String} eventType
	 * @param {Function} handler
	 */
	on: function (eventType, handler) {
		var fn = handler;
		// touch
		if (hasTouch && eventType === 'click') {
			eventType = 'touchstart';
			fn = function (e) {
				e.preventDefault();
				handler();
			};
		}
		// simplest possible event model for internal use
		this.element['on' + eventType] = fn;
		return this;
	},


	/**
	 * Move an object and its children by x and y values
	 * @param {Number} x
	 * @param {Number} y
	 */
	translate: function (x, y) {
		return this.attr({
			translateX: x,
			translateY: y
		});
	},

	/**
	 * Invert a group, rotate and flip
	 */
	invert: function () {
		var wrapper = this;
		wrapper.inverted = true;
		wrapper.updateTransform();
		return wrapper;
	},

	/**
	 * Private method to update the transform attribute based on internal
	 * properties
	 */
	updateTransform: function () {
		var wrapper = this,
			translateX = wrapper.translateX || 0,
			translateY = wrapper.translateY || 0,
			inverted = wrapper.inverted,
			rotation = wrapper.rotation,
			transform = [];

		// flipping affects translate as adjustment for flipping around the group's axis
		if (inverted) {
			translateX += wrapper.attr('width');
			translateY += wrapper.attr('height');
		}

		// apply translate
		if (translateX || translateY) {
			transform.push('translate(' + translateX + ',' + translateY + ')');
		}

		// apply rotation
		if (inverted) {
			transform.push('rotate(90) scale(-1,1)');
		} else if (rotation) { // text rotation
			transform.push('rotate(' + rotation + ' ' + wrapper.x + ' ' + wrapper.y + ')');
		}

		if (transform.length) {
			attr(wrapper.element, 'transform', transform.join(' '));
		}
	},
	/**
	 * Bring the element to the front
	 */
	toFront: function () {
		var element = this.element;
		element.parentNode.appendChild(element);
		return this;
	},


	/**
	 * Break down alignment options like align, verticalAlign, x and y
	 * to x and y relative to the chart.
	 *
	 * @param {Object} alignOptions
	 * @param {Boolean} alignByTranslate
	 * @param {Object} box The box to align to, needs a width and height
	 *
	 */
	align: function (alignOptions, alignByTranslate, box) {
		var elemWrapper = this;

		if (!alignOptions) { // called on resize
			alignOptions = elemWrapper.alignOptions;
			alignByTranslate = elemWrapper.alignByTranslate;
		} else { // first call on instanciate
			elemWrapper.alignOptions = alignOptions;
			elemWrapper.alignByTranslate = alignByTranslate;
			if (!box) { // boxes other than renderer handle this internally
				elemWrapper.renderer.alignedObjects.push(elemWrapper);
			}
		}

		box = pick(box, elemWrapper.renderer);

		var align = alignOptions.align,
			vAlign = alignOptions.verticalAlign,
			x = (box.x || 0) + (alignOptions.x || 0), // default: left align
			y = (box.y || 0) + (alignOptions.y || 0), // default: top align
			attribs = {};


		// align
		if (/^(right|center)$/.test(align)) {
			x += (box.width - (alignOptions.width || 0)) /
					{ right: 1, center: 2 }[align];
		}
		attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);


		// vertical align
		if (/^(bottom|middle)$/.test(vAlign)) {
			y += (box.height - (alignOptions.height || 0)) /
					({ bottom: 1, middle: 2 }[vAlign] || 1);

		}
		attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);

		// animate only if already placed
		elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
		elemWrapper.placed = true;
		elemWrapper.alignAttr = attribs;

		return elemWrapper;
	},

	/**
	 * Get the bounding box (width, height, x and y) for the element
	 */
	getBBox: function () {
		var bBox,
			width,
			height,
			rotation = this.rotation,
			rad = rotation * deg2rad;

		try { // fails in Firefox if the container has display: none
			// use extend because IE9 is not allowed to change width and height in case
			// of rotation (below)
			bBox = extend({}, this.element.getBBox());
		} catch (e) {
			bBox = { width: 0, height: 0 };
		}
		width = bBox.width;
		height = bBox.height;

		// adjust for rotated text
		if (rotation) {
			bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
			bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
		}

		return bBox;
	},

	/* *
	 * Manually compute width and height of rotated text from non-rotated. Shared by SVG and VML
	 * @param {Object} bBox
	 * @param {number} rotation
	 * /
	rotateBBox: function(bBox, rotation) {
		var rad = rotation * math.PI * 2 / 360, // radians
			width = bBox.width,
			height = bBox.height;


	},*/

	/**
	 * Show the element
	 */
	show: function () {
		return this.attr({ visibility: VISIBLE });
	},

	/**
	 * Hide the element
	 */
	hide: function () {
		return this.attr({ visibility: HIDDEN });
	},

	/**
	 * Add the element
	 * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
	 *    to append the element to the renderer.box.
	 */
	add: function (parent) {

		var renderer = this.renderer,
			parentWrapper = parent || renderer,
			parentNode = parentWrapper.element || renderer.box,
			childNodes = parentNode.childNodes,
			element = this.element,
			zIndex = attr(element, 'zIndex'),
			otherElement,
			otherZIndex,
			i;

		// mark as inverted
		this.parentInverted = parent && parent.inverted;

		// build formatted text
		if (this.textStr !== undefined) {
			renderer.buildText(this);
		}

		// register html spans in groups
		if (parent && this.htmlNode) {
			if (!parent.htmlNode) {
				parent.htmlNode = [];
			}
			parent.htmlNode.push(this);
		}

		// mark the container as having z indexed children
		if (zIndex) {
			parentWrapper.handleZ = true;
			zIndex = pInt(zIndex);
		}

		// insert according to this and other elements' zIndex
		if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
			for (i = 0; i < childNodes.length; i++) {
				otherElement = childNodes[i];
				otherZIndex = attr(otherElement, 'zIndex');
				if (otherElement !== element && (
						// insert before the first element with a higher zIndex
						pInt(otherZIndex) > zIndex ||
						// if no zIndex given, insert before the first element with a zIndex
						(!defined(zIndex) && defined(otherZIndex))

						)) {
					parentNode.insertBefore(element, otherElement);
					return this;
				}
			}
		}

		// default: append at the end
		parentNode.appendChild(element);

		this.added = true;

		return this;
	},

	/**
	 * Removes a child either by removeChild or move to garbageBin.
	 * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
	 */
	safeRemoveChild: function (element) {
		var parentNode = element.parentNode;
		if (parentNode) {
			parentNode.removeChild(element);
		}
	},

	/**
	 * Destroy the element and element wrapper
	 */
	destroy: function () {
		var wrapper = this,
			element = wrapper.element || {},
			shadows = wrapper.shadows,
			key,
			i;

		// remove events
		element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
		stop(wrapper); // stop running animations

		if (wrapper.clipPath) {
			wrapper.clipPath = wrapper.clipPath.destroy();
		}

		// Destroy stops in case this is a gradient object
		if (wrapper.stops) {
			for (i = 0; i < wrapper.stops.length; i++) {
				wrapper.stops[i] = wrapper.stops[i].destroy();
			}
			wrapper.stops = null;
		}

		// remove element
		wrapper.safeRemoveChild(element);

		// destroy shadows
		if (shadows) {
			each(shadows, function (shadow) {
				wrapper.safeRemoveChild(shadow);
			});
		}

		// remove from alignObjects
		erase(wrapper.renderer.alignedObjects, wrapper);

		for (key in wrapper) {
			delete wrapper[key];
		}

		return null;
	},

	/**
	 * Empty a group element
	 */
	empty: function () {
		var element = this.element,
			childNodes = element.childNodes,
			i = childNodes.length;

		while (i--) {
			element.removeChild(childNodes[i]);
		}
	},

	/**
	 * Add a shadow to the element. Must be done after the element is added to the DOM
	 * @param {Boolean} apply
	 */
	shadow: function (apply, group) {
		var shadows = [],
			i,
			shadow,
			element = this.element,

			// compensate for inverted plot area
			transform = this.parentInverted ? '(-1,-1)' : '(1,1)';


		if (apply) {
			for (i = 1; i <= 3; i++) {
				shadow = element.cloneNode(0);
				attr(shadow, {
					'isShadow': 'true',
					'stroke': 'rgb(0, 0, 0)',
					'stroke-opacity': 0.05 * i,
					'stroke-width': 7 - 2 * i,
					'transform': 'translate' + transform,
					'fill': NONE
				});

				if (group) {
					group.element.appendChild(shadow);
				} else {
					element.parentNode.insertBefore(shadow, element);
				}

				shadows.push(shadow);
			}

			this.shadows = shadows;
		}
		return this;

	}
};

/**
 * The default SVG renderer
 */
var SVGRenderer = function () {
	this.init.apply(this, arguments);
};
SVGRenderer.prototype = {

	Element: SVGElement,

	/**
	 * Initialize the SVGRenderer
	 * @param {Object} container
	 * @param {Number} width
	 * @param {Number} height
	 * @param {Boolean} forExport
	 */
	init: function (container, width, height, forExport) {
		var renderer = this,
			loc = location,
			boxWrapper;

		boxWrapper = renderer.createElement('svg')
			.attr({
				xmlns: SVG_NS,
				version: '1.1'
			});
		container.appendChild(boxWrapper.element);

		// object properties
		renderer.box = boxWrapper.element;
		renderer.boxWrapper = boxWrapper;
		renderer.alignedObjects = [];
		renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, ''); // page url used for internal references
		renderer.defs = this.createElement('defs').add();
		renderer.forExport = forExport;
		renderer.gradients = []; // Array where gradient SvgElements are stored

		renderer.setSize(width, height, false);

	},

	/**
	 * Destroys the renderer and its allocated members.
	 */
	destroy: function () {
		var renderer = this,
			i,
			rendererGradients = renderer.gradients,
			rendererDefs = renderer.defs;
		renderer.box = null;
		renderer.boxWrapper = renderer.boxWrapper.destroy();

		// Call destroy on all gradient elements
		if (rendererGradients) { // gradients are null in VMLRenderer
			for (i = 0; i < rendererGradients.length; i++) {
				renderer.gradients[i] = rendererGradients[i].destroy();
			}
			renderer.gradients = null;
		}

		// Defs are null in VMLRenderer
		// Otherwise, destroy them here.
		if (rendererDefs) {
			renderer.defs = rendererDefs.destroy();
		}

		renderer.alignedObjects = null;

		return null;
	},

	/**
	 * Create a wrapper for an SVG element
	 * @param {Object} nodeName
	 */
	createElement: function (nodeName) {
		var wrapper = new this.Element();
		wrapper.init(this, nodeName);
		return wrapper;
	},


	/**
	 * Parse a simple HTML string into SVG tspans
	 *
	 * @param {Object} textNode The parent text SVG node
	 */
	buildText: function (wrapper) {
		var textNode = wrapper.element,
			lines = pick(wrapper.textStr, '').toString()
				.replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
				.replace(/<(i|em)>/g, '<span style="font-style:italic">')
				.replace(/<a/g, '<span')
				.replace(/<\/(b|strong|i|em|a)>/g, '</span>')
				.split(/<br.*?>/g),
			childNodes = textNode.childNodes,
			styleRegex = /style="([^"]+)"/,
			hrefRegex = /href="([^"]+)"/,
			parentX = attr(textNode, 'x'),
			textStyles = wrapper.styles,
			renderAsHtml = textStyles && wrapper.useHTML && !this.forExport,
			htmlNode = wrapper.htmlNode,
			//arr, issue #38 workaround
			width = textStyles && pInt(textStyles.width),
			textLineHeight = textStyles && textStyles.lineHeight,
			lastLine,
			GET_COMPUTED_STYLE = 'getComputedStyle',
			i = childNodes.length;

		// remove old text
		while (i--) {
			textNode.removeChild(childNodes[i]);
		}

		if (width && !wrapper.added) {
			this.box.appendChild(textNode); // attach it to the DOM to read offset width
		}

		each(lines, function (line, lineNo) {
			var spans, spanNo = 0, lineHeight;

			line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
			spans = line.split('|||');

			each(spans, function (span) {
				if (span !== '' || spans.length === 1) {
					var attributes = {},
						tspan = doc.createElementNS(SVG_NS, 'tspan');
					if (styleRegex.test(span)) {
						attr(
							tspan,
							'style',
							span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
						);
					}
					if (hrefRegex.test(span)) {
						attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
						css(tspan, { cursor: 'pointer' });
					}

					span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
						.replace(/&lt;/g, '<')
						.replace(/&gt;/g, '>');

					// issue #38 workaround.
					/*if (reverse) {
						arr = [];
						i = span.length;
						while (i--) {
							arr.push(span.charAt(i));
						}
						span = arr.join('');
					}*/

					// add the text node
					tspan.appendChild(doc.createTextNode(span));

					if (!spanNo) { // first span in a line, align it to the left
						attributes.x = parentX;
					} else {
						// Firefox ignores spaces at the front or end of the tspan
						attributes.dx = 3; // space
					}

					// first span on subsequent line, add the line height
					if (!spanNo) {
						if (lineNo) {

							// allow getting the right offset height in exporting in IE
							if (!hasSVG && wrapper.renderer.forExport) {
								css(tspan, { display: 'block' });
							}

							// Webkit and opera sometimes return 'normal' as the line height. In that
							// case, webkit uses offsetHeight, while Opera falls back to 18
							lineHeight = win[GET_COMPUTED_STYLE] &&
								pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height'));

							if (!lineHeight || isNaN(lineHeight)) {
								lineHeight = textLineHeight || lastLine.offsetHeight || 18;
							}
							attr(tspan, 'dy', lineHeight);
						}
						lastLine = tspan; // record for use in next line
					}

					// add attributes
					attr(tspan, attributes);

					// append it
					textNode.appendChild(tspan);

					spanNo++;

					// check width and apply soft breaks
					if (width) {
						var words = span.replace(/-/g, '- ').split(' '),
							tooLong,
							actualWidth,
							rest = [];

						while (words.length || rest.length) {
							actualWidth = wrapper.getBBox().width;
							tooLong = actualWidth > width;
							if (!tooLong || words.length === 1) { // new line needed
								words = rest;
								rest = [];
								if (words.length) {
									tspan = doc.createElementNS(SVG_NS, 'tspan');
									attr(tspan, {
										dy: textLineHeight || 16,
										x: parentX
									});
									textNode.appendChild(tspan);

									if (actualWidth > width) { // a single word is pressing it out
										width = actualWidth;
									}
								}
							} else { // append to existing line tspan
								tspan.removeChild(tspan.firstChild);
								rest.unshift(words.pop());
							}
							if (words.length) {
								tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
							}
						}
					}
				}
			});
		});

		// Fix issue #38 and allow HTML in tooltips and other labels
		if (renderAsHtml) {
			if (!htmlNode) {
				htmlNode = wrapper.htmlNode = createElement('span', null, extend(textStyles, {
					position: ABSOLUTE,
					top: 0,
					left: 0
				}), this.box.parentNode);
			}
			htmlNode.innerHTML = wrapper.textStr;

			i = childNodes.length;
			while (i--) {
				childNodes[i].style.visibility = HIDDEN;
			}
		}
	},

	/**
	 * Make a straight line crisper by not spilling out to neighbour pixels
	 * @param {Array} points
	 * @param {Number} width
	 */
	crispLine: function (points, width) {
		// points format: [M, 0, 0, L, 100, 0]
		// normalize to a crisp line
		if (points[1] === points[4]) {
			points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
		}
		if (points[2] === points[5]) {
			points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
		}
		return points;
	},


	/**
	 * Draw a path
	 * @param {Array} path An SVG path in array form
	 */
	path: function (path) {
		return this.createElement('path').attr({
			d: path,
			fill: NONE
		});
	},

	/**
	 * Draw and return an SVG circle
	 * @param {Number} x The x position
	 * @param {Number} y The y position
	 * @param {Number} r The radius
	 */
	circle: function (x, y, r) {
		var attr = isObject(x) ?
			x :
			{
				x: x,
				y: y,
				r: r
			};

		return this.createElement('circle').attr(attr);
	},
	
	star: function (x, y, r) {
	
	},

	/**
	 * Draw and return an arc
	 * @param {Number} x X position
	 * @param {Number} y Y position
	 * @param {Number} r Radius
	 * @param {Number} innerR Inner radius like used in donut charts
	 * @param {Number} start Starting angle
	 * @param {Number} end Ending angle
	 */
	arc: function (x, y, r, innerR, start, end) {
		// arcs are defined as symbols for the ability to set
		// attributes in attr and animate

		if (isObject(x)) {
			y = x.y;
			r = x.r;
			innerR = x.innerR;
			start = x.start;
			end = x.end;
			x = x.x;
		}

		return this.symbol('arc', x || 0, y || 0, r || 0, {
			innerR: innerR || 0,
			start: start || 0,
			end: end || 0
		});
	},

	/**
	 * Draw and return a rectangle
	 * @param {Number} x Left position
	 * @param {Number} y Top position
	 * @param {Number} width
	 * @param {Number} height
	 * @param {Number} r Border corner radius
	 * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
	 */
	rect: function (x, y, width, height, r, strokeWidth) {
		if (isObject(x)) {
			y = x.y;
			width = x.width;
			height = x.height;
			r = x.r;
			strokeWidth = x.strokeWidth;
			x = x.x;
		}
		var wrapper = this.createElement('rect').attr({
			rx: r,
			ry: r,
			fill: NONE
		});

		return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
	},

	/**
	 * Resize the box and re-align all aligned elements
	 * @param {Object} width
	 * @param {Object} height
	 * @param {Boolean} animate
	 *
	 */
	setSize: function (width, height, animate) {
		var renderer = this,
			alignedObjects = renderer.alignedObjects,
			i = alignedObjects.length;

		renderer.width = width;
		renderer.height = height;

		renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
			width: width,
			height: height
		});

		while (i--) {
			alignedObjects[i].align();
		}
	},

	/**
	 * Create a group
	 * @param {String} name The group will be given a class name of 'highcharts-{name}'.
	 *     This can be used for styling and scripting.
	 */
	g: function (name) {
		var elem = this.createElement('g');
		return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
	},

	/**
	 * Display an image
	 * @param {String} src
	 * @param {Number} x
	 * @param {Number} y
	 * @param {Number} width
	 * @param {Number} height
	 */
	image: function (src, x, y, width, height) {
		var attribs = {
				preserveAspectRatio: NONE
			},
			elemWrapper;

		// optional properties
		if (arguments.length > 1) {
			extend(attribs, {
				x: x,
				y: y,
				width: width,
				height: height
			});
		}

		elemWrapper = this.createElement('image').attr(attribs);

		// set the href in the xlink namespace
		if (elemWrapper.element.setAttributeNS) {
			elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
				'href', src);
		} else {
			// could be exporting in IE
			// using href throws "not supported" in ie7 and under, requries regex shim to fix later
			elemWrapper.element.setAttribute('hc-svg-href', src);
		}

		return elemWrapper;
	},

	/**
	 * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
	 *
	 * @param {Object} symbol
	 * @param {Object} x
	 * @param {Object} y
	 * @param {Object} radius
	 * @param {Object} options
	 */
	symbol: function (symbol, x, y, radius, options) {

		var obj,

			// get the symbol definition function
			symbolFn = this.symbols[symbol],

			// check if there's a path defined for this symbol
			path = symbolFn && symbolFn(
				mathRound(x),
				mathRound(y),
				radius,
				options
			),

			imageRegex = /^url\((.*?)\)$/,
			imageSrc,
			imageSize;

		if (path) {

			obj = this.path(path);
			// expando properties for use in animate and attr
			extend(obj, {
				symbolName: symbol,
				x: x,
				y: y,
				r: radius
			});
			if (options) {
				extend(obj, options);
			}


		// image symbols
		} else if (imageRegex.test(symbol)) {

			var centerImage = function (img, size) {
				img.attr({
					width: size[0],
					height: size[1]
				}).translate(
					-mathRound(size[0] / 2),
					-mathRound(size[1] / 2)
				);
			};

			imageSrc = symbol.match(imageRegex)[1];
			imageSize = symbolSizes[imageSrc];

			// create the image synchronously, add attribs async
			obj = this.image(imageSrc)
				.attr({
					x: x,
					y: y
				});

			if (imageSize) {
				centerImage(obj, imageSize);
			} else {
				// initialize image to be 0 size so export will still function if there's no cached sizes
				obj.attr({ width: 0, height: 0 });

				// create a dummy JavaScript image to get the width and height
				createElement('img', {
					onload: function () {
						var img = this;
						centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
					},
					src: imageSrc
				});
			}

		// default circles
		} else {
			obj = this.circle(x, y, radius);
		}

		return obj;
	},

	/**
	 * An extendable collection of functions for defining symbol paths.
	 */
	symbols: {
		'square': function (x, y, radius) {
			var len = 0.707 * radius;
			return [
				M, x - len, y - len,
				L, x + len, y - len,
				x + len, y + len,
				x - len, y + len,
				'Z'
			];
		},

		'triangle': function (x, y, radius) {
			return [
				M, x, y - 1.33 * radius,
				L, x + radius, y + 0.67 * radius,
				x - radius, y + 0.67 * radius,
				'Z'
			];
		},

		'triangle-down': function (x, y, radius) {
			return [
				M, x, y + 1.33 * radius,
				L, x - radius, y - 0.67 * radius,
				x + radius, y - 0.67 * radius,
				'Z'
			];
		},
		'diamond': function (x, y, radius) {
			return [
				M, x, y - radius,
				L, x + radius, y,
				x, y + radius,
				x - radius, y,
				'Z'
			];
		},
		'arc': function (x, y, radius, options) {
			var start = options.start,
				end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
				innerRadius = options.innerR,
				cosStart = mathCos(start),
				sinStart = mathSin(start),
				cosEnd = mathCos(end),
				sinEnd = mathSin(end),
				longArc = options.end - start < mathPI ? 0 : 1;

			return [
				M,
				x + radius * cosStart,
				y + radius * sinStart,
				'A', // arcTo
				radius, // x radius
				radius, // y radius
				0, // slanting
				longArc, // long or short arc
				1, // clockwise
				x + radius * cosEnd,
				y + radius * sinEnd,
				L,
				x + innerRadius * cosEnd,
				y + innerRadius * sinEnd,
				'A', // arcTo
				innerRadius, // x radius
				innerRadius, // y radius
				0, // slanting
				longArc, // long or short arc
				0, // clockwise
				x + innerRadius * cosStart,
				y + innerRadius * sinStart,

				'Z' // close
			];
		}
	},

	/**
	 * Define a clipping rectangle
	 * @param {String} id
	 * @param {Number} x
	 * @param {Number} y
	 * @param {Number} width
	 * @param {Number} height
	 */
	clipRect: function (x, y, width, height) {
		var wrapper,
			id = PREFIX + idCounter++,

			clipPath = this.createElement('clipPath').attr({
				id: id
			}).add(this.defs);

		wrapper = this.rect(x, y, width, height, 0).add(clipPath);
		wrapper.id = id;
		wrapper.clipPath = clipPath;

		return wrapper;
	},


	/**
	 * Take a color and return it if it's a string, make it a gradient if it's a
	 * gradient configuration object
	 *
	 * @param {Object} color The color or config object
	 */
	color: function (color, elem, prop) {
		var colorObject,
			regexRgba = /^rgba/;
		if (color && color.linearGradient) {
			var renderer = this,
				strLinearGradient = 'linearGradient',
				linearGradient = color[strLinearGradient],
				id = PREFIX + idCounter++,
				gradientObject,
				stopColor,
				stopOpacity;
			gradientObject = renderer.createElement(strLinearGradient).attr({
				id: id,
				gradientUnits: 'userSpaceOnUse',
				x1: linearGradient[0],
				y1: linearGradient[1],
				x2: linearGradient[2],
				y2: linearGradient[3]
			}).add(renderer.defs);

			// Keep a reference to the gradient object so it is possible to destroy it later
			renderer.gradients.push(gradientObject);

			// The gradient needs to keep a list of stops to be able to destroy them
			gradientObject.stops = [];
			each(color.stops, function (stop) {
				var stopObject;
				if (regexRgba.test(stop[1])) {
					colorObject = Color(stop[1]);
					stopColor = colorObject.get('rgb');
					stopOpacity = colorObject.get('a');
				} else {
					stopColor = stop[1];
					stopOpacity = 1;
				}
				stopObject = renderer.createElement('stop').attr({
					offset: stop[0],
					'stop-color': stopColor,
					'stop-opacity': stopOpacity
				}).add(gradientObject);

				// Add the stop element to the gradient
				gradientObject.stops.push(stopObject);
			});

			return 'url(' + this.url + '#' + id + ')';

		// Webkit and Batik can't show rgba.
		} else if (regexRgba.test(color)) {
			colorObject = Color(color);
			attr(elem, prop + '-opacity', colorObject.get('a'));

			return colorObject.get('rgb');


		} else {
			// Remove the opacity attribute added above. Does not throw if the attribute is not there.
			elem.removeAttribute(prop + '-opacity');

			return color;
		}

	},


	/**
	 * Add text to the SVG object
	 * @param {String} str
	 * @param {Number} x Left position
	 * @param {Number} y Top position
	 * @param {Boolean} useHTML Use HTML to render the text
	 */
	text: function (str, x, y, useHTML) {

		// declare variables
		var defaultChartStyle = defaultOptions.chart.style,
			wrapper;

		x = mathRound(pick(x, 0));
		y = mathRound(pick(y, 0));

		wrapper = this.createElement('text')
			.attr({
				x: x,
				y: y,
				text: str
			})
			.css({
				fontFamily: defaultChartStyle.fontFamily,
				fontSize: defaultChartStyle.fontSize
			});

		wrapper.x = x;
		wrapper.y = y;
		wrapper.useHTML = useHTML;
		return wrapper;
	}
}; // end SVGRenderer

// general renderer
Renderer = SVGRenderer;



/* ****************************************************************************
 *                                                                            *
 * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *
 *                                                                            *
 * For applications and websites that don't need IE support, like platform    *
 * targeted mobile apps and web apps, this code can be removed.               *
 *                                                                            *
 *****************************************************************************/
var VMLRenderer;
if (!hasSVG) {

/**
 * The VML element wrapper.
 */
var VMLElement = extendClass(SVGElement, {

	/**
	 * Initialize a new VML element wrapper. It builds the markup as a string
	 * to minimize DOM traffic.
	 * @param {Object} renderer
	 * @param {Object} nodeName
	 */
	init: function (renderer, nodeName) {
		var markup =  ['<', nodeName, ' filled="f" stroked="f"'],
			style = ['position: ', ABSOLUTE, ';'];

		// divs and shapes need size
		if (nodeName === 'shape' || nodeName === DIV) {
			style.push('left:0;top:0;width:10px;height:10px;');
		}
		if (docMode8) {
			style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
		}

		markup.push(' style="', style.join(''), '"/>');

		// create element with default attributes and style
		if (nodeName) {
			markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ?
				markup.join('')
				: renderer.prepVML(markup);
			this.element = createElement(markup);
		}

		this.renderer = renderer;
	},

	/**
	 * Add the node to the given parent
	 * @param {Object} parent
	 */
	add: function (parent) {
		var wrapper = this,
			renderer = wrapper.renderer,
			element = wrapper.element,
			box = renderer.box,
			inverted = parent && parent.inverted,

			// get the parent node
			parentNode = parent ?
				parent.element || parent :
				box;


		// if the parent group is inverted, apply inversion on all children
		if (inverted) { // only on groups
			renderer.invertChild(element, parentNode);
		}

		// issue #140 workaround - related to #61 and #74
		if (docMode8 && parentNode.gVis === HIDDEN) {
			css(element, { visibility: HIDDEN });
		}

		// append it
		parentNode.appendChild(element);

		// align text after adding to be able to read offset
		wrapper.added = true;
		if (wrapper.alignOnAdd) {
			wrapper.updateTransform();
		}

		return wrapper;
	},

	/**
	 * Get or set attributes
	 */
	attr: function (hash, val) {
		var key,
			value,
			i,
			element = this.element || {},
			elemStyle = element.style,
			nodeName = element.nodeName,
			renderer = this.renderer,
			symbolName = this.symbolName,
			childNodes,
			hasSetSymbolSize,
			shadows = this.shadows,
			skipAttr,
			ret = this;

		// single key-value pair
		if (isString(hash) && defined(val)) {
			key = hash;
			hash = {};
			hash[key] = val;
		}

		// used as a getter, val is undefined
		if (isString(hash)) {
			key = hash;
			if (key === 'strokeWidth' || key === 'stroke-width') {
				ret = this.strokeweight;
			} else {
				ret = this[key];
			}

		// setter
		} else {
			for (key in hash) {
				value = hash[key];
				skipAttr = false;

				// prepare paths
				// symbols
				if (symbolName && /^(x|y|r|start|end|width|height|innerR)/.test(key)) {
					// if one of the symbol size affecting parameters are changed,
					// check all the others only once for each call to an element's
					// .attr() method
					if (!hasSetSymbolSize) {
						this.symbolAttr(hash);

						hasSetSymbolSize = true;
					}

					skipAttr = true;

				} else if (key === 'd') {
					value = value || [];
					this.d = value.join(' '); // used in getter for animation

					// convert paths
					i = value.length;
					var convertedPath = [];
					while (i--) {

						// Multiply by 10 to allow subpixel precision.
						// Substracting half a pixel seems to make the coordinates
						// align with SVG, but this hasn't been tested thoroughly
						if (isNumber(value[i])) {
							convertedPath[i] = mathRound(value[i] * 10) - 5;
						} else if (value[i] === 'Z') { // close the path
							convertedPath[i] = 'x';
						} else {
							convertedPath[i] = value[i];
						}

					}
					value = convertedPath.join(' ') || 'x';
					element.path = value;

					// update shadows
					if (shadows) {
						i = shadows.length;
						while (i--) {
							shadows[i].path = value;
						}
					}
					skipAttr = true;

				// directly mapped to css
				} else if (key === 'zIndex' || key === 'visibility') {

					// issue 61 workaround
					if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
						element.gVis = value;
						childNodes = element.childNodes;
						i = childNodes.length;
						while (i--) {
							css(childNodes[i], { visibility: value });
						}
						if (value === VISIBLE) { // issue 74
							value = null;
						}
					}

					if (value) {
						elemStyle[key] = value;
					}



					skipAttr = true;

				// width and height
				} else if (/^(width|height)$/.test(key)) {

					this[key] = value; // used in getter

					// clipping rectangle special
					if (this.updateClipping) {
						this[key] = value;
						this.updateClipping();

					} else {
						// normal
						elemStyle[key] = value;
					}

					skipAttr = true;

				// x and y
				} else if (/^(x|y)$/.test(key)) {

					this[key] = value; // used in getter

					if (element.tagName === 'SPAN') {
						this.updateTransform();

					} else {
						elemStyle[{ x: 'left', y: 'top' }[key]] = value;
					}

				// class name
				} else if (key === 'class') {
					// IE8 Standards mode has problems retrieving the className
					element.className = value;

				// stroke
				} else if (key === 'stroke') {

					value = renderer.color(value, element, key);

					key = 'strokecolor';

				// stroke width
				} else if (key === 'stroke-width' || key === 'strokeWidth') {
					element.stroked = value ? true : false;
					key = 'strokeweight';
					this[key] = value; // used in getter, issue #113
					if (isNumber(value)) {
						value += PX;
					}

				// dashStyle
				} else if (key === 'dashstyle') {
					var strokeElem = element.getElementsByTagName('stroke')[0] ||
						createElement(renderer.prepVML(['<stroke/>']), null, null, element);
					strokeElem[key] = value || 'solid';
					this.dashstyle = value; /* because changing stroke-width will change the dash length
						and cause an epileptic effect */
					skipAttr = true;

				// fill
				} else if (key === 'fill') {

					if (nodeName === 'SPAN') { // text color
						elemStyle.color = value;
					} else {
						element.filled = value !== NONE ? true : false;

						value = renderer.color(value, element, key);

						key = 'fillcolor';
					}

				// translation for animation
				} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'align') {
					if (key === 'align') {
						key = 'textAlign';
					}
					this[key] = value;
					this.updateTransform();

					skipAttr = true;
				} else if (key === 'text') { // text for rotated and non-rotated elements
					this.bBox = null;
					element.innerHTML = value;
					skipAttr = true;
				}


				// let the shadow follow the main element
				if (shadows && key === 'visibility') {
					i = shadows.length;
					while (i--) {
						shadows[i].style[key] = value;
					}
				}



				if (!skipAttr) {
					if (docMode8) { // IE8 setAttribute bug
						element[key] = value;
					} else {
						attr(element, key, value);
					}
				}
			}
		}
		return ret;
	},

	/**
	 * Set the element's clipping to a predefined rectangle
	 *
	 * @param {String} id The id of the clip rectangle
	 */
	clip: function (clipRect) {
		var wrapper = this,
			clipMembers = clipRect.members;

		clipMembers.push(wrapper);
		wrapper.destroyClip = function () {
			erase(clipMembers, wrapper);
		};
		return wrapper.css(clipRect.getCSS(wrapper.inverted));
	},

	/**
	 * Set styles for the element
	 * @param {Object} styles
	 */
	css: function (styles) {
		var wrapper = this,
			element = wrapper.element,
			textWidth = styles && element.tagName === 'SPAN' && styles.width;

		/*if (textWidth) {
			extend(styles, {
				display: 'block',
				whiteSpace: 'normal'
			});
		}*/
		if (textWidth) {
			delete styles.width;
			wrapper.textWidth = textWidth;
			wrapper.updateTransform();
		}

		wrapper.styles = extend(wrapper.styles, styles);
		css(wrapper.element, styles);

		return wrapper;
	},

	/**
	 * Removes a child either by removeChild or move to garbageBin.
	 * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
	 */
	safeRemoveChild: function (element) {
		// discardElement will detach the node from its parent before attaching it
		// to the garbage bin. Therefore it is important that the node is attached and have parent.
		var parentNode = element.parentNode;
		if (parentNode) {
			discardElement(element);
		}
	},

	/**
	 * Extend element.destroy by removing it from the clip members array
	 */
	destroy: function () {
		var wrapper = this;

		if (wrapper.destroyClip) {
			wrapper.destroyClip();
		}

		return SVGElement.prototype.destroy.apply(wrapper);
	},

	/**
	 * Remove all child nodes of a group, except the v:group element
	 */
	empty: function () {
		var element = this.element,
			childNodes = element.childNodes,
			i = childNodes.length,
			node;

		while (i--) {
			node = childNodes[i];
			node.parentNode.removeChild(node);
		}
	},

	/**
	 * VML override for calculating the bounding box based on offsets
	 *
	 * @return {Object} A hash containing values for x, y, width and height
	 */

	getBBox: function () {
		var wrapper = this,
			element = wrapper.element,
			bBox = wrapper.bBox;

		if (!bBox) {
			// faking getBBox in exported SVG in legacy IE
			if (element.nodeName === 'text') {
				element.style.position = ABSOLUTE;
			}

			bBox = wrapper.bBox = {
				x: element.offsetLeft,
				y: element.offsetTop,
				width: element.offsetWidth,
				height: element.offsetHeight
			};
		}
		return bBox;

	},

	/**
	 * Add an event listener. VML override for normalizing event parameters.
	 * @param {String} eventType
	 * @param {Function} handler
	 */
	on: function (eventType, handler) {
		// simplest possible event model for internal use
		this.element['on' + eventType] = function () {
			var evt = win.event;
			evt.target = evt.srcElement;
			handler(evt);
		};
		return this;
	},


	/**
	 * VML override private method to update elements based on internal
	 * properties based on SVG transform
	 */
	updateTransform: function () {
		// aligning non added elements is expensive
		if (!this.added) {
			this.alignOnAdd = true;
			return;
		}

		var wrapper = this,
			elem = wrapper.element,
			translateX = wrapper.translateX || 0,
			translateY = wrapper.translateY || 0,
			x = wrapper.x || 0,
			y = wrapper.y || 0,
			align = wrapper.textAlign || 'left',
			alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
			nonLeft = align && align !== 'left';

		// apply translate
		if (translateX || translateY) {
			wrapper.css({
				marginLeft: translateX,
				marginTop: translateY
			});
		}

		// apply inversion
		if (wrapper.inverted) { // wrapper is a group
			each(elem.childNodes, function (child) {
				wrapper.renderer.invertChild(child, elem);
			});
		}

		if (elem.tagName === 'SPAN') {

			var width, height,
				rotation = wrapper.rotation,
				lineHeight,
				radians = 0,
				costheta = 1,
				sintheta = 0,
				quad,
				textWidth = pInt(wrapper.textWidth),
				xCorr = wrapper.xCorr || 0,
				yCorr = wrapper.yCorr || 0,
				currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');

			if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed

				if (defined(rotation)) {
					radians = rotation * deg2rad; // deg to rad
					costheta = mathCos(radians);
					sintheta = mathSin(radians);

					// Adjust for alignment and rotation.
					// Test case: http://highcharts.com/tests/?file=text-rotation
					css(elem, {
						filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
							', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
							', sizingMethod=\'auto expand\')'].join('') : NONE
					});
				}

				width = elem.offsetWidth;
				height = elem.offsetHeight;

				// update textWidth
				if (width > textWidth) {
					css(elem, {
						width: textWidth + PX,
						display: 'block',
						whiteSpace: 'normal'
					});
					width = textWidth;
				}

				// correct x and y
				lineHeight = mathRound((pInt(elem.style.fontSize) || 12) * 1.2);
				xCorr = costheta < 0 && -width;
				yCorr = sintheta < 0 && -height;

				// correct for lineHeight and corners spilling out after rotation
				quad = costheta * sintheta < 0;
				xCorr += sintheta * lineHeight * (quad ? 1 - alignCorrection : alignCorrection);
				yCorr -= costheta * lineHeight * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);

				// correct for the length/height of the text
				if (nonLeft) {
					xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
					if (rotation) {
						yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
					}
					css(elem, {
						textAlign: align
					});
				}

				// record correction
				wrapper.xCorr = xCorr;
				wrapper.yCorr = yCorr;
			}

			// apply position with correction
			css(elem, {
				left: x + xCorr,
				top: y + yCorr
			});

			// record current text transform
			wrapper.cTT = currentTextTransform;
		}
	},

	/**
	 * Apply a drop shadow by copying elements and giving them different strokes
	 * @param {Boolean} apply
	 */
	shadow: function (apply, group) {
		var shadows = [],
			i,
			element = this.element,
			renderer = this.renderer,
			shadow,
			elemStyle = element.style,
			markup,
			path = element.path;

		// some times empty paths are not strings
		if (path && typeof path.value !== 'string') {
			path = 'x';
		}

		if (apply) {
			for (i = 1; i <= 3; i++) {
				markup = ['<shape isShadow="true" strokeweight="', (7 - 2 * i),
					'" filled="false" path="', path,
					'" coordsize="100,100" style="', element.style.cssText, '" />'];
				shadow = createElement(renderer.prepVML(markup),
					null, {
						left: pInt(elemStyle.left) + 1,
						top: pInt(elemStyle.top) + 1
					}
				);

				// apply the opacity
				markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
				createElement(renderer.prepVML(markup), null, null, shadow);


				// insert it
				if (group) {
					group.element.appendChild(shadow);
				} else {
					element.parentNode.insertBefore(shadow, element);
				}

				// record it
				shadows.push(shadow);

			}

			this.shadows = shadows;
		}
		return this;

	}
});

/**
 * The VML renderer
 */
VMLRenderer = function () {
	this.init.apply(this, arguments);
};
VMLRenderer.prototype = merge(SVGRenderer.prototype, { // inherit SVGRenderer

	Element: VMLElement,
	isIE8: userAgent.indexOf('MSIE 8.0') > -1,


	/**
	 * Initialize the VMLRenderer
	 * @param {Object} container
	 * @param {Number} width
	 * @param {Number} height
	 */
	init: function (container, width, height) {
		var renderer = this,
			boxWrapper;

		renderer.alignedObjects = [];

		boxWrapper = renderer.createElement(DIV);
		container.appendChild(boxWrapper.element);


		// generate the containing box
		renderer.box = boxWrapper.element;
		renderer.boxWrapper = boxWrapper;


		renderer.setSize(width, height, false);

		// The only way to make IE6 and IE7 print is to use a global namespace. However,
		// with IE8 the only way to make the dynamic shapes visible in screen and print mode
		// seems to be to add the xmlns attribute and the behaviour style inline.
		if (!doc.namespaces.hcv) {

			doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');

			// setup default css
			doc.createStyleSheet().cssText =
				'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
				'{ behavior:url(#default#VML); display: inline-block; } ';

		}
	},

	/**
	 * Define a clipping rectangle. In VML it is accomplished by storing the values
	 * for setting the CSS style to all associated members.
	 *
	 * @param {Number} x
	 * @param {Number} y
	 * @param {Number} width
	 * @param {Number} height
	 */
	clipRect: function (x, y, width, height) {

		// create a dummy element
		var clipRect = this.createElement();

		// mimic a rectangle with its style object for automatic updating in attr
		return extend(clipRect, {
			members: [],
			left: x,
			top: y,
			width: width,
			height: height,
			getCSS: function (inverted) {
				var rect = this,//clipRect.element.style,
					top = rect.top,
					left = rect.left,
					right = left + rect.width,
					bottom = top + rect.height,
					ret = {
						clip: 'rect(' +
							mathRound(inverted ? left : top) + 'px,' +
							mathRound(inverted ? bottom : right) + 'px,' +
							mathRound(inverted ? right : bottom) + 'px,' +
							mathRound(inverted ? top : left) + 'px)'
					};

				// issue 74 workaround
				if (!inverted && docMode8) {
					extend(ret, {
						width: right + PX,
						height: bottom + PX
					});
				}
				return ret;
			},

			// used in attr and animation to update the clipping of all members
			updateClipping: function () {
				each(clipRect.members, function (member) {
					member.css(clipRect.getCSS(member.inverted));
				});
			}
		});

	},


	/**
	 * Take a color and return it if it's a string, make it a gradient if it's a
	 * gradient configuration object, and apply opacity.
	 *
	 * @param {Object} color The color or config object
	 */
	color: function (color, elem, prop) {
		var colorObject,
			regexRgba = /^rgba/,
			markup;

		if (color && color.linearGradient) {

			var stopColor,
				stopOpacity,
				linearGradient = color.linearGradient,
				angle,
				color1,
				opacity1,
				color2,
				opacity2;

			each(color.stops, function (stop, i) {
				if (regexRgba.test(stop[1])) {
					colorObject = Color(stop[1]);
					stopColor = colorObject.get('rgb');
					stopOpacity = colorObject.get('a');
				} else {
					stopColor = stop[1];
					stopOpacity = 1;
				}

				if (!i) { // first
					color1 = stopColor;
					opacity1 = stopOpacity;
				} else {
					color2 = stopColor;
					opacity2 = stopOpacity;
				}
			});



			// calculate the angle based on the linear vector
			angle = 90  - math.atan(
				(linearGradient[3] - linearGradient[1]) / // y vector
				(linearGradient[2] - linearGradient[0]) // x vector
				) * 180 / mathPI;

			// when colors attribute is used, the meanings of opacity and o:opacity2
			// are reversed.
			markup = ['<', prop, ' colors="0% ', color1, ',100% ', color2, '" angle="', angle,
				'" opacity="', opacity2, '" o:opacity2="', opacity1,
				'" type="gradient" focus="100%" />'];
			createElement(this.prepVML(markup), null, null, elem);



		// if the color is an rgba color, split it and add a fill node
		// to hold the opacity component
		} else if (regexRgba.test(color) && elem.tagName !== 'IMG') {

			colorObject = Color(color);

			markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
			createElement(this.prepVML(markup), null, null, elem);

			return colorObject.get('rgb');


		} else {
			var strokeNodes = elem.getElementsByTagName(prop);
			if (strokeNodes.length) {
				strokeNodes[0].opacity = 1;
			}
			return color;
		}

	},

	/**
	 * Take a VML string and prepare it for either IE8 or IE6/IE7.
	 * @param {Array} markup A string array of the VML markup to prepare
	 */
	prepVML: function (markup) {
		var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
			isIE8 = this.isIE8;

		markup = markup.join('');

		if (isIE8) { // add xmlns and style inline
			markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
			if (markup.indexOf('style="') === -1) {
				markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
			} else {
				markup = markup.replace('style="', 'style="' + vmlStyle);
			}

		} else { // add namespace
			markup = markup.replace('<', '<hcv:');
		}

		return markup;
	},

	/**
	 * Create rotated and aligned text
	 * @param {String} str
	 * @param {Number} x
	 * @param {Number} y
	 */
	text: function (str, x, y) {

		var defaultChartStyle = defaultOptions.chart.style;

		return this.createElement('span')
			.attr({
				text: str,
				x: mathRound(x),
				y: mathRound(y)
			})
			.css({
				whiteSpace: 'nowrap',
				fontFamily: defaultChartStyle.fontFamily,
				fontSize: defaultChartStyle.fontSize
			});
	},

	/**
	 * Create and return a path element
	 * @param {Array} path
	 */
	path: function (path) {
		// create the shape
		return this.createElement('shape').attr({
			// subpixel precision down to 0.1 (width and height = 10px)
			coordsize: '100 100',
			d: path
		});
	},

	/**
	 * Create and return a circle element. In VML circles are implemented as
	 * shapes, which is faster than v:oval
	 * @param {Number} x
	 * @param {Number} y
	 * @param {Number} r
	 */
	circle: function (x, y, r) {
		return this.symbol('circle').attr({ x: x, y: y, r: r});
	},

	/**
	 * Create a group using an outer div and an inner v:group to allow rotating
	 * and flipping. A simple v:group would have problems with positioning
	 * child HTML elements and CSS clip.
	 *
	 * @param {String} name The name of the group
	 */
	g: function (name) {
		var wrapper,
			attribs;

		// set the class name
		if (name) {
			attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
		}

		// the div to hold HTML and clipping
		wrapper = this.createElement(DIV).attr(attribs);

		return wrapper;
	},

	/**
	 * VML override to create a regular HTML image
	 * @param {String} src
	 * @param {Number} x
	 * @param {Number} y
	 * @param {Number} width
	 * @param {Number} height
	 */
	image: function (src, x, y, width, height) {
		var obj = this.createElement('img')
			.attr({ src: src });

		if (arguments.length > 1) {
			obj.css({
				left: x,
				top: y,
				width: width,
				height: height
			});
		}
		return obj;
	},

	/**
	 * VML uses a shape for rect to overcome bugs and rotation problems
	 */
	rect: function (x, y, width, height, r, strokeWidth) {

		if (isObject(x)) {
			y = x.y;
			width = x.width;
			height = x.height;
			r = x.r;
			strokeWidth = x.strokeWidth;
			x = x.x;
		}
		var wrapper = this.symbol('rect');
		wrapper.r = r;

		return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
	},

	/**
	 * In the VML renderer, each child of an inverted div (group) is inverted
	 * @param {Object} element
	 * @param {Object} parentNode
	 */
	invertChild: function (element, parentNode) {
		var parentStyle = parentNode.style;

		css(element, {
			flip: 'x',
			left: pInt(parentStyle.width) - 10,
			top: pInt(parentStyle.height) - 10,
			rotation: -90
		});
	},

	/**
	 * Symbol definitions that override the parent SVG renderer's symbols
	 *
	 */
	symbols: {
		// VML specific arc function
		arc: function (x, y, radius, options) {
			var start = options.start,
				end = options.end,
				cosStart = mathCos(start),
				sinStart = mathSin(start),
				cosEnd = mathCos(end),
				sinEnd = mathSin(end),
				innerRadius = options.innerR,
				circleCorrection = 0.07 / radius,
				innerCorrection = (innerRadius && 0.1 / innerRadius) || 0;

			if (end - start === 0) { // no angle, don't show it.
				return ['x'];

			//} else if (end - start == 2 * mathPI) { // full circle
			} else if (2 * mathPI - end + start < circleCorrection) { // full circle
				// empirical correction found by trying out the limits for different radii
				cosEnd = -circleCorrection;
			} else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
				cosEnd = mathCos(start + innerCorrection);
			}

			return [
				'wa', // clockwise arc to
				x - radius, // left
				y - radius, // top
				x + radius, // right
				y + radius, // bottom
				x + radius * cosStart, // start x
				y + radius * sinStart, // start y
				x + radius * cosEnd, // end x
				y + radius * sinEnd, // end y


				'at', // anti clockwise arc to
				x - innerRadius, // left
				y - innerRadius, // top
				x + innerRadius, // right
				y + innerRadius, // bottom
				x + innerRadius * cosEnd, // start x
				y + innerRadius * sinEnd, // start y
				x + innerRadius * cosStart, // end x
				y + innerRadius * sinStart, // end y

				'x', // finish path
				'e' // close
			];

		},
		// Add circle symbol path. This performs significantly faster than v:oval.
		circle: function (x, y, r) {
			return [
				'wa', // clockwisearcto
				x - r, // left
				y - r, // top
				x + r, // right
				y + r, // bottom
				x + r, // start x
				y,     // start y
				x + r, // end x
				y,     // end y
				//'x', // finish path
				'e' // close
			];
		},
		/**
		 * Add rectangle symbol path which eases rotation and omits arcsize problems
		 * compared to the built-in VML roundrect shape
		 *
		 * @param {Number} left Left position
		 * @param {Number} top Top position
		 * @param {Number} r Border radius
		 * @param {Object} options Width and height
		 */

		rect: function (left, top, r, options) {
			if (!defined(options)) {
				return [];
			}
			var width = options.width,
				height = options.height,
				right = left + width,
				bottom = top + height;

			r = mathMin(r, width, height);

			return [
				M,
				left + r, top,

				L,
				right - r, top,
				'wa',
				right - 2 * r, top,
				right, top + 2 * r,
				right - r, top,
				right, top + r,

				L,
				right, bottom - r,
				'wa',
				right - 2 * r, bottom - 2 * r,
				right, bottom,
				right, bottom - r,
				right - r, bottom,

				L,
				left + r, bottom,
				'wa',
				left, bottom - 2 * r,
				left + 2 * r, bottom,
				left + r, bottom,
				left, bottom - r,

				L,
				left, top + r,
				'wa',
				left, top,
				left + 2 * r, top + 2 * r,
				left, top + r,
				left + r, top,


				'x',
				'e'
			];

		}
	}
});

// general renderer
Renderer = VMLRenderer;
}
/* ****************************************************************************
 *                                                                            *
 * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *
 *                                                                            *
 *****************************************************************************/


/**
 * The chart class
 * @param {Object} options
 * @param {Function} callback Function to run when the chart has loaded
 */
function Chart(options, callback) {

	defaultXAxisOptions = merge(defaultXAxisOptions, defaultOptions.xAxis);
	defaultYAxisOptions = merge(defaultYAxisOptions, defaultOptions.yAxis);
	defaultOptions.xAxis = defaultOptions.yAxis = null;

	// Handle regular options
	options = merge(defaultOptions, options);

	// Define chart variables
	var optionsChart = options.chart,
		optionsMargin = optionsChart.margin,
		margin = isObject(optionsMargin) ?
			optionsMargin :
			[optionsMargin, optionsMargin, optionsMargin, optionsMargin],
		optionsMarginTop = pick(optionsChart.marginTop, margin[0]),
		optionsMarginRight = pick(optionsChart.marginRight, margin[1]),
		optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]),
		optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]),
		spacingTop = optionsChart.spacingTop,
		spacingRight = optionsChart.spacingRight,
		spacingBottom = optionsChart.spacingBottom,
		spacingLeft = optionsChart.spacingLeft,
		spacingBox,
		chartTitleOptions,
		chartSubtitleOptions,
		plotTop,
		marginRight,
		marginBottom,
		plotLeft,
		axisOffset,
		renderTo,
		renderToClone,
		container,
		containerId,
		containerWidth,
		containerHeight,
		chartWidth,
		chartHeight,
		oldChartWidth,
		oldChartHeight,
		chartBackground,
		plotBackground,
		plotBGImage,
		plotBorder,
		chart = this,
		chartEvents = optionsChart.events,
		runChartClick = chartEvents && !!chartEvents.click,
		eventType,
		isInsidePlot, // function
		tooltip,
		mouseIsDown,
		loadingDiv,
		loadingSpan,
		loadingShown,
		plotHeight,
		plotWidth,
		tracker,
		trackerGroup,
		placeTrackerGroup,
		legend,
		legendWidth,
		legendHeight,
		chartPosition,// = getPosition(container),
		hasCartesianSeries = optionsChart.showAxes,
		isResizing = 0,
		axes = [],
		maxTicks, // handle the greatest amount of ticks on grouped axes
		series = [],
		inverted,
		renderer,
		tooltipTick,
		tooltipInterval,
		hoverX,
		drawChartBox, // function
		getMargins, // function
		resetMargins, // function
		setChartSize, // function
		resize,
		zoom, // function
		zoomOut; // function


	/**
	 * Create a new axis object
	 * @param {Object} options
	 */
	function Axis(userOptions) {

		// Define variables
		var isXAxis = userOptions.isX,
			opposite = userOptions.opposite, // needed in setOptions
			horiz = inverted ? !isXAxis : isXAxis,
			side = horiz ?
				(opposite ? 0 : 2) : // top : bottom
				(opposite ? 1 : 3),  // right : left
			stacks = {},

			options = merge(
				isXAxis ? defaultXAxisOptions : defaultYAxisOptions,
				[defaultTopAxisOptions, defaultRightAxisOptions,
					defaultBottomAxisOptions, defaultLeftAxisOptions][side],
				userOptions
			),

			axis = this,
			axisTitle,
			type = options.type,
			isDatetimeAxis = type === 'datetime',
			isHockeyPeriodAxis = options.type == 'hockeyperiod',
			isLog = type === 'logarithmic',
			offset = options.offset || 0,
			xOrY = isXAxis ? 'x' : 'y',
			axisLength,
			transA, // translation factor
			oldTransA, // used for prerendering
			transB = horiz ? plotLeft : marginBottom, // translation addend
			translate, // fn
			getPlotLinePath, // fn
			axisGroup,
			gridGroup,
			axisLine,
			dataMin,
			dataMax,
			associatedSeries,
			userMin,
			userMax,
			max = null,
			min = null,
			oldMin,
			oldMax,
			minPadding = options.minPadding,
			maxPadding = options.maxPadding,
			isLinked = defined(options.linkedTo),
			ignoreMinPadding, // can be set to true by a column or bar series
			ignoreMaxPadding,
			usePercentage,
			events = options.events,
			eventType,
			plotLinesAndBands = [],
			tickInterval,
			minorTickInterval,
			magnitude,
			tickPositions, // array containing predefined positions
			ticks = {},
			minorTicks = {},
			alternateBands = {},
			tickAmount,
			labelOffset,
			axisTitleMargin,// = options.title.margin,
			dateTimeLabelFormat,
			categories = options.categories,
			labelFormatter = options.labels.formatter ||  // can be overwritten by dynamic format
				function () {
					var value = this.value,
						ret;

					if (dateTimeLabelFormat) { // datetime axis
						ret = dateFormat(dateTimeLabelFormat, value);

					} else if (tickInterval % 1000000 === 0) { // use M abbreviation
						ret = (value / 1000000) + 'M';

					} else if (tickInterval % 1000 === 0) { // use k abbreviation
						ret = (value / 1000) + 'k';

					} else if (!categories && value >= 1000) { // add thousands separators
						ret = numberFormat(value, 0);

					} else { // strings (categories) and small numbers
						ret = value;
					}
					return ret;
				},

			staggerLines = horiz && options.labels.staggerLines,
			reversed = options.reversed,
			tickmarkOffset = (categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;

		/**
		 * The Tick class
		 */
		function Tick(pos, minor) {
			var tick = this;
			tick.pos = pos;
			tick.minor = minor;
			tick.isNew = true;

			if (!minor) {
				tick.addLabel();
			}
		}
		Tick.prototype = {
			/**
			 * Write the tick label
			 */
			addLabel: function () {
				var pos = this.pos,
					labelOptions = options.labels,
					absoluteLabels = options.absoluteLabels,
					str,
					withLabel = !((pos === min && !pick(options.showFirstLabel, 1)) ||
						(pos === max && !pick(options.showLastLabel, 0))),
					width = (categories && horiz && categories.length &&
						!labelOptions.step && !labelOptions.staggerLines &&
						!labelOptions.rotation &&
						plotWidth / categories.length) ||
						(!horiz && plotWidth / 2),
					css,
					value = categories && defined(categories[pos]) ? categories[pos] : pos,
					label = this.label;


				// get the string
				str = labelFormatter.call({
						isFirst: pos === tickPositions[0],
						isLast: pos === tickPositions[tickPositions.length - 1],
						dateTimeLabelFormat: dateTimeLabelFormat,
						value: isLog ? lin2log(value) : value
					});
				if(absoluteLabels){
					str = Math.abs(str);
				}

				// prepare CSS
				css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
				css = extend(css, labelOptions.style);

				// first call
				if (label === UNDEFINED) {
					this.label =
						defined(str) && withLabel && labelOptions.enabled ?
							renderer.text(
									str,
									0,
									0,
									labelOptions.useHTML
								)
								.attr({
									align: labelOptions.align,
									rotation: labelOptions.rotation
								})
								// without position absolute, IE export sometimes is wrong
								.css(css)
								.add(axisGroup) :
							null;

				// update
				} else if (label) {
					label.attr({ text: str })
						.css(css);
				}
			},
			/**
			 * Get the offset height or width of the label
			 */
			getLabelSize: function () {
				var label = this.label;
				return label ?
					((this.labelBBox = label.getBBox()))[horiz ? 'height' : 'width'] :
					0;
				},
			/**
			 * Put everything in place
			 *
			 * @param index {Number}
			 * @param old {Boolean} Use old coordinates to prepare an animation into new position
			 */
			render: function (index, old) {
				var tick = this,
					major = !tick.minor,
					label = tick.label,
					pos = tick.pos,
					labelOptions = options.labels,
					gridLine = tick.gridLine,
					gridLineWidth = major ? options.gridLineWidth : options.minorGridLineWidth,
					gridLineColor = major ? options.gridLineColor : options.minorGridLineColor,
					dashStyle = major ?
						options.gridLineDashStyle :
						options.minorGridLineDashStyle,
					gridLinePath,
					mark = tick.mark,
					markPath,
					tickLength = major ? options.tickLength : options.minorTickLength,
					tickWidth = major ? options.tickWidth : (options.minorTickWidth || 0),
					tickColor = major ? options.tickColor : options.minorTickColor,
					tickPosition = major ? options.tickPosition : options.minorTickPosition,
					step = labelOptions.step,
					cHeight = (old && oldChartHeight) || chartHeight,
					attribs,
					x,
					y;

				// get x and y position for ticks and labels
				x = horiz ?
					translate(pos + tickmarkOffset, null, null, old) + transB :
					plotLeft + offset + (opposite ? ((old && oldChartWidth) || chartWidth) - marginRight - plotLeft : 0);

				y = horiz ?
					cHeight - marginBottom + offset - (opposite ? plotHeight : 0) :
					cHeight - translate(pos + tickmarkOffset, null, null, old) - transB;

				// create the grid line
				if (gridLineWidth) {
					gridLinePath = getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);

					if (gridLine === UNDEFINED) {
						attribs = {
							stroke: gridLineColor,
							'stroke-width': gridLineWidth
						};
						if (dashStyle) {
							attribs.dashstyle = dashStyle;
						}
						if (major) {
							attribs.zIndex = 1;
						}
						tick.gridLine = gridLine =
							gridLineWidth ?
								renderer.path(gridLinePath)
									.attr(attribs).add(gridGroup) :
								null;
					}

					// If the parameter 'old' is set, the current call will be followed
					// by another call, therefore do not do any animations this time
					if (!old && gridLine && gridLinePath) {
						gridLine.animate({
							d: gridLinePath
						});
					}
				}

				// create the tick mark
				if (tickWidth) {

					// negate the length
					if (tickPosition === 'inside') {
						tickLength = -tickLength;
					}
					if (opposite) {
						tickLength = -tickLength;
					}

					markPath = renderer.crispLine([
						M,
						x,
						y,
						L,
						x + (horiz ? 0 : -tickLength),
						y + (horiz ? tickLength : 0)
					], tickWidth);

					if (mark) { // updating
						mark.animate({
							d: markPath
						});
					} else { // first time
						tick.mark = renderer.path(
							markPath
						).attr({
							stroke: tickColor,
							'stroke-width': tickWidth
						}).add(axisGroup);
					}
				}

				// the label is created on init - now move it into place
				if (label && !isNaN(x)) {
					x = x + labelOptions.x - (tickmarkOffset && horiz ?
						tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
					y = y + labelOptions.y - (tickmarkOffset && !horiz ?
						tickmarkOffset * transA * (reversed ? 1 : -1) : 0);

					// vertically centered
					if (!defined(labelOptions.y)) {
						y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
					}


					// correct for staggered labels
					if (staggerLines) {
						y += (index / (step || 1) % staggerLines) * 16;
					}
					// apply step
					if (step) {
						// show those indices dividable by step
						label[index % step ? 'hide' : 'show']();
					}

					label[tick.isNew ? 'attr' : 'animate']({
						x: x,
						y: y
					});
				}

				tick.isNew = false;
			},
			/**
			 * Destructor for the tick prototype
			 */
			destroy: function () {
				destroyObjectProperties(this);
			}
		};

		/**
		 * The object wrapper for plot lines and plot bands
		 * @param {Object} options
		 */
		function PlotLineOrBand(options) {
			var plotLine = this;
			if (options) {
				plotLine.options = options;
				plotLine.id = options.id;
			}

			//plotLine.render()
			return plotLine;
		}

		PlotLineOrBand.prototype = {

		/**
		 * Render the plot line or plot band. If it is already existing,
		 * move it.
		 */
		render: function () {
			var plotLine = this,
				options = plotLine.options,
				optionsLabel = options.label,
				label = plotLine.label,
				width = options.width,
				to = options.to,
				from = options.from,
				value = options.value,
				toPath, // bands only
				dashStyle = options.dashStyle,
				svgElem = plotLine.svgElem,
				path = [],
				addEvent,
				eventType,
				xs,
				ys,
				x,
				y,
				color = options.color,
				zIndex = options.zIndex,
				events = options.events,
				attribs;

			// logarithmic conversion
			if (isLog) {
				from = log2lin(from);
				to = log2lin(to);
				value = log2lin(value);
			}

			// plot line
			if (width) {
				path = getPlotLinePath(value, width);
				attribs = {
					stroke: color,
					'stroke-width': width
				};
				if (dashStyle) {
					attribs.dashstyle = dashStyle;
				}
			} else if (defined(from) && defined(to)) { // plot band
				// keep within plot area
				from = mathMax(from, min);
				to = mathMin(to, max);

				toPath = getPlotLinePath(to);
				path = getPlotLinePath(from);
				if (path && toPath) {
					path.push(
						toPath[4],
						toPath[5],
						toPath[1],
						toPath[2]
					);
				} else { // outside the axis area
					path = null;
				}
				attribs = {
					fill: color
				};
			} else {
				return;
			}
			// zIndex
			if (defined(zIndex)) {
				attribs.zIndex = zIndex;
			}

			// common for lines and bands
			if (svgElem) {
				if (path) {
					svgElem.animate({
						d: path
					}, null, svgElem.onGetPath);
				} else {
					svgElem.hide();
					svgElem.onGetPath = function () {
						svgElem.show();
					};
				}
			} else if (path && path.length) {
				plotLine.svgElem = svgElem = renderer.path(path)
					.attr(attribs).add();

				// events
				if (events) {
					addEvent = function (eventType) {
						svgElem.on(eventType, function (e) {
							events[eventType].apply(plotLine, [e]);
						});
					};
					for (eventType in events) {
						addEvent(eventType);
					}
				}
			}

			// the plot band/line label
			if (optionsLabel && defined(optionsLabel.text) && path && path.length && plotWidth > 0 && plotHeight > 0) {
				// apply defaults
				optionsLabel = merge({
					align: horiz && toPath && 'center',
					x: horiz ? !toPath && 4 : 10,
					verticalAlign : !horiz && toPath && 'middle',
					y: horiz ? toPath ? 16 : 10 : toPath ? 6 : -4,
					rotation: horiz && !toPath && 90
				}, optionsLabel);

				// add the SVG element
				if (!label) {
					plotLine.label = label = renderer.text(
							optionsLabel.text,
							0,
							0
						)
						.attr({
							align: optionsLabel.textAlign || optionsLabel.align,
							rotation: optionsLabel.rotation,
							zIndex: zIndex
						})
						.css(optionsLabel.style)
						.add();
				}

				// get the bounding box and align the label
				xs = [path[1], path[4], pick(path[6], path[1])];
				ys = [path[2], path[5], pick(path[7], path[2])];
				x = mathMin.apply(math, xs);
				y = mathMin.apply(math, ys);

				label.align(optionsLabel, false, {
					x: x,
					y: y,
					width: mathMax.apply(math, xs) - x,
					height: mathMax.apply(math, ys) - y
				});
				label.show();

			} else if (label) { // move out of sight
				label.hide();
			}

			// chainable
			return plotLine;
		},

		/**
		 * Remove the plot line or band
		 */
		destroy: function () {
			var obj = this;

			destroyObjectProperties(obj);

			// remove it from the lookup
			erase(plotLinesAndBands, obj);
		}
		};

		/**
		 * The class for stack items
		 */
		function StackItem(options, isNegative, x, stackOption) {
			var stackItem = this;

			// Tells if the stack is negative
			stackItem.isNegative = isNegative;

			// Save the options to be able to style the label
			stackItem.options = options;

			// Save the x value to be able to position the label later
			stackItem.x = x;

			// Save the stack option on the series configuration object
			stackItem.stack = stackOption;

			// The align options and text align varies on whether the stack is negative and
			// if the chart is inverted or not.
			// First test the user supplied value, then use the dynamic.
			stackItem.alignOptions = {
				align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
				verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
				y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
				x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
			};

			stackItem.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
		}

		StackItem.prototype = {
			destroy: function () {
				destroyObjectProperties(this);
			},

			/**
			 * Sets the total of this stack. Should be called when a serie is hidden or shown
			 * since that will affect the total of other stacks.
			 */
			setTotal: function (total) {
				this.total = total;
				this.cum = total;
			},

			/**
			 * Renders the stack total label and adds it to the stack label group.
			 */
			render: function (group) {
				var stackItem = this,									// aliased this
					str = stackItem.options.formatter.call(stackItem);	// format the text in the label

				// Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
				if (stackItem.label) {
					stackItem.label.attr({text: str, visibility: HIDDEN});
				// Create new label
				} else {
					stackItem.label =
						chart.renderer.text(str, 0, 0)				// dummy positions, actual position updated with setOffset method in columnseries
							.css(stackItem.options.style)			// apply style
							.attr({align: stackItem.textAlign,			// fix the text-anchor
								rotation: stackItem.options.rotation,	// rotation
								visibility: HIDDEN })					// hidden until setOffset is called
							.add(group);							// add to the labels-group
				}
			},

			/**
			 * Sets the offset that the stack has from the x value and repositions the label.
			 */
			setOffset: function (xOffset, xWidth) {
				var stackItem = this,										// aliased this
					neg = stackItem.isNegative,								// special treatment is needed for negative stacks
					y = axis.translate(stackItem.total),					// stack value translated mapped to chart coordinates
					yZero = axis.translate(0),								// stack origin
					h = mathAbs(y - yZero),									// stack height
					x = chart.xAxis[0].translate(stackItem.x) + xOffset,	// stack x position
					plotHeight = chart.plotHeight,
					stackBox = {	// this is the box for the complete stack
							x: inverted ? (neg ? y : y - h) : x,
							y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
							width: inverted ? h : xWidth,
							height: inverted ? xWidth : h
					};

				if (stackItem.label) {
					stackItem.label
						.align(stackItem.alignOptions, null, stackBox)	// align the label to the box
						.attr({visibility: VISIBLE});					// set visibility
				}
			}
		};

		/**
		 * Get the minimum and maximum for the series of each axis
		 */
		function getSeriesExtremes() {
			var posStack = [],
				negStack = [],
				run;

			// reset dataMin and dataMax in case we're redrawing
			dataMin = dataMax = null;

			// get an overview of what series are associated with this axis
			associatedSeries = [];

			each(series, function (serie) {
				run = false;


				// match this axis against the series' given or implicated axis
				each(['xAxis', 'yAxis'], function (strAxis) {
					if (
						// the series is a cartesian type, and...
						serie.isCartesian &&
						// we're in the right x or y dimension, and...
						((strAxis === 'xAxis' && isXAxis) || (strAxis === 'yAxis' && !isXAxis)) && (
							// the axis number is given in the options and matches this axis index, or
							(serie.options[strAxis] === options.index) ||
							// the axis index is not given
							(serie.options[strAxis] === UNDEFINED && options.index === 0)
						)
					) {
						serie[strAxis] = axis;
						associatedSeries.push(serie);

						// the series is visible, run the min/max detection
						run = true;
					}
				});
				// ignore hidden series if opted
				if (!serie.visible && optionsChart.ignoreHiddenSeries) {
					run = false;
				}

				if (run) {

					var stacking,
						posPointStack,
						negPointStack,
						stackKey,
						stackOption,
						negKey;

					if (!isXAxis) {
						stacking = serie.options.stacking;
						usePercentage = stacking === 'percent';

						// create a stack for this particular series type
						if (stacking) {
							stackOption = serie.options.stack;
							stackKey = serie.type + pick(stackOption, '');
							negKey = '-' + stackKey;
							serie.stackKey = stackKey; // used in translate

							posPointStack = posStack[stackKey] || []; // contains the total values for each x
							posStack[stackKey] = posPointStack;

							negPointStack = negStack[negKey] || [];
							negStack[negKey] = negPointStack;
						}
						if (usePercentage) {
							dataMin = 0;
							dataMax = 99;
						}
					}
					if (serie.isCartesian) { // line, column etc. need axes, pie doesn't
						each(serie.data, function (point) {
							var pointX = point.x,
								pointY = point.y,
								isNegative = pointY < 0,
								pointStack = isNegative ? negPointStack : posPointStack,
								key = isNegative ? negKey : stackKey,
								totalPos,
								pointLow;

							// initial values
							if (dataMin === null) {

								// start out with the first point
								dataMin = dataMax = point[xOrY];
							}

							// x axis
							if (isXAxis) {
								if (pointX > dataMax) {
									dataMax = pointX;
								} else if (pointX < dataMin) {
									dataMin = pointX;
								}
							} else if (defined(pointY)) { // y axis
								if (stacking) {
									pointStack[pointX] =
										defined(pointStack[pointX]) ?
										pointStack[pointX] + pointY : pointY;
								}
								totalPos = pointStack ? pointStack[pointX] : pointY;
								pointLow = pick(point.low, totalPos);
								if (!usePercentage) {
									if (totalPos > dataMax) {
										dataMax = totalPos;
									} else if (pointLow < dataMin) {
										dataMin = pointLow;
									}
								}
								if (stacking) {
									// add the series
									if (!stacks[key]) {
										stacks[key] = {};
									}

									// If the StackItem is there, just update the values,
									// if not, create one first
									if (!stacks[key][pointX]) {
										stacks[key][pointX] = new StackItem(options.stackLabels, isNegative, pointX, stackOption);
									}
									stacks[key][pointX].setTotal(totalPos);
								}
							}
						});


						// For column, areas and bars, set the minimum automatically to zero
						// and prevent that minPadding is added in setScale
						if (/(area|column|bar)/.test(serie.type) && !isXAxis) {
							var threshold = 0; // use series.options.threshold?
							if (dataMin >= threshold) {
								dataMin = threshold;
								ignoreMinPadding = true;
							} else if (dataMax < threshold) {
								dataMax = threshold;
								ignoreMaxPadding = true;
							}
						}
					}
				}
			});

		}

		/**
		 * Translate from axis value to pixel position on the chart, or back
		 *
		 */
		translate = function (val, backwards, cvsCoord, old, handleLog) {
			var sign = 1,
				cvsOffset = 0,
				localA = old ? oldTransA : transA,
				localMin = old ? oldMin : min,
				returnValue;

			if (!localA) {
				localA = transA;
			}

			if (cvsCoord) {
				sign *= -1; // canvas coordinates inverts the value
				cvsOffset = axisLength;
			}
			if (reversed) { // reversed axis
				sign *= -1;
				cvsOffset -= sign * axisLength;
			}

			if (backwards) { // reverse translation
				if (reversed) {
					val = axisLength - val;
				}
				returnValue = val / localA + localMin; // from chart pixel to value
				if (isLog && handleLog) {
					returnValue = lin2log(returnValue);
				}

			} else { // normal translation
				if (isLog && handleLog) {
					val = log2lin(val);
				}
				returnValue = sign * (val - localMin) * localA + cvsOffset; // from value to chart pixel
			}

			return returnValue;
		};

		/**
		 * Create the path for a plot line that goes from the given value on
		 * this axis, across the plot to the opposite side
		 * @param {Number} value
		 * @param {Number} lineWidth Used for calculation crisp line
		 * @param {Number] old Use old coordinates (for resizing and rescaling)
		 */
		getPlotLinePath = function (value, lineWidth, old) {
			var x1,
				y1,
				x2,
				y2,
				translatedValue = translate(value, null, null, old),
				cHeight = (old && oldChartHeight) || chartHeight,
				cWidth = (old && oldChartWidth) || chartWidth,
				skip;

			x1 = x2 = mathRound(translatedValue + transB);
			y1 = y2 = mathRound(cHeight - translatedValue - transB);

			if (isNaN(translatedValue)) { // no min or max
				skip = true;

			} else if (horiz) {
				y1 = plotTop;
				y2 = cHeight - marginBottom;
				if (x1 < plotLeft || x1 > plotLeft + plotWidth) {
					skip = true;
				}
			} else {
				x1 = plotLeft;
				x2 = cWidth - marginRight;
				if (y1 < plotTop || y1 > plotTop + plotHeight) {
					skip = true;
				}
			}
			return skip ?
				null :
				renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
		};


		/**
		 * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
		 * @param {Number} interval
		 */
		function normalizeTickInterval(interval, multiples) {
			var normalized, i;

			// round to a tenfold of 1, 2, 2.5 or 5
			magnitude = multiples ? 1 : math.pow(10, mathFloor(math.log(interval) / math.LN10));
			normalized = interval / magnitude;

			// multiples for a linear scale
			if (!multiples) {
				multiples = [1, 2, 2.5, 5, 10];
				//multiples = [1, 2, 2.5, 4, 5, 7.5, 10];

				// the allowDecimals option
				if (options.allowDecimals === false || isLog) {
					if (magnitude === 1) {
						multiples = [1, 2, 5, 10];
					} else if (magnitude <= 0.1) {
						multiples = [1 / magnitude];
					}
				}
			}

			// normalize the interval to the nearest multiple
			for (i = 0; i < multiples.length; i++) {
				interval = multiples[i];
				if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
					break;
				}
			}

			// multiply back to the correct magnitude
			interval *= magnitude;

			return interval;
		}

		/**
		 * Set the tick positions to a time unit that makes sense, for example
		 * on the first of each month or on every Monday.
		 */
		function setDateTimeTickPositions() {
			tickPositions = [];
			var i,
				useUTC = defaultOptions.global.useUTC,
				oneSecond = 1000 / timeFactor,
				oneMinute = 60000 / timeFactor,
				oneHour = 3600000 / timeFactor,
				oneDay = 24 * 3600000 / timeFactor,
				oneWeek = 7 * 24 * 3600000 / timeFactor,
				oneMonth = 30 * 24 * 3600000 / timeFactor,
				oneYear = 31556952000 / timeFactor,

				units = [[
					'second',						// unit name
					oneSecond,						// fixed incremental unit
					[1, 2, 5, 10, 15, 30]			// allowed multiples
				], [
					'minute',						// unit name
					oneMinute,						// fixed incremental unit
					[1, 2, 5, 10, 15, 30]			// allowed multiples
				], [
					'hour',							// unit name
					oneHour,						// fixed incremental unit
					[1, 2, 3, 4, 6, 8, 12]			// allowed multiples
				], [
					'day',							// unit name
					oneDay,							// fixed incremental unit
					[1, 2]							// allowed multiples
				], [
					'week',							// unit name
					oneWeek,						// fixed incremental unit
					[1, 2]							// allowed multiples
				], [
					'month',
					oneMonth,
					[1, 2, 3, 4, 6]
				], [
					'year',
					oneYear,
					null
				]],

				unit = units[6], // default unit is years
				interval = unit[1],
				multiples = unit[2];

			// loop through the units to find the one that best fits the tickInterval
			for (i = 0; i < units.length; i++) {
				unit = units[i];
				interval = unit[1];
				multiples = unit[2];


				if (units[i + 1]) {
					// lessThan is in the middle between the highest multiple and the next unit.
					var lessThan = (interval * multiples[multiples.length - 1] +
								units[i + 1][1]) / 2;

					// break and keep the current unit
					if (tickInterval <= lessThan) {
						break;
					}
				}
			}

			// prevent 2.5 years intervals, though 25, 250 etc. are allowed
			if (interval === oneYear && tickInterval < 5 * interval) {
				multiples = [1, 2, 5];
			}

			// get the minimum value by flooring the date
			var multitude = normalizeTickInterval(tickInterval / interval, multiples),
				minYear, // used in months and years as a basis for Date.UTC()
				minDate = new Date(min * timeFactor);

			minDate.setMilliseconds(0);

			if (interval >= oneSecond) { // second
				minDate.setSeconds(interval >= oneMinute ? 0 :
					multitude * mathFloor(minDate.getSeconds() / multitude));
			}

			if (interval >= oneMinute) { // minute
				minDate[setMinutes](interval >= oneHour ? 0 :
					multitude * mathFloor(minDate[getMinutes]() / multitude));
			}

			if (interval >= oneHour) { // hour
				minDate[setHours](interval >= oneDay ? 0 :
					multitude * mathFloor(minDate[getHours]() / multitude));
			}

			if (interval >= oneDay) { // day
				minDate[setDate](interval >= oneMonth ? 1 :
					multitude * mathFloor(minDate[getDate]() / multitude));
			}

			if (interval >= oneMonth) { // month
				minDate[setMonth](interval >= oneYear ? 0 :
					multitude * mathFloor(minDate[getMonth]() / multitude));
				minYear = minDate[getFullYear]();
			}

			if (interval >= oneYear) { // year
				minYear -= minYear % multitude;
				minDate[setFullYear](minYear);
			}

			// week is a special case that runs outside the hierarchy
			if (interval === oneWeek) {
				// get start of current week, independent of multitude
				minDate[setDate](minDate[getDate]() - minDate[getDay]() +
					options.startOfWeek);
			}


			// get tick positions
			i = 1; // prevent crash just in case
			minYear = minDate[getFullYear]();
			var time = minDate.getTime() / timeFactor,
				minMonth = minDate[getMonth](),
				minDateDate = minDate[getDate]();

			// iterate and add tick positions at appropriate values
			while (time < max && i < plotWidth) {
				tickPositions.push(time);

				// if the interval is years, use Date.UTC to increase years
				if (interval === oneYear) {
					time = makeTime(minYear + i * multitude, 0) / timeFactor;

				// if the interval is months, use Date.UTC to increase months
				} else if (interval === oneMonth) {
					time = makeTime(minYear, minMonth + i * multitude) / timeFactor;

				// if we're using global time, the interval is not fixed as it jumps
				// one hour at the DST crossover
				} else if (!useUTC && (interval === oneDay || interval === oneWeek)) {
					time = makeTime(minYear, minMonth, minDateDate +
						i * multitude * (interval === oneDay ? 1 : 7));

				// else, the interval is fixed and we use simple addition
				} else {
					time += interval * multitude;
				}

				i++;
			}
			// push the last time
			tickPositions.push(time);


			// dynamic label formatter
			dateTimeLabelFormat = options.dateTimeLabelFormats[unit[0]];
		}
		
		function setHockeyPeriodTickPositions() {
			tickPositions = [];
			var i,
				useUTC = defaultOptions.global.useUTC,
				oneSecond = 1000 / timeFactor,
				oneMinute = 60000 / timeFactor,
				oneHour = 3600000 / timeFactor,
				oneDay = 24 * 3600000 / timeFactor,
				oneWeek = 7 * 24 * 3600000 / timeFactor,
				oneMonth = 30 * 24 * 3600000 / timeFactor,
				oneYear = 31556952000 / timeFactor,
			
				units = [[
					'second',						// unit name
					oneSecond,						// fixed incremental unit
					[1, 2, 5, 10, 15, 30]			// allowed multiples
				], [
					'minute',						// unit name
					oneMinute,						// fixed incremental unit
					[1, 2, 5]						// allowed multiples
				], [
					'hour',							// unit name
					oneHour,						// fixed incremental unit
					[1, 2, 3, 4, 6, 8, 12]			// allowed multiples
				], [
					'day',							// unit name
					oneDay,							// fixed incremental unit
					[1, 2]							// allowed multiples
				], [
					'week',							// unit name
					oneWeek,						// fixed incremental unit
					[1, 2]							// allowed multiples
				], [
					'month',
					oneMonth,
					[1, 2, 3, 4, 6]
				], [
					'year',
					oneYear,
					null
				]],
			
				unit = units[6], // default unit is years
				interval = unit[1], 
				multiples = unit[2];
			
			// loop through the units to find the one that best fits the tickInterval
			for (i = 0; i < units.length; i++)  {
				unit = units[i];
				interval = unit[1];
				multiples = unit[2];
				
				
				if (units[i+1]) {
					// lessThan is in the middle between the highest multiple and the next unit.
					var lessThan = (interval * multiples[multiples.length - 1] + 
								units[i + 1][1]) / 2;
							
					// break and keep the current unit
					if (tickInterval <= lessThan) {
						break;
					}
				}
			}
			
			// prevent 2.5 years intervals, though 25, 250 etc. are allowed
			if (interval == oneYear && tickInterval < 5 * interval) {
				multiples = [1, 2, 5];
			}
	
			// get the minimum value by flooring the date
			var multitude = normalizeTickInterval(tickInterval / interval, multiples),
				minYear, // used in months and years as a basis for Date.UTC()
				minDate = new Date(min * timeFactor);
				
			minDate.setMilliseconds(0);
			
			if (interval >= oneSecond) { // second
				minDate.setSeconds(interval >= oneMinute ? 0 :
					multitude * mathFloor(minDate.getSeconds() / multitude));
			}
	
			if (interval >= oneMinute) { // minute
				minDate[setMinutes](interval >= oneHour ? 0 :
					multitude * mathFloor(minDate[getMinutes]() / multitude));
			}
	
			if (interval >= oneHour) { // hour
				minDate[setHours](interval >= oneDay ? 0 :
					multitude * mathFloor(minDate[getHours]() / multitude));
			}
	
			if (interval >= oneDay) { // day
				minDate[setDate](interval >= oneMonth ? 1 :
					multitude * mathFloor(minDate[getDate]() / multitude));
			}
					
			if (interval >= oneMonth) { // month
				minDate[setMonth](interval >= oneYear ? 0 :
					multitude * mathFloor(minDate[getMonth]() / multitude));
				minYear = minDate[getFullYear]();
			}
			
			if (interval >= oneYear) { // year
				minYear -= minYear % multitude;
				minDate[setFullYear](minYear);
			}
			
			// week is a special case that runs outside the hierarchy
			if (interval == oneWeek) {
				// get start of current week, independent of multitude
				minDate[setDate](minDate[getDate]() - minDate[getDay]() + 
					options.startOfWeek);
			}
			
			
			// get tick positions
			i = 1; // prevent crash just in case
			minYear = minDate[getFullYear]();
			var time = minDate.getTime() / timeFactor,
				minMonth = minDate[getMonth](),
				minDateDate = minDate[getDate]();
				
			// iterate and add tick positions at appropriate values
			while (time < max && i < plotWidth) {
				tickPositions.push(time);
				
				// if the interval is years, use Date.UTC to increase years
				if (interval == oneYear) {
					time = makeTime(minYear + i * multitude, 0) / timeFactor;
				
				// if the interval is months, use Date.UTC to increase months
				} else if (interval == oneMonth) {
					time = makeTime(minYear, minMonth + i * multitude) / timeFactor;
					
				// if we're using global time, the interval is not fixed as it jumps
				// one hour at the DST crossover
				} else if (!useUTC && (interval == oneDay || interval == oneWeek)) {
					time = makeTime(minYear, minMonth, minDateDate + 
						i * multitude * (interval == oneDay ? 1 : 7));
					
				// else, the interval is fixed and we use simple addition
				} else {
					time += interval * multitude;
				}
				
				i++;
			}
			// push the last time
			tickPositions.push(time);
			
			// dynamic label formatter 
			if (!options.labels.formatter) {
				labelFormatter = function() {
					return dateFormat(options.dateTimeLabelFormats[unit[0]], this.value, 1);
				};
			}
			
		}
		
		/**
		 * Fix JS round off float errors
		 * @param {Number} num
		 */
		function correctFloat(num) {
			var invMag, ret = num;
			magnitude = pick(magnitude, math.pow(10, mathFloor(math.log(tickInterval) / math.LN10)));

			if (magnitude < 1) {
				invMag = mathRound(1 / magnitude)  * 10;
				ret = mathRound(num * invMag) / invMag;
			}
			return ret;
		}

		/**
		 * Set the tick positions of a linear axis to round values like whole tens or every five.
		 */
		function setLinearTickPositions() {

			var i,
				roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
				roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval);

			tickPositions = [];

			// populate the intermediate values
			i = correctFloat(roundedMin);
			while (i <= roundedMax) {
				tickPositions.push(i);
				i = correctFloat(i + tickInterval);
			}

		}

		/**
		 * Set the tick positions to round values and optionally extend the extremes
		 * to the nearest tick
		 */
		function setTickPositions() {
			var length,
				catPad,
				linkedParent,
				linkedParentExtremes,
				tickIntervalOption = options.tickInterval,
				tickPixelIntervalOption = options.tickPixelInterval,
				maxZoom = options.maxZoom || (
					isXAxis && !defined(options.min) && !defined(options.max) ?
						mathMin(chart.smallestInterval * 5, dataMax - dataMin) :
						null
				),
				zoomOffset;


			axisLength = horiz ? plotWidth : plotHeight;

			// linked axis gets the extremes from the parent axis
			if (isLinked) {
				linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
				linkedParentExtremes = linkedParent.getExtremes();
				min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
				max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
			} else { // initial min and max from the extreme data values
				min = pick(userMin, options.min, dataMin);
				max = pick(userMax, options.max, dataMax);
			}

			if (isLog) {
				min = log2lin(min);
				max = log2lin(max);
			}

			// maxZoom exceeded, just center the selection
			if (max - min < maxZoom) {
				zoomOffset = (maxZoom - max + min) / 2;
				// if min and max options have been set, don't go beyond it
				min = mathMax(min - zoomOffset, pick(options.min, min - zoomOffset), dataMin);
				max = mathMin(min + maxZoom, pick(options.max, min + maxZoom), dataMax);
			}

			// pad the values to get clear of the chart's edges
			if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) {
				length = (max - min) || 1;
				if (!defined(options.min) && !defined(userMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) {
					min -= length * minPadding;
				}
				if (!defined(options.max) && !defined(userMax)  && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) {
					max += length * maxPadding;
				}
			}

			// get tickInterval
			if (min === max) {
				tickInterval = 1;
			} else if (isLinked && !tickIntervalOption &&
					tickPixelIntervalOption === linkedParent.options.tickPixelInterval) {
				tickInterval = linkedParent.tickInterval;
			} else {
				tickInterval = pick(
					tickIntervalOption,
					categories ? // for categoried axis, 1 is default, for linear axis use tickPix
						1 :
						(max - min) * tickPixelIntervalOption / axisLength
				);
			}

			if (!isDatetimeAxis && !isHockeyPeriodAxis && !defined(options.tickInterval)) { // linear
				tickInterval = normalizeTickInterval(tickInterval);
			}
			axis.tickInterval = tickInterval; // record for linked axis

			// get minorTickInterval
			minorTickInterval = options.minorTickInterval === 'auto' && tickInterval ?
					tickInterval / 5 : options.minorTickInterval;

			// find the tick positions
			if (isDatetimeAxis) {
				setDateTimeTickPositions();
			} else if (isHockeyPeriodAxis) {
				setHockeyPeriodTickPositions();
			}else {
				setLinearTickPositions();
			}

			if (!isLinked) {
				// pad categorised axis to nearest half unit
				if (categories || (isXAxis && chart.hasColumn)) {
					catPad = (categories ? 1 : tickInterval) * 0.5;
					if (categories || !defined(pick(options.min, userMin))) {
						min -= catPad;
					}
					if (categories || !defined(pick(options.max, userMax))) {
						max += catPad;
					}
				}

				// reset min/max or remove extremes based on start/end on tick
				var roundedMin = tickPositions[0],
					roundedMax = tickPositions[tickPositions.length - 1];

				if (options.startOnTick) {
					min = roundedMin;
				} else if (min > roundedMin) {
					tickPositions.shift();
				}

				if (options.endOnTick) {
					max = roundedMax;
				} else if (max < roundedMax) {
					tickPositions.pop();
				}

				// record the greatest number of ticks for multi axis
				if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
					maxTicks = {
						x: 0,
						y: 0
					};
				}

				if (!isDatetimeAxis && !isHockeyPeriodAxis && tickPositions.length > maxTicks[xOrY]) {
					maxTicks[xOrY] = tickPositions.length;
				}
			}


		}

		/**
		 * When using multiple axes, adjust the number of ticks to match the highest
		 * number of ticks in that group
		 */
		function adjustTickAmount() {

			if (maxTicks && !isDatetimeAxis && !isHockeyPeriodAxis && !categories && !isLinked) { // only apply to linear scale
				var oldTickAmount = tickAmount,
					calculatedTickAmount = tickPositions.length;

				// set the axis-level tickAmount to use below
				tickAmount = maxTicks[xOrY];

				if (calculatedTickAmount < tickAmount) {
					while (tickPositions.length < tickAmount) {
						tickPositions.push(correctFloat(
							tickPositions[tickPositions.length - 1] + tickInterval
						));
					}
					transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
					max = tickPositions[tickPositions.length - 1];

				}
				if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
					axis.isDirty = true;
				}
			}

		}

		/**
		 * Set the scale based on data min and max, user set min and max or options
		 *
		 */
		function setScale() {
			var type,
				i;

			oldMin = min;
			oldMax = max;

			// get data extremes if needed
			getSeriesExtremes();

			// get fixed positions based on tickInterval
			setTickPositions();

			// the translation factor used in translate function
			oldTransA = transA;
			transA = axisLength / ((max - min) || 1);

			// reset stacks
			if (!isXAxis) {
				for (type in stacks) {
					for (i in stacks[type]) {
						stacks[type][i].cum = stacks[type][i].total;
					}
				}
			}

			// mark as dirty if it is not already set to dirty and extremes have changed
			if (!axis.isDirty) {
				axis.isDirty = (min !== oldMin || max !== oldMax);
			}

		}

		/**
		 * Set the extremes and optionally redraw
		 * @param {Number} newMin
		 * @param {Number} newMax
		 * @param {Boolean} redraw
		 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
		 *    configuration
		 *
		 */
		function setExtremes(newMin, newMax, redraw, animation) {

			redraw = pick(redraw, true); // defaults to true

			fireEvent(axis, 'setExtremes', { // fire an event to enable syncing of multiple charts
				min: newMin,
				max: newMax
			}, function () { // the default event handler

				userMin = newMin;
				userMax = newMax;


				// redraw
				if (redraw) {
					chart.redraw(animation);
				}
			});

		}

		/**
		 * Get the actual axis extremes
		 */
		function getExtremes() {
			return {
				min: min,
				max: max,
				dataMin: dataMin,
				dataMax: dataMax,
				userMin: userMin,
				userMax: userMax
			};
		}

		/**
		 * Get the zero plane either based on zero or on the min or max value.
		 * Used in bar and area plots
		 */
		function getThreshold(threshold) {
			if (min > threshold) {
				threshold = min;
			} else if (max < threshold) {
				threshold = max;
			}

			return translate(threshold, 0, 1);
		}

		/**
		 * Add a plot band or plot line after render time
		 *
		 * @param options {Object} The plotBand or plotLine configuration object
		 */
		function addPlotBandOrLine(options) {
			var obj = new PlotLineOrBand(options).render();
			plotLinesAndBands.push(obj);
			return obj;
		}

		/**
		 * Render the tick labels to a preliminary position to get their sizes
		 */
		function getOffset() {

			var hasData = associatedSeries.length && defined(min) && defined(max),
				titleOffset = 0,
				titleMargin = 0,
				axisTitleOptions = options.title,
				labelOptions = options.labels,
				directionFactor = [-1, 1, 1, -1][side],
				n;

			if (!axisGroup) {
				axisGroup = renderer.g('axis')
					.attr({ zIndex: 7 })
					.add();
				gridGroup = renderer.g('grid')
					.attr({ zIndex: 1 })
					.add();
			}

			labelOffset = 0; // reset

			if (hasData || isLinked) {
				each(tickPositions, function (pos) {
					if (!ticks[pos]) {
						ticks[pos] = new Tick(pos);
					} else {
						ticks[pos].addLabel(); // update labels depending on tick interval
					}

					// left side must be align: right and right side must have align: left for labels
					if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {

						// get the highest offset
						labelOffset = mathMax(
							ticks[pos].getLabelSize(),
							labelOffset
						);
					}

				});

				if (staggerLines) {
					labelOffset += (staggerLines - 1) * 16;
				}

			} else { // doesn't have data
				for (n in ticks) {
					ticks[n].destroy();
					delete ticks[n];
				}
			}

			if (axisTitleOptions && axisTitleOptions.text) {
				if (!axisTitle) {
					axisTitle = axis.axisTitle = renderer.text(
						axisTitleOptions.text,
						0,
						0,
						axisTitleOptions.useHTML
					)
					.attr({
						zIndex: 7,
						rotation: axisTitleOptions.rotation || 0,
						align:
							axisTitleOptions.textAlign ||
							{ low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
					})
					.css(axisTitleOptions.style)
					.add();
					axisTitle.isNew = true;
				}

				titleOffset = axisTitle.getBBox()[horiz ? 'height' : 'width'];
				titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);

			}

			// handle automatic or user set offset
			offset = directionFactor * (options.offset || axisOffset[side]);

			axisTitleMargin =
				labelOffset +
				(side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) +
				titleMargin;

			axisOffset[side] = mathMax(
				axisOffset[side],
				axisTitleMargin + titleOffset + directionFactor * offset
			);

		}

		/**
		 * Render the axis
		 */
		function render() {
			var axisTitleOptions = options.title,
				stackLabelOptions = options.stackLabels,
				alternateGridColor = options.alternateGridColor,
				lineWidth = options.lineWidth,
				lineLeft,
				lineTop,
				linePath,
				hasRendered = chart.hasRendered,
				slideInTicks = hasRendered && defined(oldMin) && !isNaN(oldMin),
				hasData = associatedSeries.length && defined(min) && defined(max);

			// update metrics
			axisLength = horiz ? plotWidth : plotHeight;
			transA = axisLength / ((max - min) || 1);
			transB = horiz ? plotLeft : marginBottom; // translation addend

			// If the series has data draw the ticks. Else only the line and title
			if (hasData || isLinked) {

				// minor ticks
				if (minorTickInterval && !categories) {
					var pos = min + (tickPositions[0] - min) % minorTickInterval;
					for (; pos <= max; pos += minorTickInterval) {
						if (!minorTicks[pos]) {
							minorTicks[pos] = new Tick(pos, true);
						}

						// render new ticks in old position
						if (slideInTicks && minorTicks[pos].isNew) {
							minorTicks[pos].render(null, true);
						}


						minorTicks[pos].isActive = true;
						minorTicks[pos].render();
					}
				}

				// major ticks
				each(tickPositions, function (pos, i) {
					// linked axes need an extra check to find out if
					if (!isLinked || (pos >= min && pos <= max)) {

						// render new ticks in old position
						if (slideInTicks && ticks[pos].isNew) {
							ticks[pos].render(i, true);
						}

						ticks[pos].isActive = true;
						ticks[pos].render(i);
					}
				});

				// alternate grid color
				if (alternateGridColor) {
					each(tickPositions, function (pos, i) {
						if (i % 2 === 0 && pos < max) {
							/*plotLinesAndBands.push(new PlotLineOrBand({
								from: pos,
								to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
								color: alternateGridColor
							}));*/

							if (!alternateBands[pos]) {
								alternateBands[pos] = new PlotLineOrBand();
							}
							alternateBands[pos].options = {
								from: pos,
								to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
								color: alternateGridColor
							};
							alternateBands[pos].render();
							alternateBands[pos].isActive = true;
						}
					});
				}

				// custom plot bands (behind grid lines)
				/*if (!hasRendered) { // only first time
					each(options.plotBands || [], function(plotBandOptions) {
						plotLinesAndBands.push(new PlotLineOrBand(
							extend({ zIndex: 1 }, plotBandOptions)
						).render());
					});
				}*/




				// custom plot lines and bands
				if (!hasRendered) { // only first time
					each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
						plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
					});
				}



			} // end if hasData

			// remove inactive ticks
			each([ticks, minorTicks, alternateBands], function (coll) {
				var pos;
				for (pos in coll) {
					if (!coll[pos].isActive) {
						coll[pos].destroy();
						delete coll[pos];
					} else {
						coll[pos].isActive = false; // reset
					}
				}
			});




			// Static items. As the axis group is cleared on subsequent calls
			// to render, these items are added outside the group.
			// axis line
			if (lineWidth) {
				lineLeft = plotLeft + (opposite ? plotWidth : 0) + offset;
				lineTop = chartHeight - marginBottom - (opposite ? plotHeight : 0) + offset;

				linePath = renderer.crispLine([
						M,
						horiz ?
							plotLeft :
							lineLeft,
						horiz ?
							lineTop :
							plotTop,
						L,
						horiz ?
							chartWidth - marginRight :
							lineLeft,
						horiz ?
							lineTop :
							chartHeight - marginBottom
					], lineWidth);
				if (!axisLine) {
					axisLine = renderer.path(linePath)
						.attr({
							stroke: options.lineColor,
							'stroke-width': lineWidth,
							zIndex: 7
						})
						.add();
				} else {
					axisLine.animate({ d: linePath });
				}

			}

			if (axisTitle) {
				// compute anchor points for each of the title align options
				var margin = horiz ? plotLeft : plotTop,
					fontSize = pInt(axisTitleOptions.style.fontSize || 12),
				// the position in the length direction of the axis
				alongAxis = {
					low: margin + (horiz ? 0 : axisLength),
					middle: margin + axisLength / 2,
					high: margin + (horiz ? axisLength : 0)
				}[axisTitleOptions.align],

				// the position in the perpendicular direction of the axis
				offAxis = (horiz ? plotTop + plotHeight : plotLeft) +
					(horiz ? 1 : -1) * // horizontal axis reverses the margin
					(opposite ? -1 : 1) * // so does opposite axes
					axisTitleMargin +
					//(isIE ? fontSize / 3 : 0)+ // preliminary fix for vml's centerline
					(side === 2 ? fontSize : 0);

				axisTitle[axisTitle.isNew ? 'attr' : 'animate']({
					x: horiz ?
						alongAxis :
						offAxis + (opposite ? plotWidth : 0) + offset +
							(axisTitleOptions.x || 0), // x
					y: horiz ?
						offAxis - (opposite ? plotHeight : 0) + offset :
						alongAxis + (axisTitleOptions.y || 0) // y
				});
				axisTitle.isNew = false;
			}

			// Stacked totals:
			if (stackLabelOptions && stackLabelOptions.enabled) {
				var stackKey, oneStack, stackCategory,
					stackTotalGroup = axis.stackTotalGroup;

				// Create a separate group for the stack total labels
				if (!stackTotalGroup) {
					axis.stackTotalGroup = stackTotalGroup =
						renderer.g('stack-labels')
							.attr({
								visibility: VISIBLE,
								zIndex: 6
							})
							.translate(plotLeft, plotTop)
							.add();
				}

				// Render each stack total
				for (stackKey in stacks) {
					oneStack = stacks[stackKey];
					for (stackCategory in oneStack) {
						oneStack[stackCategory].render(stackTotalGroup);
					}
				}
			}
			// End stacked totals

			axis.isDirty = false;
		}

		/**
		 * Remove a plot band or plot line from the chart by id
		 * @param {Object} id
		 */
		function removePlotBandOrLine(id) {
			var i = plotLinesAndBands.length;
			while (i--) {
				if (plotLinesAndBands[i].id === id) {
					plotLinesAndBands[i].destroy();
				}
			}
		}

		/**
		 * Redraw the axis to reflect changes in the data or axis extremes
		 */
		function redraw() {

			// hide tooltip and hover states
			if (tracker.resetTracker) {
				tracker.resetTracker();
			}

			// render the axis
			render();

			// move plot lines and bands
			each(plotLinesAndBands, function (plotLine) {
				plotLine.render();
			});

			// mark associated series as dirty and ready for redraw
			each(associatedSeries, function (series) {
				series.isDirty = true;
			});

		}

		/**
		 * Set new axis categories and optionally redraw
		 * @param {Array} newCategories
		 * @param {Boolean} doRedraw
		 */
		function setCategories(newCategories, doRedraw) {
				// set the categories
				axis.categories = userOptions.categories = categories = newCategories;

				// force reindexing tooltips
				each(associatedSeries, function (series) {
					series.translate();
					series.setTooltipPoints(true);
				});


				// optionally redraw
				axis.isDirty = true;

				if (pick(doRedraw, true)) {
					chart.redraw();
				}
		}

		/**
		 * Destroys an Axis instance.
		 */
		function destroy() {
			var stackKey;

			// Remove the events
			removeEvent(axis);

			// Destroy each stack total
			for (stackKey in stacks) {
				destroyObjectProperties(stacks[stackKey]);

				stacks[stackKey] = null;
			}

			// Destroy stack total group
			if (axis.stackTotalGroup) {
				axis.stackTotalGroup = axis.stackTotalGroup.destroy();
			}

			// Destroy collections
			each([ticks, minorTicks, alternateBands, plotLinesAndBands], function (coll) {
				destroyObjectProperties(coll);
			});

			// Destroy local variables
			each([axisLine, axisGroup, gridGroup, axisTitle], function (obj) {
				if (obj) {
					obj.destroy();
				}
			});
			axisLine = axisGroup = gridGroup = axisTitle = null;
		}


		// Run Axis

		// inverted charts have reversed xAxes as default
		if (inverted && isXAxis && reversed === UNDEFINED) {
			reversed = true;
		}


		// expose some variables
		extend(axis, {
			addPlotBand: addPlotBandOrLine,
			addPlotLine: addPlotBandOrLine,
			adjustTickAmount: adjustTickAmount,
			categories: categories,
			getExtremes: getExtremes,
			getPlotLinePath: getPlotLinePath,
			getThreshold: getThreshold,
			isXAxis: isXAxis,
			options: options,
			plotLinesAndBands: plotLinesAndBands,
			getOffset: getOffset,
			render: render,
			setCategories: setCategories,
			setExtremes: setExtremes,
			setScale: setScale,
			setTickPositions: setTickPositions,
			translate: translate,
			redraw: redraw,
			removePlotBand: removePlotBandOrLine,
			removePlotLine: removePlotBandOrLine,
			reversed: reversed,
			stacks: stacks,
			destroy: destroy
		});

		// register event listeners
		for (eventType in events) {
			addEvent(axis, eventType, events[eventType]);
		}

		// set min and max
		setScale();

	} // end Axis


	/**
	 * The toolbar object
	 */
	function Toolbar() {
		var buttons = {};

		/*jslint unparam: true*//* allow the unused param title until Toolbar rewrite*/
		function add(id, text, title, fn) {
			if (!buttons[id]) {
				var button = renderer.text(
					text,
					0,
					0
				)
				.css(options.toolbar.itemStyle)
				.align({
					align: 'left',
					x: marginRight,
					y: plotTop - 12
				})
				.on('click', fn)
				/*.on('touchstart', function(e) {
					e.stopPropagation(); // don't fire the container event
					fn();
				})*/
				.attr({
					align: 'left',
					zIndex: 20
				})
				.add();
				buttons[id] = button;
			}
		}
		/*jslint unparam: false*/

		function remove(id) {
			discardElement(buttons[id].element);
			buttons[id] = null;
		}

		// public
		return {
			add: add,
			remove: remove
		};
	}

	/**
	 * The tooltip object
	 * @param {Object} options Tooltip options
	 */
	function Tooltip(options) {
		var currentSeries,
			borderWidth = options.borderWidth,
			crosshairsOptions = options.crosshairs,
			crosshairs = [],
			style = options.style,
			shared = options.shared,
			padding = pInt(style.padding),
			boxOffLeft = borderWidth + padding, // off left/top position as IE can't
				//properly handle negative positioned shapes
			tooltipIsHidden = true,
			boxWidth,
			boxHeight,
			currentX = 0,
			currentY = 0;

		// remove padding CSS and apply padding on box instead
		style.padding = 0;

		// create the elements
		var group = renderer.g('tooltip')
			.attr({	zIndex: 8 })
			.add(),

			box = renderer.rect(boxOffLeft, boxOffLeft, 0, 0, options.borderRadius, borderWidth)
				.attr({
					fill: options.backgroundColor,
					'stroke-width': borderWidth
				})
				.add(group)
				.shadow(options.shadow),
			label = renderer.text('', padding + boxOffLeft, pInt(style.fontSize) + padding + boxOffLeft, options.useHTML)
				.attr({ zIndex: 1 })
				.css(style)
				.add(group);

		group.hide();

		/**
		 * Destroy the tooltip and its elements.
		 */
		function destroy() {
			each(crosshairs, function (crosshair) {
				if (crosshair) {
					crosshair.destroy();
				}
			});

			// Destroy and clear local variables
			each([box, label, group], function (obj) {
				if (obj) {
					obj.destroy();
				}
			});
			box = label = group = null;
		}

		/**
		 * In case no user defined formatter is given, this will be used
		 */
		function defaultFormatter() {
			var pThis = this,
				items = pThis.points || splat(pThis),
				xAxis = items[0].series.xAxis,
				x = pThis.x,
				isDateTime = xAxis && xAxis.options.type === 'datetime',
				useHeader = isString(x) || isDateTime,
				s;

			// build the header
			s = useHeader ?
				['<span style="font-size: 10px">' +
				(isDateTime ? dateFormat('%A, %b %e, %Y', x) :  x) +
				'</span>'] : [];

			// build the values
			each(items, function (item) {
				s.push(item.point.tooltipFormatter(useHeader));
			});
			return s.join('<br/>');
		}

		/**
		 * Provide a soft movement for the tooltip
		 *
		 * @param {Number} finalX
		 * @param {Number} finalY
		 */
		function move(finalX, finalY) {

			currentX = tooltipIsHidden ? finalX : (2 * currentX + finalX) / 3;
			currentY = tooltipIsHidden ? finalY : (currentY + finalY) / 2;

			group.translate(currentX, currentY);


			// run on next tick of the mouse tracker
			if (mathAbs(finalX - currentX) > 1 || mathAbs(finalY - currentY) > 1) {
				tooltipTick = function () {
					move(finalX, finalY);
				};
			} else {
				tooltipTick = null;
			}
		}

		/**
		 * Hide the tooltip
		 */
		function hide() {
			if (!tooltipIsHidden) {
				var hoverPoints = chart.hoverPoints;

				group.hide();

				each(crosshairs, function (crosshair) {
					if (crosshair) {
						crosshair.hide();
					}
				});

				// hide previous hoverPoints and set new
				if (hoverPoints) {
					each(hoverPoints, function (point) {
						point.setState();
					});
				}
				chart.hoverPoints = null;


				tooltipIsHidden = true;
			}

		}

		/**
		 * Refresh the tooltip's text and position.
		 * @param {Object} point
		 *
		 */
		function refresh(point) {
			var x,
				y,
				show,
				bBox,
				plotX,
				plotY = 0,
				textConfig = {},
				text,
				pointConfig = [],
				tooltipPos = point.tooltipPos,
				formatter = options.formatter || defaultFormatter,
				hoverPoints = chart.hoverPoints,
				placedTooltipPoint;

			// shared tooltip, array is sent over
			if (shared) {

				// hide previous hoverPoints and set new
				if (hoverPoints) {
					each(hoverPoints, function (point) {
						point.setState();
					});
				}
				chart.hoverPoints = point;

				each(point, function (item) {
					/*var series = item.series,
						hoverPoint = series.hoverPoint;
					if (hoverPoint) {
						hoverPoint.setState();
					}
					series.hoverPoint = item;*/
					item.setState(HOVER_STATE);
					plotY += item.plotY; // for average

					pointConfig.push(item.getLabelConfig());
				});

				plotX = point[0].plotX;
				plotY = mathRound(plotY) / point.length; // mathRound because Opera 10 has problems here

				textConfig = {
					x: point[0].category
				};
				textConfig.points = pointConfig;
				point = point[0];

			// single point tooltip
			} else {
				textConfig = point.getLabelConfig();
			}
			text = formatter.call(textConfig);

			// register the current series
			currentSeries = point.series;

			// get the reference point coordinates (pie charts use tooltipPos)
			plotX = shared ? plotX : point.plotX;
			plotY = shared ? plotY : point.plotY;
			x = mathRound(tooltipPos ? tooltipPos[0] : (inverted ? plotWidth - plotY : plotX));
			y = mathRound(tooltipPos ? tooltipPos[1] : (inverted ? plotHeight - plotX : plotY));


			// hide tooltip if the point falls outside the plot
			show = shared || !point.series.isCartesian || isInsidePlot(x, y);

			// update the inner HTML
			if (text === false || !show) {
				hide();
			} else {

				// show it
				if (tooltipIsHidden) {
					group.show();
					tooltipIsHidden = false;
				}

				// update text
				label.attr({
					text: text
				});

				// get the bounding box
				bBox = label.getBBox();
				boxWidth = bBox.width + 2 * padding;
				boxHeight = bBox.height + 2 * padding;

				// set the size of the box
				box.attr({
					width: boxWidth,
					height: boxHeight,
					stroke: options.borderColor || point.color || currentSeries.color || '#606060'
				});

				placedTooltipPoint = placeBox(boxWidth, boxHeight, plotLeft, plotTop, plotWidth, plotHeight, {x: x, y: y});

				// do the move
				move(mathRound(placedTooltipPoint.x - boxOffLeft), mathRound(placedTooltipPoint.y - boxOffLeft));
			}


			// crosshairs
			if (crosshairsOptions) {
				crosshairsOptions = splat(crosshairsOptions); // [x, y]

				var path,
					i = crosshairsOptions.length,
					attribs,
					axis;

				while (i--) {
					axis = point.series[i ? 'yAxis' : 'xAxis'];
					if (crosshairsOptions[i] && axis) {
						path = axis
							.getPlotLinePath(point[i ? 'y' : 'x'], 1);
						if (crosshairs[i]) {
							crosshairs[i].attr({ d: path, visibility: VISIBLE });

						} else {
							attribs = {
								'stroke-width': crosshairsOptions[i].width || 1,
								stroke: crosshairsOptions[i].color || '#C0C0C0',
								zIndex: 2
							};
							if (crosshairsOptions[i].dashStyle) {
								attribs.dashstyle = crosshairsOptions[i].dashStyle;
							}
							crosshairs[i] = renderer.path(path)
								.attr(attribs)
								.add();
						}
					}
				}
			}
		}



		// public members
		return {
			shared: shared,
			refresh: refresh,
			hide: hide,
			destroy: destroy
		};
	}

	/**
	 * The mouse tracker object
	 * @param {Object} options
	 */
	function MouseTracker(options) {


		var mouseDownX,
			mouseDownY,
			hasDragged,
			selectionMarker,
			zoomType = optionsChart.zoomType,
			zoomX = /x/.test(zoomType),
			zoomY = /y/.test(zoomType),
			zoomHor = (zoomX && !inverted) || (zoomY && inverted),
			zoomVert = (zoomY && !inverted) || (zoomX && inverted);

		/**
		 * Add crossbrowser support for chartX and chartY
		 * @param {Object} e The event object in standard browsers
		 */
		function normalizeMouseEvent(e) {
			var ePos,
				pageZoomFix = isWebKit &&
					doc.width / doc.body.scrollWidth -
					1, // #224, #348
				chartPosLeft,
				chartPosTop,
				chartX,
				chartY;

			// common IE normalizing
			e = e || win.event;
			if (!e.target) {
				e.target = e.srcElement;
			}

			// iOS
			ePos = e.touches ? e.touches.item(0) : e;

			// in certain cases, get mouse position
			if (e.type !== 'mousemove' || win.opera || pageZoomFix) { // only Opera needs position on mouse move, see below
				chartPosition = getPosition(container);
				chartPosLeft = chartPosition.left;
				chartPosTop = chartPosition.top;
			}

			// chartX and chartY
			if (isIE) { // IE including IE9 that has chartX but in a different meaning
				chartX = e.x;
				chartY = e.y;
			} else {
				if (ePos.layerX === UNDEFINED) { // Opera and iOS
					chartX = ePos.pageX - chartPosLeft;
					chartY = ePos.pageY - chartPosTop;
				} else {
					chartX = e.layerX;
					chartY = e.layerY;
				}
			}

			// correct for page zoom bug in WebKit
			if (pageZoomFix) {
				chartX += mathRound((pageZoomFix + 1) * chartPosLeft - chartPosLeft);
				chartY += mathRound((pageZoomFix + 1) * chartPosTop - chartPosTop);
			}

			return extend(e, {
				chartX: chartX,
				chartY: chartY
			});
		}

		/**
		 * Get the click position in terms of axis values.
		 *
		 * @param {Object} e A mouse event
		 */
		function getMouseCoordinates(e) {
			var coordinates = {
				xAxis: [],
				yAxis: []
			};
			each(axes, function (axis) {
				var translate = axis.translate,
					isXAxis = axis.isXAxis,
					isHorizontal = inverted ? !isXAxis : isXAxis;

				coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
					axis: axis,
					value: translate(
						isHorizontal ?
							e.chartX - plotLeft  :
							plotHeight - e.chartY + plotTop,
						true
					)
				});
			});
			return coordinates;
		}

		/**
		 * With line type charts with a single tracker, get the point closest to the mouse
		 */
		function onmousemove(e) {
			var point,
				points,
				hoverPoint = chart.hoverPoint,
				hoverSeries = chart.hoverSeries,
				i,
				j,
				distance = chartWidth,
				index = inverted ? e.chartY : e.chartX - plotLeft; // wtf?

			// shared tooltip
			if (tooltip && options.shared) {
				points = [];

				// loop over all series and find the ones with points closest to the mouse
				i = series.length;
				for (j = 0; j < i; j++) {
					if (series[j].visible && series[j].tooltipPoints.length) {
						point = series[j].tooltipPoints[index];
						point._dist = mathAbs(index - point.plotX);
						distance = mathMin(distance, point._dist);
						points.push(point);
					}
				}
				// remove furthest points
				i = points.length;
				while (i--) {
					if (points[i]._dist > distance) {
						points.splice(i, 1);
					}
				}
				// refresh the tooltip if necessary
				if (points.length && (points[0].plotX !== hoverX)) {
					tooltip.refresh(points);
					hoverX = points[0].plotX;
				}
			}

			// separate tooltip and general mouse events
			if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker

				// get the point
				point = hoverSeries.tooltipPoints[index];

				// a new point is hovered, refresh the tooltip
				if (point && point !== hoverPoint) {

					// trigger the events
					point.onMouseOver();

				}
			}
		}



		/**
		 * Reset the tracking by hiding the tooltip, the hover series state and the hover point
		 */
		function resetTracker() {
			var hoverSeries = chart.hoverSeries,
				hoverPoint = chart.hoverPoint;

			if (hoverPoint) {
				hoverPoint.onMouseOut();
			}

			if (hoverSeries) {
				hoverSeries.onMouseOut();
			}

			if (tooltip) {
				tooltip.hide();
			}

			hoverX = null;
		}

		/**
		 * Mouse up or outside the plot area
		 */
		function drop() {
			if (selectionMarker) {
				var selectionData = {
						xAxis: [],
						yAxis: []
					},
					selectionBox = selectionMarker.getBBox(),
					selectionLeft = selectionBox.x - plotLeft,
					selectionTop = selectionBox.y - plotTop;


				// a selection has been made
				if (hasDragged) {

					// record each axis' min and max
					each(axes, function (axis) {
						var translate = axis.translate,
							isXAxis = axis.isXAxis,
							isHorizontal = inverted ? !isXAxis : isXAxis,
							selectionMin = translate(
								isHorizontal ?
									selectionLeft :
									plotHeight - selectionTop - selectionBox.height,
								true,
								0,
								0,
								1
							),
							selectionMax = translate(
								isHorizontal ?
									selectionLeft + selectionBox.width :
									plotHeight - selectionTop,
								true,
								0,
								0,
								1
							);

							selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
								axis: axis,
								min: mathMin(selectionMin, selectionMax), // for reversed axes,
								max: mathMax(selectionMin, selectionMax)
							});

					});
					fireEvent(chart, 'selection', selectionData, zoom);

				}
				selectionMarker = selectionMarker.destroy();
			}

			chart.mouseIsDown = mouseIsDown = hasDragged = false;
			removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);

		}

		/**
		 * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
		 */
		function hideTooltipOnMouseMove(e) {
			var pageX = defined(e.pageX) ? e.pageX : e.page.x, // In mootools the event is wrapped and the page x/y position is named e.page.x
				pageY = defined(e.pageX) ? e.pageY : e.page.y; // Ref: http://mootools.net/docs/core/Types/DOMEvent

			if (chartPosition &&
					!isInsidePlot(pageX - chartPosition.left - plotLeft,
						pageY - chartPosition.top - plotTop)) {
				resetTracker();
			}
		}

		/**
		 * Set the JS events on the container element
		 */
		function setDOMEvents() {
			var lastWasOutsidePlot = true;
			/*
			 * Record the starting position of a dragoperation
			 */
			container.onmousedown = function (e) {
				e = normalizeMouseEvent(e);

				// issue #295, dragging not always working in Firefox
				if (!hasTouch && e.preventDefault) {
					e.preventDefault();
				}

				// record the start position
				chart.mouseIsDown = mouseIsDown = true;
				mouseDownX = e.chartX;
				mouseDownY = e.chartY;

				addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
			};

			// The mousemove, touchmove and touchstart event handler
			var mouseMove = function (e) {

				// let the system handle multitouch operations like two finger scroll
				// and pinching
				if (e && e.touches && e.touches.length > 1) {
					return;
				}

				// normalize
				e = normalizeMouseEvent(e);
				if (!hasTouch) { // not for touch devices
					e.returnValue = false;
				}

				var chartX = e.chartX,
					chartY = e.chartY,
					isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop);

				// cache chart position for issue #149 fix
				if (!chartPosition) {
					chartPosition = getPosition(container);
				}

				// on touch devices, only trigger click if a handler is defined
				if (hasTouch && e.type === 'touchstart') {
					if (attr(e.target, 'isTracker')) {
						if (!chart.runTrackerClick) {
							e.preventDefault();
						}
					} else if (!runChartClick && !isOutsidePlot) {
						e.preventDefault();
					}
				}

				// cancel on mouse outside
				if (isOutsidePlot) {

					/*if (!lastWasOutsidePlot) {
						// reset the tracker
						resetTracker();
					}*/

					// drop the selection if any and reset mouseIsDown and hasDragged
					//drop();
					if (chartX < plotLeft) {
						chartX = plotLeft;
					} else if (chartX > plotLeft + plotWidth) {
						chartX = plotLeft + plotWidth;
					}

					if (chartY < plotTop) {
						chartY = plotTop;
					} else if (chartY > plotTop + plotHeight) {
						chartY = plotTop + plotHeight;
					}

				}

				if (mouseIsDown && e.type !== 'touchstart') { // make selection

					// determine if the mouse has moved more than 10px
					hasDragged = Math.sqrt(
						Math.pow(mouseDownX - chartX, 2) +
						Math.pow(mouseDownY - chartY, 2)
					);
					if (hasDragged > 10) {

						// make a selection
						if (hasCartesianSeries && (zoomX || zoomY) &&
								isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop)) {
							if (!selectionMarker) {
								selectionMarker = renderer.rect(
									plotLeft,
									plotTop,
									zoomHor ? 1 : plotWidth,
									zoomVert ? 1 : plotHeight,
									0
								)
								.attr({
									fill: optionsChart.selectionMarkerFill || 'rgba(69,114,167,0.25)',
									zIndex: 7
								})
								.add();
							}
						}

						// adjust the width of the selection marker
						if (selectionMarker && zoomHor) {
							var xSize = chartX - mouseDownX;
							selectionMarker.attr({
								width: mathAbs(xSize),
								x: (xSize > 0 ? 0 : xSize) + mouseDownX
							});
						}
						// adjust the height of the selection marker
						if (selectionMarker && zoomVert) {
							var ySize = chartY - mouseDownY;
							selectionMarker.attr({
								height: mathAbs(ySize),
								y: (ySize > 0 ? 0 : ySize) + mouseDownY
							});
						}
					}

				} else if (!isOutsidePlot) {
					// show the tooltip
					onmousemove(e);
				}

				lastWasOutsidePlot = isOutsidePlot;

				// when outside plot, allow touch-drag by returning true
				return isOutsidePlot || !hasCartesianSeries;
			};

			/*
			 * When the mouse enters the container, run mouseMove
			 */
			container.onmousemove = mouseMove;

			/*
			 * When the mouse leaves the container, hide the tracking (tooltip).
			 */
			addEvent(container, 'mouseleave', resetTracker);

			// issue #149 workaround
			// The mouseleave event above does not always fire. Whenever the mouse is moving
			// outside the plotarea, hide the tooltip
			addEvent(doc, 'mousemove', hideTooltipOnMouseMove);

			container.ontouchstart = function (e) {
				// For touch devices, use touchmove to zoom
				if (zoomX || zoomY) {
					container.onmousedown(e);
				}
				// Show tooltip and prevent the lower mouse pseudo event
				mouseMove(e);
			};

			/*
			 * Allow dragging the finger over the chart to read the values on touch
			 * devices
			 */
			container.ontouchmove = mouseMove;

			/*
			 * Allow dragging the finger over the chart to read the values on touch
			 * devices
			 */
			container.ontouchend = function () {
				if (hasDragged) {
					resetTracker();
				}
			};


			// MooTools 1.2.3 doesn't fire this in IE when using addEvent
			container.onclick = function (e) {
				var hoverPoint = chart.hoverPoint;
				e = normalizeMouseEvent(e);

				e.cancelBubble = true; // IE specific


				if (!hasDragged) {
					if (hoverPoint && attr(e.target, 'isTracker')) {
						var plotX = hoverPoint.plotX,
							plotY = hoverPoint.plotY;

						// add page position info
						extend(hoverPoint, {
							pageX: chartPosition.left + plotLeft +
								(inverted ? plotWidth - plotY : plotX),
							pageY: chartPosition.top + plotTop +
								(inverted ? plotHeight - plotX : plotY)
						});

						// the series click event
						fireEvent(hoverPoint.series, 'click', extend(e, {
							point: hoverPoint
						}));

						// the point click event
						hoverPoint.firePointEvent('click', e);

					} else {
						extend(e, getMouseCoordinates(e));

						// fire a click event in the chart
						if (isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
							fireEvent(chart, 'click', e);
						}
					}


				}
				// reset mouseIsDown and hasDragged
				hasDragged = false;
			};

		}

		/**
		 * Destroys the MouseTracker object and disconnects DOM events.
		 */
		function destroy() {
			// Destroy the tracker group element
			if (chart.trackerGroup) {
				chart.trackerGroup = trackerGroup = chart.trackerGroup.destroy();
			}

			removeEvent(doc, 'mousemove', hideTooltipOnMouseMove);
			container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null;
		}

		/**
		 * Create the image map that listens for mouseovers
		 */
		placeTrackerGroup = function () {

			// first create - plot positions is not final at this stage
			if (!trackerGroup) {
				chart.trackerGroup = trackerGroup = renderer.g('tracker')
					.attr({ zIndex: 9 })
					.add();

			// then position - this happens on load and after resizing and changing
			// axis or box positions
			} else {
				trackerGroup.translate(plotLeft, plotTop);
				if (inverted) {
					trackerGroup.attr({
						width: chart.plotWidth,
						height: chart.plotHeight
					}).invert();
				}
			}
		};


		// Run MouseTracker
		placeTrackerGroup();
		if (options.enabled) {
			chart.tooltip = tooltip = Tooltip(options);
		}

		setDOMEvents();

		// set the fixed interval ticking for the smooth tooltip
		tooltipInterval = setInterval(function () {
			if (tooltipTick) {
				tooltipTick();
			}
		}, 32);

		// expose properties
		extend(this, {
			zoomX: zoomX,
			zoomY: zoomY,
			resetTracker: resetTracker,
			destroy: destroy
		});
	}



	/**
	 * The overview of the chart's series
	 */
	var Legend = function () {

		var options = chart.options.legend;

		if (!options.enabled) {
			return;
		}

		var horizontal = options.layout === 'horizontal',
			symbolWidth = options.symbolWidth,
			symbolPadding = options.symbolPadding,
			allItems,
			style = options.style,
			itemStyle = options.itemStyle,
			itemHoverStyle = options.itemHoverStyle,
			itemHiddenStyle = options.itemHiddenStyle,
			padding = pInt(style.padding),
			y = 18,
			initialItemX = 4 + padding + symbolWidth + symbolPadding,
			itemX,
			itemY,
			lastItemY,
			itemHeight = 0,
			box,
			legendBorderWidth = options.borderWidth,
			legendBackgroundColor = options.backgroundColor,
			legendGroup,
			offsetWidth,
			widthOption = options.width,
			series = chart.series,
			reversedLegend = options.reversed;



		/**
		 * Set the colors for the legend item
		 * @param {Object} item A Series or Point instance
		 * @param {Object} visible Dimmed or colored
		 */
		function colorizeItem(item, visible) {
			var legendItem = item.legendItem,
				legendLine = item.legendLine,
				legendSymbol = item.legendSymbol,
				hiddenColor = itemHiddenStyle.color,
				textColor = visible ? options.itemStyle.color : hiddenColor,
				lineColor = visible ? item.color : hiddenColor,
				symbolAttr = visible ? item.pointAttr[NORMAL_STATE] : {
					stroke: hiddenColor,
					fill: hiddenColor
				};

			if (legendItem) {
				legendItem.css({ fill: textColor });
			}
			if (legendLine) {
				legendLine.attr({ stroke: lineColor });
			}
			if (legendSymbol) {
				legendSymbol.attr(symbolAttr);
			}

		}

		/**
		 * Position the legend item
		 * @param {Object} item A Series or Point instance
		 * @param {Object} visible Dimmed or colored
		 */
		function positionItem(item, itemX, itemY) {
			var legendItem = item.legendItem,
				legendLine = item.legendLine,
				legendSymbol = item.legendSymbol,
				checkbox = item.checkbox;
			if (legendItem) {
				legendItem.attr({
					x: itemX,
					y: itemY
				});
			}
			if (legendLine) {
				legendLine.translate(itemX, itemY - 4);
			}
			if (legendSymbol) {
				legendSymbol.attr({
					x: itemX + legendSymbol.xOff,
					y: itemY + legendSymbol.yOff
				});
			}
			if (checkbox) {
				checkbox.x = itemX;
				checkbox.y = itemY;
			}
		}

		/**
		 * Destroy a single legend item
		 * @param {Object} item The series or point
		 */
		function destroyItem(item) {
			var checkbox = item.checkbox;

			// pull out from the array
			//erase(allItems, item);

			// destroy SVG elements
			each(['legendItem', 'legendLine', 'legendSymbol'], function (key) {
				if (item[key]) {
					item[key].destroy();
				}
			});

			if (checkbox) {
				discardElement(item.checkbox);
			}


		}

		/**
		 * Destroys the legend.
		 */
		function destroy() {
			if (box) {
				box = box.destroy();
			}

			if (legendGroup) {
				legendGroup = legendGroup.destroy();
			}
		}

		/**
		 * Position the checkboxes after the width is determined
		 */
		function positionCheckboxes() {
			each(allItems, function (item) {
				var checkbox = item.checkbox,
					alignAttr = legendGroup.alignAttr;
				if (checkbox) {
					css(checkbox, {
						left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 40) + PX,
						top: (alignAttr.translateY + checkbox.y - 11) + PX
					});
				}
			});
		}

		/**
		 * Render a single specific legend item
		 * @param {Object} item A series or point
		 */
		function renderItem(item) {
			var bBox,
				itemWidth,
				legendSymbol,
				symbolX,
				symbolY,
				simpleSymbol,
				li = item.legendItem,
				series = item.series || item,
				itemOptions = series.options,
				strokeWidth = (itemOptions && itemOptions.borderWidth) || 0;

			if (!li) { // generate it once, later move it

				// let these series types use a simple symbol
				simpleSymbol = /^(bar|pie|area|column)$/.test(series.type);

				// generate the list item text
				item.legendItem = li = renderer.text(
						options.labelFormatter.call(item),
						0,
						0
					)
					.css(item.visible ? itemStyle : itemHiddenStyle)
					.on('mouseover', function () {
						item.setState(HOVER_STATE);
						li.css(itemHoverStyle);
					})
					.on('mouseout', function () {
						li.css(item.visible ? itemStyle : itemHiddenStyle);
						item.setState();
					})
					.on('click', function () {
						var strLegendItemClick = 'legendItemClick',
							fnLegendItemClick = function () {
								item.setVisible();
							};

						// click the name or symbol
						if (item.firePointEvent) { // point
							item.firePointEvent(strLegendItemClick, null, fnLegendItemClick);
						} else {
							fireEvent(item, strLegendItemClick, null, fnLegendItemClick);
						}
					})
					.attr({ zIndex: 2 })
					.add(legendGroup);

				// draw the line
				if (!simpleSymbol && itemOptions && itemOptions.lineWidth) {
					var attrs = {
							'stroke-width': itemOptions.lineWidth,
							zIndex: 2
						};
					if (itemOptions.dashStyle) {
						attrs.dashstyle = itemOptions.dashStyle;
					}
					item.legendLine = renderer.path([
						M,
						-symbolWidth - symbolPadding,
						0,
						L,
						-symbolPadding,
						0
					])
					.attr(attrs)
					.add(legendGroup);
				}

				// draw a simple symbol
				if (simpleSymbol) { // bar|pie|area|column

					legendSymbol = renderer.rect(
						(symbolX = -symbolWidth - symbolPadding),
						(symbolY = -11),
						symbolWidth,
						12,
						2
					).attr({
						//'stroke-width': 0,
						zIndex: 3
					}).add(legendGroup);

				// draw the marker
				} else if (itemOptions && itemOptions.marker && itemOptions.marker.enabled) {
					legendSymbol = renderer.symbol(
						item.symbol,
						(symbolX = -symbolWidth / 2 - symbolPadding),
						(symbolY = -4),
						itemOptions.marker.radius
					)
					//.attr(item.pointAttr[NORMAL_STATE])
					.attr({ zIndex: 3 })
					.add(legendGroup);

				}
				if (legendSymbol) {
					legendSymbol.xOff = symbolX + (strokeWidth % 2 / 2);
					legendSymbol.yOff = symbolY + (strokeWidth % 2 / 2);
				}

				item.legendSymbol = legendSymbol;

				// colorize the items
				colorizeItem(item, item.visible);


				// add the HTML checkbox on top
				if (itemOptions && itemOptions.showCheckbox) {
					item.checkbox = createElement('input', {
						type: 'checkbox',
						checked: item.selected,
						defaultChecked: item.selected // required by IE7
					}, options.itemCheckboxStyle, container);

					addEvent(item.checkbox, 'click', function (event) {
						var target = event.target;
						fireEvent(item, 'checkboxClick', {
								checked: target.checked
							},
							function () {
								item.select();
							}
						);
					});
				}
			}


			// calculate the positions for the next line
			bBox = li.getBBox();

			itemWidth = item.legendItemWidth =
				options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding;
			itemHeight = bBox.height;

			// if the item exceeds the width, start a new line
			if (horizontal && itemX - initialItemX + itemWidth >
					(widthOption || (chartWidth - 2 * padding - initialItemX))) {
				itemX = initialItemX;
				itemY += itemHeight;
			}
			lastItemY = itemY;

			// position the newly generated or reordered items
			positionItem(item, itemX, itemY);

			// advance
			if (horizontal) {
				itemX += itemWidth;
			} else {
				itemY += itemHeight;
			}

			// the width of the widest item
			offsetWidth = widthOption || mathMax(
				horizontal ? itemX - initialItemX : itemWidth,
				offsetWidth
			);



			// add it all to an array to use below
			//allItems.push(item);
		}

		/**
		 * Render the legend. This method can be called both before and after
		 * chart.render. If called after, it will only rearrange items instead
		 * of creating new ones.
		 */
		function renderLegend() {
			itemX = initialItemX;
			itemY = y;
			offsetWidth = 0;
			lastItemY = 0;

			if (!legendGroup) {
				legendGroup = renderer.g('legend')
					.attr({ zIndex: 7 })
					.add();
			}


			// add each series or point
			allItems = [];
			each(series, function (serie) {
				var seriesOptions = serie.options;

				if (!seriesOptions.showInLegend) {
					return;
				}

				// use points or series for the legend item depending on legendType
				allItems = allItems.concat(seriesOptions.legendType === 'point' ?
					serie.data :
					serie
				);

			});

			// sort by legendIndex
			stableSort(allItems, function (a, b) {
				return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
			});

			// reversed legend
			if (reversedLegend) {
				allItems.reverse();
			}

			// render the items
			each(allItems, renderItem);



			// Draw the border
			legendWidth = widthOption || offsetWidth;
			legendHeight = lastItemY - y + itemHeight;

			if (legendBorderWidth || legendBackgroundColor) {
				legendWidth += 2 * padding;
				legendHeight += 2 * padding;

				if (!box) {
					box = renderer.rect(
						0,
						0,
						legendWidth,
						legendHeight,
						options.borderRadius,
						legendBorderWidth || 0
					).attr({
						stroke: options.borderColor,
						'stroke-width': legendBorderWidth || 0,
						fill: legendBackgroundColor || NONE
					})
					.add(legendGroup)
					.shadow(options.shadow);
					box.isNew = true;

				} else if (legendWidth > 0 && legendHeight > 0) {
					box[box.isNew ? 'attr' : 'animate'](
						box.crisp(null, null, null, legendWidth, legendHeight)
					);
					box.isNew = false;
				}

				// hide the border if no items
				box[allItems.length ? 'show' : 'hide']();
			}

			// 1.x compatibility: positioning based on style
			var props = ['left', 'right', 'top', 'bottom'],
				prop,
				i = 4;
			while (i--) {
				prop = props[i];
				if (style[prop] && style[prop] !== 'auto') {
					options[i < 2 ? 'align' : 'verticalAlign'] = prop;
					options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1);
				}
			}

			if (allItems.length) {
				legendGroup.align(extend(options, {
					width: legendWidth,
					height: legendHeight
				}), true, spacingBox);
			}

			if (!isResizing) {
				positionCheckboxes();
			}
		}


		// run legend
		renderLegend();

		// move checkboxes
		addEvent(chart, 'endResize', positionCheckboxes);

		// expose
		return {
			colorizeItem: colorizeItem,
			destroyItem: destroyItem,
			renderLegend: renderLegend,
			destroy: destroy
		};
	};






	/**
	 * Initialize an individual series, called internally before render time
	 */
	function initSeries(options) {
		var type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
			typeClass = seriesTypes[type],
			serie,
			hasRendered = chart.hasRendered;

		// an inverted chart can't take a column series and vice versa
		if (hasRendered) {
			if (inverted && type === 'column') {
				typeClass = seriesTypes.bar;
			} else if (!inverted && type === 'bar') {
				typeClass = seriesTypes.column;
			}
		}

		serie = new typeClass();

		serie.init(chart, options);

		// set internal chart properties
		if (!hasRendered && serie.inverted) {
			inverted = true;
		}
		if (serie.isCartesian) {
			hasCartesianSeries = serie.isCartesian;
		}

		series.push(serie);

		return serie;
	}

	/**
	 * Add a series dynamically after  time
	 *
	 * @param {Object} options The config options
	 * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	 *    configuration
	 *
	 * @return {Object} series The newly created series object
	 */
	function addSeries(options, redraw, animation) {
		var series;

		if (options) {
			setAnimation(animation, chart);
			redraw = pick(redraw, true); // defaults to true

			fireEvent(chart, 'addSeries', { options: options }, function () {
				series = initSeries(options);
				series.isDirty = true;

				chart.isDirtyLegend = true; // the series array is out of sync with the display
				if (redraw) {
					chart.redraw();
				}
			});
		}

		return series;
	}

	/**
	 * Check whether a given point is within the plot area
	 *
	 * @param {Number} x Pixel x relative to the coordinateSystem
	 * @param {Number} y Pixel y relative to the coordinateSystem
	 */
	isInsidePlot = function (x, y) {
		return x >= 0 &&
			x <= plotWidth &&
			y >= 0 &&
			y <= plotHeight;
	};

	/**
	 * Adjust all axes tick amounts
	 */
	function adjustTickAmounts() {
		if (optionsChart.alignTicks !== false) {
			each(axes, function (axis) {
				axis.adjustTickAmount();
			});
		}
		maxTicks = null;
	}

	/**
	 * Redraw legend, axes or series based on updated data
	 *
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	 *    configuration
	 */
	function redraw(animation) {
		var redrawLegend = chart.isDirtyLegend,
			hasStackedSeries,
			isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
			seriesLength = series.length,
			i = seriesLength,
			clipRect = chart.clipRect,
			serie;

		setAnimation(animation, chart);

		// link stacked series
		while (i--) {
			serie = series[i];
			if (serie.isDirty && serie.options.stacking) {
				hasStackedSeries = true;
				break;
			}
		}
		if (hasStackedSeries) { // mark others as dirty
			i = seriesLength;
			while (i--) {
				serie = series[i];
				if (serie.options.stacking) {
					serie.isDirty = true;
				}
			}
		}

		// handle updated data in the series
		each(series, function (serie) {
			if (serie.isDirty) { // prepare the data so axis can read it
				serie.cleanData();
				serie.getSegments();

				if (serie.options.legendType === 'point') {
					redrawLegend = true;
				}
			}
		});

		// handle added or removed series
		if (redrawLegend && legend.renderLegend) { // series or pie points are added or removed
			// draw legend graphics
			legend.renderLegend();

			chart.isDirtyLegend = false;
		}

		if (hasCartesianSeries) {
			if (!isResizing) {

				// reset maxTicks
				maxTicks = null;

				// set axes scales
				each(axes, function (axis) {
					axis.setScale();
				});
			}
			adjustTickAmounts();
			getMargins();

			// redraw axes
			each(axes, function (axis) {
				if (axis.isDirty || isDirtyBox) {
					axis.redraw();
					isDirtyBox = true; // always redraw box to reflect changes in the axis labels
				}
			});


		}

		// the plot areas size has changed
		if (isDirtyBox) {
			drawChartBox();
			placeTrackerGroup();

			// move clip rect
			if (clipRect) {
				stop(clipRect);
				clipRect.animate({ // for chart resize
					width: chart.plotSizeX,
					height: chart.plotSizeY
				});
			}

		}


		// redraw affected series
		each(series, function (serie) {
			if (serie.isDirty && serie.visible &&
					(!serie.isCartesian || serie.xAxis)) { // issue #153
				serie.redraw();
			}
		});


		// hide tooltip and hover states
		if (tracker && tracker.resetTracker) {
			tracker.resetTracker();
		}

		// fire the event
		fireEvent(chart, 'redraw');
	}



	/**
	 * Dim the chart and show a loading text or symbol
	 * @param {String} str An optional text to show in the loading label instead of the default one
	 */
	function showLoading(str) {
		var loadingOptions = options.loading;

		// create the layer at the first call
		if (!loadingDiv) {
			loadingDiv = createElement(DIV, {
				className: 'highcharts-loading'
			}, extend(loadingOptions.style, {
				left: plotLeft + PX,
				top: plotTop + PX,
				width: plotWidth + PX,
				height: plotHeight + PX,
				zIndex: 10,
				display: NONE
			}), container);

			loadingSpan = createElement(
				'span',
				null,
				loadingOptions.labelStyle,
				loadingDiv
			);

		}

		// update text
		loadingSpan.innerHTML = str || options.lang.loading;

		// show it
		if (!loadingShown) {
			css(loadingDiv, { opacity: 0, display: '' });
			animate(loadingDiv, {
				opacity: loadingOptions.style.opacity
			}, {
				duration: loadingOptions.showDuration
			});
			loadingShown = true;
		}
	}
	/**
	 * Hide the loading layer
	 */
	function hideLoading() {
		animate(loadingDiv, {
			opacity: 0
		}, {
			duration: options.loading.hideDuration,
			complete: function () {
				css(loadingDiv, { display: NONE });
			}
		});
		loadingShown = false;
	}

	/**
	 * Get an axis, series or point object by id.
	 * @param id {String} The id as given in the configuration options
	 */
	function get(id) {
		var i,
			j,
			data;

		// search axes
		for (i = 0; i < axes.length; i++) {
			if (axes[i].options.id === id) {
				return axes[i];
			}
		}

		// search series
		for (i = 0; i < series.length; i++) {
			if (series[i].options.id === id) {
				return series[i];
			}
		}

		// search points
		for (i = 0; i < series.length; i++) {
			data = series[i].data;
			for (j = 0; j < data.length; j++) {
				if (data[j].id === id) {
					return data[j];
				}
			}
		}
		return null;
	}

	/**
	 * Create the Axis instances based on the config options
	 */
	function getAxes() {
		var xAxisOptions = options.xAxis || {},
			yAxisOptions = options.yAxis || {},
			axis;

		// make sure the options are arrays and add some members
		xAxisOptions = splat(xAxisOptions);
		each(xAxisOptions, function (axis, i) {
			axis.index = i;
			axis.isX = true;
		});

		yAxisOptions = splat(yAxisOptions);
		each(yAxisOptions, function (axis, i) {
			axis.index = i;
		});

		// concatenate all axis options into one array
		axes = xAxisOptions.concat(yAxisOptions);

		// loop the options and construct axis objects
		chart.xAxis = [];
		chart.yAxis = [];
		axes = map(axes, function (axisOptions) {
			axis = new Axis(axisOptions);
			chart[axis.isXAxis ? 'xAxis' : 'yAxis'].push(axis);

			return axis;
		});

		adjustTickAmounts();
	}


	/**
	 * Get the currently selected points from all series
	 */
	function getSelectedPoints() {
		var points = [];
		each(series, function (serie) {
			points = points.concat(grep(serie.data, function (point) {
				return point.selected;
			}));
		});
		return points;
	}

	/**
	 * Get the currently selected series
	 */
	function getSelectedSeries() {
		return grep(series, function (serie) {
			return serie.selected;
		});
	}

	/**
	 * Zoom out to 1:1
	 */
	zoomOut = function () {
		fireEvent(chart, 'selection', { resetSelection: true }, zoom);
		chart.toolbar.remove('zoom');

	};
	/**
	 * Zoom into a given portion of the chart given by axis coordinates
	 * @param {Object} event
	 */
	zoom = function (event) {
		columnWidths = [];
		// add button to reset selection
		var lang = defaultOptions.lang,
			animate = chart.pointCount < 100;
		chart.toolbar.add('zoom', lang.resetZoom, lang.resetZoomTitle, zoomOut);

		// if zoom is called with no arguments, reset the axes
		if (!event || event.resetSelection) {
			each(axes, function (axis) {
				axis.setExtremes(null, null, false, animate);
			});
		} else { // else, zoom in on all axes
			each(event.xAxis.concat(event.yAxis), function (axisData) {
				var axis = axisData.axis;

				// don't zoom more than maxZoom
				if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
					axis.setExtremes(axisData.min, axisData.max, false, animate);
				}
			});
		}

		// redraw chart
		redraw();
	};

	/**
	 * Show the title and subtitle of the chart
	 *
	 * @param titleOptions {Object} New title options
	 * @param subtitleOptions {Object} New subtitle options
	 *
	 */
	function setTitle(titleOptions, subtitleOptions) {

		chartTitleOptions = merge(options.title, titleOptions);
		chartSubtitleOptions = merge(options.subtitle, subtitleOptions);

		// add title and subtitle
		each([
			['title', titleOptions, chartTitleOptions],
			['subtitle', subtitleOptions, chartSubtitleOptions]
		], function (arr) {
			var name = arr[0],
				title = chart[name],
				titleOptions = arr[1],
				chartTitleOptions = arr[2];

			if (title && titleOptions) {
				title = title.destroy(); // remove old
			}
			if (chartTitleOptions && chartTitleOptions.text && !title) {
				chart[name] = renderer.text(
					chartTitleOptions.text,
					0,
					0,
					chartTitleOptions.useHTML
				)
				.attr({
					align: chartTitleOptions.align,
					'class': 'highcharts-' + name,
					zIndex: 1
				})
				.css(chartTitleOptions.style)
				.add()
				.align(chartTitleOptions, false, spacingBox);
			}
		});

	}

	/**
	 * Get chart width and height according to options and container size
	 */
	function getChartSize() {
//begin updates for sportsnet
		containerWidth = (renderToClone || renderTo).offsetWidth || parseInt($(renderTo).css('width'));
		containerHeight = (renderToClone || renderTo).offsetHeight || parseInt($(renderTo).css('height'));
		// containerWidth = (renderToClone || renderTo).offsetWidth;
		// containerHeight = (renderToClone || renderTo).offsetHeight;
//end updates for sportsnet
		chart.chartWidth = chartWidth = optionsChart.width || containerWidth || 600;
		chart.chartHeight = chartHeight = optionsChart.height ||
			// the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
			(containerHeight > 19 ? containerHeight : 400);
	}


	/**
	 * Get the containing element, determine the size and create the inner container
	 * div to hold the chart
	 */
	function getContainer() {
		renderTo = optionsChart.renderTo;
		containerId = PREFIX + idCounter++;

		if (isString(renderTo)) {
			renderTo = doc.getElementById(renderTo);
		}

		// remove previous chart
		renderTo.innerHTML = '';

		// If the container doesn't have an offsetWidth, it has or is a child of a node
		// that has display:none. We need to temporarily move it out to a visible
		// state to determine the size, else the legend and tooltips won't render
		// properly
//begin updates for sportsnet
		var v2renderWidth = renderTo.offsetWidth || parseInt($(renderTo).css('width'));
		if (!v2renderWidth) {
//		if (!renderTo.offsetWidth) {
//end updates for sportsnet
			renderToClone = renderTo.cloneNode(0);
			css(renderToClone, {
				position: ABSOLUTE,
				top: '-9999px',
				display: ''
			});
			doc.body.appendChild(renderToClone);
		}

		// get the width and height
		getChartSize();

		// create the inner container
		chart.container = container = createElement(DIV, {
				className: 'highcharts-container' +
					(optionsChart.className ? ' ' + optionsChart.className : ''),
				id: containerId
			}, extend({
				position: RELATIVE,
				overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
					// content overflow in IE
				width: chartWidth + PX,
				height: chartHeight + PX,
				textAlign: 'left',
				lineHeight: 'normal'
			}, optionsChart.style),
			renderToClone || renderTo
		);

		chart.renderer = renderer =
			optionsChart.forExport ? // force SVG, used for SVG export
				new SVGRenderer(container, chartWidth, chartHeight, true) :
				new Renderer(container, chartWidth, chartHeight);

		// Issue 110 workaround:
		// In Firefox, if a div is positioned by percentage, its pixel position may land
		// between pixels. The container itself doesn't display this, but an SVG element
		// inside this container will be drawn at subpixel precision. In order to draw
		// sharp lines, this must be compensated for. This doesn't seem to work inside
		// iframes though (like in jsFiddle).
		var subPixelFix, rect;
		if (isFirefox && container.getBoundingClientRect) {
			subPixelFix = function () {
				css(container, { left: 0, top: 0 });
				rect = container.getBoundingClientRect();
				css(container, {
					left: (-(rect.left - pInt(rect.left))) + PX,
					top: (-(rect.top - pInt(rect.top))) + PX
				});
			};

			// run the fix now
			subPixelFix();

			// run it on resize
			addEvent(win, 'resize', subPixelFix);

			// remove it on chart destroy
			addEvent(chart, 'destroy', function () {
				removeEvent(win, 'resize', subPixelFix);
			});
		}
	}

	/**
	 * Calculate margins by rendering axis labels in a preliminary position. Title,
	 * subtitle and legend have already been rendered at this stage, but will be
	 * moved into their final positions
	 */
	getMargins = function () {
		var legendOptions = options.legend,
			legendMargin = pick(legendOptions.margin, 10),
			legendX = legendOptions.x,
			legendY = legendOptions.y,
			align = legendOptions.align,
			verticalAlign = legendOptions.verticalAlign,
			titleOffset;

		resetMargins();

		// adjust for title and subtitle
		if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) {
			titleOffset = mathMax(
				(chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
				(chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
			);
			if (titleOffset) {
				plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
			}
		}
		// adjust for legend
		if (legendOptions.enabled && !legendOptions.floating) {
			if (align === 'right') { // horizontal alignment handled first
				if (!defined(optionsMarginRight)) {
					marginRight = mathMax(
						marginRight,
						legendWidth - legendX + legendMargin + spacingRight
					);
				}
			} else if (align === 'left') {
				if (!defined(optionsMarginLeft)) {
					plotLeft = mathMax(
						plotLeft,
						legendWidth + legendX + legendMargin + spacingLeft
					);
				}

			} else if (verticalAlign === 'top') {
				if (!defined(optionsMarginTop)) {
					plotTop = mathMax(
						plotTop,
						legendHeight + legendY + legendMargin + spacingTop
					);
				}

			} else if (verticalAlign === 'bottom') {
				if (!defined(optionsMarginBottom)) {
					marginBottom = mathMax(
						marginBottom,
						legendHeight - legendY + legendMargin + spacingBottom
					);
				}
			}
		}

		// pre-render axes to get labels offset width
		if (hasCartesianSeries) {
			each(axes, function (axis) {
				axis.getOffset();
			});
		}

		if (!defined(optionsMarginLeft)) {
			plotLeft += axisOffset[3];
		}
		if (!defined(optionsMarginTop)) {
			plotTop += axisOffset[0];
		}
		if (!defined(optionsMarginBottom)) {
			marginBottom += axisOffset[2];
		}
		if (!defined(optionsMarginRight)) {
			marginRight += axisOffset[1];
		}

		setChartSize();

	};

	/**
	 * Add the event handlers necessary for auto resizing
	 *
	 */
	function initReflow() {
		var reflowTimeout;
		function reflow() {
			var width = optionsChart.width || renderTo.offsetWidth,
				height = optionsChart.height || renderTo.offsetHeight;

			if (width && height) { // means container is display:none
				if (width !== containerWidth || height !== containerHeight) {
					clearTimeout(reflowTimeout);
					reflowTimeout = setTimeout(function () {
						resize(width, height, false);
					}, 100);
				}
				containerWidth = width;
				containerHeight = height;
			}
		}
		addEvent(win, 'resize', reflow);
		addEvent(chart, 'destroy', function () {
			removeEvent(win, 'resize', reflow);
		});
	}

	/**
	 * Fires endResize event on chart instance.
	 */
	function fireEndResize() {
		fireEvent(chart, 'endResize', null, function () {
			isResizing -= 1;
		});
	}

	/**
	 * Resize the chart to a given width and height
	 * @param {Number} width
	 * @param {Number} height
	 * @param {Object|Boolean} animation
	 */
	resize = function (width, height, animation) {
		var chartTitle = chart.title,
			chartSubtitle = chart.subtitle;

		isResizing += 1;

		// set the animation for the current process
		setAnimation(animation, chart);

		oldChartHeight = chartHeight;
		oldChartWidth = chartWidth;
		chart.chartWidth = chartWidth = mathRound(width);
		chart.chartHeight = chartHeight = mathRound(height);

		css(container, {
			width: chartWidth + PX,
			height: chartHeight + PX
		});
		renderer.setSize(chartWidth, chartHeight, animation);

		// update axis lengths for more correct tick intervals:
		plotWidth = chartWidth - plotLeft - marginRight;
		plotHeight = chartHeight - plotTop - marginBottom;

		// handle axes
		maxTicks = null;
		each(axes, function (axis) {
			axis.isDirty = true;
			axis.setScale();
		});

		// make sure non-cartesian series are also handled
		each(series, function (serie) {
			serie.isDirty = true;
		});

		chart.isDirtyLegend = true; // force legend redraw
		chart.isDirtyBox = true; // force redraw of plot and chart border

		getMargins();

		// move titles
		if (chartTitle) {
			chartTitle.align(null, null, spacingBox);
		}
		if (chartSubtitle) {
			chartSubtitle.align(null, null, spacingBox);
		}

		redraw(animation);


		oldChartHeight = null;
		fireEvent(chart, 'resize');

		// fire endResize and set isResizing back
		// If animation is disabled, fire without delay
		if (globalAnimation === false) {
			fireEndResize();
		} else { // else set a timeout with the animation duration
			setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
		}
	};

	/**
	 * Set the public chart properties. This is done before and after the pre-render
	 * to determine margin sizes
	 */
	setChartSize = function () {

		chart.plotLeft = plotLeft = mathRound(plotLeft);
		chart.plotTop = plotTop = mathRound(plotTop);
		chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - marginRight);
		chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - marginBottom);

		chart.plotSizeX = inverted ? plotHeight : plotWidth;
		chart.plotSizeY = inverted ? plotWidth : plotHeight;

		spacingBox = {
			x: spacingLeft,
			y: spacingTop,
			width: chartWidth - spacingLeft - spacingRight,
			height: chartHeight - spacingTop - spacingBottom
		};
	};

	/**
	 * Initial margins before auto size margins are applied
	 */
	resetMargins = function () {
		plotTop = pick(optionsMarginTop, spacingTop);
		marginRight = pick(optionsMarginRight, spacingRight);
		marginBottom = pick(optionsMarginBottom, spacingBottom);
		plotLeft = pick(optionsMarginLeft, spacingLeft);
		axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
	};

	/**
	 * Draw the borders and backgrounds for chart and plot area
	 */
	drawChartBox = function () {
		var chartBorderWidth = optionsChart.borderWidth || 0,
			chartBackgroundColor = optionsChart.backgroundColor,
			plotBackgroundColor = optionsChart.plotBackgroundColor,
			plotBackgroundImage = optionsChart.plotBackgroundImage,
			mgn,
			plotSize = {
				x: plotLeft,
				y: plotTop,
				width: plotWidth,
				height: plotHeight
			};

		// Chart area
		mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);

		if (chartBorderWidth || chartBackgroundColor) {
			if (!chartBackground) {
				chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
						optionsChart.borderRadius, chartBorderWidth)
					.attr({
						stroke: optionsChart.borderColor,
						'stroke-width': chartBorderWidth,
						fill: chartBackgroundColor || NONE
					})
					.add()
					.shadow(optionsChart.shadow);
			} else { // resize
				chartBackground.animate(
					chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
				);
			}
		}


		// Plot background
		if (plotBackgroundColor) {
			if (!plotBackground) {
				plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
					.attr({
						fill: plotBackgroundColor
					})
					.add()
					.shadow(optionsChart.plotShadow);
			} else {
				plotBackground.animate(plotSize);
			}
		}
		if (plotBackgroundImage) {
			if (!plotBGImage) {
				plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
					.add();
			} else {
				plotBGImage.animate(plotSize);
			}
		}

		// Plot area border
		if (optionsChart.plotBorderWidth) {
			if (!plotBorder) {
				plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, optionsChart.plotBorderWidth)
					.attr({
						stroke: optionsChart.plotBorderColor,
						'stroke-width': optionsChart.plotBorderWidth,
						zIndex: 4
					})
					.add();
			} else {
				plotBorder.animate(
					plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
				);
			}
		}

		// reset
		chart.isDirtyBox = false;
	};

	/**
	 * Render all graphics for the chart
	 */
	function render() {
		var labels = options.labels,
			credits = options.credits,
			creditsHref;

		// Title
		setTitle();


		// Legend
		legend = chart.legend = new Legend();

		// Get margins by pre-rendering axes
		getMargins();
		each(axes, function (axis) {
			axis.setTickPositions(true); // update to reflect the new margins
		});
		adjustTickAmounts();
		getMargins(); // second pass to check for new labels


		// Draw the borders and backgrounds
		drawChartBox();

		// Axes
		if (hasCartesianSeries) {
			each(axes, function (axis) {
				axis.render();
			});
		}


		// The series
		if (!chart.seriesGroup) {
			chart.seriesGroup = renderer.g('series-group')
				.attr({ zIndex: 3 })
				.add();
		}
		each(series, function (serie) {
			serie.translate();
			serie.setTooltipPoints();
			serie.render();
		});


		// Labels
		if (labels.items) {
			each(labels.items, function () {
				var style = extend(labels.style, this.style),
					x = pInt(style.left) + plotLeft,
					y = pInt(style.top) + plotTop + 12;

				// delete to prevent rewriting in IE
				delete style.left;
				delete style.top;

				renderer.text(
					this.html,
					x,
					y
				)
				.attr({ zIndex: 2 })
				.css(style)
				.add();

			});
		}

		// Toolbar (don't redraw)
		if (!chart.toolbar) {
			chart.toolbar = Toolbar();
		}

		// Credits
		if (credits.enabled && !chart.credits) {
			creditsHref = credits.href;
			chart.credits = renderer.text(
				credits.text,
				0,
				0
			)
			.on('click', function () {
				if (creditsHref) {
					location.href = creditsHref;
				}
			})
			.attr({
				align: credits.position.align,
				zIndex: 8
			})
			.css(credits.style)
			.add()
			.align(credits.position);
		}

		placeTrackerGroup();

		// Set flag
		chart.hasRendered = true;

		// If the chart was rendered outside the top container, put it back in
		if (renderToClone) {
			renderTo.appendChild(container);
			discardElement(renderToClone);
			//updatePosition(container);
		}
	}

	/**
	 * Clean up memory usage
	 */
	function destroy() {
		var i,
			parentNode = container && container.parentNode;

		// If the chart is destroyed already, do nothing.
		// This will happen if if a script invokes chart.destroy and
		// then it will be called again on win.unload
		if (chart === null) {
			return;
		}

		// fire the chart.destoy event
		fireEvent(chart, 'destroy');

		// remove events
		removeEvent(win, 'unload', destroy);
		removeEvent(chart);

		// ==== Destroy collections:
		// Destroy axes
		i = axes.length;
		while (i--) {
			axes[i] = axes[i].destroy();
		}

		// Destroy each series
		i = series.length;
		while (i--) {
			series[i] = series[i].destroy();
		}

		// ==== Destroy chart properties:
		each(['title', 'subtitle', 'seriesGroup', 'clipRect', 'credits', 'tracker'], function (name) {
			var prop = chart[name];

			if (prop) {
				chart[name] = prop.destroy();
			}
		});

		// ==== Destroy local variables:
		each([chartBackground, plotBorder, plotBackground, legend, tooltip, renderer, tracker], function (obj) {
			if (obj && obj.destroy) {
				obj.destroy();
			}
		});
		chartBackground = plotBorder = plotBackground = legend = tooltip = renderer = tracker = null;

		// remove container and all SVG
		if (container) { // can break in IE when destroyed before finished loading
			container.innerHTML = '';
			removeEvent(container);
			if (parentNode) {
				discardElement(container);
			}

			// IE6 leak
			container = null;
		}

		// memory and CPU leak
		clearInterval(tooltipInterval);

		// clean it all up
		for (i in chart) {
			delete chart[i];
		}

		chart = null;
	}
	/**
	 * Prepare for first rendering after all data are loaded
	 */
	function firstRender() {

		// VML namespaces can't be added until after complete. Listening
		// for Perini's doScroll hack is not enough.
		var ONREADYSTATECHANGE = 'onreadystatechange',
			COMPLETE = 'complete';
		// Note: in spite of JSLint's complaints, win == win.top is required
		/*jslint eqeq: true*/
		if (!hasSVG && win == win.top && doc.readyState !== COMPLETE) {
		/*jslint eqeq: false*/
			doc.attachEvent(ONREADYSTATECHANGE, function () {
				doc.detachEvent(ONREADYSTATECHANGE, firstRender);
				if (doc.readyState === COMPLETE) {
					firstRender();
				}
			});
			return;
		}

		// create the container
		getContainer();
		
		columnWidths = [];

		resetMargins();
		setChartSize();

		// Initialize the series
		each(options.series || [], function (serieOptions) {
			initSeries(serieOptions);
		});

		// Set the common inversion and transformation for inverted series after initSeries
		chart.inverted = inverted = pick(inverted, options.chart.inverted);


		getAxes();


		chart.render = render;

		// depends on inverted and on margins being set
		chart.tracker = tracker = new MouseTracker(options.tooltip);

		//globalAnimation = false;
		render();

		fireEvent(chart, 'load');

		//globalAnimation = true;

		// run callbacks
		if (callback) {
			callback.apply(chart, [chart]);
		}
		each(chart.callbacks, function (fn) {
			fn.apply(chart, [chart]);
		});
	}

	// Run chart


	// Destroy the chart and free up memory.
	addEvent(win, 'unload', destroy);

	// Set up auto resize
	if (optionsChart.reflow !== false) {
		addEvent(chart, 'load', initReflow);
	}

	// Chart event handlers
	if (chartEvents) {
		for (eventType in chartEvents) {
			addEvent(chart, eventType, chartEvents[eventType]);
		}
	}


	chart.options = options;
	chart.series = series;





	// Expose methods and variables
	chart.addSeries = addSeries;
	chart.animation = pick(optionsChart.animation, true);
	chart.destroy = destroy;
	chart.get = get;
	chart.getSelectedPoints = getSelectedPoints;
	chart.getSelectedSeries = getSelectedSeries;
	chart.hideLoading = hideLoading;
	chart.isInsidePlot = isInsidePlot;
	chart.redraw = redraw;
	chart.setSize = resize;
	chart.setTitle = setTitle;
	chart.showLoading = showLoading;
	chart.pointCount = 0;
	chart.counters = new ChartCounters();
	/*
	if ($) $(function() {
		$container = $('#container');
		var origChartWidth,
			origChartHeight;
		if ($container) {
			$('<button>+</button>')
				.insertBefore($container)
				.click(function() {
					if (origChartWidth === UNDEFINED) {
						origChartWidth = chartWidth;
						origChartHeight = chartHeight;
					}
					chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
				});
			$('<button>-</button>')
				.insertBefore($container)
				.click(function() {
					if (origChartWidth === UNDEFINED) {
						origChartWidth = chartWidth;
						origChartHeight = chartHeight;
					}
					chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
				});
			$('<button>1:1</button>')
				.insertBefore($container)
				.click(function() {
					if (origChartWidth === UNDEFINED) {
						origChartWidth = chartWidth;
						origChartHeight = chartHeight;
					}
					chart.resize(origChartWidth, origChartHeight);
				});
		}
	})
	*/




	firstRender();


} // end Chart

// Hook for exporting module
Chart.prototype.callbacks = [];
/**
 * The Point object and prototype. Inheritable and used as base for PiePoint
 */
var Point = function () {};
Point.prototype = {

	/**
	 * Initialize the point
	 * @param {Object} series The series object containing this point
	 * @param {Object} options The data in either number, array or object format
	 */
	init: function (series, options) {
		var point = this,
			counters = series.chart.counters,
			defaultColors;
		point.series = series;
		point.applyOptions(options);
		point.pointAttr = {};

		if (series.options.colorByPoint) {
			defaultColors = series.chart.options.colors;
			if (!point.options) {
				point.options = {};
			}
			point.color = point.options.color = point.color || defaultColors[counters.color++];

			// loop back to zero
			counters.wrapColor(defaultColors.length);
		}

		series.chart.pointCount++;
		return point;
	},
	/**
	 * Apply the options containing the x and y data and possible some extra properties.
	 * This is called on point init or from point.update.
	 *
	 * @param {Object} options
	 */
	applyOptions: function (options) {
		var point = this,
			series = point.series;

		point.config = options;

		// onedimensional array input
		if (isNumber(options) || options === null) {
			point.y = options;
		} else if (isObject(options) && !isNumber(options.length)) { // object input
			// copy options directly to point
			extend(point, options);
			point.options = options;
		} else if (isString(options[0])) { // categorized data with name in first position
			point.name = options[0];
			point.y = options[1];
		} else if (isNumber(options[0])) { // two-dimentional array
			point.x = options[0];
			point.y = options[1];
		}

		/*
		 * If no x is set by now, get auto incremented value. All points must have an
		 * x value, however the y value can be null to create a gap in the series
		 */
		if (point.x === UNDEFINED) {
			point.x = series.autoIncrement();
		}

	},

	/**
	 * Destroy a point to clear memory. Its reference still stays in series.data.
	 */
	destroy: function () {
		var point = this,
			series = point.series,
			hoverPoints = series.chart.hoverPoints,
			prop;

		series.chart.pointCount--;

		if (hoverPoints) {
			point.setState();
			erase(hoverPoints, point);
		}
		if (point === series.chart.hoverPoint) {
			point.onMouseOut();
		}


		// remove all events
		removeEvent(point);

		each(['graphic', 'tracker', 'group', 'dataLabel', 'connector', 'shadowGroup'], function (prop) {
			if (point[prop]) {
				point[prop].destroy();
			}
		});

		if (point.legendItem) { // pies have legend items
			point.series.chart.legend.destroyItem(point);
		}

		for (prop in point) {
			point[prop] = null;
		}


	},

	/**
	 * Return the configuration hash needed for the data label and tooltip formatters
	 */
	getLabelConfig: function () {
		var point = this;
		return {
			x: point.category,
			y: point.y,
			series: point.series,
			point: point,
			percentage: point.percentage,
			total: point.total || point.stackTotal
		};
	},

	/**
	 * Toggle the selection status of a point
	 * @param {Boolean} selected Whether to select or unselect the point.
	 * @param {Boolean} accumulate Whether to add to the previous selection. By default,
	 *     this happens if the control key (Cmd on Mac) was pressed during clicking.
	 */
	select: function (selected, accumulate) {
		var point = this,
			series = point.series,
			chart = series.chart;

		selected = pick(selected, !point.selected);

		// fire the event with the defalut handler
		point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
			point.selected = selected;
			point.setState(selected && SELECT_STATE);

			// unselect all other points unless Ctrl or Cmd + click
			if (!accumulate) {
				each(chart.getSelectedPoints(), function (loopPoint) {
					if (loopPoint.selected && loopPoint !== point) {
						loopPoint.selected = false;
						loopPoint.setState(NORMAL_STATE);
						loopPoint.firePointEvent('unselect');
					}
				});
			}
		});
	},

	onMouseOver: function () {
		var point = this,
			chart = point.series.chart,
			tooltip = chart.tooltip,
			hoverPoint = chart.hoverPoint;

		// set normal state to previous series
		if (hoverPoint && hoverPoint !== point) {
			hoverPoint.onMouseOut();
		}

		// trigger the event
		point.firePointEvent('mouseOver');

		// update the tooltip
		if (tooltip && !tooltip.shared) {
			tooltip.refresh(point);
		}

		// hover this
		point.setState(HOVER_STATE);
		chart.hoverPoint = point;
	},

	onMouseOut: function () {
		var point = this;
		point.firePointEvent('mouseOut');

		point.setState();
		point.series.chart.hoverPoint = null;
	},

	/**
	 * Extendable method for formatting each point's tooltip line
	 *
	 * @param {Boolean} useHeader Whether a common header is used for multiple series in the tooltip
	 *
	 * @return {String} A string to be concatenated in to the common tooltip text
	 */
	tooltipFormatter: function (useHeader) {
		var point = this,
			series = point.series;

		return ['<span style="color:' + series.color + '">', (point.name || series.name), '</span>: ',
			(!useHeader ? ('<b>x = ' + (point.name || point.x) + ',</b> ') : ''),
			'<b>', (!useHeader ? 'y = ' : ''), point.y, '</b>'].join('');

	},

	/**
	 * Update the point with new options (typically x/y data) and optionally redraw the series.
	 *
	 * @param {Object} options Point options as defined in the series.data array
	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	 *    configuration
	 *
	 */
	update: function (options, redraw, animation) {
		var point = this,
			series = point.series,
			graphic = point.graphic,
			chart = series.chart;

		redraw = pick(redraw, true);

		// fire the event with a default handler of doing the update
		point.firePointEvent('update', { options: options }, function () {

			point.applyOptions(options);

			// update visuals
			if (isObject(options)) {
				series.getAttribs();
				if (graphic) {
					graphic.attr(point.pointAttr[series.state]);
				}
			}

			// redraw
			series.isDirty = true;
			if (redraw) {
				chart.redraw(animation);
			}
		});
	},

	/**
	 * Remove a point and optionally redraw the series and if necessary the axes
	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	 *    configuration
	 */
	remove: function (redraw, animation) {
		var point = this,
			series = point.series,
			chart = series.chart,
			data = series.data;

		setAnimation(animation, chart);
		redraw = pick(redraw, true);

		// fire the event with a default handler of removing the point
		point.firePointEvent('remove', null, function () {

			erase(data, point);

			point.destroy();


			// redraw
			series.isDirty = true;
			if (redraw) {
				chart.redraw();
			}
		});


	},

	/**
	 * Fire an event on the Point object. Must not be renamed to fireEvent, as this
	 * causes a name clash in MooTools
	 * @param {String} eventType
	 * @param {Object} eventArgs Additional event arguments
	 * @param {Function} defaultFunction Default event handler
	 */
	firePointEvent: function (eventType, eventArgs, defaultFunction) {
		var point = this,
			series = this.series,
			seriesOptions = series.options;

		// load event handlers on demand to save time on mouseover/out
		if (seriesOptions.point.events[eventType] ||
			(point.options && point.options.events && point.options.events[eventType])) {
			this.importEvents();
		}

		// add default handler if in selection mode
		if (eventType === 'click' && seriesOptions.allowPointSelect) {
			defaultFunction = function (event) {
				// Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
				point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
			};
		}

		fireEvent(this, eventType, eventArgs, defaultFunction);
	},
	/**
	 * Import events from the series' and point's options. Only do it on
	 * demand, to save processing time on hovering.
	 */
	importEvents: function () {
		if (!this.hasImportedEvents) {
			var point = this,
				options = merge(point.series.options.point, point.options),
				events = options.events,
				eventType;

			point.events = events;

			for (eventType in events) {
				addEvent(point, eventType, events[eventType]);
			}
			this.hasImportedEvents = true;

		}
	},

	/**
	 * Set the point's state
	 * @param {String} state
	 */
	setState: function (state) {
		var point = this,
			series = point.series,
			stateOptions = series.options.states,
			markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
			normalDisabled = markerOptions && !markerOptions.enabled,
			markerStateOptions = markerOptions && markerOptions.states[state],
			stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
			stateMarkerGraphic = series.stateMarkerGraphic,
			chart = series.chart,
			pointAttr = point.pointAttr;

		state = state || NORMAL_STATE; // empty string

		if (
				// already has this state
				state === point.state ||
				// selected points don't respond to hover
				(point.selected && state !== SELECT_STATE) ||
				// series' state options is disabled
				(stateOptions[state] && stateOptions[state].enabled === false) ||
				// point marker's state options is disabled
				(state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))

			) {
			return;
		}

		// apply hover styles to the existing point
		if (point.graphic) {
			point.graphic.attr(pointAttr[state]);
		} else {
			// if a graphic is not applied to each point in the normal state, create a shared
			// graphic for the hover state
			if (state) {
				if (!stateMarkerGraphic) {
					series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.circle(
						0,
						0,
						pointAttr[state].r
					)
					.attr(pointAttr[state])
					.add(series.group);
				}

				stateMarkerGraphic.translate(
					point.plotX,
					point.plotY
				);
			}

			if (stateMarkerGraphic) {
				stateMarkerGraphic[state ? 'show' : 'hide']();
			}
		}

		point.state = state;
	}
};

/**
 * The base function which all other series types inherit from
 * @param {Object} chart
 * @param {Object} options
 */
var Series = function () {};

Series.prototype = {

	isCartesian: true,
	type: 'line',
	pointClass: Point,
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
		stroke: 'lineColor',
		'stroke-width': 'lineWidth',
		fill: 'fillColor',
		r: 'radius'
	},
	init: function (chart, options) {
		var series = this,
			eventType,
			events,
			//pointEvent,
			index = chart.series.length;

		series.chart = chart;
		options = series.setOptions(options); // merge with plotOptions

		// set some variables
		extend(series, {
			index: index,
			options: options,
			name: options.name || 'Series ' + (index + 1),
			state: NORMAL_STATE,
			pointAttr: {},
			visible: options.visible !== false, // true by default
			selected: options.selected === true // false by default
		});

		// register event listeners
		events = options.events;
		for (eventType in events) {
			addEvent(series, eventType, events[eventType]);
		}
		if (
			(events && events.click) ||
			(options.point && options.point.events && options.point.events.click) ||
			options.allowPointSelect
		) {
			chart.runTrackerClick = true;
		}

		series.getColor();
		series.getSymbol();


		// set the data
		series.setData(options.data, false);

	},


	/**
	 * Return an auto incremented x value based on the pointStart and pointInterval options.
	 * This is only used if an x value is not given for the point that calls autoIncrement.
	 */
	autoIncrement: function () {
		var series = this,
			options = series.options,
			xIncrement = series.xIncrement;

		xIncrement = pick(xIncrement, options.pointStart, 0);

		series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);

		series.xIncrement = xIncrement + series.pointInterval;
		return xIncrement;
	},

	/**
	 * Sort the data and remove duplicates
	 */
	cleanData: function () {
		var series = this,
			chart = series.chart,
			data = series.data,
			closestPoints,
			smallestInterval,
			chartSmallestInterval = chart.smallestInterval,
			interval,
			i;

		// sort the data points
		stableSort(data, function (a, b) {
			return (a.x - b.x);
		});

		// remove points with equal x values
		// record the closest distance for calculation of column widths
		/*for (i = data.length - 1; i >= 0; i--) {
			if (data[i - 1]) {
				if (data[i - 1].x == data[i].x)	{
					data[i - 1].destroy();
					data.splice(i - 1, 1); // remove the duplicate
				}
			}
		}*/

		// connect nulls
		if (series.options.connectNulls) {
			for (i = data.length - 1; i >= 0; i--) {
				if (data[i].y === null && data[i - 1] && data[i + 1]) {
					data.splice(i, 1);
				}
			}
		}

		// find the closes pair of points
		for (i = data.length - 1; i >= 0; i--) {
			if (data[i - 1]) {
				interval = data[i].x - data[i - 1].x;
				if (interval > 0 && (smallestInterval === UNDEFINED || interval < smallestInterval)) {
					smallestInterval = interval;
					closestPoints = i;
				}
			}
		}

		if (chartSmallestInterval === UNDEFINED || smallestInterval < chartSmallestInterval) {
			chart.smallestInterval = smallestInterval;
		}
		series.closestPoints = closestPoints;
	},

	/**
	 * Divide the series data into segments divided by null values. Also sort
	 * the data points and delete duplicate values.
	 */
	getSegments: function () {
		var lastNull = -1,
			segments = [],
			data = this.data;

		// create the segments
		each(data, function (point, i) {
			if (point.y === null) {
				if (i > lastNull + 1) {
					segments.push(data.slice(lastNull + 1, i));
				}
				lastNull = i;
			} else if (i === data.length - 1) { // last value
				segments.push(data.slice(lastNull + 1, i + 1));
			}
		});
		this.segments = segments;


	},
	/**
	 * Set the series options by merging from the options tree
	 * @param {Object} itemOptions
	 */
	setOptions: function (itemOptions) {
		var plotOptions = this.chart.options.plotOptions,
			options = merge(
				plotOptions[this.type],
				plotOptions.series,
				itemOptions
			);

		return options;

	},
	/**
	 * Get the series' color
	 */
	getColor: function () {
		var defaultColors = this.chart.options.colors,
			counters = this.chart.counters;
		this.color = this.options.color || defaultColors[counters.color++] || '#0000ff';
		counters.wrapColor(defaultColors.length);
	},
	/**
	 * Get the series' symbol
	 */
	getSymbol: function () {
		var defaultSymbols = this.chart.options.symbols,
			counters = this.chart.counters;
		this.symbol = this.options.marker.symbol || defaultSymbols[counters.symbol++];
		counters.wrapSymbol(defaultSymbols.length);
	},

	/**
	 * Add a point dynamically after chart load time
	 * @param {Object} options Point options as given in series.data
	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
	 * @param {Boolean} shift If shift is true, a point is shifted off the start
	 *    of the series as one is appended to the end.
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	 *    configuration
	 */
	addPoint: function (options, redraw, shift, animation) {
		var series = this,
			data = series.data,
			graph = series.graph,
			area = series.area,
			chart = series.chart,
			point = (new series.pointClass()).init(series, options);

		setAnimation(animation, chart);

		if (graph && shift) { // make graph animate sideways
			graph.shift = shift;
		}
		if (area) {
			area.shift = shift;
			area.isArea = true;
		}

		redraw = pick(redraw, true);

		data.push(point);
		if (shift) {
			data[0].remove(false);
		}
		series.getAttribs();


		// redraw
		series.isDirty = true;
		if (redraw) {
			chart.redraw();
		}
	},

	/**
	 * Replace the series data with a new set of data
	 * @param {Object} data
	 * @param {Object} redraw
	 */
	setData: function (data, redraw) {
		var series = this,
			oldData = series.data,
			initialColor = series.initialColor,
			chart = series.chart,
			i = (oldData && oldData.length) || 0;

		series.xIncrement = null; // reset for new data
		if (defined(initialColor)) { // reset colors for pie
			chart.counters.color = initialColor;
		}

		data = map(splat(data || []), function (pointOptions) {
			return (new series.pointClass()).init(series, pointOptions);
		});

		// destroy old points
		while (i--) {
			oldData[i].destroy();
		}

		// set the data
		series.data = data;

		series.cleanData();
		series.getSegments();


		// cache attributes for shapes
		series.getAttribs();

		// redraw
		series.isDirty = true;
		chart.isDirtyBox = true;
		if (pick(redraw, true)) {
			chart.redraw(false);
		}
	},

	/**
	 * Remove a series and optionally redraw the chart
	 *
	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
	 *    configuration
	 */

	remove: function (redraw, animation) {
		var series = this,
			chart = series.chart;
		redraw = pick(redraw, true);

		if (!series.isRemoving) {  /* prevent triggering native event in jQuery
				(calling the remove function from the remove event) */
			series.isRemoving = true;

			// fire the event with a default handler of removing the point
			fireEvent(series, 'remove', null, function () {


				// destroy elements
				series.destroy();


				// redraw
				chart.isDirtyLegend = chart.isDirtyBox = true;
				if (redraw) {
					chart.redraw(animation);
				}
			});

		}
		series.isRemoving = false;
	},

	/**
	 * Translate data points from raw data values to chart specific positioning data
	 * needed later in drawPoints, drawGraph and drawTracker.
	 */
	translate: function () {
		var series = this,
			chart = series.chart,
			stacking = series.options.stacking,
			categories = series.xAxis.categories,
			yAxis = series.yAxis,
			data = series.data,
			i = data.length;

		// do the translation
		while (i--) {
			var point = data[i],
				xValue = point.x,
				yValue = point.y,
				yBottom = point.low,
				stack = yAxis.stacks[(yValue < 0 ? '-' : '') + series.stackKey],
				pointStack,
				pointStackTotal;
			point.plotX = series.xAxis.translate(xValue);

			// calculate the bottom y value for stacked series
			if (stacking && series.visible && stack && stack[xValue]) {
				pointStack = stack[xValue];
				pointStackTotal = pointStack.total;
				pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
				yValue = yBottom + yValue;

				if (stacking === 'percent') {
					yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
					yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
				}

				point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
				point.stackTotal = pointStackTotal;
			}

			if (defined(yBottom)) {
				point.yBottom = yAxis.translate(yBottom, 0, 1, 0, 1);
			}

			// set the y value
			if (yValue !== null) {
				point.plotY = yAxis.translate(yValue, 0, 1, 0, 1);
			}

			// set client related positions for mouse tracking
			point.clientX = chart.inverted ?
				chart.plotHeight - point.plotX :
				point.plotX; // for mouse tracking

			// some API data
			point.category = categories && categories[point.x] !== UNDEFINED ?
				categories[point.x] : point.x;

		}
	},
	/**
	 * Memoize tooltip texts and positions
	 */
	setTooltipPoints: function (renew) {
		var series = this,
			chart = series.chart,
			inverted = chart.inverted,
			data = [],
			plotSize = mathRound((inverted ? chart.plotTop : chart.plotLeft) + chart.plotSizeX),
			low,
			high,
			tooltipPoints = []; // a lookup array for each pixel in the x dimension

		// renew
		if (renew) {
			series.tooltipPoints = null;
		}

		// concat segments to overcome null values
		each(series.segments, function (segment) {
			data = data.concat(segment);
		});

		// loop the concatenated data and apply each point to all the closest
		// pixel positions
		if (series.xAxis && series.xAxis.reversed) {
			data = data.reverse();//reverseArray(data);
		}

		each(data, function (point, i) {

			low = data[i - 1] ? data[i - 1]._high + 1 : 0;
			high = point._high = data[i + 1] ?
				(mathFloor((point.plotX + (data[i + 1] ? data[i + 1].plotX : plotSize)) / 2)) :
				plotSize;

			while (low <= high) {
				tooltipPoints[inverted ? plotSize - low++ : low++] = point;
			}
		});
		series.tooltipPoints = tooltipPoints;
	},




	/**
	 * Series mouse over handler
	 */
	onMouseOver: function () {
		var series = this,
			chart = series.chart,
			hoverSeries = chart.hoverSeries;

		if (!hasTouch && chart.mouseIsDown) {
			return;
		}

		// set normal state to previous series
		if (hoverSeries && hoverSeries !== series) {
			hoverSeries.onMouseOut();
		}

		// trigger the event, but to save processing time,
		// only if defined
		if (series.options.events.mouseOver) {
			fireEvent(series, 'mouseOver');
		}


		// bring to front
		// Todo: optimize. This is one of two operations slowing down the tooltip in Firefox.
		// Can the tracking be done otherwise?
		if (series.tracker) {
			series.tracker.toFront();
		}

		// hover this
		series.setState(HOVER_STATE);
		chart.hoverSeries = series;
	},

	/**
	 * Series mouse out handler
	 */
	onMouseOut: function () {
		// trigger the event only if listeners exist
		var series = this,
			options = series.options,
			chart = series.chart,
			tooltip = chart.tooltip,
			hoverPoint = chart.hoverPoint;

		// trigger mouse out on the point, which must be in this series
		if (hoverPoint) {
			hoverPoint.onMouseOut();
		}

		// fire the mouse out event
		if (series && options.events.mouseOut) {
			fireEvent(series, 'mouseOut');
		}


		// hide the tooltip
		if (tooltip && !options.stickyTracking) {
			tooltip.hide();
		}

		// set normal state
		series.setState();
		chart.hoverSeries = null;
	},

	/**
	 * Animate in the series
	 */
	animate: function (init) {
		var series = this,
			chart = series.chart,
			clipRect = series.clipRect,
			animation = series.options.animation;

		if (animation && !isObject(animation)) {
			animation = {};
		}

		if (init) { // initialize the animation
			if (!clipRect.isAnimating) { // apply it only for one of the series
				clipRect.attr('width', 0);
				clipRect.isAnimating = true;
			}

		} else { // run the animation
			clipRect.animate({
				width: chart.plotSizeX
			}, animation);

			// delete this function to allow it only once
			this.animate = null;
		}
	},


	/**
	 * Draw the markers
	 */
	drawPoints: function () {
		var series = this,
			pointAttr,
			data = series.data,
			chart = series.chart,
			plotX,
			plotY,
			i,
			point,
			radius,
			graphic;

		if (series.options.marker.enabled) {
			i = data.length;
			while (i--) {
				point = data[i];
				plotX = point.plotX;
				plotY = point.plotY;
				graphic = point.graphic;

				// only draw the point if y is defined
				if (plotY !== UNDEFINED && !isNaN(plotY)) {

					/* && removed this code because points stayed after zoom
						point.plotX >= 0 && point.plotX <= chart.plotSizeX &&
						point.plotY >= 0 && point.plotY <= chart.plotSizeY*/

					// shortcuts
					pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
					radius = pointAttr.r;

					if (graphic) { // update
						graphic.animate({
							x: plotX,
							y: plotY,
							r: radius
						});
					} else {
						point.graphic = chart.renderer.symbol(
							pick(point.marker && point.marker.symbol, series.symbol),
							plotX,
							plotY,
							radius
						)
						.attr(pointAttr)
						.add(series.group);
					}
				}
			}
		}

	},

	/**
	 * Convert state properties from API naming conventions to SVG attributes
	 *
	 * @param {Object} options API options object
	 * @param {Object} base1 SVG attribute object to inherit from
	 * @param {Object} base2 Second level SVG attribute object to inherit from
	 */
	convertAttribs: function (options, base1, base2, base3) {
		var conversion = this.pointAttrToOptions,
			attr,
			option,
			obj = {};

		options = options || {};
		base1 = base1 || {};
		base2 = base2 || {};
		base3 = base3 || {};

		for (attr in conversion) {
			option = conversion[attr];
			obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
		}
		return obj;
	},

	/**
	 * Get the state attributes. Each series type has its own set of attributes
	 * that are allowed to change on a point's state change. Series wide attributes are stored for
	 * all series, and additionally point specific attributes are stored for all
	 * points with individual marker options. If such options are not defined for the point,
	 * a reference to the series wide attributes is stored in point.pointAttr.
	 */
	getAttribs: function () {
		var series = this,
			normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
			stateOptions = normalOptions.states,
			stateOptionsHover = stateOptions[HOVER_STATE],
			pointStateOptionsHover,
			seriesColor = series.color,
			normalDefaults = {
				stroke: seriesColor,
				fill: seriesColor
			},
			data = series.data,
			i,
			point,
			seriesPointAttr = [],
			pointAttr,
			pointAttrToOptions = series.pointAttrToOptions,
			hasPointSpecificOptions,
			key;

		// series type specific modifications
		if (series.options.marker) { // line, spline, area, areaspline, scatter

			// if no hover radius is given, default to normal radius + 2
			stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
			stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;

		} else { // column, bar, pie

			// if no hover color is given, brighten the normal color
			stateOptionsHover.color = stateOptionsHover.color ||
				Color(stateOptionsHover.color || seriesColor)
					.brighten(stateOptionsHover.brightness).get();
		}

		// general point attributes for the series normal state
		seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);

		// HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
		each([HOVER_STATE, SELECT_STATE], function (state) {
			seriesPointAttr[state] =
					series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
		});

		// set it
		series.pointAttr = seriesPointAttr;


		// Generate the point-specific attribute collections if specific point
		// options are given. If not, create a referance to the series wide point
		// attributes
		i = data.length;
		while (i--) {
			point = data[i];
			normalOptions = (point.options && point.options.marker) || point.options;
			if (normalOptions && normalOptions.enabled === false) {
				normalOptions.radius = 0;
			}
			hasPointSpecificOptions = false;

			// check if the point has specific visual options
			if (point.options) {
				for (key in pointAttrToOptions) {
					if (defined(normalOptions[pointAttrToOptions[key]])) {
						hasPointSpecificOptions = true;
					}
				}
			}



			// a specific marker config object is defined for the individual point:
			// create it's own attribute collection
			if (hasPointSpecificOptions) {

				pointAttr = [];
				stateOptions = normalOptions.states || {}; // reassign for individual point
				pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};

				// if no hover color is given, brighten the normal color
				if (!series.options.marker) { // column, bar, point
					pointStateOptionsHover.color =
						Color(pointStateOptionsHover.color || point.options.color)
							.brighten(pointStateOptionsHover.brightness ||
								stateOptionsHover.brightness).get();

				}

				// normal point state inherits series wide normal state
				pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]);

				// inherit from point normal and series hover
				pointAttr[HOVER_STATE] = series.convertAttribs(
					stateOptions[HOVER_STATE],
					seriesPointAttr[HOVER_STATE],
					pointAttr[NORMAL_STATE]
				);
				// inherit from point normal and series hover
				pointAttr[SELECT_STATE] = series.convertAttribs(
					stateOptions[SELECT_STATE],
					seriesPointAttr[SELECT_STATE],
					pointAttr[NORMAL_STATE]
				);



			// no marker config object is created: copy a reference to the series-wide
			// attribute collection
			} else {
				pointAttr = seriesPointAttr;
			}

			point.pointAttr = pointAttr;

		}

	},


	/**
	 * Clear DOM objects and free up memory
	 */
	destroy: function () {
		var series = this,
			chart = series.chart,
			seriesClipRect = series.clipRect,
			//chartSeries = series.chart.series,
			issue134 = /\/5[0-9\.]+ (Safari|Mobile)\//.test(userAgent), // todo: update when Safari bug is fixed
			destroy,
			prop;

		// add event hook
		fireEvent(series, 'destroy');

		// remove all events
		removeEvent(series);

		// remove legend items
		if (series.legendItem) {
			series.chart.legend.destroyItem(series);
		}

		// destroy all points with their elements
		each(series.data, function (point) {
			point.destroy();
		});

		// If this series clipRect is not the global one (which is removed on chart.destroy) we
		// destroy it here.
		if (seriesClipRect && seriesClipRect !== chart.clipRect) {
			series.clipRect = seriesClipRect.destroy();
		}

		// destroy all SVGElements associated to the series
		each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker'], function (prop) {
			if (series[prop]) {

				// issue 134 workaround
				destroy = issue134 && prop === 'group' ?
					'hide' :
					'destroy';

				series[prop][destroy]();
			}
		});

		// remove from hoverSeries
		if (chart.hoverSeries === series) {
			chart.hoverSeries = null;
		}
		erase(chart.series, series);

		// clear all members
		for (prop in series) {
			delete series[prop];
		}
	},

	/**
	 * Draw the data labels
	 */
	drawDataLabels: function () {
		if (this.options.dataLabels.enabled) {
			var series = this,
				x,
				y,
				data = series.data,
				seriesOptions = series.options,
				options = seriesOptions.dataLabels,
				str,
				dataLabelsGroup = series.dataLabelsGroup,
				chart = series.chart,
				renderer = chart.renderer,
				inverted = chart.inverted,
				seriesType = series.type,
				color,
				stacking = seriesOptions.stacking,
				isBarLike = seriesType === 'column' || seriesType === 'bar',
				vAlignIsNull = options.verticalAlign === null,
				yIsNull = options.y === null;

			if (isBarLike) {
				if (stacking) {
					// In stacked series the default label placement is inside the bars
					if (vAlignIsNull) {
						options = merge(options, {verticalAlign: 'middle'});
					}

					// If no y delta is specified, try to create a good default
					if (yIsNull) {
						options = merge(options, {y: {top: 14, middle: 4, bottom: -6}[options.verticalAlign]});
					}
				} else {
					// In non stacked series the default label placement is on top of the bars
					if (vAlignIsNull) {
						options = merge(options, {verticalAlign: 'top'});
					}
				}
			}

			// create a separate group for the data labels to avoid rotation
			if (!dataLabelsGroup) {
				dataLabelsGroup = series.dataLabelsGroup =
					renderer.g('data-labels')
						.attr({
							visibility: series.visible ? VISIBLE : HIDDEN,
							zIndex: 6
						})
						.translate(chart.plotLeft, chart.plotTop)
						.add();
			} else {
				dataLabelsGroup.translate(chart.plotLeft, chart.plotTop);
			}

			// determine the color
			color = options.color;
			if (color === 'auto') { // 1.0 backwards compatibility
				color = null;
			}
			options.style.color = pick(color, series.color, 'black');

			// make the labels for each point
			each(data, function (point) {
				var barX = point.barX,
					plotX = (barX && barX + point.barW / 2) || point.plotX || -999,
					plotY = pick(point.plotY, -999),
					dataLabel = point.dataLabel,
					align = options.align,
					individualYDelta = yIsNull ? (point.y >= 0 ? -6 : 12) : options.y;

				// get the string
				str = options.formatter.call(point.getLabelConfig());
				x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
				y = (inverted ? chart.plotHeight - plotX : plotY) + individualYDelta;

				// in columns, align the string to the column
				if (seriesType === 'column') {
					x += { left: -1, right: 1 }[align] * point.barW / 2 || 0;
				}

				if (inverted && point.y < 0) {
					align = 'right';
					x -= 10;
				}

				// update existing label
				if (dataLabel) {
					// vertically centered
					if (inverted && !options.y) {
						y = y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2;
					}
					dataLabel
						.attr({
							text: str
						}).animate({
							x: x,
							y: y
						});
				// create new label
				} else if (defined(str)) {
					dataLabel = point.dataLabel = renderer.text(
						str,
						x,
						y
					)
					.attr({
						align: align,
						rotation: options.rotation,
						zIndex: 1
					})
					.css(options.style)
					.add(dataLabelsGroup);
					// vertically centered
					if (inverted && !options.y) {
						dataLabel.attr({
							y: y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2
						});
					}
				}


				/*if (series.isCartesian) {
					dataLabel[chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
				}*/

				if (isBarLike && seriesOptions.stacking && dataLabel) {
					var barY = point.barY,
						barW = point.barW,
						barH = point.barH;

					dataLabel.align(options, null,
						{
							x: inverted ? chart.plotWidth - barY - barH : barX,
							y: inverted ? chart.plotHeight - barX - barW : barY,
							width: inverted ? barH : barW,
							height: inverted ? barW : barH
						});
				}
			});
		}
	},

	/**
	 * Draw the actual graph
	 */
	drawGraph: function () {
		var series = this,
			options = series.options,
			chart = series.chart,
			graph = series.graph,
			graphPath = [],
			fillColor,
			area = series.area,
			group = series.group,
			color = options.lineColor || series.color,
			lineWidth = options.lineWidth,
			dashStyle =  options.dashStyle,
			segmentPath,
			renderer = chart.renderer,
			translatedThreshold = series.yAxis.getThreshold(options.threshold || 0),
			useArea = /^area/.test(series.type),
			singlePoints = [], // used in drawTracker
			areaPath = [],
			attribs;


		// divide into segments and build graph and area paths
		each(series.segments, function (segment) {
			segmentPath = [];

			// build the segment line
			each(segment, function (point, i) {

				if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
					segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));

				} else {

					// moveTo or lineTo
					segmentPath.push(i ? L : M);

					// step line?
					if (i && options.step) {
						var lastPoint = segment[i - 1];
						segmentPath.push(
							point.plotX,
							lastPoint.plotY
						);
					}

					// normal line to next point
					segmentPath.push(
						point.plotX,
						point.plotY
					);
				}
			});

			// add the segment to the graph, or a single point for tracking
			if (segment.length > 1) {
				graphPath = graphPath.concat(segmentPath);
			} else {
				singlePoints.push(segment[0]);
			}

			// build the area
			if (useArea) {
				var areaSegmentPath = [],
					i,
					segLength = segmentPath.length;
				for (i = 0; i < segLength; i++) {
					areaSegmentPath.push(segmentPath[i]);
				}
				if (segLength === 3) { // for animation from 1 to two points
					areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
				}
				if (options.stacking && series.type !== 'areaspline') {
					// follow stack back. Todo: implement areaspline
					for (i = segment.length - 1; i >= 0; i--) {
						areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
					}

				} else { // follow zero line back
					areaSegmentPath.push(
						L,
						segment[segment.length - 1].plotX,
						translatedThreshold,
						L,
						segment[0].plotX,
						translatedThreshold
					);
				}
				areaPath = areaPath.concat(areaSegmentPath);
			}
		});

		// used in drawTracker:
		series.graphPath = graphPath;
		series.singlePoints = singlePoints;

		// draw the area if area series or areaspline
		if (useArea) {
			fillColor = pick(
				options.fillColor,
				Color(series.color).setOpacity(options.fillOpacity || 0.75).get()
			);
			if (area) {
				area.animate({ d: areaPath });

			} else {
				// draw the area
				series.area = series.chart.renderer.path(areaPath)
					.attr({
						fill: fillColor
					}).add(group);
			}
		}

		// draw the graph
		if (graph) {
			stop(graph); // cancel running animations, #459
			graph.animate({ d: graphPath });

		} else {
			if (lineWidth) {
				attribs = {
					'stroke': color,
					'stroke-width': lineWidth
				};
				if (dashStyle) {
					attribs.dashstyle = dashStyle;
				}

				series.graph = renderer.path(graphPath)
					.attr(attribs).add(group).shadow(options.shadow);
			}
		}
	},


	/**
	 * Render the graph and markers
	 */
	render: function () {
		var series = this,
			chart = series.chart,
			group,
			setInvert,
			options = series.options,
			animation = options.animation,
			doAnimation = animation && series.animate,
			duration = doAnimation ? (animation && animation.duration) || 500 : 0,
			clipRect = series.clipRect,
			renderer = chart.renderer;


		// Add plot area clipping rectangle. If this is before chart.hasRendered,
		// create one shared clipRect.
		if (!clipRect) {
			clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ?
				chart.clipRect :
				renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY);
			if (!chart.clipRect) {
				chart.clipRect = clipRect;
			}
		}


		// the group
		if (!series.group) {
			group = series.group = renderer.g('series');

			if (chart.inverted) {
				setInvert = function () {
					group.attr({
						width: chart.plotWidth,
						height: chart.plotHeight
					}).invert();
				};

				setInvert(); // do it now
				addEvent(chart, 'resize', setInvert); // do it on resize
				addEvent(series, 'destroy', function () {
					removeEvent(chart, 'resize', setInvert);
				});
			}
			group.clip(series.clipRect)
				.attr({
					visibility: series.visible ? VISIBLE : HIDDEN,
					zIndex: options.zIndex
				})
				.translate(chart.plotLeft, chart.plotTop)
				.add(chart.seriesGroup);
		}

		series.drawDataLabels();

		// initiate the animation
		if (doAnimation) {
			series.animate(true);
		}

		// cache attributes for shapes
		//series.getAttribs();

		// draw the graph if any
		if (series.drawGraph) {
			series.drawGraph();
		}

		// draw the points
		series.drawPoints();

		// draw the mouse tracking area
		if (series.options.enableMouseTracking !== false) {
			series.drawTracker();
		}

		// run the animation
		if (doAnimation) {
			series.animate();
		}

		// finish the individual clipRect
		setTimeout(function () {
			clipRect.isAnimating = false;
			group = series.group; // can be destroyed during the timeout
			if (group && clipRect !== chart.clipRect && clipRect.renderer) {
				group.clip((series.clipRect = chart.clipRect));
				clipRect.destroy();
			}
		}, duration);


		series.isDirty = false; // means data is in accordance with what you see

	},

	/**
	 * Redraw the series after an update in the axes.
	 */
	redraw: function () {
		var series = this,
			chart = series.chart,
			group = series.group;

		/*if (clipRect) {
			stop(clipRect);
			clipRect.animate({ // for chart resize
				width: chart.plotSizeX,
				height: chart.plotSizeY
			});
		}*/

		// reposition on resize
		if (group) {
			if (chart.inverted) {
				group.attr({
					width: chart.plotWidth,
					height: chart.plotHeight
				});
			}

			group.animate({
				translateX: chart.plotLeft,
				translateY: chart.plotTop
			});
		}

		series.translate();
		series.setTooltipPoints(true);
		series.render();
	},

	/**
	 * Set the state of the graph
	 */
	setState: function (state) {
		var series = this,
			options = series.options,
			graph = series.graph,
			stateOptions = options.states,
			lineWidth = options.lineWidth;

		state = state || NORMAL_STATE;

		if (series.state !== state) {
			series.state = state;

			if (stateOptions[state] && stateOptions[state].enabled === false) {
				return;
			}

			if (state) {
				lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
			}

			if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
				graph.attr({ // use attr because animate will cause any other animation on the graph to stop
					'stroke-width': lineWidth
				}, state ? 0 : 500);
			}
		}
	},

	/**
	 * Set the visibility of the graph
	 *
	 * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
	 *        the visibility is toggled.
	 */
	setVisible: function (vis, redraw) {
		var series = this,
			chart = series.chart,
			legendItem = series.legendItem,
			seriesGroup = series.group,
			seriesTracker = series.tracker,
			dataLabelsGroup = series.dataLabelsGroup,
			showOrHide,
			i,
			data = series.data,
			point,
			ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
			oldVisibility = series.visible;

		// if called without an argument, toggle visibility
		series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
		showOrHide = vis ? 'show' : 'hide';

		// show or hide series
		if (seriesGroup) { // pies don't have one
			seriesGroup[showOrHide]();
		}

		// show or hide trackers
		if (seriesTracker) {
			seriesTracker[showOrHide]();
		} else {
			i = data.length;
			while (i--) {
				point = data[i];
				if (point.tracker) {
					point.tracker[showOrHide]();
				}
			}
		}


		if (dataLabelsGroup) {
			dataLabelsGroup[showOrHide]();
		}

		if (legendItem) {
			chart.legend.colorizeItem(series, vis);
		}


		// rescale or adapt to resized chart
		series.isDirty = true;
		// in a stack, all other series are affected
		if (series.options.stacking) {
			each(chart.series, function (otherSeries) {
				if (otherSeries.options.stacking && otherSeries.visible) {
					otherSeries.isDirty = true;
				}
			});
		}

		if (ignoreHiddenSeries) {
			chart.isDirtyBox = true;
		}
		if (redraw !== false) {
			chart.redraw();
		}

		fireEvent(series, showOrHide);
	},

	/**
	 * Show the graph
	 */
	show: function () {
		this.setVisible(true);
	},

	/**
	 * Hide the graph
	 */
	hide: function () {
		this.setVisible(false);
	},


	/**
	 * Set the selected state of the graph
	 *
	 * @param selected {Boolean} True to select the series, false to unselect. If
	 *        UNDEFINED, the selection state is toggled.
	 */
	select: function (selected) {
		var series = this;
		// if called without an argument, toggle
		series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;

		if (series.checkbox) {
			series.checkbox.checked = selected;
		}

		fireEvent(series, selected ? 'select' : 'unselect');
	},


	/**
	 * Draw the tracker object that sits above all data labels and markers to
	 * track mouse events on the graph or points. For the line type charts
	 * the tracker uses the same graphPath, but with a greater stroke width
	 * for better control.
	 */
	drawTracker: function () {
		var series = this,
			options = series.options,
			trackerPath = [].concat(series.graphPath),
			trackerPathLength = trackerPath.length,
			chart = series.chart,
			snap = chart.options.tooltip.snap,
			tracker = series.tracker,
			cursor = options.cursor,
			css = cursor && { cursor: cursor },
			singlePoints = series.singlePoints,
			singlePoint,
			i;

		// Extend end points. A better way would be to use round linecaps,
		// but those are not clickable in VML.
		if (trackerPathLength) {
			i = trackerPathLength + 1;
			while (i--) {
				if (trackerPath[i] === M) { // extend left side
					trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
				}
				if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
					trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
				}
			}
		}

		// handle single points
		for (i = 0; i < singlePoints.length; i++) {
			singlePoint = singlePoints[i];
			trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
				L, singlePoint.plotX + snap, singlePoint.plotY);
		}

		// draw the tracker
		if (tracker) {
			tracker.attr({ d: trackerPath });

		} else { // create
			series.tracker = chart.renderer.path(trackerPath)
				.attr({
					isTracker: true,
					stroke: TRACKER_FILL,
					fill: NONE,
					'stroke-width' : options.lineWidth + 2 * snap,
					visibility: series.visible ? VISIBLE : HIDDEN,
					zIndex: options.zIndex || 1
				})
				.on(hasTouch ? 'touchstart' : 'mouseover', function () {
					if (chart.hoverSeries !== series) {
						series.onMouseOver();
					}
				})
				.on('mouseout', function () {
					if (!options.stickyTracking) {
						series.onMouseOut();
					}
				})
				.css(css)
				.add(chart.trackerGroup);
		}

	}

}; // end Series prototype


/**
 * LineSeries object
 */
var LineSeries = extendClass(Series);
seriesTypes.line = LineSeries;

/**
 * AreaSeries object
 */
var AreaSeries = extendClass(Series, {
	type: 'area'
});
seriesTypes.area = AreaSeries;




/**
 * SplineSeries object
 */
var SplineSeries = extendClass(Series, {
	type: 'spline',

	/**
	 * Draw the actual graph
	 */
	getPointSpline: function (segment, point, i) {
		var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
			denom = smoothing + 1,
			plotX = point.plotX,
			plotY = point.plotY,
			lastPoint = segment[i - 1],
			nextPoint = segment[i + 1],
			leftContX,
			leftContY,
			rightContX,
			rightContY,
			ret;

		// find control points
		if (i && i < segment.length - 1) {
			var lastX = lastPoint.plotX,
				lastY = lastPoint.plotY,
				nextX = nextPoint.plotX,
				nextY = nextPoint.plotY,
				correction;

			leftContX = (smoothing * plotX + lastX) / denom;
			leftContY = (smoothing * plotY + lastY) / denom;
			rightContX = (smoothing * plotX + nextX) / denom;
			rightContY = (smoothing * plotY + nextY) / denom;

			// have the two control points make a straight line through main point
			correction = ((rightContY - leftContY) * (rightContX - plotX)) /
				(rightContX - leftContX) + plotY - rightContY;

			leftContY += correction;
			rightContY += correction;

			// to prevent false extremes, check that control points are between
			// neighbouring points' y values
			if (leftContY > lastY && leftContY > plotY) {
				leftContY = mathMax(lastY, plotY);
				rightContY = 2 * plotY - leftContY; // mirror of left control point
			} else if (leftContY < lastY && leftContY < plotY) {
				leftContY = mathMin(lastY, plotY);
				rightContY = 2 * plotY - leftContY;
			}
			if (rightContY > nextY && rightContY > plotY) {
				rightContY = mathMax(nextY, plotY);
				leftContY = 2 * plotY - rightContY;
			} else if (rightContY < nextY && rightContY < plotY) {
				rightContY = mathMin(nextY, plotY);
				leftContY = 2 * plotY - rightContY;
			}

			// record for drawing in next point
			point.rightContX = rightContX;
			point.rightContY = rightContY;

		}

		// moveTo or lineTo
		if (!i) {
			ret = [M, plotX, plotY];
		} else { // curve from last point to this
			ret = [
				'C',
				lastPoint.rightContX || lastPoint.plotX,
				lastPoint.rightContY || lastPoint.plotY,
				leftContX || plotX,
				leftContY || plotY,
				plotX,
				plotY
			];
			lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
		}
		return ret;
	}
});
seriesTypes.spline = SplineSeries;



/**
 * AreaSplineSeries object
 */
var AreaSplineSeries = extendClass(SplineSeries, {
	type: 'areaspline'
});
seriesTypes.areaspline = AreaSplineSeries;

/**
 * ColumnSeries object
 */
var ColumnSeries = extendClass(Series, {
	type: 'column',
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
		stroke: 'borderColor',
		'stroke-width': 'borderWidth',
		fill: 'color',
		r: 'borderRadius'
	},
	init: function () {
		Series.prototype.init.apply(this, arguments);

		var series = this,
			chart = series.chart;

		// flag the chart in order to pad the x axis
		chart.hasColumn = true;

		// if the series is added dynamically, force redraw of other
		// series affected by a new column
		if (chart.hasRendered) {
			each(chart.series, function (otherSeries) {
				if (otherSeries.type === series.type) {
					otherSeries.isDirty = true;
				}
			});
		}
	},

	/**
	 * Translate each point to the plot area coordinate system and find shape positions
	 */
	translate: function () {
		var series = this,
			chart = series.chart,
			options = series.options,
			stacking = options.stacking,
			borderWidth = options.borderWidth,
			columnCount = 0,
			reversedXAxis = series.xAxis.reversed,
			categories = series.xAxis.categories,
			stackGroups = {},
			stackKey,
			columnIndex;

		Series.prototype.translate.apply(series);

		// Get the total number of column type series.
		// This is called on every series. Consider moving this logic to a
		// chart.orderStacks() function and call it on init, addSeries and removeSeries
		each(chart.series, function (otherSeries) {
			if (otherSeries.type === series.type && otherSeries.visible) {
				if (otherSeries.options.stacking) {
					stackKey = otherSeries.stackKey;
					if (stackGroups[stackKey] === UNDEFINED) {
						stackGroups[stackKey] = columnCount++;
					}
					columnIndex = stackGroups[stackKey];
				} else {
					columnIndex = columnCount++;
				}
				otherSeries.columnIndex = columnIndex;
			}
		});

		// calculate the width and position of each column based on
		// the number of column series in the plot, the groupPadding
		// and the pointPadding options
		var data = series.data,
			closestPoints = series.closestPoints,
			categoryWidth = mathAbs(
				data[1] ? data[closestPoints].plotX - data[closestPoints - 1].plotX :
				chart.plotSizeX / ((categories && categories.length) || 1)
			),
			groupPadding = categoryWidth * options.groupPadding,
			groupWidth = categoryWidth - 2 * groupPadding,
			pointOffsetWidth = groupWidth / columnCount,
			optionPointWidth = options.pointWidth,
			pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
				pointOffsetWidth * options.pointPadding,
			pointWidth = mathMax(pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), 1),
			colIndex = (reversedXAxis ? columnCount -
				series.columnIndex : series.columnIndex) || 0,
			pointXOffset = pointPadding + (groupPadding + colIndex *
				pointOffsetWidth - (categoryWidth / 2)) *
				(reversedXAxis ? -1 : 1),
			threshold = options.threshold || 0,
			translatedThreshold = series.yAxis.getThreshold(threshold),
			minPointLength = pick(options.minPointLength, 5);
			
			if(options.equalWidths == true){
				columnWidths.push(pointWidth);
			}

		// record the new values
		each(data, function (point) {
			var plotY = point.plotY,
				yBottom = point.yBottom || translatedThreshold,
				barX = point.plotX + pointXOffset,
				barY = mathCeil(mathMin(plotY, yBottom)),
				barH = mathCeil(mathMax(plotY, yBottom) - barY),
				stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
				trackerY,
				shapeArgs;

			// Record the offset'ed position and width of the bar to be able to align the stacking total correctly
			if (stacking && series.visible && stack && stack[point.x]) {
				stack[point.x].setOffset(pointXOffset, pointWidth);
			}

			// handle options.minPointLength and tracker for small points
			if (mathAbs(barH) < minPointLength) {
				if (minPointLength) {
					barH = minPointLength;
					barY =
						mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
							yBottom - minPointLength : // keep position
							translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
				}
				trackerY = barY - 3;
			}

			extend(point, {
				barX: barX,
				barY: barY,
				barW: pointWidth,
				barH: barH
			});

			// create shape type and shape args that are reused in drawPoints and drawTracker
			point.shapeType = 'rect';
			shapeArgs = extend(chart.renderer.Element.prototype.crisp.apply({}, [
				borderWidth,
				barX,
				barY,
				pointWidth,
				barH
			]), {
				r: options.borderRadius
			});
			if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
				shapeArgs.y -= 1;
				shapeArgs.height += 1;
			}
			point.shapeArgs = shapeArgs;

			// make small columns responsive to mouse
			point.trackerArgs = defined(trackerY) && merge(point.shapeArgs, {
				height: mathMax(6, barH + 3),
				y: trackerY
			});
		});

	},

	getSymbol: function () {
	},

	/**
	 * Columns have no graph
	 */
	drawGraph: function () {},

	/**
	 * Draw the columns. For bars, the series.group is rotated, so the same coordinates
	 * apply for columns and bars. This method is inherited by scatter series.
	 *
	 */
	drawPoints: function () {
		var series = this,
			options = series.options,
			renderer = series.chart.renderer,
			graphic,
			shapeArgs;


		// draw the columns
		each(series.data, function (point) {
			var plotY = point.plotY;
			if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
				graphic = point.graphic;
				shapeArgs = point.shapeArgs;
				if (graphic) { // update
					stop(graphic);
					graphic.animate(shapeArgs);

				} else {
					point.graphic = renderer[point.shapeType](shapeArgs)
						.attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
						.add(series.group)
						.shadow(options.shadow);
				}

			}
		});
	},
	/**
	 * Draw the individual tracker elements.
	 * This method is inherited by scatter and pie charts too.
	 */
	drawTracker: function () {
		var series = this,
			chart = series.chart,
			renderer = chart.renderer,
			shapeArgs,
			tracker,
			trackerLabel = +new Date(),
			options = series.options,
			cursor = options.cursor,
			css = cursor && { cursor: cursor },
			rel;

		each(series.data, function (point) {
			tracker = point.tracker;
			shapeArgs = point.trackerArgs || point.shapeArgs;
			delete shapeArgs.strokeWidth;
			if (point.y !== null) {
				if (tracker) {// update
					tracker.attr(shapeArgs);

				} else {
					point.tracker =
						renderer[point.shapeType](shapeArgs)
						.attr({
							isTracker: trackerLabel,
							fill: TRACKER_FILL,
							visibility: series.visible ? VISIBLE : HIDDEN,
							zIndex: options.zIndex || 1
						})
						.on(hasTouch ? 'touchstart' : 'mouseover', function (event) {
							rel = event.relatedTarget || event.fromElement;
							if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
								series.onMouseOver();
							}
							point.onMouseOver();

						})
						.on('mouseout', function (event) {
							if (!options.stickyTracking) {
								rel = event.relatedTarget || event.toElement;
								if (attr(rel, 'isTracker') !== trackerLabel) {
									series.onMouseOut();
								}
							}
						})
						.css(css)
						.add(point.group || chart.trackerGroup); // pies have point group - see issue #118
				}
			}
		});
	},


	/**
	 * Animate the column heights one by one from zero
	 * @param {Boolean} init Whether to initialize the animation or run it
	 */
	animate: function (init) {
		var series = this,
			data = series.data;

		if (!init) { // run the animation
			/*
			 * Note: Ideally the animation should be initialized by calling
			 * series.group.hide(), and then calling series.group.show()
			 * after the animation was started. But this rendered the shadows
			 * invisible in IE8 standards mode. If the columns flicker on large
			 * datasets, this is the cause.
			 */

			each(data, function (point) {
				var graphic = point.graphic,
					shapeArgs = point.shapeArgs;

				if (graphic) {
					// start values
					graphic.attr({
						height: 0,
						y: series.yAxis.translate(0, 0, 1)
					});

					// animate
					graphic.animate({
						height: shapeArgs.height,
						y: shapeArgs.y
					}, series.options.animation);
				}
			});


			// delete this function to allow it only once
			series.animate = null;
		}

	},
	/**
	 * Remove this series from the chart
	 */
	remove: function () {
		var series = this,
			chart = series.chart;

		// column and bar series affects other series of the same type
		// as they are either stacked or grouped
		if (chart.hasRendered) {
			each(chart.series, function (otherSeries) {
				if (otherSeries.type === series.type) {
					otherSeries.isDirty = true;
				}
			});
		}

		Series.prototype.remove.apply(series, arguments);
	}
});
seriesTypes.column = ColumnSeries;

var BarSeries = extendClass(ColumnSeries, {
	type: 'bar',
	init: function (chart) {
		chart.inverted = this.inverted = true;
		ColumnSeries.prototype.init.apply(this, arguments);
	}
});
seriesTypes.bar = BarSeries;

/**
 * The scatter series class
 */
var ScatterSeries = extendClass(Series, {
	type: 'scatter',

	/**
	 * Extend the base Series' translate method by adding shape type and
	 * arguments for the point trackers
	 */
	translate: function () {
		var series = this;

		Series.prototype.translate.apply(series);

		each(series.data, function (point) {
			point.shapeType = 'circle';
			point.shapeArgs = {
				x: point.plotX,
				y: point.plotY,
				r: series.chart.options.tooltip.snap
			};
		});
	},


	/**
	 * Create individual tracker elements for each point
	 */
	//drawTracker: ColumnSeries.prototype.drawTracker,
	drawTracker: function () {
		var series = this,
			cursor = series.options.cursor,
			css = cursor && { cursor: cursor },
			graphic;

		each(series.data, function (point) {
			graphic = point.graphic;
			if (graphic) { // doesn't exist for null points
				graphic
					.attr({ isTracker: true })
					.on('mouseover', function () {
						series.onMouseOver();
						point.onMouseOver();
					})
					.on('mouseout', function () {
						if (!series.options.stickyTracking) {
							series.onMouseOut();
						}
					})
					.css(css);
			}
		});

	},

	/**
	 * Cleaning the data is not necessary in a scatter plot
	 */
	cleanData: function () {}
});
seriesTypes.scatter = ScatterSeries;

/**
 * Extended point object for pies
 */
var PiePoint = extendClass(Point, {
	/**
	 * Initiate the pie slice
	 */
	init: function () {

		Point.prototype.init.apply(this, arguments);

		var point = this,
			toggleSlice;

		//visible: options.visible !== false,
		extend(point, {
			visible: point.visible !== false,
			name: pick(point.name, 'Slice')
		});

		// add event listener for select
		toggleSlice = function () {
			point.slice();
		};
		addEvent(point, 'select', toggleSlice);
		addEvent(point, 'unselect', toggleSlice);

		return point;
	},

	/**
	 * Toggle the visibility of the pie slice
	 * @param {Boolean} vis Whether to show the slice or not. If undefined, the
	 *    visibility is toggled
	 */
	setVisible: function (vis) {
		var point = this,
			chart = point.series.chart,
			tracker = point.tracker,
			dataLabel = point.dataLabel,
			connector = point.connector,
			shadowGroup = point.shadowGroup,
			method;

		// if called without an argument, toggle visibility
		point.visible = vis = vis === UNDEFINED ? !point.visible : vis;

		method = vis ? 'show' : 'hide';

		point.group[method]();
		if (tracker) {
			tracker[method]();
		}
		if (dataLabel) {
			dataLabel[method]();
		}
		if (connector) {
			connector[method]();
		}
		if (shadowGroup) {
			shadowGroup[method]();
		}
		if (point.legendItem) {
			chart.legend.colorizeItem(point, vis);
		}
	},

	/**
	 * Set or toggle whether the slice is cut out from the pie
	 * @param {Boolean} sliced When undefined, the slice state is toggled
	 * @param {Boolean} redraw Whether to redraw the chart. True by default.
	 */
	slice: function (sliced, redraw, animation) {
		var point = this,
			series = point.series,
			chart = series.chart,
			slicedTranslation = point.slicedTranslation,
			translation;

		setAnimation(animation, chart);

		// redraw is true by default
		redraw = pick(redraw, true);

		// if called without an argument, toggle
		sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;

		translation = {
			translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
			translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
		};
		point.group.animate(translation);
		if (point.shadowGroup) {
			point.shadowGroup.animate(translation);
		}

	}
});

/**
 * The Pie series class
 */
var PieSeries = extendClass(Series, {
	type: 'pie',
	isCartesian: false,
	pointClass: PiePoint,
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
		stroke: 'borderColor',
		'stroke-width': 'borderWidth',
		fill: 'color'
	},

	/**
	 * Pies have one color each point
	 */
	getColor: function () {
		// record first color for use in setData
		this.initialColor = this.chart.counters.color;
	},

	/**
	 * Animate the column heights one by one from zero
	 */
	animate: function () {
		var series = this,
			data = series.data;

		each(data, function (point) {
			var graphic = point.graphic,
				args = point.shapeArgs,
				up = -mathPI / 2;

			if (graphic) {
				// start values
				graphic.attr({
					r: 0,
					start: up,
					end: up
				});

				// animate
				graphic.animate({
					r: args.r,
					start: args.start,
					end: args.end
				}, series.options.animation);
			}
		});

		// delete this function to allow it only once
		series.animate = null;

	},
	/**
	 * Do translation for pie slices
	 */
	translate: function () {
		var total = 0,
			series = this,
			cumulative = -0.25, // start at top
			precision = 1000, // issue #172
			options = series.options,
			slicedOffset = options.slicedOffset,
			connectorOffset = slicedOffset + options.borderWidth,
			positions = options.center.concat([options.size, options.innerSize || 0]),
			chart = series.chart,
			plotWidth = chart.plotWidth,
			plotHeight = chart.plotHeight,
			start,
			end,
			angle,
			data = series.data,
			circ = 2 * mathPI,
			fraction,
			smallestSize = mathMin(plotWidth, plotHeight),
			isPercent,
			radiusX, // the x component of the radius vector for a given point
			radiusY,
			labelDistance = options.dataLabels.distance;

		// get positions - either an integer or a percentage string must be given
		positions = map(positions, function (length, i) {

			isPercent = /%$/.test(length);
			return isPercent ?
				// i == 0: centerX, relative to width
				// i == 1: centerY, relative to height
				// i == 2: size, relative to smallestSize
				// i == 4: innerSize, relative to smallestSize
				[plotWidth, plotHeight, smallestSize, smallestSize][i] *
					pInt(length) / 100 :
				length;
		});

		// utility for getting the x value from a given y, used for anticollision logic in data labels
		series.getX = function (y, left) {

			angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));

			return positions[0] +
				(left ? -1 : 1) *
				(mathCos(angle) * (positions[2] / 2 + labelDistance));
		};

		// set center for later use
		series.center = positions;

		// get the total sum
		each(data, function (point) {
			total += point.y;
		});

		each(data, function (point) {
			// set start and end angle
			fraction = total ? point.y / total : 0;
			start = mathRound(cumulative * circ * precision) / precision;
			cumulative += fraction;
			end = mathRound(cumulative * circ * precision) / precision;

			// set the shape
			point.shapeType = 'arc';
			point.shapeArgs = {
				x: positions[0],
				y: positions[1],
				r: positions[2] / 2,
				innerR: positions[3] / 2,
				start: start,
				end: end
			};

			// center for the sliced out slice
			angle = (end + start) / 2;
			point.slicedTranslation = map([
				mathCos(angle) * slicedOffset + chart.plotLeft,
				mathSin(angle) * slicedOffset + chart.plotTop
			], mathRound);

			// set the anchor point for tooltips
			radiusX = mathCos(angle) * positions[2] / 2;
			radiusY = mathSin(angle) * positions[2] / 2;
			point.tooltipPos = [
				positions[0] + radiusX * 0.7,
				positions[1] + radiusY * 0.7
			];

			// set the anchor point for data labels
			point.labelPos = [
				positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
				positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
				positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
				positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
				positions[0] + radiusX, // landing point for connector
				positions[1] + radiusY, // a/a
				labelDistance < 0 ? // alignment
					'center' :
					angle < circ / 4 ? 'left' : 'right', // alignment
				angle // center angle
			];

			// API properties
			point.percentage = fraction * 100;
			point.total = total;

		});


		this.setTooltipPoints();
	},

	/**
	 * Render the slices
	 */
	render: function () {
		var series = this;

		// cache attributes for shapes
		//series.getAttribs();

		this.drawPoints();

		// draw the mouse tracking area
		if (series.options.enableMouseTracking !== false) {
			series.drawTracker();
		}

		this.drawDataLabels();

		if (series.options.animation && series.animate) {
			series.animate();
		}

		series.isDirty = false; // means data is in accordance with what you see
	},

	/**
	 * Draw the data points
	 */
	drawPoints: function () {
		var series = this,
			chart = series.chart,
			renderer = chart.renderer,
			groupTranslation,
			//center,
			graphic,
			group,
			shadow = series.options.shadow,
			shadowGroup,
			shapeArgs;


		// draw the slices
		each(series.data, function (point) {
			graphic = point.graphic;
			shapeArgs = point.shapeArgs;
			group = point.group;
			shadowGroup = point.shadowGroup;

			// put the shadow behind all points
			if (shadow && !shadowGroup) {
				shadowGroup = point.shadowGroup = renderer.g('shadow')
					.attr({ zIndex: 4 })
					.add();
			}

			// create the group the first time
			if (!group) {
				group = point.group = renderer.g('point')
					.attr({ zIndex: 5 })
					.add();
			}

			// if the point is sliced, use special translation, else use plot area traslation
			groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
			group.translate(groupTranslation[0], groupTranslation[1]);
			if (shadowGroup) {
				shadowGroup.translate(groupTranslation[0], groupTranslation[1]);
			}


			// draw the slice
			if (graphic) {
				graphic.animate(shapeArgs);
			} else {
				point.graphic =
					renderer.arc(shapeArgs)
					.attr(extend(
						point.pointAttr[NORMAL_STATE],
						{ 'stroke-linejoin': 'round' }
					))
					.add(point.group)
					.shadow(shadow, shadowGroup);
			}

			// detect point specific visibility
			if (point.visible === false) {
				point.setVisible(false);
			}

		});

	},

	/**
	 * Override the base drawDataLabels method by pie specific functionality
	 */
	drawDataLabels: function () {
		var series = this,
			data = series.data,
			point,
			chart = series.chart,
			options = series.options.dataLabels,
			connectorPadding = pick(options.connectorPadding, 10),
			connectorWidth = pick(options.connectorWidth, 1),
			connector,
			connectorPath,
			softConnector = pick(options.softConnector, true),
			distanceOption = options.distance,
			seriesCenter = series.center,
			radius = seriesCenter[2] / 2,
			centerY = seriesCenter[1],
			outside = distanceOption > 0,
			dataLabel,
			labelPos,
			labelHeight,
			halves = [// divide the points into right and left halves for anti collision
				[], // right
				[]  // left
			],
			x,
			y,
			visibility,
			rankArr,
			sort,
			i = 2,
			j;

		// get out if not enabled
		if (!options.enabled) {
			return;
		}

		// run parent method
		Series.prototype.drawDataLabels.apply(series);

		// arrange points for detection collision
		each(data, function (point) {
			if (point.dataLabel) { // it may have been cancelled in the base method (#407)
				halves[
					point.labelPos[7] < mathPI / 2 ? 0 : 1
				].push(point);
			}
		});
		halves[1].reverse();

		// define the sorting algorithm
		sort = function (a, b) {
			return b.y - a.y;
		};

		// assume equal label heights
		labelHeight = halves[0][0] && halves[0][0].dataLabel && pInt(halves[0][0].dataLabel.styles.lineHeight);

		/* Loop over the points in each quartile, starting from the top and bottom
		 * of the pie to detect overlapping labels.
		 */
		while (i--) {

			var slots = [],
				slotsLength,
				usedSlots = [],
				points = halves[i],
				pos,
				length = points.length,
				slotIndex;


			// build the slots
			for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
				slots.push(pos);
				// visualize the slot
				/*
				var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
					slotY = pos + chart.plotTop;
				if (!isNaN(slotX)) {
					chart.renderer.rect(slotX, slotY - 7, 100, labelHeight)
						.attr({
							'stroke-width': 1,
							stroke: 'silver'
						})
						.add();
					chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
						.attr({
							fill: 'silver'
						}).add();
				}
				// */
			}
			slotsLength = slots.length;

			// if there are more values than available slots, remove lowest values
			if (length > slotsLength) {
				// create an array for sorting and ranking the points within each quarter
				rankArr = [].concat(points);
				rankArr.sort(sort);
				j = length;
				while (j--) {
					rankArr[j].rank = j;
				}
				j = length;
				while (j--) {
					if (points[j].rank >= slotsLength) {
						points.splice(j, 1);
					}
				}
				length = points.length;
			}

			// The label goes to the nearest open slot, but not closer to the edge than
			// the label's index.
			for (j = 0; j < length; j++) {

				point = points[j];
				labelPos = point.labelPos;

				var closest = 9999,
					distance,
					slotI;

				// find the closest slot index
				for (slotI = 0; slotI < slotsLength; slotI++) {
					distance = mathAbs(slots[slotI] - labelPos[1]);
					if (distance < closest) {
						closest = distance;
						slotIndex = slotI;
					}
				}

				// if that slot index is closer to the edges of the slots, move it
				// to the closest appropriate slot
				if (slotIndex < j && slots[j] !== null) { // cluster at the top
					slotIndex = j;
				} else if (slotsLength  < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom
					slotIndex = slotsLength - length + j;
					while (slots[slotIndex] === null) { // make sure it is not taken
						slotIndex++;
					}
				} else {
					// Slot is taken, find next free slot below. In the next run, the next slice will find the
					// slot above these, because it is the closest one
					while (slots[slotIndex] === null) { // make sure it is not taken
						slotIndex++;
					}
				}

				usedSlots.push({ i: slotIndex, y: slots[slotIndex] });
				slots[slotIndex] = null; // mark as taken
			}
			// sort them in order to fill in from the top
			usedSlots.sort(sort);


			// now the used slots are sorted, fill them up sequentially
			for (j = 0; j < length; j++) {

				point = points[j];
				labelPos = point.labelPos;
				dataLabel = point.dataLabel;
				var slot = usedSlots.pop(),
					naturalY = labelPos[1];

				visibility = point.visible === false ? HIDDEN : VISIBLE;
				slotIndex = slot.i;

				// if the slot next to currrent slot is free, the y value is allowed
				// to fall back to the natural position
				y = slot.y;
				if ((naturalY > y && slots[slotIndex + 1] !== null) ||
						(naturalY < y &&  slots[slotIndex - 1] !== null)) {
					y = naturalY;
				}

				// get the x - use the natural x position for first and last slot, to prevent the top
				// and botton slice connectors from touching each other on either side
				x = series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);

				// move or place the data label
				dataLabel
					.attr({
						visibility: visibility,
						align: labelPos[6]
					})[dataLabel.moved ? 'animate' : 'attr']({
						x: x + options.x +
							({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
						y: y + options.y
					});
				dataLabel.moved = true;

				// draw the connector
				if (outside && connectorWidth) {
					connector = point.connector;

					connectorPath = softConnector ? [
						M,
						x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
						'C',
						x, y, // first break, next to the label
						2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
						labelPos[2], labelPos[3], // second break
						L,
						labelPos[4], labelPos[5] // base
					] : [
						M,
						x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
						L,
						labelPos[2], labelPos[3], // second break
						L,
						labelPos[4], labelPos[5] // base
					];

					if (connector) {
						connector.animate({ d: connectorPath });
						connector.attr('visibility', visibility);

					} else {
						point.connector = connector = series.chart.renderer.path(connectorPath).attr({
							'stroke-width': connectorWidth,
							stroke: options.connectorColor || point.color || '#606060',
							visibility: visibility,
							zIndex: 3
						})
						.translate(chart.plotLeft, chart.plotTop)
						.add();
					}
				}
			}
		}
	},

	/**
	 * Draw point specific tracker objects. Inherit directly from column series.
	 */
	drawTracker: ColumnSeries.prototype.drawTracker,

	/**
	 * Pies don't have point marker symbols
	 */
	getSymbol: function () {}

});
seriesTypes.pie = PieSeries;


// global variables
win.Highcharts = {
	Chart: Chart,
	dateFormat: dateFormat,
	pathAnim: pathAnim,
	getOptions: getOptions,
	hasRtlBug: hasRtlBug,
	numberFormat: numberFormat,
	Point: Point,
	Color: Color,
	Renderer: Renderer,
	seriesTypes: seriesTypes,
	setOptions: setOptions,
	Series: Series,

	// Expose utility funcitons for modules
	addEvent: addEvent,
	removeEvent: removeEvent,
	createElement: createElement,
	discardElement: discardElement,
	css: css,
	each: each,
	extend: extend,
	map: map,
	merge: merge,
	pick: pick,
	extendClass: extendClass,
	product: 'Highcharts',
	version: '2.1.9'
};
}());
/******************************************************************************
Name:    Highslide JS
Version: 4.1.8 (October 27 2009)
Config:  default +events +unobtrusive +imagemap +slideshow +positioning +transitions +viewport +thumbstrip +inline +ajax +iframe +flash
Author:  Torstein Hønsi
Support: http://highslide.com/support

Licence:
Highslide JS is licensed under a Creative Commons Attribution-NonCommercial 2.5
License (http://creativecommons.org/licenses/by-nc/2.5/).

You are free:
	* to copy, distribute, display, and perform the work
	* to make derivative works

Under the following conditions:
	* Attribution. You must attribute the work in the manner  specified by  the
	  author or licensor.
	* Noncommercial. You may not use this work for commercial purposes.

* For  any  reuse  or  distribution, you  must make clear to others the license
  terms of this work.
* Any  of  these  conditions  can  be  waived  if  you  get permission from the 
  copyright holder.

Your fair use and other rights are in no way affected by the above.
******************************************************************************/
if (!hs) { var hs = {
// Language strings
lang : {
	cssDirection: 'ltr',
	loadingText : 'Loading...',
	loadingTitle : 'Click to cancel',
	focusTitle : 'Click to bring to front',
	fullExpandTitle : 'Expand to actual size (f)',
	creditsText : 'Powered by <i>Highslide JS</i>',
	creditsTitle : 'Go to the Highslide JS homepage',
	previousText : 'Previous',
	nextText : 'Next', 
	moveText : 'Move',
	closeText : 'Close', 
	closeTitle : 'Close (esc)', 
	resizeTitle : 'Resize',
	playText : 'Play',
	playTitle : 'Play slideshow (spacebar)',
	pauseText : 'Pause',
	pauseTitle : 'Pause slideshow (spacebar)',
	previousTitle : 'Previous (arrow left)',
	nextTitle : 'Next (arrow right)',
	moveTitle : 'Move',
	fullExpandText : '1:1',
	number: 'Image %1 of %2',
	restoreTitle : 'Click to close image, click and drag to move. Use arrow keys for next and previous.'
},
// See http://highslide.com/ref for examples of settings  
graphicsDir : 'highslide/graphics/',
expandCursor : 'zoomin.cur', // null disables
restoreCursor : 'zoomout.cur', // null disables
expandDuration : 250, // milliseconds
restoreDuration : 250,
marginLeft : 15,
marginRight : 15,
marginTop : 15,
marginBottom : 15,
zIndexCounter : 1001, // adjust to other absolutely positioned elements
loadingOpacity : 0.75,
allowMultipleInstances: true,
numberOfImagesToPreload : 5,
outlineWhileAnimating : 2, // 0 = never, 1 = always, 2 = HTML only 
outlineStartOffset : 3, // ends at 10
padToMinWidth : false, // pad the popup width to make room for wide caption
fullExpandPosition : 'bottom right',
fullExpandOpacity : 1,
showCredits : true, // you can set this to false if you want
creditsHref : 'http://highslide.com/',
creditsTarget : '_self',
enableKeyListener : true,
openerTagNames : ['a', 'area'], // Add more to allow slideshow indexing
transitions : [],
transitionDuration: 250,
dimmingOpacity: 0, // Lightbox style dimming background
dimmingDuration: 50, // 0 for instant dimming

allowWidthReduction : false,
allowHeightReduction : true,
preserveContent : true, // Preserve changes made to the content and position of HTML popups.
objectLoadTime : 'before', // Load iframes 'before' or 'after' expansion.
cacheAjax : true, // Cache ajax popups for instant display. Can be overridden for each popup.
anchor : 'auto', // where the image expands from
align : 'auto', // position in the client (overrides anchor)
targetX: null, // the id of a target element
targetY: null,
dragByHeading: true,
minWidth: 200,
minHeight: 200,
allowSizeReduction: true, // allow the image to reduce to fit client size. If false, this overrides minWidth and minHeight
outlineType : 'drop-shadow', // set null to disable outlines
skin : {
	controls:
		'<div class="highslide-controls"><ul>'+
			'<li class="highslide-previous">'+
				'<a href="#" title="{hs.lang.previousTitle}">'+
				'<span>{hs.lang.previousText}</span></a>'+
			'</li>'+
			'<li class="highslide-play">'+
				'<a href="#" title="{hs.lang.playTitle}">'+
				'<span>{hs.lang.playText}</span></a>'+
			'</li>'+
			'<li class="highslide-pause">'+
				'<a href="#" title="{hs.lang.pauseTitle}">'+
				'<span>{hs.lang.pauseText}</span></a>'+
			'</li>'+
			'<li class="highslide-next">'+
				'<a href="#" title="{hs.lang.nextTitle}">'+
				'<span>{hs.lang.nextText}</span></a>'+
			'</li>'+
			'<li class="highslide-move">'+
				'<a href="#" title="{hs.lang.moveTitle}">'+
				'<span>{hs.lang.moveText}</span></a>'+
			'</li>'+
			'<li class="highslide-full-expand">'+
				'<a href="#" title="{hs.lang.fullExpandTitle}">'+
				'<span>{hs.lang.fullExpandText}</span></a>'+
			'</li>'+
			'<li class="highslide-close">'+
				'<a href="#" title="{hs.lang.closeTitle}" >'+
				'<span>{hs.lang.closeText}</span></a>'+
			'</li>'+
		'</ul></div>'
	,
	contentWrapper:
		'<div class="highslide-header"><ul>'+
			'<li class="highslide-previous">'+
				'<a href="#" title="{hs.lang.previousTitle}" onclick="return hs.previous(this)">'+
				'<span>{hs.lang.previousText}</span></a>'+
			'</li>'+
			'<li class="highslide-next">'+
				'<a href="#" title="{hs.lang.nextTitle}" onclick="return hs.next(this)">'+
				'<span>{hs.lang.nextText}</span></a>'+
			'</li>'+
			'<li class="highslide-move">'+
				'<a href="#" title="{hs.lang.moveTitle}" onclick="return false">'+
				'<span>{hs.lang.moveText}</span></a>'+
			'</li>'+
			'<li class="highslide-close">'+
				'<a href="#" title="{hs.lang.closeTitle}" onclick="return hs.close(this)">'+
				'<span>{hs.lang.closeText}</span></a>'+
			'</li>'+
		'</ul></div>'+
		'<div class="highslide-body"></div>'+
		'<div class="highslide-footer"><div>'+
			'<span class="highslide-resize" title="{hs.lang.resizeTitle}"><span></span></span>'+
		'</div></div>'
},
// END OF YOUR SETTINGS


// declare internal properties
preloadTheseImages : [],
continuePreloading: true,
expanders : [],
overrides : [
	'allowSizeReduction',
	'useBox',
	'anchor',
	'align',
	'targetX',
	'targetY',
	'outlineType',
	'outlineWhileAnimating',
	'captionId',
	'captionText',
	'captionEval',
	'captionOverlay',
	'headingId',
	'headingText',
	'headingEval',
	'headingOverlay',
	'creditsPosition',
	'dragByHeading',
	'autoplay',
	'numberPosition',
	'transitions',
	'dimmingOpacity',
	
	'width',
	'height',
	
	'contentId',
	'allowWidthReduction',
	'allowHeightReduction',
	'preserveContent',
	'maincontentId',
	'maincontentText',
	'maincontentEval',
	'objectType',	
	'cacheAjax',	
	'objectWidth',
	'objectHeight',
	'objectLoadTime',	
	'swfOptions',
	'wrapperClassName',
	'minWidth',
	'minHeight',
	'maxWidth',
	'maxHeight',
	'pageOrigin',
	'slideshowGroup',
	'easing',
	'easingClose',
	'fadeInOut',
	'src'
],
overlays : [],
idCounter : 0,
oPos : {
	x: ['leftpanel', 'left', 'center', 'right', 'rightpanel'],
	y: ['above', 'top', 'middle', 'bottom', 'below']
},
mouse: {},
headingOverlay: {},
captionOverlay: {},
swfOptions: { flashvars: {}, params: {}, attributes: {} },
timers : [],

slideshows : [],

pendingOutlines : {},
sleeping : [],
preloadTheseAjax : [],
cacheBindings : [],
cachedGets : {},
clones : {},
onReady: [],
uaVersion: /Trident\/4\.0/.test(navigator.userAgent) ? 8 :
	parseFloat((navigator.userAgent.toLowerCase().match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1]),
ie : (document.all && !window.opera),
safari : /Safari/.test(navigator.userAgent),
geckoMac : /Macintosh.+rv:1\.[0-8].+Gecko/.test(navigator.userAgent),

$ : function (id) {
	if (id) return document.getElementById(id);
},

push : function (arr, val) {
	arr[arr.length] = val;
},

createElement : function (tag, attribs, styles, parent, nopad) {
	var el = document.createElement(tag);
	if (attribs) hs.extend(el, attribs);
	if (nopad) hs.setStyles(el, {padding: 0, border: 'none', margin: 0});
	if (styles) hs.setStyles(el, styles);
	if (parent) parent.appendChild(el);	
	return el;
},

extend : function (el, attribs) {
	for (var x in attribs) el[x] = attribs[x];
	return el;
},

setStyles : function (el, styles) {
	for (var x in styles) {
		if (hs.ie && x == 'opacity') {
			if (styles[x] > 0.99) el.style.removeAttribute('filter');
			else el.style.filter = 'alpha(opacity='+ (styles[x] * 100) +')';
		}
		else el.style[x] = styles[x];
	}
},
animate: function(el, prop, opt) {
	var start,
		end,
		unit;
	if (typeof opt != 'object' || opt === null) {
		var args = arguments;
		opt = {
			duration: args[2],
			easing: args[3],
			complete: args[4]
		};
	}
	if (typeof opt.duration != 'number') opt.duration = 250;
	opt.easing = Math[opt.easing] || Math.easeInQuad;
	opt.curAnim = hs.extend({}, prop);
	for (var name in prop) {
		var e = new hs.fx(el, opt , name );
		
		start = parseFloat(hs.css(el, name)) || 0;
		end = parseFloat(prop[name]);
		unit = name != 'opacity' ? 'px' : '';
		
		e.custom( start, end, unit );
	}	
},
css: function(el, prop) {
	if (el.style[prop]) {
		return el.style[prop];
	} else if (document.defaultView) {
		return document.defaultView.getComputedStyle(el, null).getPropertyValue(prop);

	} else {
		if (prop == 'opacity') prop = 'filter';
		var val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b){ return b.toUpperCase(); })];
		if (prop == 'filter') 
			val = val.replace(/alpha\(opacity=([0-9]+)\)/, 
				function (a, b) { return b / 100 });
		return val === '' ? 1 : val;
	} 
},

getPageSize : function () {
	var d = document, w = window, iebody = d.compatMode && d.compatMode != 'BackCompat' 
		? d.documentElement : d.body;
	
	var width = hs.ie ? iebody.clientWidth : 
			(d.documentElement.clientWidth || self.innerWidth),
		height = hs.ie ? iebody.clientHeight : self.innerHeight;
	
	hs.page = {
		width: width,
		height: height,		
		scrollLeft: hs.ie ? iebody.scrollLeft : pageXOffset,
		scrollTop: hs.ie ? iebody.scrollTop : pageYOffset
	};
	return hs.page;
},

getPosition : function(el)	{
	if (/area/i.test(el.tagName)) {
		var imgs = document.getElementsByTagName('img');
		for (var i = 0; i < imgs.length; i++) {
			var u = imgs[i].useMap;
			if (u && u.replace(/^.*?#/, '') == el.parentNode.name) {
				el = imgs[i];
				break;
			}
		}
	}
	var p = { x: el.offsetLeft, y: el.offsetTop };
	while (el.offsetParent)	{
		el = el.offsetParent;
		p.x += el.offsetLeft;
		p.y += el.offsetTop;
		if (el != document.body && el != document.documentElement) {
			p.x -= el.scrollLeft;
			p.y -= el.scrollTop;
		}
	}
	return p;
},

expand : function(a, params, custom, type) {
	if (!a) a = hs.createElement('a', null, { display: 'none' }, hs.container);
	if (typeof a.getParams == 'function') return params;
	if (type == 'html') {
		for (var i = 0; i < hs.sleeping.length; i++) {
			if (hs.sleeping[i] && hs.sleeping[i].a == a) {
				hs.sleeping[i].awake();
				hs.sleeping[i] = null;
				return false;
			}
		}
		hs.hasHtmlExpanders = true;
	}	
	try {	
		new hs.Expander(a, params, custom, type);
		return false;
	} catch (e) { return true; }
},

htmlExpand : function(a, params, custom) {
	return hs.expand(a, params, custom, 'html');
},

getSelfRendered : function() {
	return hs.createElement('div', { 
		className: 'highslide-html-content', 
		innerHTML: hs.replaceLang(hs.skin.contentWrapper) 
	});
},
getElementByClass : function (el, tagName, className) {
	var els = el.getElementsByTagName(tagName);
	for (var i = 0; i < els.length; i++) {
    	if ((new RegExp(className)).test(els[i].className)) {
			return els[i];
		}
	}
	return null;
},
replaceLang : function(s) {
	s = s.replace(/\s/g, ' ');
	var re = /{hs\.lang\.([^}]+)\}/g,
		matches = s.match(re),
		lang;
	if (matches) for (var i = 0; i < matches.length; i++) {
		lang = matches[i].replace(re, "$1");
		if (typeof hs.lang[lang] != 'undefined') s = s.replace(matches[i], hs.lang[lang]);
	}
	return s;
},


setClickEvents : function () {
	var els = document.getElementsByTagName('a');
	for (var i = 0; i < els.length; i++) {
		var type = hs.isUnobtrusiveAnchor(els[i]);
		if (type && !els[i].hsHasSetClick) {
			(function(){
				var t = type;
				if (hs.fireEvent(hs, 'onSetClickEvent', { element: els[i], type: t })) {
					els[i].onclick =(type == 'image') ?function() { return hs.expand(this) }:
						function() { return hs.htmlExpand(this, { objectType: t } );};
				}
			})();
			els[i].hsHasSetClick = true;	
		}
	}
	hs.getAnchors();
},
isUnobtrusiveAnchor: function(el) {
	if (el.rel == 'highslide') return 'image';
	else if (el.rel == 'highslide-ajax') return 'ajax';
	else if (el.rel == 'highslide-iframe') return 'iframe';
	else if (el.rel == 'highslide-swf') return 'swf';
},

getCacheBinding : function (a) {
	for (var i = 0; i < hs.cacheBindings.length; i++) {
		if (hs.cacheBindings[i][0] == a) {
			var c = hs.cacheBindings[i][1];
			hs.cacheBindings[i][1] = c.cloneNode(1);
			return c;
		}
	}
	return null;
},

preloadAjax : function (e) {
	var arr = hs.getAnchors();
	for (var i = 0; i < arr.htmls.length; i++) {
		var a = arr.htmls[i];
		if (hs.getParam(a, 'objectType') == 'ajax' && hs.getParam(a, 'cacheAjax'))
			hs.push(hs.preloadTheseAjax, a);
	}
	
	hs.preloadAjaxElement(0);
},

preloadAjaxElement : function (i) {
	if (!hs.preloadTheseAjax[i]) return;
	var a = hs.preloadTheseAjax[i];
	var cache = hs.getNode(hs.getParam(a, 'contentId'));
	if (!cache) cache = hs.getSelfRendered();
	var ajax = new hs.Ajax(a, cache, 1);	
   	ajax.onError = function () { };
   	ajax.onLoad = function () {
   		hs.push(hs.cacheBindings, [a, cache]);
   		hs.preloadAjaxElement(i + 1);
   	};
   	ajax.run();
},

focusTopmost : function() {
	var topZ = 0, 
		topmostKey = -1,
		expanders = hs.expanders,
		exp,
		zIndex;
	for (var i = 0; i < expanders.length; i++) {
		exp = expanders[i];
		if (exp) {
			zIndex = exp.wrapper.style.zIndex;
			if (zIndex && zIndex > topZ) {
				topZ = zIndex;				
				topmostKey = i;
			}
		}
	}
	if (topmostKey == -1) hs.focusKey = -1;
	else expanders[topmostKey].focus();
},

getParam : function (a, param) {
	a.getParams = a.onclick;
	var p = a.getParams ? a.getParams() : null;
	a.getParams = null;
	
	return (p && typeof p[param] != 'undefined') ? p[param] : 
		(typeof hs[param] != 'undefined' ? hs[param] : null);
},

getSrc : function (a) {
	var src = hs.getParam(a, 'src');
	if (src) return src;
	return a.href;
},

getNode : function (id) {
	var node = hs.$(id), clone = hs.clones[id], a = {};
	if (!node && !clone) return null;
	if (!clone) {
		clone = node.cloneNode(true);
		clone.id = '';
		hs.clones[id] = clone;
		return node;
	} else {
		return clone.cloneNode(true);
	}
},

discardElement : function(d) {
	if (d) hs.garbageBin.appendChild(d);
	hs.garbageBin.innerHTML = '';
},
dim : function(exp) {
	if (!hs.dimmer) {
		hs.dimmer = hs.createElement ('div', {
				className: 'highslide-dimming highslide-viewport-size',
				owner: '',
				onclick: function() {
					if (hs.fireEvent(hs, 'onDimmerClick'))
					
						hs.close();
				}
			}, {
                visibility: 'visible',
				opacity: 0
			}, hs.container, true);
	}

	hs.dimmer.style.display = '';

	hs.dimmer.owner += '|'+ exp.key;
	if (hs.geckoMac && hs.dimmingGeckoFix)
		hs.setStyles(hs.dimmer, {
			background: 'url('+ hs.graphicsDir + 'geckodimmer.png)',
			opacity: 1
		});
	else
		hs.animate(hs.dimmer, { opacity: exp.dimmingOpacity }, hs.dimmingDuration);
},
undim : function(key) {
	if (!hs.dimmer) return;
	if (typeof key != 'undefined') hs.dimmer.owner = hs.dimmer.owner.replace('|'+ key, '');

	if (
		(typeof key != 'undefined' && hs.dimmer.owner != '')
		|| (hs.upcoming && hs.getParam(hs.upcoming, 'dimmingOpacity'))
	) return;

	if (hs.geckoMac && hs.dimmingGeckoFix) hs.dimmer.style.display = 'none';
	else hs.animate(hs.dimmer, { opacity: 0 }, hs.dimmingDuration, null, function() {
		hs.dimmer.style.display = 'none';
	});
},
transit : function (adj, exp) {
	var last = exp || hs.getExpander();
	exp = last;
	if (hs.upcoming) return false;
	else hs.last = last;
	hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
	try {
		hs.upcoming = adj;
		adj.onclick(); 		
	} catch (e){
		hs.last = hs.upcoming = null;
	}
	try {
		if (!adj || exp.transitions[1] != 'crossfade')
		exp.close();
	} catch (e) {}
	return false;
},

previousOrNext : function (el, op) {
	var exp = hs.getExpander(el);
	if (exp) return hs.transit(exp.getAdjacentAnchor(op), exp);
	else return false;
},

previous : function (el) {
	return hs.previousOrNext(el, -1);
},

next : function (el) {
	return hs.previousOrNext(el, 1);	
},

keyHandler : function(e) {
	if (!e) e = window.event;
	if (!e.target) e.target = e.srcElement; // ie
	if (typeof e.target.form != 'undefined') return true; // form element has focus
	if (!hs.fireEvent(hs, 'onKeyDown', e)) return true;
	var exp = hs.getExpander();
	
	var op = null;
	switch (e.keyCode) {
		case 70: // f
			if (exp) exp.doFullExpand();
			return true;
		case 32: // Space
			op = 2;
			break;
		case 34: // Page Down
		case 39: // Arrow right
		case 40: // Arrow down
			op = 1;
			break;
		case 8:  // Backspace
		case 33: // Page Up
		case 37: // Arrow left
		case 38: // Arrow up
			op = -1;
			break;
		case 27: // Escape
		case 13: // Enter
			op = 0;
	}
	if (op !== null) {
		hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
		if (!hs.enableKeyListener) return true;
		
		if (e.preventDefault) e.preventDefault();
    	else e.returnValue = false;
    	if (exp) {
			if (op == 0) {
				exp.close();
			} else if (op == 2) {
				if (exp.slideshow) exp.slideshow.hitSpace();
			} else {
				if (exp.slideshow) exp.slideshow.pause();
				hs.previousOrNext(exp.key, op);
			}
			return false;
		}
	}
	return true;
},


registerOverlay : function (overlay) {
	hs.push(hs.overlays, hs.extend(overlay, { hsId: 'hsId'+ hs.idCounter++ } ));
},


addSlideshow : function (options) {
	var sg = options.slideshowGroup;
	if (typeof sg == 'object') {
		for (var i = 0; i < sg.length; i++) {
			var o = {};
			for (var x in options) o[x] = options[x];
			o.slideshowGroup = sg[i];
			hs.push(hs.slideshows, o);
		}
	} else {
		hs.push(hs.slideshows, options);
	}
},

getWrapperKey : function (element, expOnly) {
	var el, re = /^highslide-wrapper-([0-9]+)$/;
	// 1. look in open expanders
	el = element;
	while (el.parentNode)	{
		if (el.hsKey !== undefined) return el.hsKey;
		if (el.id && re.test(el.id)) return el.id.replace(re, "$1");
		el = el.parentNode;
	}
	// 2. look in thumbnail
	if (!expOnly) {
		el = element;
		while (el.parentNode)	{
			if (el.tagName && hs.isHsAnchor(el)) {
				for (var key = 0; key < hs.expanders.length; key++) {
					var exp = hs.expanders[key];
					if (exp && exp.a == el) return key;
				}
			}
			el = el.parentNode;
		}
	}
	return null; 
},

getExpander : function (el, expOnly) {
	if (typeof el == 'undefined') return hs.expanders[hs.focusKey] || null;
	if (typeof el == 'number') return hs.expanders[el] || null;
	if (typeof el == 'string') el = hs.$(el);
	return hs.expanders[hs.getWrapperKey(el, expOnly)] || null;
},

isHsAnchor : function (a) {
	return (a.onclick && a.onclick.toString().replace(/\s/g, ' ').match(/hs.(htmlE|e)xpand/));
},

reOrder : function () {
	for (var i = 0; i < hs.expanders.length; i++)
		if (hs.expanders[i] && hs.expanders[i].isExpanded) hs.focusTopmost();
},
fireEvent : function (obj, evt, args) {
	return obj && obj[evt] ? (obj[evt](obj, args) !== false) : true;
},

mouseClickHandler : function(e) 
{	
	if (!e) e = window.event;
	if (e.button > 1) return true;
	if (!e.target) e.target = e.srcElement;
	
	var el = e.target;
	while (el.parentNode
		&& !(/highslide-(image|move|html|resize)/.test(el.className)))
	{
		el = el.parentNode;
	}
	var exp = hs.getExpander(el);
	if (exp && (exp.isClosing || !exp.isExpanded)) return true;
		
	if (exp && e.type == 'mousedown') {
		if (e.target.form) return true;
		var match = el.className.match(/highslide-(image|move|resize)/);
		if (match) {
			hs.dragArgs = { 
				exp: exp , 
				type: match[1], 
				left: exp.x.pos, 
				width: exp.x.size, 
				top: exp.y.pos, 
				height: exp.y.size, 
				clickX: e.clientX, 
				clickY: e.clientY
			};
			
			
			hs.addEventListener(document, 'mousemove', hs.dragHandler);
			if (e.preventDefault) e.preventDefault(); // FF
			
			if (/highslide-(image|html)-blur/.test(exp.content.className)) {
				exp.focus();
				hs.hasFocused = true;
			}
			return false;
		}
		else if (/highslide-html/.test(el.className) && hs.focusKey != exp.key) {
			exp.focus();
			exp.doShowHide('hidden');
		}
	} else if (e.type == 'mouseup') {
		
		hs.removeEventListener(document, 'mousemove', hs.dragHandler);
		
		if (hs.dragArgs) {
			if (hs.styleRestoreCursor && hs.dragArgs.type == 'image') 
				hs.dragArgs.exp.content.style.cursor = hs.styleRestoreCursor;
			var hasDragged = hs.dragArgs.hasDragged;
			
			if (!hasDragged &&!hs.hasFocused && !/(move|resize)/.test(hs.dragArgs.type)) {
				if (hs.fireEvent(exp, 'onImageClick'))
				exp.close();
			} 
			else if (hasDragged || (!hasDragged && hs.hasHtmlExpanders)) {
				hs.dragArgs.exp.doShowHide('hidden');
			}
			
			if (hs.dragArgs.exp.releaseMask) 
				hs.dragArgs.exp.releaseMask.style.display = 'none';
			
			if (hasDragged) hs.fireEvent(hs.dragArgs.exp, 'onDrop', hs.dragArgs);
			hs.hasFocused = false;
			hs.dragArgs = null;
		
		} else if (/highslide-image-blur/.test(el.className)) {
			el.style.cursor = hs.styleRestoreCursor;		
		}
	}
	return false;
},

dragHandler : function(e)
{
	if (!hs.dragArgs) return true;
	if (!e) e = window.event;
	var a = hs.dragArgs, exp = a.exp;
	if (exp.iframe) {		
		if (!exp.releaseMask) exp.releaseMask = hs.createElement('div', null, 
			{ position: 'absolute', width: exp.x.size+'px', height: exp.y.size+'px', 
				left: exp.x.cb+'px', top: exp.y.cb+'px', zIndex: 4,	background: (hs.ie ? 'white' : 'none'), 
				opacity: 0.01 }, 
			exp.wrapper, true);
		if (exp.releaseMask.style.display == 'none')
			exp.releaseMask.style.display = '';
	}
	
	a.dX = e.clientX - a.clickX;
	a.dY = e.clientY - a.clickY;	
	
	var distance = Math.sqrt(Math.pow(a.dX, 2) + Math.pow(a.dY, 2));
	if (!a.hasDragged) a.hasDragged = (a.type != 'image' && distance > 0)
		|| (distance > (hs.dragSensitivity || 5));
	
	if (a.hasDragged && e.clientX > 5 && e.clientY > 5) {
		if (!hs.fireEvent(exp, 'onDrag', a)) return false;
		
		if (a.type == 'resize') exp.resize(a);
		else {
			exp.moveTo(a.left + a.dX, a.top + a.dY);
			if (a.type == 'image') exp.content.style.cursor = 'move';
		}
	}
	return false;
},

wrapperMouseHandler : function (e) {
	try {
		if (!e) e = window.event;
		var over = /mouseover/i.test(e.type); 
		if (!e.target) e.target = e.srcElement; // ie
		if (hs.ie) e.relatedTarget = 
			over ? e.fromElement : e.toElement; // ie
		var exp = hs.getExpander(e.target);
		if (!exp.isExpanded) return;
		if (!exp || !e.relatedTarget || hs.getExpander(e.relatedTarget, true) == exp 
			|| hs.dragArgs) return;
		hs.fireEvent(exp, over ? 'onMouseOver' : 'onMouseOut', e);
		for (var i = 0; i < exp.overlays.length; i++) (function() {
			var o = hs.$('hsId'+ exp.overlays[i]);
			if (o && o.hideOnMouseOut) {
				if (over) hs.setStyles(o, { visibility: 'visible', display: '' });
				hs.animate(o, { opacity: over ? o.opacity : 0 }, o.dur);
			}
		})();	
	} catch (e) {}
},
addEventListener : function (el, event, func) {
	if (el == document && event == 'ready') {
		hs.push(hs.onReady, func);
	}
	try {
		el.addEventListener(event, func, false);
	} catch (e) {
		try {
			el.detachEvent('on'+ event, func);
			el.attachEvent('on'+ event, func);
		} catch (e) {
			el['on'+ event] = func;
		}
	} 
},

removeEventListener : function (el, event, func) {
	try {
		el.removeEventListener(event, func, false);
	} catch (e) {
		try {
			el.detachEvent('on'+ event, func);
		} catch (e) {
			el['on'+ event] = null;
		}
	}
},

preloadFullImage : function (i) {
	if (hs.continuePreloading && hs.preloadTheseImages[i] && hs.preloadTheseImages[i] != 'undefined') {
		var img = document.createElement('img');
		img.onload = function() { 
			img = null;
			hs.preloadFullImage(i + 1);
		};
		img.src = hs.preloadTheseImages[i];
	}
},
preloadImages : function (number) {
	if (number && typeof number != 'object') hs.numberOfImagesToPreload = number;
	
	var arr = hs.getAnchors();
	for (var i = 0; i < arr.images.length && i < hs.numberOfImagesToPreload; i++) {
		hs.push(hs.preloadTheseImages, hs.getSrc(arr.images[i]));
	}
	
	// preload outlines
	if (hs.outlineType)	new hs.Outline(hs.outlineType, function () { hs.preloadFullImage(0)} );
	else
	
	hs.preloadFullImage(0);
	
	// preload cursor
	if (hs.restoreCursor) var cur = hs.createElement('img', { src: hs.graphicsDir + hs.restoreCursor });
},


init : function () {
	if (!hs.container) {
	
		hs.getPageSize();
		hs.ieLt7 = hs.ie && hs.uaVersion < 7;
		hs.ie6SSL = hs.ieLt7 && location.protocol == 'https:';
		for (var x in hs.langDefaults) {
			if (typeof hs[x] != 'undefined') hs.lang[x] = hs[x];
			else if (typeof hs.lang[x] == 'undefined' && typeof hs.langDefaults[x] != 'undefined') 
				hs.lang[x] = hs.langDefaults[x];
		}
		
		hs.container = hs.createElement('div', {
				className: 'highslide-container'
			}, {
				position: 'absolute',
				left: 0, 
				top: 0, 
				width: '100%', 
				zIndex: hs.zIndexCounter,
				direction: 'ltr'
			}, 
			document.body,
			true
		);
		hs.loading = hs.createElement('a', {
				className: 'highslide-loading',
				title: hs.lang.loadingTitle,
				innerHTML: hs.lang.loadingText,
				href: 'javascript:;'
			}, {
				position: 'absolute',
				top: '-9999px',
				opacity: hs.loadingOpacity,
				zIndex: 1
			}, hs.container
		);
		hs.garbageBin = hs.createElement('div', null, { display: 'none' }, hs.container);
		hs.viewport = hs.createElement('div', {
				className: 'highslide-viewport highslide-viewport-size'
			}, {
				visibility: (hs.safari && hs.uaVersion < 525) ? 'visible' : 'hidden'
			}, hs.container, 1
		);
		hs.clearing = hs.createElement('div', null, 
			{ clear: 'both', paddingTop: '1px' }, null, true);
		
		// http://www.robertpenner.com/easing/ 
		Math.linearTween = function (t, b, c, d) {
			return c*t/d + b;
		};
		Math.easeInQuad = function (t, b, c, d) {
			return c*(t/=d)*t + b;
		};
		Math.easeOutQuad = function (t, b, c, d) {
			return -c *(t/=d)*(t-2) + b;
		};
		
		hs.hideSelects = hs.ieLt7;
		hs.hideIframes = ((window.opera && hs.uaVersion < 9) || navigator.vendor == 'KDE' 
			|| (hs.ie && hs.uaVersion < 5.5));
		hs.fireEvent(this, 'onActivate');
	}
},
ready : function() {
	if (hs.isReady) return;
	hs.isReady = true;
	for (var i = 0; i < hs.onReady.length; i++) hs.onReady[i]();
},

updateAnchors : function() {
	var el, els, all = [], images = [], htmls = [],groups = {}, re;
		
	for (var i = 0; i < hs.openerTagNames.length; i++) {
		els = document.getElementsByTagName(hs.openerTagNames[i]);
		for (var j = 0; j < els.length; j++) {
			el = els[j];
			re = hs.isHsAnchor(el);
			if (re) {
				hs.push(all, el);
				if (re[0] == 'hs.expand') hs.push(images, el);
				else if (re[0] == 'hs.htmlExpand') hs.push(htmls, el);
				var g = hs.getParam(el, 'slideshowGroup') || 'none';
				if (!groups[g]) groups[g] = [];
				hs.push(groups[g], el);
			}
		}
	}
	hs.anchors = { all: all, groups: groups, images: images, htmls: htmls };
	return hs.anchors;
	
},

getAnchors : function() {
	return hs.anchors || hs.updateAnchors();
},


close : function(el) {
	var exp = hs.getExpander(el);
	if (exp) exp.close();
	return false;
}
}; // end hs object
hs.fx = function( elem, options, prop ){
	this.options = options;
	this.elem = elem;
	this.prop = prop;

	if (!options.orig) options.orig = {};
};
hs.fx.prototype = {
	update: function(){
		(hs.fx.step[this.prop] || hs.fx.step._default)(this);
		
		if (this.options.step)
			this.options.step.call(this.elem, this.now, this);

	},
	custom: function(from, to, unit){
		this.startTime = (new Date()).getTime();
		this.start = from;
		this.end = to;
		this.unit = unit;// || this.unit || "px";
		this.now = this.start;
		this.pos = this.state = 0;

		var self = this;
		function t(gotoEnd){
			return self.step(gotoEnd);
		}

		t.elem = this.elem;

		if ( t() && hs.timers.push(t) == 1 ) {
			hs.timerId = setInterval(function(){
				var timers = hs.timers;

				for ( var i = 0; i < timers.length; i++ )
					if ( !timers[i]() )
						timers.splice(i--, 1);

				if ( !timers.length ) {
					clearInterval(hs.timerId);
				}
			}, 13);
		}
	},
	step: function(gotoEnd){
		var t = (new Date()).getTime();
		if ( gotoEnd || t >= this.options.duration + this.startTime ) {
			this.now = this.end;
			this.pos = this.state = 1;
			this.update();

			this.options.curAnim[ this.prop ] = true;

			var done = true;
			for ( var i in this.options.curAnim )
				if ( this.options.curAnim[i] !== true )
					done = false;

			if ( done ) {
				if (this.options.complete) this.options.complete.call(this.elem);
			}
			return false;
		} else {
			var n = t - this.startTime;
			this.state = n / this.options.duration;
			this.pos = this.options.easing(n, 0, 1, this.options.duration);
			this.now = this.start + ((this.end - this.start) * this.pos);
			this.update();
		}
		return true;
	}

};

hs.extend( hs.fx, {
	step: {

		opacity: function(fx){
			hs.setStyles(fx.elem, { opacity: fx.now });
		},

		_default: function(fx){
			try {
				if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
					fx.elem.style[ fx.prop ] = fx.now + fx.unit;
				else
					fx.elem[ fx.prop ] = fx.now;
			} catch (e) {}
		}
	}
});

hs.Outline =  function (outlineType, onLoad) {
	this.onLoad = onLoad;
	this.outlineType = outlineType;
	var v = hs.uaVersion, tr;
	
	this.hasAlphaImageLoader = hs.ie && v >= 5.5 && v < 7;
	if (!outlineType) {
		if (onLoad) onLoad();
		return;
	}
	
	hs.init();
	this.table = hs.createElement(
		'table', { 
			cellSpacing: 0 
		}, {
			visibility: 'hidden',
			position: 'absolute',
			borderCollapse: 'collapse',
			width: 0
		},
		hs.container,
		true
	);
	var tbody = hs.createElement('tbody', null, null, this.table, 1);
	
	this.td = [];
	for (var i = 0; i <= 8; i++) {
		if (i % 3 == 0) tr = hs.createElement('tr', null, { height: 'auto' }, tbody, true);
		this.td[i] = hs.createElement('td', null, null, tr, true);
		var style = i != 4 ? { lineHeight: 0, fontSize: 0} : { position : 'relative' };
		hs.setStyles(this.td[i], style);
	}
	this.td[4].className = outlineType +' highslide-outline';
	
	this.preloadGraphic(); 
};

hs.Outline.prototype = {
preloadGraphic : function () {
	var src = hs.graphicsDir + (hs.outlinesDir || "outlines/")+ this.outlineType +".png";
				
	var appendTo = hs.safari ? hs.container : null;
	this.graphic = hs.createElement('img', null, { position: 'absolute', 
		top: '-9999px' }, appendTo, true); // for onload trigger
	
	var pThis = this;
	this.graphic.onload = function() { pThis.onGraphicLoad(); };
	
	this.graphic.src = src;
},

onGraphicLoad : function () {
	var o = this.offset = this.graphic.width / 4,
		pos = [[0,0],[0,-4],[-2,0],[0,-8],0,[-2,-8],[0,-2],[0,-6],[-2,-2]],
		dim = { height: (2*o) +'px', width: (2*o) +'px' };
	for (var i = 0; i <= 8; i++) {
		if (pos[i]) {
			if (this.hasAlphaImageLoader) {
				var w = (i == 1 || i == 7) ? '100%' : this.graphic.width +'px';
				var div = hs.createElement('div', null, { width: '100%', height: '100%', position: 'relative', overflow: 'hidden'}, this.td[i], true);
				hs.createElement ('div', null, { 
						filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale, src='"+ this.graphic.src + "')", 
						position: 'absolute',
						width: w, 
						height: this.graphic.height +'px',
						left: (pos[i][0]*o)+'px',
						top: (pos[i][1]*o)+'px'
					}, 
				div,
				true);
			} else {
				hs.setStyles(this.td[i], { background: 'url('+ this.graphic.src +') '+ (pos[i][0]*o)+'px '+(pos[i][1]*o)+'px'});
			}
			
			if (window.opera && (i == 3 || i ==5)) 
				hs.createElement('div', null, dim, this.td[i], true);
			
			hs.setStyles (this.td[i], dim);
		}
	}
	this.graphic = null;
	if (hs.pendingOutlines[this.outlineType]) hs.pendingOutlines[this.outlineType].destroy();
	hs.pendingOutlines[this.outlineType] = this;
	if (this.onLoad) this.onLoad();
},
	
setPosition : function (pos, offset, vis, dur, easing) {
	var exp = this.exp,
		stl = exp.wrapper.style,
		offset = offset || 0,
		pos = pos || {
			x: exp.x.pos + offset,
			y: exp.y.pos + offset,
			w: exp.x.get('wsize') - 2 * offset,
			h: exp.y.get('wsize') - 2 * offset
		};
	if (vis) this.table.style.visibility = (pos.h >= 4 * this.offset) 
		? 'visible' : 'hidden';
	hs.setStyles(this.table, {
		left: (pos.x - this.offset) +'px',
		top: (pos.y - this.offset) +'px',
		width: (pos.w + 2 * this.offset) +'px'
	});
	
	pos.w -= 2 * this.offset;
	pos.h -= 2 * this.offset;
	hs.setStyles (this.td[4], {
		width: pos.w >= 0 ? pos.w +'px' : 0,
		height: pos.h >= 0 ? pos.h +'px' : 0
	});
	if (this.hasAlphaImageLoader) this.td[3].style.height 
		= this.td[5].style.height = this.td[4].style.height;	
	
},
	
destroy : function(hide) {
	if (hide) this.table.style.visibility = 'hidden';
	else hs.discardElement(this.table);
}
};

hs.Dimension = function(exp, dim) {
	this.exp = exp;
	this.dim = dim;
	this.ucwh = dim == 'x' ? 'Width' : 'Height';
	this.wh = this.ucwh.toLowerCase();
	this.uclt = dim == 'x' ? 'Left' : 'Top';
	this.lt = this.uclt.toLowerCase();
	this.ucrb = dim == 'x' ? 'Right' : 'Bottom';
	this.rb = this.ucrb.toLowerCase();
	this.p1 = this.p2 = 0;
};
hs.Dimension.prototype = {
get : function(key) {
	switch (key) {
		case 'loadingPos':
			return this.tpos + this.tb + (this.t - hs.loading['offset'+ this.ucwh]) / 2;
		case 'loadingPosXfade':
			return this.pos + this.cb+ this.p1 + (this.size - hs.loading['offset'+ this.ucwh]) / 2;
		case 'wsize':
			return this.size + 2 * this.cb + this.p1 + this.p2;
		case 'fitsize':
			return this.clientSize - this.marginMin - this.marginMax;
		case 'maxsize':
			return this.get('fitsize') - 2 * this.cb - this.p1 - this.p2 ;
		case 'opos':
			return this.pos - (this.exp.outline ? this.exp.outline.offset : 0);
		case 'osize':
			return this.get('wsize') + (this.exp.outline ? 2*this.exp.outline.offset : 0);
		case 'imgPad':
			return this.imgSize ? Math.round((this.size - this.imgSize) / 2) : 0;
		
	}
},
calcBorders: function() {
	// correct for borders
	this.cb = (this.exp.content['offset'+ this.uclt] - this.t) / 2;
	
	this.marginMax = hs['margin'+ this.ucrb];
},
calcThumb: function() {
	this.t = this.exp.el[this.wh] ? parseInt(this.exp.el[this.wh]) : 
		this.exp.el['offset'+ this.ucwh];
	this.tpos = this.exp.tpos[this.dim];
	this.tb = (this.exp.el['offset'+ this.ucwh] - this.t) / 2;
	if (this.tpos == 0 || this.tpos == -1) {
		this.tpos = (hs.page[this.wh] / 2) + hs.page['scroll'+ this.uclt];		
	};
},
calcExpanded: function() {
	var exp = this.exp;
	this.justify = 'auto';
	
	// get alignment
	if (exp.align == 'center') this.justify = 'center';
	else if (new RegExp(this.lt).test(exp.anchor)) this.justify = null;
	else if (new RegExp(this.rb).test(exp.anchor)) this.justify = 'max';
	
	
	// size and position
	this.pos = this.tpos - this.cb + this.tb;
	
	if (this.maxHeight && this.dim == 'x')
		exp.maxWidth = Math.min(exp.maxWidth || this.full, exp.maxHeight * this.full / exp.y.full); 
		
	this.size = Math.min(this.full, exp['max'+ this.ucwh] || this.full);
	this.minSize = exp.allowSizeReduction ? 
		Math.min(exp['min'+ this.ucwh], this.full) :this.full;
	if (exp.isImage && exp.useBox)	{
		this.size = exp[this.wh];
		this.imgSize = this.full;
	}
	if (this.dim == 'x' && hs.padToMinWidth) this.minSize = exp.minWidth;
	this.target = exp['target'+ this.dim.toUpperCase()];
	this.marginMin = hs['margin'+ this.uclt];
	this.scroll = hs.page['scroll'+ this.uclt];
	this.clientSize = hs.page[this.wh];
},
setSize: function(i) {
	var exp = this.exp;
	if (exp.isImage && (exp.useBox || hs.padToMinWidth)) {
		this.imgSize = i;
		this.size = Math.max(this.size, this.imgSize);
		exp.content.style[this.lt] = this.get('imgPad')+'px';
	} else
	this.size = i;
	
	exp.content.style[this.wh] = i +'px';
	exp.wrapper.style[this.wh] = this.get('wsize') +'px';
	if (exp.outline) exp.outline.setPosition();
	if (exp.releaseMask) exp.releaseMask.style[this.wh] = i +'px';
	if (this.dim == 'y' && exp.iDoc && exp.body.style.height != 'auto') try {
		exp.iDoc.body.style.overflow = 'auto';
	} catch (e) {}
	if (exp.isHtml) {
		var d = exp.scrollerDiv;
		if (this.sizeDiff === undefined)
			this.sizeDiff = exp.innerContent['offset'+ this.ucwh] - d['offset'+ this.ucwh];
		d.style[this.wh] = (this.size - this.sizeDiff) +'px';
			
		if (this.dim == 'x') exp.mediumContent.style.width = 'auto';
		if (exp.body) exp.body.style[this.wh] = 'auto';
	}
	if (this.dim == 'x' && exp.overlayBox) exp.sizeOverlayBox(true);
	if (this.dim == 'x' && exp.slideshow && exp.isImage) {
		if (i == this.full) exp.slideshow.disable('full-expand');
		else exp.slideshow.enable('full-expand');
	}
},
setPos: function(i) {
	this.pos = i;
	this.exp.wrapper.style[this.lt] = i +'px';	
	
	if (this.exp.outline) this.exp.outline.setPosition();
	
}
};

hs.Expander = function(a, params, custom, contentType) {
	if (document.readyState && hs.ie && !hs.isReady) {
		hs.addEventListener(document, 'ready', function() {
			new hs.Expander(a, params, custom, contentType);
		});
		return;
	} 
	this.a = a;
	this.custom = custom;
	this.contentType = contentType || 'image';
	this.isHtml = (contentType == 'html');
	this.isImage = !this.isHtml;
	
	hs.continuePreloading = false;
	this.overlays = [];
	this.last = hs.last;
	hs.last = null;
	hs.init();
	var key = this.key = hs.expanders.length;
	// override inline parameters
	for (var i = 0; i < hs.overrides.length; i++) {
		var name = hs.overrides[i];
		this[name] = params && typeof params[name] != 'undefined' ?
			params[name] : hs[name];
	}
	if (!this.src) this.src = a.href;
	
	// get thumb
	var el = (params && params.thumbnailId) ? hs.$(params.thumbnailId) : a;
	el = this.thumb = el.getElementsByTagName('img')[0] || el;
	this.thumbsUserSetId = el.id || a.id;
	if (!hs.fireEvent(this, 'onInit')) return true;
	
	// check if already open
	for (var i = 0; i < hs.expanders.length; i++) {
		if (hs.expanders[i] && hs.expanders[i].a == a 
			&& !(this.last && this.transitions[1] == 'crossfade')) {
			hs.expanders[i].focus();
			return false;
		}
	}	

	// cancel other
	if (!hs.allowSimultaneousLoading) for (var i = 0; i < hs.expanders.length; i++) {
		if (hs.expanders[i] && hs.expanders[i].thumb != el && !hs.expanders[i].onLoadStarted) {
			hs.expanders[i].cancelLoading();
		}
	}
	hs.expanders[key] = this;
	if (!hs.allowMultipleInstances && !hs.upcoming) {
		if (hs.expanders[key-1]) hs.expanders[key-1].close();
		if (typeof hs.focusKey != 'undefined' && hs.expanders[hs.focusKey])
			hs.expanders[hs.focusKey].close();
	}
	
	// initiate metrics
	this.el = el;
	this.tpos = this.pageOrigin || hs.getPosition(el);
	hs.getPageSize();
	var x = this.x = new hs.Dimension(this, 'x');
	x.calcThumb();
	var y = this.y = new hs.Dimension(this, 'y');
	y.calcThumb();
	if (/area/i.test(el.tagName)) this.getImageMapAreaCorrection(el);
	this.wrapper = hs.createElement(
		'div', {
			id: 'highslide-wrapper-'+ this.key,
			className: 'highslide-wrapper '+ this.wrapperClassName
		}, {
			visibility: 'hidden',
			position: 'absolute',
			zIndex: hs.zIndexCounter += 2
		}, null, true );
	
	this.wrapper.onmouseover = this.wrapper.onmouseout = hs.wrapperMouseHandler;
	if (this.contentType == 'image' && this.outlineWhileAnimating == 2)
		this.outlineWhileAnimating = 0;
	
	// get the outline
	if (!this.outlineType 
		|| (this.last && this.isImage && this.transitions[1] == 'crossfade')) {
		this[this.contentType +'Create']();
	
	} else if (hs.pendingOutlines[this.outlineType]) {
		this.connectOutline();
		this[this.contentType +'Create']();
	
	} else {
		this.showLoading();
		var exp = this;
		new hs.Outline(this.outlineType, 
			function () {
				exp.connectOutline();
				exp[exp.contentType +'Create']();
			} 
		);
	}
	return true;
};

hs.Expander.prototype = {
error : function(e) {
	if (hs.debug) alert ('Line '+ e.lineNumber +': '+ e.message);
	else window.location.href = this.src;
},

connectOutline : function() {
	var outline = this.outline = hs.pendingOutlines[this.outlineType];
	outline.exp = this;
	outline.table.style.zIndex = this.wrapper.style.zIndex - 1;
	hs.pendingOutlines[this.outlineType] = null;
},

showLoading : function() {
	if (this.onLoadStarted || this.loading) return;
	
	this.loading = hs.loading;
	var exp = this;
	this.loading.onclick = function() {
		exp.cancelLoading();
	};
	
	
	if (!hs.fireEvent(this, 'onShowLoading')) return;
	var exp = this, 
		l = this.x.get('loadingPos') +'px',
		t = this.y.get('loadingPos') +'px';
	if (!tgt && this.last && this.transitions[1] == 'crossfade') 
		var tgt = this.last; 
	if (tgt) {
		l = tgt.x.get('loadingPosXfade') +'px';
		t = tgt.y.get('loadingPosXfade') +'px';
		this.loading.style.zIndex = hs.zIndexCounter++;
	}
	setTimeout(function () { 
		if (exp.loading) hs.setStyles(exp.loading, { left: l, top: t, zIndex: hs.zIndexCounter++ })}
	, 100);
},

imageCreate : function() {
	var exp = this;
	
	var img = document.createElement('img');
    this.content = img;
    img.onload = function () {
    	if (hs.expanders[exp.key]) exp.contentLoaded(); 
	};
    if (hs.blockRightClick) img.oncontextmenu = function() { return false; };
    img.className = 'highslide-image';
    hs.setStyles(img, {
    	visibility: 'hidden',
    	display: 'block',
    	position: 'absolute',
		maxWidth: '9999px',
		zIndex: 3
	});
    img.title = hs.lang.restoreTitle;
    if (hs.safari) hs.container.appendChild(img);
    if (hs.ie && hs.flushImgSize) img.src = null;
	img.src = this.src;
	
	this.showLoading();
},

htmlCreate : function () {
	if (!hs.fireEvent(this, 'onBeforeGetContent')) return;
	
	this.content = hs.getCacheBinding(this.a);
	if (!this.content) 
		this.content = hs.getNode(this.contentId);
	if (!this.content) 
		this.content = hs.getSelfRendered();
	this.getInline(['maincontent']);
	if (this.maincontent) {
		var body = hs.getElementByClass(this.content, 'div', 'highslide-body');
		if (body) body.appendChild(this.maincontent);
		this.maincontent.style.display = 'block';
	}
	hs.fireEvent(this, 'onAfterGetContent');
	
	var innerContent = this.innerContent = this.content;
	
	if (/(swf|iframe)/.test(this.objectType)) this.setObjContainerSize(innerContent);
	
	// the content tree
	hs.container.appendChild(this.wrapper);
	hs.setStyles( this.wrapper, { 
		position: 'static',
		padding: '0 '+ hs.marginRight +'px 0 '+ hs.marginLeft +'px'
	});
	this.content = hs.createElement(
    	'div', {
    		className: 'highslide-html' 
    	}, {
			position: 'relative',
			zIndex: 3,
			height: 0,
			overflow: 'hidden'
		},
		this.wrapper
	);
	this.mediumContent = hs.createElement('div', null, null, this.content, 1);
	this.mediumContent.appendChild(innerContent);
	
	hs.setStyles (innerContent, { 
		position: 'relative',
		display: 'block',
		direction: hs.lang.cssDirection || ''
	});
	if (this.width) innerContent.style.width = this.width +'px';
	if (this.height) hs.setStyles(innerContent, {
		height: this.height +'px',
		overflow: 'hidden'
	});
	if (innerContent.offsetWidth < this.minWidth)
		innerContent.style.width = this.minWidth +'px';
		
	
    
	if (this.objectType == 'ajax' && !hs.getCacheBinding(this.a)) {
		this.showLoading();
    	var exp = this;
    	var ajax = new hs.Ajax(this.a, innerContent);
		ajax.src = this.src;
    	ajax.onLoad = function () {	if (hs.expanders[exp.key]) exp.contentLoaded(); };
    	ajax.onError = function () { location.href = exp.src; };
    	ajax.run();
	}
    else
    
    if (this.objectType == 'iframe' && this.objectLoadTime == 'before') {
		this.writeExtendedContent();
	}
    else
    	this.contentLoaded();
},

contentLoaded : function() {
	try {	
		if (!this.content) return;
		this.content.onload = null;
		if (this.onLoadStarted) return;
		else this.onLoadStarted = true;
		
		var x = this.x, y = this.y;
		
		if (this.loading) {
			hs.setStyles(this.loading, { top: '-9999px' });
			this.loading = null;
			hs.fireEvent(this, 'onHideLoading');
		}
		if (this.isImage) {	
			x.full = this.content.width;
			y.full = this.content.height;
			
			hs.setStyles(this.content, {
				width: x.t +'px',
				height: y.t +'px'
			});
			this.wrapper.appendChild(this.content);
			hs.container.appendChild(this.wrapper);
		} else if (this.htmlGetSize) this.htmlGetSize();
		
		x.calcBorders();
		y.calcBorders();
		
		hs.setStyles (this.wrapper, {
			left: (x.tpos + x.tb - x.cb) +'px',
			top: (y.tpos + x.tb - y.cb) +'px'
		});
		
		
		this.initSlideshow();
		this.getOverlays();
		
		var ratio = x.full / y.full;
		x.calcExpanded();
		this.justify(x);
		
		y.calcExpanded();
		this.justify(y);
		if (this.isHtml) this.htmlSizeOperations();
		if (this.overlayBox) this.sizeOverlayBox(0, 1);

		
		if (this.allowSizeReduction) {
			if (this.isImage)
				this.correctRatio(ratio);
			else this.fitOverlayBox();
			var ss = this.slideshow;			
			if (ss && this.last && ss.controls && ss.fixedControls) {
				var pos = ss.overlayOptions.position || '', p;
				for (var dim in hs.oPos) for (var i = 0; i < 5; i++) {
					p = this[dim];
					if (pos.match(hs.oPos[dim][i])) {
						p.pos = this.last[dim].pos 
							+ (this.last[dim].p1 - p.p1)
							+ (this.last[dim].size - p.size) * [0, 0, .5, 1, 1][i];
						if (ss.fixedControls == 'fit') {
							if (p.pos + p.size + p.p1 + p.p2 > p.scroll + p.clientSize - p.marginMax)
								p.pos = p.scroll + p.clientSize - p.size - p.marginMin - p.marginMax - p.p1 - p.p2;
							if (p.pos < p.scroll + p.marginMin) p.pos = p.scroll + p.marginMin; 
						} 
					}
				}
			}
			if (this.isImage && this.x.full > (this.x.imgSize || this.x.size)) {
				this.createFullExpand();
				if (this.overlays.length == 1) this.sizeOverlayBox();
			}
		}
		this.show();
		
	} catch (e) {
		this.error(e);
	}
},


setObjContainerSize : function(parent, auto) {
	var c = hs.getElementByClass(parent, 'DIV', 'highslide-body');
	if (/(iframe|swf)/.test(this.objectType)) {
		if (this.objectWidth) c.style.width = this.objectWidth +'px';
		if (this.objectHeight) c.style.height = this.objectHeight +'px';
	}
},

writeExtendedContent : function () {
	if (this.hasExtendedContent) return;
	var exp = this;
	this.body = hs.getElementByClass(this.innerContent, 'DIV', 'highslide-body');
	if (this.objectType == 'iframe') {
		this.showLoading();
		var ruler = hs.clearing.cloneNode(1);
		this.body.appendChild(ruler);
		this.newWidth = this.innerContent.offsetWidth;
		if (!this.objectWidth) this.objectWidth = ruler.offsetWidth;
		var hDiff = this.innerContent.offsetHeight - this.body.offsetHeight,
			h = this.objectHeight || hs.page.height - hDiff - hs.marginTop - hs.marginBottom,
			onload = this.objectLoadTime == 'before' ? 
				' onload="if (hs.expanders['+ this.key +']) hs.expanders['+ this.key +'].contentLoaded()" ' : '';
		this.body.innerHTML += '<iframe name="hs'+ (new Date()).getTime() +'" frameborder="0" key="'+ this.key +'" '
			+' style="width:'+ this.objectWidth +'px; height:'+ h +'px" '
			+ onload +' src="'+ this.src +'" ></iframe>';
		this.ruler = this.body.getElementsByTagName('div')[0];
		this.iframe = this.body.getElementsByTagName('iframe')[0];
		
		if (this.objectLoadTime == 'after') this.correctIframeSize();
		
	}
	if (this.objectType == 'swf') {
		this.body.id = this.body.id || 'hs-flash-id-' + this.key;
		var a = this.swfOptions;
		if (!a.params) a.params = {};
		if (typeof a.params.wmode == 'undefined') a.params.wmode = 'transparent';
		if (swfobject) swfobject.embedSWF(this.src, this.body.id, this.objectWidth, this.objectHeight, 
			a.version || '7', a.expressInstallSwfurl, a.flashvars, a.params, a.attributes);
	}
	this.hasExtendedContent = true;
},
htmlGetSize : function() {
	if (this.iframe && !this.objectHeight) { // loadtime before
		this.iframe.style.height = this.body.style.height = this.getIframePageHeight() +'px';
	}
	this.innerContent.appendChild(hs.clearing);
	if (!this.x.full) this.x.full = this.innerContent.offsetWidth;
    this.y.full = this.innerContent.offsetHeight;
    this.innerContent.removeChild(hs.clearing);
    if (hs.ie && this.newHeight > parseInt(this.innerContent.currentStyle.height)) { // ie css bug
		this.newHeight = parseInt(this.innerContent.currentStyle.height);
	}
	hs.setStyles( this.wrapper, { position: 'absolute',	padding: '0'});
	hs.setStyles( this.content, { width: this.x.t +'px', height: this.y.t +'px'});
	
},

getIframePageHeight : function() {
	var h;
	try {
		var doc = this.iDoc = this.iframe.contentDocument || this.iframe.contentWindow.document;
		var clearing = doc.createElement('div');
		clearing.style.clear = 'both';
		doc.body.appendChild(clearing);
		h = clearing.offsetTop;
		if (hs.ie) h += parseInt(doc.body.currentStyle.marginTop) 
			+ parseInt(doc.body.currentStyle.marginBottom) - 1;
	} catch (e) { // other domain
		h = 300;
	}
	return h;
},
correctIframeSize : function () {
	var wDiff = this.innerContent.offsetWidth - this.ruler.offsetWidth;
	hs.discardElement(this.ruler);
	if (wDiff < 0) wDiff = 0;
	
	var hDiff = this.innerContent.offsetHeight - this.iframe.offsetHeight;
	if (this.iDoc && !this.objectHeight && !this.height && this.y.size == this.y.full) try {
		this.iDoc.body.style.overflow = 'hidden';
	} catch (e) {}
	hs.setStyles(this.iframe, { 
		width: Math.abs(this.x.size - wDiff) +'px', 
		height: Math.abs(this.y.size - hDiff) +'px'
	});
    hs.setStyles(this.body, { 
		width: this.iframe.style.width, 
    	height: this.iframe.style.height
	});
    	
    this.scrollingContent = this.iframe;
    this.scrollerDiv = this.scrollingContent;
	
},
htmlSizeOperations : function () {
	
	this.setObjContainerSize(this.innerContent);
	
	
	if (this.objectType == 'swf' && this.objectLoadTime == 'before') this.writeExtendedContent();	
	
    // handle minimum size
    if (this.x.size < this.x.full && !this.allowWidthReduction) this.x.size = this.x.full;
    if (this.y.size < this.y.full && !this.allowHeightReduction) this.y.size = this.y.full;
	this.scrollerDiv = this.innerContent;
    hs.setStyles(this.mediumContent, { 
		position: 'relative',
		width: this.x.size +'px'
	});
    hs.setStyles(this.innerContent, { 
    	border: 'none',
    	width: 'auto',
    	height: 'auto'
    });
	var node = hs.getElementByClass(this.innerContent, 'DIV', 'highslide-body');
    if (node && !/(iframe|swf)/.test(this.objectType)) {
    	var cNode = node; // wrap to get true size
    	node = hs.createElement(cNode.nodeName, null, {overflow: 'hidden'}, null, true);
    	cNode.parentNode.insertBefore(node, cNode);
    	node.appendChild(hs.clearing); // IE6
    	node.appendChild(cNode);
    	
    	var wDiff = this.innerContent.offsetWidth - node.offsetWidth;
    	var hDiff = this.innerContent.offsetHeight - node.offsetHeight;
		node.removeChild(hs.clearing);
    	
    	var kdeBugCorr = hs.safari || navigator.vendor == 'KDE' ? 1 : 0; // KDE repainting bug
    	hs.setStyles(node, { 
    			width: (this.x.size - wDiff - kdeBugCorr) +'px', 
    			height: (this.y.size - hDiff) +'px',
    			overflow: 'auto', 
    			position: 'relative' 
    		} 
    	);
		if (kdeBugCorr && cNode.offsetHeight > node.offsetHeight)	{
    		node.style.width = (parseInt(node.style.width) + kdeBugCorr) + 'px';
		}
    	this.scrollingContent = node;
    	this.scrollerDiv = this.scrollingContent;
	}
    if (this.iframe && this.objectLoadTime == 'before') this.correctIframeSize();
    if (!this.scrollingContent && this.y.size < this.mediumContent.offsetHeight) this.scrollerDiv = this.content;
	
	if (this.scrollerDiv == this.content && !this.allowWidthReduction && !/(iframe|swf)/.test(this.objectType)) {
		this.x.size += 17; // room for scrollbars
	}
	if (this.scrollerDiv && this.scrollerDiv.offsetHeight > this.scrollerDiv.parentNode.offsetHeight) {
		setTimeout("try { hs.expanders["+ this.key +"].scrollerDiv.style.overflow = 'auto'; } catch(e) {}",
			 hs.expandDuration);
	}
},

getImageMapAreaCorrection : function(area) {
	var c = area.coords.split(',');
	for (var i = 0; i < c.length; i++) c[i] = parseInt(c[i]);
	
	if (area.shape.toLowerCase() == 'circle') {
		this.x.tpos += c[0] - c[2];
		this.y.tpos += c[1] - c[2];
		this.x.t = this.y.t = 2 * c[2];
	} else {
		var maxX, maxY, minX = maxX = c[0], minY = maxY = c[1];
		for (var i = 0; i < c.length; i++) {
			if (i % 2 == 0) {
				minX = Math.min(minX, c[i]);
				maxX = Math.max(maxX, c[i]);
			} else {
				minY = Math.min(minY, c[i]);
				maxY = Math.max(maxY, c[i]);
			}
		}
		this.x.tpos += minX;
		this.x.t = maxX - minX;
		this.y.tpos += minY;
		this.y.t = maxY - minY;
	}
},
justify : function (p, moveOnly) {
	var tgtArr, tgt = p.target, dim = p == this.x ? 'x' : 'y';
	
	if (tgt && tgt.match(/ /)) {
		tgtArr = tgt.split(' ');
		tgt = tgtArr[0];
	}
	if (tgt && hs.$(tgt)) {
		p.pos = hs.getPosition(hs.$(tgt))[dim];
		if (tgtArr && tgtArr[1] && tgtArr[1].match(/^[-]?[0-9]+px$/)) 
			p.pos += parseInt(tgtArr[1]);
		if (p.size < p.minSize) p.size = p.minSize;
		
	} else if (p.justify == 'auto' || p.justify == 'center') {
	
		var hasMovedMin = false;
		
		var allowReduce = p.exp.allowSizeReduction;
		if (p.justify == 'center')
			p.pos = Math.round(p.scroll + (p.clientSize + p.marginMin - p.marginMax - p.get('wsize')) / 2);
		else
			p.pos = Math.round(p.pos - ((p.get('wsize') - p.t) / 2));
		if (p.pos < p.scroll + p.marginMin) {
			p.pos = p.scroll + p.marginMin;
			hasMovedMin = true;		
		}
		if (!moveOnly && p.size < p.minSize) {
			p.size = p.minSize;
			allowReduce = false;
		}
		if (p.pos + p.get('wsize') > p.scroll + p.clientSize - p.marginMax) {
			if (!moveOnly && hasMovedMin && allowReduce) {
				p.size = Math.min(p.size, p.get(dim == 'y' ? 'fitsize' : 'maxsize'));
			} else if (p.get('wsize') < p.get('fitsize')) {
				p.pos = p.scroll + p.clientSize - p.marginMax - p.get('wsize');
			} else { // image larger than viewport
				p.pos = p.scroll + p.marginMin;
				if (!moveOnly && allowReduce) p.size = p.get(dim == 'y' ? 'fitsize' : 'maxsize');
			}			
		}
		
		if (!moveOnly && p.size < p.minSize) {
			p.size = p.minSize;
			allowReduce = false;
		}
		
	
	} else if (p.justify == 'max') {
		p.pos = Math.floor(p.pos - p.size + p.t);
	}
	
		
	if (p.pos < p.marginMin) {
		var tmpMin = p.pos;
		p.pos = p.marginMin; 
		
		if (allowReduce && !moveOnly) p.size = p.size - (p.pos - tmpMin);
		
	}
},

correctRatio : function(ratio) {
	var x = this.x, 
		y = this.y,
		changed = false,
		xSize = Math.min(x.full, x.size),
		ySize = Math.min(y.full, y.size),
		useBox = (this.useBox || hs.padToMinWidth);
	
	if (xSize / ySize > ratio) { // width greater
		xSize = ySize * ratio;
		if (xSize < x.minSize) { // below minWidth
			xSize = x.minSize;
			ySize = xSize / ratio;
		}
		changed = true;
	
	} else if (xSize / ySize < ratio) { // height greater
		ySize = xSize / ratio;
		changed = true;
	}
	
	if (hs.padToMinWidth && x.full < x.minSize) {
		x.imgSize = x.full;
		y.size = y.imgSize = y.full;
	} else if (this.useBox) {
		x.imgSize = xSize;
		y.imgSize = ySize;
	} else {
		x.size = xSize;
		y.size = ySize;
	}
	changed = this.fitOverlayBox(useBox ? null : ratio, changed);
	if (useBox && y.size < y.imgSize) {
		y.imgSize = y.size;
		x.imgSize = y.size * ratio;
	}
	if (changed || useBox) {
		x.pos = x.tpos - x.cb + x.tb;
		x.minSize = x.size;
		this.justify(x, true);
	
		y.pos = y.tpos - y.cb + y.tb;
		y.minSize = y.size;
		this.justify(y, true);
		if (this.overlayBox) this.sizeOverlayBox();
	}
},
fitOverlayBox : function(ratio, changed) {
	var x = this.x, y = this.y;
	if (this.overlayBox && (this.isImage || this.allowHeightReduction)) {
		while (y.size > this.minHeight && x.size > this.minWidth 
				&&  y.get('wsize') > y.get('fitsize')) {
			y.size -= 10;
			if (ratio) x.size = y.size * ratio;
			this.sizeOverlayBox(0, 1);
			changed = true;
		}
	}
	return changed;
},

reflow : function () {
	if (this.scrollerDiv) {
		var h = /iframe/i.test(this.scrollerDiv.tagName) ? (this.getIframePageHeight() + 1) +'px' : 'auto';
		if (this.body) this.body.style.height = h;
		this.scrollerDiv.style.height = h;
		this.y.setSize(this.innerContent.offsetHeight);
	}
},

show : function () {
	var x = this.x, y = this.y;
	this.doShowHide('hidden');
	hs.fireEvent(this, 'onBeforeExpand');
	if (this.slideshow && this.slideshow.thumbstrip) this.slideshow.thumbstrip.selectThumb();
	
	// Apply size change
	this.changeSize(
		1, {
			wrapper: {
				width : x.get('wsize'),
				height : y.get('wsize'),
				left: x.pos,
				top: y.pos
			},
			content: {
				left: x.p1 + x.get('imgPad'),
				top: y.p1 + y.get('imgPad'),
				width:x.imgSize ||x.size,
				height:y.imgSize ||y.size
			}
		},
		hs.expandDuration
	);
},

changeSize : function(up, to, dur) {
	// transition
	var trans = this.transitions,
	other = up ? (this.last ? this.last.a : null) : hs.upcoming,
	t = (trans[1] && other 
			&& hs.getParam(other, 'transitions')[1] == trans[1]) ?
		trans[1] : trans[0];
		
	if (this[t] && t != 'expand') {
		this[t](up, to);
		return;
	}
	
	if (this.outline && !this.outlineWhileAnimating) {
		if (up) this.outline.setPosition();
		else this.outline.destroy(
				(this.isHtml && this.preserveContent));
	}
	
	
	if (!up) this.destroyOverlays();
	
	var exp = this,
		x = exp.x,
		y = exp.y,
		easing = this.easing;
	if (!up) easing = this.easingClose || easing;
	var after = up ?
		function() {
				
			if (exp.outline) exp.outline.table.style.visibility = "visible";
			setTimeout(function() {
				exp.afterExpand();
			}, 50);
		} :
		function() {
			exp.afterClose();
		};
	if (up) hs.setStyles( this.wrapper, {
		width: x.t +'px',
		height: y.t +'px'
	});
	if (up && this.isHtml) {
		hs.setStyles(this.wrapper, {
			left: (x.tpos - x.cb + x.tb) +'px',
			top: (y.tpos - y.cb + y.tb) +'px'
		});
	}
	if (this.fadeInOut) {
		hs.setStyles(this.wrapper, { opacity: up ? 0 : 1 });
		hs.extend(to.wrapper, { opacity: up });
	}
	hs.animate( this.wrapper, to.wrapper, {
		duration: dur,
		easing: easing,
		step: function(val, args) {
			if (exp.outline && exp.outlineWhileAnimating && args.prop == 'top') {
				var fac = up ? args.pos : 1 - args.pos;
				var pos = {
					w: x.t + (x.get('wsize') - x.t) * fac,
					h: y.t + (y.get('wsize') - y.t) * fac,
					x: x.tpos + (x.pos - x.tpos) * fac,
					y: y.tpos + (y.pos - y.tpos) * fac
				};
				exp.outline.setPosition(pos, 0, 1);				
			}
			if (exp.isHtml) {	
				if (args.prop == 'left') 
					exp.mediumContent.style.left = (x.pos - val) +'px';
				if (args.prop == 'top') 
					exp.mediumContent.style.top = (y.pos - val) +'px';
			}
		}
	});
	hs.animate( this.content, to.content, dur, easing, after);
	if (up) {
		this.wrapper.style.visibility = 'visible';
		this.content.style.visibility = 'visible';
		if (this.isHtml) this.innerContent.style.visibility = 'visible';
	}
},



fade : function(up, to) {
	this.outlineWhileAnimating = false;
	var exp = this,	t = up ? hs.expandDuration : 0;
	
	if (up) {
		hs.animate(this.wrapper, to.wrapper, 0);
		hs.setStyles(this.wrapper, { opacity: 0, visibility: 'visible' });
		hs.animate(this.content, to.content, 0);
		this.content.style.visibility = 'visible';

		hs.animate(this.wrapper, { opacity: 1 }, t, null, 
			function() { exp.afterExpand(); });
	}
	
	if (this.outline) {
		this.outline.table.style.zIndex = this.wrapper.style.zIndex;
		var dir = up || -1, 
			offset = this.outline.offset,
			startOff = up ? 3 : offset,
			endOff = up? offset : 3;
		for (var i = startOff; dir * i <= dir * endOff; i += dir, t += 25) {
			(function() {
				var o = up ? endOff - i : startOff - i;
				setTimeout(function() {
					exp.outline.setPosition(0, o, 1);
				}, t);
			})();
		}
	}
	
	
	if (up) {}//setTimeout(function() { exp.afterExpand(); }, t+50);
	else {
		setTimeout( function() {
			if (exp.outline) exp.outline.destroy(exp.preserveContent);
			
			exp.destroyOverlays();
	
			hs.animate( exp.wrapper, { opacity: 0 }, hs.restoreDuration, null, function(){
				exp.afterClose();
			});
		}, t);		
	}
},
crossfade : function (up, to, from) {
	if (!up) return;
	var exp = this, 
		last = this.last,
		x = this.x,
		y = this.y,
		lastX = last.x,
		lastY = last.y,
		wrapper = this.wrapper,
		content = this.content,
		overlayBox = this.overlayBox;
	hs.removeEventListener(document, 'mousemove', hs.dragHandler);
	
	hs.setStyles(content, { 
		width: (x.imgSize || x.size) +'px', 
		height: (y.imgSize || y.size) +'px'		
	});
	if (overlayBox) overlayBox.style.overflow = 'visible';
	this.outline = last.outline;
	if (this.outline) this.outline.exp = exp;
	last.outline = null;
	var fadeBox = hs.createElement('div', {
			className: 'highslide-'+ this.contentType
		}, { 
			position: 'absolute', 
			zIndex: 4,
			overflow: 'hidden',
			display: 'none'
		}
	);
	var names = { oldImg: last, newImg: this };
	for (var n in names) { 	
		this[n] = names[n].content.cloneNode(1);
		hs.setStyles(this[n], {
			position: 'absolute',
			border: 0,
			visibility: 'visible'
		});
		fadeBox.appendChild(this[n]);
	}
	wrapper.appendChild(fadeBox);
	if (this.isHtml) hs.setStyles(this.mediumContent, { 
		left: 0,
		top: 0
	});
	if (overlayBox) {
		overlayBox.className = '';
		wrapper.appendChild(overlayBox);
	}
	fadeBox.style.display = '';
	last.content.style.display = 'none';
	
	
	if (hs.safari) {
		var match = navigator.userAgent.match(/Safari\/([0-9]{3})/);
		if (match && parseInt(match[1]) < 525) this.wrapper.style.visibility = 'visible';
	}
	hs.animate(wrapper, {
		width: x.size
	}, {
		duration: hs.transitionDuration, 
		step: function(val, args) {
			var pos = args.pos,
				invPos = 1 - pos;
			var prop,
				size = {}, 
				props = ['pos', 'size', 'p1', 'p2'];
			for (var n in props) {
				prop = props[n];
				size['x'+ prop] = Math.round(invPos * lastX[prop] + pos * x[prop]);
				size['y'+ prop] = Math.round(invPos * lastY[prop] + pos * y[prop]);
				size.ximgSize = Math.round(
					invPos * (lastX.imgSize || lastX.size) + pos * (x.imgSize || x.size));
				size.ximgPad = Math.round(invPos * lastX.get('imgPad') + pos * x.get('imgPad'));
				size.yimgSize = Math.round(
					invPos * (lastY.imgSize || lastY.size) + pos * (y.imgSize || y.size));
				size.yimgPad = Math.round(invPos * lastY.get('imgPad') + pos * y.get('imgPad'));
			}
			if (exp.outline) exp.outline.setPosition({ 
				x: size.xpos, 
				y: size.ypos, 
				w: size.xsize + size.xp1 + size.xp2 + 2 * x.cb, 
				h: size.ysize + size.yp1 + size.yp2 + 2 * y.cb
			});
			last.wrapper.style.clip = 'rect('
				+ (size.ypos - lastY.pos)+'px, '
				+ (size.xsize + size.xp1 + size.xp2 + size.xpos + 2 * lastX.cb - lastX.pos) +'px, '
				+ (size.ysize + size.yp1 + size.yp2 + size.ypos + 2 * lastY.cb - lastY.pos) +'px, '
				+ (size.xpos - lastX.pos)+'px)';
				
			hs.setStyles(content, {
				top: (size.yp1 + y.get('imgPad')) +'px',
				left: (size.xp1 + x.get('imgPad')) +'px',
				marginTop: (y.pos - size.ypos) +'px',
				marginLeft: (x.pos - size.xpos) +'px'
			});
			hs.setStyles(wrapper, {
				top: size.ypos +'px',
				left: size.xpos +'px',
				width: (size.xp1 + size.xp2 + size.xsize + 2 * x.cb)+ 'px',
				height: (size.yp1 + size.yp2 + size.ysize + 2 * y.cb) + 'px'
			});
			hs.setStyles(fadeBox, {
				width: (size.ximgSize || size.xsize) + 'px',
				height: (size.yimgSize || size.ysize) +'px',
				left: (size.xp1 + size.ximgPad)  +'px',
				top: (size.yp1 + size.yimgPad) +'px',
				visibility: 'visible'
			});
			
			hs.setStyles(exp.oldImg, {
				top: (lastY.pos - size.ypos + lastY.p1 - size.yp1 + lastY.get('imgPad') - size.yimgPad)+'px',
				left: (lastX.pos - size.xpos + lastX.p1 - size.xp1 + lastX.get('imgPad') - size.ximgPad)+'px'
			});		
			
			hs.setStyles(exp.newImg, {
				opacity: pos,
				top: (y.pos - size.ypos + y.p1 - size.yp1 + y.get('imgPad') - size.yimgPad) +'px',
				left: (x.pos - size.xpos + x.p1 - size.xp1 + x.get('imgPad') - size.ximgPad) +'px'
			});
			if (overlayBox) hs.setStyles(overlayBox, {
				width: size.xsize + 'px',
				height: size.ysize +'px',
				left: (size.xp1 + x.cb)  +'px',
				top: (size.yp1 + y.cb) +'px'
			});
		},
		complete: function () {
			wrapper.style.visibility = content.style.visibility = 'visible';
			content.style.display = 'block';
			hs.discardElement(fadeBox);
			exp.afterExpand();
			last.afterClose();
			exp.last = null;
		}
		
	});
},
reuseOverlay : function(o, el) {
	if (!this.last) return false;
	for (var i = 0; i < this.last.overlays.length; i++) {
		var oDiv = hs.$('hsId'+ this.last.overlays[i]);
		if (oDiv && oDiv.hsId == o.hsId) {
			this.genOverlayBox();
			oDiv.reuse = this.key;
			hs.push(this.overlays, this.last.overlays[i]);
			return true;
		}
	}
	return false;
},


afterExpand : function() {
	this.isExpanded = true;
	
	this.a.className += ' highslide-active-anchor';	
	this.focus();
	
	if (this.isHtml && this.objectLoadTime == 'after') this.writeExtendedContent();
	if (this.iframe) {
		try {
			var exp = this,
				doc = this.iframe.contentDocument || this.iframe.contentWindow.document;
			hs.addEventListener(doc, 'mousedown', function () {
				if (hs.focusKey != exp.key) exp.focus();
			});
		} catch(e) {}
		if (hs.ie && typeof this.isClosing != 'boolean') // first open 
			this.iframe.style.width = (this.objectWidth - 1) +'px'; // hasLayout
	}
	if (this.dimmingOpacity) hs.dim(this);
	if (hs.upcoming && hs.upcoming == this.a) hs.upcoming = null;
	this.prepareNextOutline();
	var p = hs.page, mX = hs.mouse.x + p.scrollLeft, mY = hs.mouse.y + p.scrollTop;
	this.mouseIsOver = this.x.pos < mX && mX < this.x.pos + this.x.get('wsize')
		&& this.y.pos < mY && mY < this.y.pos + this.y.get('wsize');	
	if (this.overlayBox) this.showOverlays();
	hs.fireEvent(this, 'onAfterExpand');
	
},


prepareNextOutline : function() {
	var key = this.key;
	var outlineType = this.outlineType;
	new hs.Outline(outlineType, 
		function () { try { hs.expanders[key].preloadNext(); } catch (e) {} });
},


preloadNext : function() {
	var next = this.getAdjacentAnchor(1);
	if (next && next.onclick.toString().match(/hs\.expand/)) 
		var img = hs.createElement('img', { src: hs.getSrc(next) });
},


getAdjacentAnchor : function(op) {
	var current = this.getAnchorIndex(), as = hs.anchors.groups[this.slideshowGroup || 'none'];
	
	/*< ? if ($cfg->slideshow) : ?>s*/
	if (!as[current + op] && this.slideshow && this.slideshow.repeat) {
		if (op == 1) return as[0];
		else if (op == -1) return as[as.length-1];
	}
	/*< ? endif ?>s*/
	return as[current + op] || null;
},

getAnchorIndex : function() {
	var arr = hs.getAnchors().groups[this.slideshowGroup || 'none'];
	if (arr) for (var i = 0; i < arr.length; i++) {
		if (arr[i] == this.a) return i; 
	}
	return null;
},


getNumber : function() {
	if (this[this.numberPosition]) {
		var arr = hs.anchors.groups[this.slideshowGroup || 'none'];
		if (arr) {
			var s = hs.lang.number.replace('%1', this.getAnchorIndex() + 1).replace('%2', arr.length);
			this[this.numberPosition].innerHTML = 
				'<div class="highslide-number">'+ s +'</div>'+ this[this.numberPosition].innerHTML;
		}
	}
},
initSlideshow : function() {
	if (!this.last) {
		for (var i = 0; i < hs.slideshows.length; i++) {
			var ss = hs.slideshows[i], sg = ss.slideshowGroup;
			if (typeof sg == 'undefined' || sg === null || sg === this.slideshowGroup) 
				this.slideshow = new hs.Slideshow(this.key, ss);
		} 
	} else {
		this.slideshow = this.last.slideshow;
	}
	var ss = this.slideshow;
	if (!ss) return;
	var key = ss.expKey = this.key;
	
	ss.checkFirstAndLast();
	ss.disable('full-expand');
	if (ss.controls) {
		this.createOverlay(hs.extend(ss.overlayOptions || {}, {
			overlayId: ss.controls,
			hsId: 'controls',
			zIndex: 5
		}));
	}
	if (ss.thumbstrip) ss.thumbstrip.add(this);
	if (!this.last && this.autoplay) ss.play(true);
	if (ss.autoplay) {
		ss.autoplay = setTimeout(function() {
			hs.next(key);
		}, (ss.interval || 500));
	}
},

cancelLoading : function() {
	hs.discardElement (this.wrapper);
	hs.expanders[this.key] = null;
	if (hs.upcoming == this.a) hs.upcoming = null;
	hs.undim(this.key);
	if (this.loading) hs.loading.style.left = '-9999px';
	hs.fireEvent(this, 'onHideLoading');
},

writeCredits : function () {
	if (this.credits) return;
	this.credits = hs.createElement('a', {
		href: hs.creditsHref,
		target: hs.creditsTarget,
		className: 'highslide-credits',
		innerHTML: hs.lang.creditsText,
		title: hs.lang.creditsTitle
	});
	this.createOverlay({ 
		overlayId: this.credits, 
		position: this.creditsPosition || 'top left', 
		hsId: 'credits' 
	});
},

getInline : function(types, addOverlay) {
	for (var i = 0; i < types.length; i++) {
		var type = types[i], s = null;
		if (type == 'caption' && !hs.fireEvent(this, 'onBeforeGetCaption')) return;
		else if (type == 'heading' && !hs.fireEvent(this, 'onBeforeGetHeading')) return;
		if (!this[type +'Id'] && this.thumbsUserSetId)  
			this[type +'Id'] = type +'-for-'+ this.thumbsUserSetId;
		if (this[type +'Id']) this[type] = hs.getNode(this[type +'Id']);
		if (!this[type] && !this[type +'Text'] && this[type +'Eval']) try {
			s = eval(this[type +'Eval']);
		} catch (e) {}
		if (!this[type] && this[type +'Text']) {
			s = this[type +'Text'];
		}
		if (!this[type] && !s) {
			this[type] = hs.getNode(this.a['_'+ type + 'Id']);
			if (!this[type]) {
				var next = this.a.nextSibling;
				while (next && !hs.isHsAnchor(next)) {
					if ((new RegExp('highslide-'+ type)).test(next.className || null)) {
						if (!next.id) this.a['_'+ type + 'Id'] = next.id = 'hsId'+ hs.idCounter++;
						this[type] = hs.getNode(next.id);
						break;
					}
					next = next.nextSibling;
				}
			}
		}
		if (!this[type] && !s && this.numberPosition == type) s = '\n';
		
		if (!this[type] && s) this[type] = hs.createElement('div', 
				{ className: 'highslide-'+ type, innerHTML: s } );
		
		if (addOverlay && this[type]) {
			var o = { position: (type == 'heading') ? 'above' : 'below' };
			for (var x in this[type+'Overlay']) o[x] = this[type+'Overlay'][x];
			o.overlayId = this[type];
			this.createOverlay(o);
		}
	}
},


// on end move and resize
doShowHide : function(visibility) {
	if (hs.hideSelects) this.showHideElements('SELECT', visibility);
	if (hs.hideIframes) this.showHideElements('IFRAME', visibility);
	if (hs.geckoMac) this.showHideElements('*', visibility);
},
showHideElements : function (tagName, visibility) {
	var els = document.getElementsByTagName(tagName);
	var prop = tagName == '*' ? 'overflow' : 'visibility';
	for (var i = 0; i < els.length; i++) {
		if (prop == 'visibility' || (document.defaultView.getComputedStyle(
				els[i], "").getPropertyValue('overflow') == 'auto'
				|| els[i].getAttribute('hidden-by') != null)) {
			var hiddenBy = els[i].getAttribute('hidden-by');
			if (visibility == 'visible' && hiddenBy) {
				hiddenBy = hiddenBy.replace('['+ this.key +']', '');
				els[i].setAttribute('hidden-by', hiddenBy);
				if (!hiddenBy) els[i].style[prop] = els[i].origProp;
			} else if (visibility == 'hidden') { // hide if behind
				var elPos = hs.getPosition(els[i]);
				elPos.w = els[i].offsetWidth;
				elPos.h = els[i].offsetHeight;
				if (!this.dimmingOpacity) { // hide all if dimming
				
					var clearsX = (elPos.x + elPos.w < this.x.get('opos') 
						|| elPos.x > this.x.get('opos') + this.x.get('osize'));
					var clearsY = (elPos.y + elPos.h < this.y.get('opos') 
						|| elPos.y > this.y.get('opos') + this.y.get('osize'));
				}
				var wrapperKey = hs.getWrapperKey(els[i]);
				if (!clearsX && !clearsY && wrapperKey != this.key) { // element falls behind image
					if (!hiddenBy) {
						els[i].setAttribute('hidden-by', '['+ this.key +']');
						els[i].origProp = els[i].style[prop];
						els[i].style[prop] = 'hidden';
						
					} else if (hiddenBy.indexOf('['+ this.key +']') == -1) {
						els[i].setAttribute('hidden-by', hiddenBy + '['+ this.key +']');
					}
				} else if ((hiddenBy == '['+ this.key +']' || hs.focusKey == wrapperKey)
						&& wrapperKey != this.key) { // on move
					els[i].setAttribute('hidden-by', '');
					els[i].style[prop] = els[i].origProp || '';
				} else if (hiddenBy && hiddenBy.indexOf('['+ this.key +']') > -1) {
					els[i].setAttribute('hidden-by', hiddenBy.replace('['+ this.key +']', ''));
				}
						
			}
		}
	}
},

focus : function() {
	this.wrapper.style.zIndex = hs.zIndexCounter += 2;
	// blur others
	for (var i = 0; i < hs.expanders.length; i++) {
		if (hs.expanders[i] && i == hs.focusKey) {
			var blurExp = hs.expanders[i];
			blurExp.content.className += ' highslide-'+ blurExp.contentType +'-blur';
			if (blurExp.isImage) {
				blurExp.content.style.cursor = hs.ie ? 'hand' : 'pointer';
				blurExp.content.title = hs.lang.focusTitle;	
			}	
			hs.fireEvent(blurExp, 'onBlur');
		}
	}
	
	// focus this
	if (this.outline) this.outline.table.style.zIndex 
		= this.wrapper.style.zIndex - 1;
	this.content.className = 'highslide-'+ this.contentType;
	if (this.isImage) {
		this.content.title = hs.lang.restoreTitle;
		
		if (hs.restoreCursor) {
			hs.styleRestoreCursor = window.opera ? 'pointer' : 'url('+ hs.graphicsDir + hs.restoreCursor +'), pointer';
			if (hs.ie && hs.uaVersion < 6) hs.styleRestoreCursor = 'hand';
			this.content.style.cursor = hs.styleRestoreCursor;
		}
	}
	hs.focusKey = this.key;	
	hs.addEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);	
	hs.fireEvent(this, 'onFocus');	
},
moveTo: function(x, y) {
	this.x.setPos(x);
	this.y.setPos(y);
},
resize : function (e) {
	var w, h, r = e.width / e.height;
	w = Math.max(e.width + e.dX, Math.min(this.minWidth, this.x.full));
	if (this.isImage && Math.abs(w - this.x.full) < 12) w = this.x.full;
	h = this.isHtml ? e.height + e.dY : w / r;
	if (h < Math.min(this.minHeight, this.y.full)) {
		h = Math.min(this.minHeight, this.y.full);
		if (this.isImage) w = h * r;
	}
	this.resizeTo(w, h);
},
resizeTo: function(w, h) {
	this.y.setSize(h);
	this.x.setSize(w);
	this.wrapper.style.height = this.y.get('wsize') +'px';
},

close : function() {
	if (this.isClosing || !this.isExpanded) return;
	if (this.transitions[1] == 'crossfade' && hs.upcoming) {
		hs.getExpander(hs.upcoming).cancelLoading();
		hs.upcoming = null;
	}
	if (!hs.fireEvent(this, 'onBeforeClose')) return;
	this.isClosing = true;
	if (this.slideshow && !hs.upcoming) this.slideshow.pause();
	
	hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
	
	try {
		if (this.isHtml) this.htmlPrepareClose();
		this.content.style.cursor = 'default';
		this.changeSize(
			0, {
				wrapper: {
					width : this.x.t,
					height : this.y.t,
					left: this.x.tpos - this.x.cb + this.x.tb,
					top: this.y.tpos - this.y.cb + this.y.tb
				},
				content: {
					left: 0,
					top: 0,
					width: this.x.t,
					height: this.y.t
				}
			}, hs.restoreDuration
		);
	} catch (e) { this.afterClose(); }
},

htmlPrepareClose : function() {
	if (hs.geckoMac) { // bad redraws
		if (!hs.mask) hs.mask = hs.createElement('div', null, 
			{ position: 'absolute' }, hs.container);
		hs.setStyles(hs.mask, { width: this.x.size +'px', height: this.y.size +'px', 
			left: this.x.pos +'px', top: this.y.pos +'px', display: 'block' });			
	}
	if (this.objectType == 'swf') try { hs.$(this.body.id).StopPlay(); } catch (e) {}
	
	if (this.objectLoadTime == 'after' && !this.preserveContent) this.destroyObject();		
	if (this.scrollerDiv && this.scrollerDiv != this.scrollingContent) 
		this.scrollerDiv.style.overflow = 'hidden';
},

destroyObject : function () {
	if (hs.ie && this.iframe)
		try { this.iframe.contentWindow.document.body.innerHTML = ''; } catch (e) {}
	if (this.objectType == 'swf') swfobject.removeSWF(this.body.id);
	this.body.innerHTML = '';
},

sleep : function() {
	if (this.outline) this.outline.table.style.display = 'none';
	this.releaseMask = null;
	this.wrapper.style.display = 'none';
	hs.push(hs.sleeping, this);
},

awake : function() {try {
	
	hs.expanders[this.key] = this;
	
	if (!hs.allowMultipleInstances &&hs.focusKey != this.key) {	
		try { hs.expanders[hs.focusKey].close(); } catch (e){}
	}
	
	var z = hs.zIndexCounter++, stl = { display: '', zIndex: z };
	hs.setStyles (this.wrapper, stl);
	this.isClosing = false;
	
	var o = this.outline || 0;
	if (o) {
		if (!this.outlineWhileAnimating) stl.visibility = 'hidden';
		hs.setStyles (o.table, stl);		
	}
	if (this.slideshow) {
		this.initSlideshow();
	}
		
	this.show();
} catch (e) {}


},

createOverlay : function (o) {
	var el = o.overlayId, 
		relToVP = (o.relativeTo == 'viewport' && !/panel$/.test(o.position));
	if (typeof el == 'string') el = hs.getNode(el);
	if (o.html) el = hs.createElement('div', { innerHTML: o.html });
	if (!el || typeof el == 'string') return;
	if (!hs.fireEvent(this, 'onCreateOverlay', { overlay: el })) return;
	el.style.display = 'block';
	o.hsId = o.hsId || o.overlayId; 
	if (this.transitions[1] == 'crossfade' && this.reuseOverlay(o, el)) return;
	this.genOverlayBox();
	var width = o.width && /^[0-9]+(px|%)$/.test(o.width) ? o.width : 'auto';
	if (/^(left|right)panel$/.test(o.position) && !/^[0-9]+px$/.test(o.width)) width = '200px';
	var overlay = hs.createElement(
		'div', {
			id: 'hsId'+ hs.idCounter++,
			hsId: o.hsId
		}, {
			position: 'absolute',
			visibility: 'hidden',
			width: width,
			direction: hs.lang.cssDirection || '',
			opacity: 0
		},
		relToVP ? hs.viewport :this.overlayBox,
		true
	);
	if (relToVP) overlay.hsKey = this.key;
	
	overlay.appendChild(el);
	hs.extend(overlay, {
		opacity: 1,
		offsetX: 0,
		offsetY: 0,
		dur: (o.fade === 0 || o.fade === false || (o.fade == 2 && hs.ie)) ? 0 : 250
	});
	hs.extend(overlay, o);
	
		
	if (this.gotOverlays) {
		this.positionOverlay(overlay);
		if (!overlay.hideOnMouseOut || this.mouseIsOver) 
			hs.animate(overlay, { opacity: overlay.opacity }, overlay.dur);
	}
	hs.push(this.overlays, hs.idCounter - 1);
},
positionOverlay : function(overlay) {
	var p = overlay.position || 'middle center',
		relToVP = (overlay.relativeTo == 'viewport'),
		offX = overlay.offsetX,
		offY = overlay.offsetY;
	if (relToVP) {
		hs.viewport.style.display = 'block';
		overlay.hsKey = this.key;
		if (overlay.offsetWidth > overlay.parentNode.offsetWidth)
			overlay.style.width = '100%';
	} else
	if (overlay.parentNode != this.overlayBox) this.overlayBox.appendChild(overlay);
	if (/left$/.test(p)) overlay.style.left = offX +'px'; 
	
	if (/center$/.test(p))	hs.setStyles (overlay, { 
		left: '50%',
		marginLeft: (offX - Math.round(overlay.offsetWidth / 2)) +'px'
	});	
	
	if (/right$/.test(p)) overlay.style.right = - offX +'px';
		
	if (/^leftpanel$/.test(p)) { 
		hs.setStyles(overlay, {
			right: '100%',
			marginRight: this.x.cb +'px',
			top: - this.y.cb +'px',
			bottom: - this.y.cb +'px',
			overflow: 'auto'
		});		 
		this.x.p1 = overlay.offsetWidth;
	
	} else if (/^rightpanel$/.test(p)) {
		hs.setStyles(overlay, {
			left: '100%',
			marginLeft: this.x.cb +'px',
			top: - this.y.cb +'px',
			bottom: - this.y.cb +'px',
			overflow: 'auto'
		});
		this.x.p2 = overlay.offsetWidth;
	}
	var parOff = overlay.parentNode.offsetHeight;
	overlay.style.height = 'auto';
	if (relToVP && overlay.offsetHeight > parOff)
		overlay.style.height = hs.ieLt7 ? parOff +'px' : '100%';

	if (/^top/.test(p)) overlay.style.top = offY +'px'; 
	if (/^middle/.test(p))	hs.setStyles (overlay, { 
		top: '50%', 
		marginTop: (offY - Math.round(overlay.offsetHeight / 2)) +'px'
	});	
	if (/^bottom/.test(p)) overlay.style.bottom = - offY +'px';
	if (/^above$/.test(p)) {
		hs.setStyles(overlay, {
			left: (- this.x.p1 - this.x.cb) +'px',
			right: (- this.x.p2 - this.x.cb) +'px',
			bottom: '100%',
			marginBottom: this.y.cb +'px',
			width: 'auto'
		});
		this.y.p1 = overlay.offsetHeight;
	
	} else if (/^below$/.test(p)) {
		hs.setStyles(overlay, {
			position: 'relative',
			left: (- this.x.p1 - this.x.cb) +'px',
			right: (- this.x.p2 - this.x.cb) +'px',
			top: '100%',
			marginTop: this.y.cb +'px',
			width: 'auto'
		});
		this.y.p2 = overlay.offsetHeight;
		overlay.style.position = 'absolute';
	}
},

getOverlays : function() {	
	this.getInline(['heading', 'caption'], true);
	this.getNumber();
	if (this.caption) hs.fireEvent(this, 'onAfterGetCaption');
	if (this.heading) hs.fireEvent(this, 'onAfterGetHeading');
	if (this.heading && this.dragByHeading) this.heading.className += ' highslide-move';
	if (hs.showCredits) this.writeCredits();
	for (var i = 0; i < hs.overlays.length; i++) {
		var o = hs.overlays[i], tId = o.thumbnailId, sg = o.slideshowGroup;
		if ((!tId && !sg) || (tId && tId == this.thumbsUserSetId)
				|| (sg && sg === this.slideshowGroup)) {
			if (this.isImage || (this.isHtml && o.useOnHtml))
			this.createOverlay(o);
		}
	}
	var os = [];
	for (var i = 0; i < this.overlays.length; i++) {
		var o = hs.$('hsId'+ this.overlays[i]);
		if (/panel$/.test(o.position)) this.positionOverlay(o);
		else hs.push(os, o);
	}
	for (var i = 0; i < os.length; i++) this.positionOverlay(os[i]);
	this.gotOverlays = true;
},
genOverlayBox : function() {
	if (!this.overlayBox) this.overlayBox = hs.createElement (
		'div', {
			className: this.wrapperClassName
		}, {
			position : 'absolute',
			width: (this.x.size || (this.useBox ? this.width : null) 
				|| this.x.full) +'px',
			height: (this.y.size || this.y.full) +'px',
			visibility : 'hidden',
			overflow : 'hidden',
			zIndex : hs.ie ? 4 : 'auto'
		},
		hs.container,
		true
	);
},
sizeOverlayBox : function(doWrapper, doPanels) {
	var overlayBox = this.overlayBox, 
		x = this.x,
		y = this.y;
	hs.setStyles( overlayBox, {
		width: x.size +'px', 
		height: y.size +'px'
	});
	if (doWrapper || doPanels) {
		for (var i = 0; i < this.overlays.length; i++) {
			var o = hs.$('hsId'+ this.overlays[i]);
			var ie6 = (hs.ieLt7 || document.compatMode == 'BackCompat');
			if (o && /^(above|below)$/.test(o.position)) {
				if (ie6) {
					o.style.width = (overlayBox.offsetWidth + 2 * x.cb
						+ x.p1 + x.p2) +'px';
				}
				y[o.position == 'above' ? 'p1' : 'p2'] = o.offsetHeight;
			}
			if (o && ie6 && /^(left|right)panel$/.test(o.position)) {
				o.style.height = (overlayBox.offsetHeight + 2* y.cb) +'px';
			}
		}
	}
	if (doWrapper) {
		hs.setStyles(this.content, {
			top: y.p1 +'px'
		});
		hs.setStyles(overlayBox, {
			top: (y.p1 + y.cb) +'px'
		});
	}
},

showOverlays : function() {
	var b = this.overlayBox;
	b.className = '';
	hs.setStyles(b, {
		top: (this.y.p1 + this.y.cb) +'px',
		left: (this.x.p1 + this.x.cb) +'px',
		overflow : 'visible'
	});
	if (hs.safari) b.style.visibility = 'visible';
	this.wrapper.appendChild (b);
	for (var i = 0; i < this.overlays.length; i++) {
		var o = hs.$('hsId'+ this.overlays[i]);
		o.style.zIndex = o.zIndex || 4;
		if (!o.hideOnMouseOut || this.mouseIsOver) {
			o.style.visibility = 'visible';
			hs.setStyles(o, { visibility: 'visible', display: '' });
			hs.animate(o, { opacity: o.opacity }, o.dur);
		}
	}
},

destroyOverlays : function() {
	if (!this.overlays.length) return;
	if (this.slideshow) {
		var c = this.slideshow.controls;
		if (c && hs.getExpander(c) == this) c.parentNode.removeChild(c);
	}
	for (var i = 0; i < this.overlays.length; i++) {
		var o = hs.$('hsId'+ this.overlays[i]);
		if (o && o.parentNode == hs.viewport && hs.getExpander(o) == this) hs.discardElement(o);
	}
	if (this.isHtml && this.preserveContent) {
		this.overlayBox.style.top = '-9999px';
		hs.container.appendChild(this.overlayBox);
	} else
	hs.discardElement(this.overlayBox);
},



createFullExpand : function () {
	if (this.slideshow && this.slideshow.controls) {
		this.slideshow.enable('full-expand');
		return;
	}
	this.fullExpandLabel = hs.createElement(
		'a', {
			href: 'javascript:hs.expanders['+ this.key +'].doFullExpand();',
			title: hs.lang.fullExpandTitle,
			className: 'highslide-full-expand'
		}
	);
	if (!hs.fireEvent(this, 'onCreateFullExpand')) return;
	
	this.createOverlay({ 
		overlayId: this.fullExpandLabel, 
		position: hs.fullExpandPosition, 
		hideOnMouseOut: true, 
		opacity: hs.fullExpandOpacity
	});
},

doFullExpand : function () {
	try {
		if (!hs.fireEvent(this, 'onDoFullExpand')) return;
		if (this.fullExpandLabel) hs.discardElement(this.fullExpandLabel);
		
		this.focus();
		var xSize = this.x.size;
		this.resizeTo(this.x.full, this.y.full);
		
		var xpos = this.x.pos - (this.x.size - xSize) / 2;
		if (xpos < hs.marginLeft) xpos = hs.marginLeft;
		
		this.moveTo(xpos, this.y.pos);
		this.doShowHide('hidden');
	
	} catch (e) {
		this.error(e);
	}
},


afterClose : function () {
	this.a.className = this.a.className.replace('highslide-active-anchor', '');
	
	this.doShowHide('visible');	
	
	if (this.isHtml && this.preserveContent
			 && this.transitions[1] != 'crossfade') {
		this.sleep();
	} else {
		if (this.outline && this.outlineWhileAnimating) this.outline.destroy();
	
		hs.discardElement(this.wrapper);
	}
	if (hs.mask) hs.mask.style.display = 'none';
	this.destroyOverlays();
	if (!hs.viewport.childNodes.length) hs.viewport.style.display = 'none';
	
	if (this.dimmingOpacity) hs.undim(this.key);
	hs.fireEvent(this, 'onAfterClose');
	hs.expanders[this.key] = null;		
	hs.reOrder();
}

};


// hs.Ajax object prototype
hs.Ajax = function (a, content, pre) {
	this.a = a;
	this.content = content;
	this.pre = pre;
};

hs.Ajax.prototype = {
run : function () {
	var xhr;
	if (!this.src) this.src = hs.getSrc(this.a);
	if (this.src.match('#')) {
		var arr = this.src.split('#');
		this.src = arr[0];
		this.id = arr[1];
	}
	if (hs.cachedGets[this.src]) {
		this.cachedGet = hs.cachedGets[this.src];
		if (this.id) this.getElementContent();
		else this.loadHTML();
		return;
	}
	try { xhr = new XMLHttpRequest(); }
	catch (e) {
		try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); }
		catch (e) {
			try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
			catch (e) { this.onError(); }
		}
	}
	var pThis = this; 
	xhr.onreadystatechange = function() {
		if(pThis.xhr.readyState == 4) {
			if (pThis.id) pThis.getElementContent();
			else pThis.loadHTML();
		}
	};
	var src = this.src;
	this.xhr = xhr;
	if (hs.forceAjaxReload) 
		src = src.replace(/$/, (/\?/.test(src) ? '&' : '?') +'dummy='+ (new Date()).getTime());
	xhr.open('GET', src, true);
	xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
	xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
	xhr.send(null);
},

getElementContent : function() {
	hs.init();
	var attribs = window.opera || hs.ie6SSL ? { src: 'about:blank' } : null;
	
	this.iframe = hs.createElement('iframe', attribs, 
		{ position: 'absolute', top: '-9999px' }, hs.container);
		
	this.loadHTML();
},

loadHTML : function() {
	var s = this.cachedGet || this.xhr.responseText,
		regBody;
	if (this.pre) hs.cachedGets[this.src] = s;
	if (!hs.ie || hs.uaVersion >= 5.5) {
		s = s.replace(new RegExp('<link[^>]*>', 'gi'), '')
			.replace(new RegExp('<script[^>]*>.*?</script>', 'gi'), '');
		if (this.iframe) {
			var doc = this.iframe.contentDocument;
			if (!doc && this.iframe.contentWindow) doc = this.iframe.contentWindow.document;
			if (!doc) { // Opera
				var pThis = this;
				setTimeout(function() {	pThis.loadHTML(); }, 25);
				return;
			}
			doc.open();
			doc.write(s);
			doc.close();
			try { s = doc.getElementById(this.id).innerHTML; } catch (e) {
				try { s = this.iframe.document.getElementById(this.id).innerHTML; } catch (e) {} // opera
			}
			hs.discardElement(this.iframe);
		} else {
			regBody = /(<body[^>]*>|<\/body>)/ig;
			if (regBody.test(s)) s = s.split(regBody)[hs.ie ? 1 : 2];
			
		}
	}
	hs.getElementByClass(this.content, 'DIV', 'highslide-body').innerHTML = s;
	this.onLoad();
	for (var x in this) this[x] = null;
}
};


hs.Slideshow = function (expKey, options) {
	if (hs.dynamicallyUpdateAnchors !== false) hs.updateAnchors();
	this.expKey = expKey;
	for (var x in options) this[x] = options[x];
	if (this.useControls) this.getControls();
	if (this.thumbstrip) this.thumbstrip = hs.Thumbstrip(this);
};
hs.Slideshow.prototype = {
getControls: function() {
	this.controls = hs.createElement('div', { innerHTML: hs.replaceLang(hs.skin.controls) }, 
		null, hs.container);
	
	var buttons = ['play', 'pause', 'previous', 'next', 'move', 'full-expand', 'close'];
	this.btn = {};
	var pThis = this;
	for (var i = 0; i < buttons.length; i++) {
		this.btn[buttons[i]] = hs.getElementByClass(this.controls, 'li', 'highslide-'+ buttons[i]);
		this.enable(buttons[i]);
	}
	this.btn.pause.style.display = 'none';
	//this.disable('full-expand');
},
checkFirstAndLast: function() {
	if (this.repeat || !this.controls) return;
	var exp = hs.expanders[this.expKey],
		cur = exp.getAnchorIndex(), 
		re = /disabled$/;
	if (cur == 0) 
		this.disable('previous');
	else if (re.test(this.btn.previous.getElementsByTagName('a')[0].className))
		this.enable('previous');
	if (cur + 1 == hs.anchors.groups[exp.slideshowGroup || 'none'].length) {
		this.disable('next');
		this.disable('play');
	} else if (re.test(this.btn.next.getElementsByTagName('a')[0].className)) {
		this.enable('next');
		this.enable('play');
	}
},
enable: function(btn) {
	if (!this.btn) return;
	var sls = this, a = this.btn[btn].getElementsByTagName('a')[0], re = /disabled$/;
	a.onclick = function() {
		sls[btn]();
		return false;
	};
	if (re.test(a.className)) a.className = a.className.replace(re, '');
},
disable: function(btn) {
	if (!this.btn) return;
	var a = this.btn[btn].getElementsByTagName('a')[0];
	a.onclick = function() { return false; };
	if (!/disabled$/.test(a.className)) a.className += ' disabled';
},
hitSpace: function() {
	if (this.autoplay) this.pause();
	else this.play();
},
play: function(wait) {
	if (this.btn) {
		this.btn.play.style.display = 'none';
		this.btn.pause.style.display = '';
	}
	
	this.autoplay = true;	
	if (!wait) hs.next(this.expKey);
},
pause: function() {
	if (this.btn) {
		this.btn.pause.style.display = 'none';
		this.btn.play.style.display = '';
	}
	
	clearTimeout(this.autoplay);
	this.autoplay = null;
},
previous: function() {
	this.pause();
	hs.previous(this.btn.previous);
},
next: function() {
	this.pause();
	hs.next(this.btn.next);
},
move: function() {},
'full-expand': function() {
	hs.getExpander().doFullExpand();
},
close: function() {
	hs.close(this.btn.close);
}
};
hs.Thumbstrip = function(slideshow) {
	function add (exp) {
		hs.extend(options || {}, {
			overlayId: dom,
			hsId: 'thumbstrip',
			className: 'highslide-thumbstrip-'+ mode +'-overlay ' + (options.className || '')
		});
		if (hs.ieLt7) options.fade = 0;
		exp.createOverlay(options);
		hs.setStyles(dom.parentNode, { overflow: 'hidden' });
	};
	
	function scroll (delta) {	
		selectThumb(undefined, Math.round(delta * dom[isX ? 'offsetWidth' : 'offsetHeight'] * 0.7));
	};
	
	function selectThumb (i, scrollBy) {
		if (i === undefined) for (var j = 0; j < group.length; j++) {
			if (group[j] == hs.expanders[slideshow.expKey].a) {
				i = j;
				break;
			}
		}
		if (i === undefined) return;
		var as = dom.getElementsByTagName('a'),
			active = as[i],
			cell = active.parentNode,
			left = isX ? 'Left' : 'Top',
			right = isX ? 'Right' : 'Bottom',
			width = isX ? 'Width' : 'Height',
			offsetLeft = 'offset' + left,
			offsetWidth = 'offset' + width,
			overlayWidth = div.parentNode.parentNode[offsetWidth],
			minTblPos = overlayWidth - table[offsetWidth],
			curTblPos = parseInt(table.style[isX ? 'left' : 'top']) || 0,
			tblPos = curTblPos,
			mgnRight = 20;
		if (scrollBy !== undefined) {
			tblPos = curTblPos - scrollBy;
			
			if (minTblPos > 0) minTblPos = 0;
			if (tblPos > 0) tblPos = 0;
			if (tblPos < minTblPos) tblPos = minTblPos;
			
	
		} else {
			for (var j = 0; j < as.length; j++) as[j].className = '';
			active.className = 'highslide-active-anchor';
			var activeLeft = i > 0 ? as[i - 1].parentNode[offsetLeft] : cell[offsetLeft],
				activeRight = cell[offsetLeft] + cell[offsetWidth] + 
					(as[i + 1] ? as[i + 1].parentNode[offsetWidth] : 0);
			if (activeRight > overlayWidth - curTblPos) tblPos = overlayWidth - activeRight;
			else if (activeLeft < -curTblPos) tblPos = -activeLeft;
		}
		var markerPos = cell[offsetLeft] + (cell[offsetWidth] - marker[offsetWidth]) / 2 + tblPos;
		hs.animate(table, isX ? { left: tblPos } : { top: tblPos }, null, 'easeOutQuad');
		hs.animate(marker, isX ? { left: markerPos } : { top: markerPos }, null, 'easeOutQuad');
		scrollUp.style.display = tblPos < 0 ? 'block' : 'none';
		scrollDown.style.display = (tblPos > minTblPos)  ? 'block' : 'none';
		
	};
	

	// initialize
	var group = hs.anchors.groups[hs.expanders[slideshow.expKey].slideshowGroup || 'none'],
		options = slideshow.thumbstrip,
		mode = options.mode || 'horizontal',
		floatMode = (mode == 'float'),
		tree = floatMode ? ['div', 'ul', 'li', 'span'] : ['table', 'tbody', 'tr', 'td'],
		isX = (mode == 'horizontal'),
		dom = hs.createElement('div', {
				className: 'highslide-thumbstrip highslide-thumbstrip-'+ mode,
				innerHTML:
					'<div class="highslide-thumbstrip-inner">'+
					'<'+ tree[0] +'><'+ tree[1] +'></'+ tree[1] +'></'+ tree[0] +'></div>'+
					'<div class="highslide-scroll-up"><div></div></div>'+
					'<div class="highslide-scroll-down"><div></div></div>'+
					'<div class="highslide-marker"><div></div></div>'
			}, {
				display: 'none'
			}, hs.container),
		domCh = dom.childNodes,
		div = domCh[0],
		scrollUp = domCh[1],
		scrollDown = domCh[2],
		marker = domCh[3],
		table = div.firstChild,
		tbody = dom.getElementsByTagName(tree[1])[0],
		tr;
	for (var i = 0; i < group.length; i++) {
		if (i == 0 || !isX) tr = hs.createElement(tree[2], null, null, tbody);
		(function(){
			var a = group[i],
				cell = hs.createElement(tree[3], null, null, tr),
				pI = i;
			hs.createElement('a', {
				href: a.href,
				onclick: function() {
					hs.getExpander(this).focus();
					return hs.transit(a);
				},
				innerHTML: hs.stripItemFormatter ? hs.stripItemFormatter(a) : a.innerHTML
			}, null, cell);
		})();
	}
	if (!floatMode) {
		scrollUp.onclick = function () { scroll(-1); };
		scrollDown.onclick = function() { scroll(1); };
		hs.addEventListener(tbody, document.onmousewheel !== undefined ? 
				'mousewheel' : 'DOMMouseScroll', function(e) {        
			var delta = 0;
	        e = e || window.event;
	        if (e.wheelDelta) {
				delta = e.wheelDelta/120;
				if (hs.opera) delta = -delta;
	        } else if (e.detail) {
				delta = -e.detail/3;
	        }
	        if (delta) scroll(-delta * 0.2);
			if (e.preventDefault) e.preventDefault();
			e.returnValue = false;
		});
	}
	
	return {
		add: add,
		selectThumb: selectThumb
	}
};
hs.langDefaults = hs.lang;
// history
var HsExpander = hs.Expander;
if (hs.ie && window == window.top) {
	(function () {
		try {
			document.documentElement.doScroll('left');
		} catch (e) {
			setTimeout(arguments.callee, 50);
			return;
		}
		hs.ready();
	})();
}
hs.addEventListener(document, 'DOMContentLoaded', hs.ready);
hs.addEventListener(window, 'load', hs.ready);

// set handlers
hs.addEventListener(document, 'ready', function() {
	if (hs.expandCursor || hs.dimmingOpacity) {
		var style = hs.createElement('style', { type: 'text/css' }, null, 
			document.getElementsByTagName('HEAD')[0]);
		
//BEGIN EDIT - hotfix from highslide site added for IE9 (dec 7, 2011; chris bennett)
	      function addRule(sel, dec) {      
	         if (hs.ie && hs.uaVersion < 9) {
	            var last = document.styleSheets[document.styleSheets.length - 1];
	            if (typeof(last.addRule) == "object") last.addRule(sel, dec);
	         } else {
	            style.appendChild(document.createTextNode(sel + " {" + dec + "}"));
	         }
	      }
		// function addRule(sel, dec) {		
			// if (!hs.ie) {
				// style.appendChild(document.createTextNode(sel + " {" + dec + "}"));
			// } else {
				// var last = document.styleSheets[document.styleSheets.length - 1];
				// if (typeof(last.addRule) == "object") last.addRule(sel, dec);
			// }
		// }
//END EDIT
		function fix(prop) {
			return 'expression( ( ( ignoreMe = document.documentElement.'+ prop +
				' ? document.documentElement.'+ prop +' : document.body.'+ prop +' ) ) + \'px\' );';
		}
		if (hs.expandCursor) addRule ('.highslide img', 
			'cursor: url('+ hs.graphicsDir + hs.expandCursor +'), pointer !important;');
		addRule ('.highslide-viewport-size',
			hs.ie && (hs.uaVersion < 7 || document.compatMode == 'BackCompat') ?
				'position: absolute; '+
				'left:'+ fix('scrollLeft') +
				'top:'+ fix('scrollTop') +
				'width:'+ fix('clientWidth') +
				'height:'+ fix('clientHeight') :
				'position: fixed; width: 100%; height: 100%; left: 0; top: 0');
	}
});
hs.addEventListener(window, 'resize', function() {
	hs.getPageSize();
	if (hs.viewport) for (var i = 0; i < hs.viewport.childNodes.length; i++) {
		var node = hs.viewport.childNodes[i],
			exp = hs.getExpander(node);
		exp.positionOverlay(node);
		if (node.hsId == 'thumbstrip') exp.slideshow.thumbstrip.selectThumb();
	}
});
hs.addEventListener(document, 'mousemove', function(e) {
	hs.mouse = { x: e.clientX, y: e.clientY	};
});
hs.addEventListener(document, 'mousedown', hs.mouseClickHandler);
hs.addEventListener(document, 'mouseup', hs.mouseClickHandler);
hs.addEventListener(document, 'ready', hs.setClickEvents);
hs.addEventListener(window, 'load', hs.preloadImages);
hs.addEventListener(window, 'load', hs.preloadAjax);
}// Copyright 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
document.createElement("canvas").getContext||(function(){var s=Math,j=s.round,F=s.sin,G=s.cos,V=s.abs,W=s.sqrt,k=10,v=k/2;function X(){return this.context_||(this.context_=new H(this))}var L=Array.prototype.slice;function Y(b,a){var c=L.call(arguments,2);return function(){return b.apply(a,c.concat(L.call(arguments)))}}var M={init:function(b){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var a=b||document;a.createElement("canvas");a.attachEvent("onreadystatechange",Y(this.init_,this,a))}},init_:function(b){b.namespaces.g_vml_||
b.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML");b.namespaces.g_o_||b.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML");if(!b.styleSheets.ex_canvas_){var a=b.createStyleSheet();a.owningElement.id="ex_canvas_";a.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}g_o_\\:*{behavior:url(#default#VML)}"}var c=b.getElementsByTagName("canvas"),d=0;for(;d<c.length;d++)this.initElement(c[d])},
initElement:function(b){if(!b.getContext){b.getContext=X;b.innerHTML="";b.attachEvent("onpropertychange",Z);b.attachEvent("onresize",$);var a=b.attributes;if(a.width&&a.width.specified)b.style.width=a.width.nodeValue+"px";else b.width=b.clientWidth;if(a.height&&a.height.specified)b.style.height=a.height.nodeValue+"px";else b.height=b.clientHeight}return b}};function Z(b){var a=b.srcElement;switch(b.propertyName){case "width":a.style.width=a.attributes.width.nodeValue+"px";a.getContext().clearRect();
break;case "height":a.style.height=a.attributes.height.nodeValue+"px";a.getContext().clearRect();break}}function $(b){var a=b.srcElement;if(a.firstChild){a.firstChild.style.width=a.clientWidth+"px";a.firstChild.style.height=a.clientHeight+"px"}}M.init();var N=[],B=0;for(;B<16;B++){var C=0;for(;C<16;C++)N[B*16+C]=B.toString(16)+C.toString(16)}function I(){return[[1,0,0],[0,1,0],[0,0,1]]}function y(b,a){var c=I(),d=0;for(;d<3;d++){var f=0;for(;f<3;f++){var h=0,g=0;for(;g<3;g++)h+=b[d][g]*a[g][f];c[d][f]=
h}}return c}function O(b,a){a.fillStyle=b.fillStyle;a.lineCap=b.lineCap;a.lineJoin=b.lineJoin;a.lineWidth=b.lineWidth;a.miterLimit=b.miterLimit;a.shadowBlur=b.shadowBlur;a.shadowColor=b.shadowColor;a.shadowOffsetX=b.shadowOffsetX;a.shadowOffsetY=b.shadowOffsetY;a.strokeStyle=b.strokeStyle;a.globalAlpha=b.globalAlpha;a.arcScaleX_=b.arcScaleX_;a.arcScaleY_=b.arcScaleY_;a.lineScale_=b.lineScale_}function P(b){var a,c=1;b=String(b);if(b.substring(0,3)=="rgb"){var d=b.indexOf("(",3),f=b.indexOf(")",d+
1),h=b.substring(d+1,f).split(",");a="#";var g=0;for(;g<3;g++)a+=N[Number(h[g])];if(h.length==4&&b.substr(3,1)=="a")c=h[3]}else a=b;return{color:a,alpha:c}}function aa(b){switch(b){case "butt":return"flat";case "round":return"round";case "square":default:return"square"}}function H(b){this.m_=I();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.fillStyle=this.strokeStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=k*1;this.globalAlpha=1;this.canvas=b;
var a=b.ownerDocument.createElement("div");a.style.width=b.clientWidth+"px";a.style.height=b.clientHeight+"px";a.style.overflow="hidden";a.style.position="absolute";b.appendChild(a);this.element_=a;this.lineScale_=this.arcScaleY_=this.arcScaleX_=1}var i=H.prototype;i.clearRect=function(){this.element_.innerHTML=""};i.beginPath=function(){this.currentPath_=[]};i.moveTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"moveTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};
i.lineTo=function(b,a){var c=this.getCoords_(b,a);this.currentPath_.push({type:"lineTo",x:c.x,y:c.y});this.currentX_=c.x;this.currentY_=c.y};i.bezierCurveTo=function(b,a,c,d,f,h){var g=this.getCoords_(f,h),l=this.getCoords_(b,a),e=this.getCoords_(c,d);Q(this,l,e,g)};function Q(b,a,c,d){b.currentPath_.push({type:"bezierCurveTo",cp1x:a.x,cp1y:a.y,cp2x:c.x,cp2y:c.y,x:d.x,y:d.y});b.currentX_=d.x;b.currentY_=d.y}i.quadraticCurveTo=function(b,a,c,d){var f=this.getCoords_(b,a),h=this.getCoords_(c,d),g={x:this.currentX_+
0.6666666666666666*(f.x-this.currentX_),y:this.currentY_+0.6666666666666666*(f.y-this.currentY_)};Q(this,g,{x:g.x+(h.x-this.currentX_)/3,y:g.y+(h.y-this.currentY_)/3},h)};i.arc=function(b,a,c,d,f,h){c*=k;var g=h?"at":"wa",l=b+G(d)*c-v,e=a+F(d)*c-v,m=b+G(f)*c-v,r=a+F(f)*c-v;if(l==m&&!h)l+=0.125;var n=this.getCoords_(b,a),o=this.getCoords_(l,e),q=this.getCoords_(m,r);this.currentPath_.push({type:g,x:n.x,y:n.y,radius:c,xStart:o.x,yStart:o.y,xEnd:q.x,yEnd:q.y})};i.rect=function(b,a,c,d){this.moveTo(b,
a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath()};i.strokeRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.stroke();this.currentPath_=f};i.fillRect=function(b,a,c,d){var f=this.currentPath_;this.beginPath();this.moveTo(b,a);this.lineTo(b+c,a);this.lineTo(b+c,a+d);this.lineTo(b,a+d);this.closePath();this.fill();this.currentPath_=f};i.createLinearGradient=function(b,
a,c,d){var f=new D("gradient");f.x0_=b;f.y0_=a;f.x1_=c;f.y1_=d;return f};i.createRadialGradient=function(b,a,c,d,f,h){var g=new D("gradientradial");g.x0_=b;g.y0_=a;g.r0_=c;g.x1_=d;g.y1_=f;g.r1_=h;return g};i.drawImage=function(b){var a,c,d,f,h,g,l,e,m=b.runtimeStyle.width,r=b.runtimeStyle.height;b.runtimeStyle.width="auto";b.runtimeStyle.height="auto";var n=b.width,o=b.height;b.runtimeStyle.width=m;b.runtimeStyle.height=r;if(arguments.length==3){a=arguments[1];c=arguments[2];h=g=0;l=d=n;e=f=o}else if(arguments.length==
5){a=arguments[1];c=arguments[2];d=arguments[3];f=arguments[4];h=g=0;l=n;e=o}else if(arguments.length==9){h=arguments[1];g=arguments[2];l=arguments[3];e=arguments[4];a=arguments[5];c=arguments[6];d=arguments[7];f=arguments[8]}else throw Error("Invalid number of arguments");var q=this.getCoords_(a,c),t=[];t.push(" <g_vml_:group",' coordsize="',k*10,",",k*10,'"',' coordorigin="0,0"',' style="width:',10,"px;height:",10,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]){var E=[];E.push("M11=",
this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",j(q.x/k),",","Dy=",j(q.y/k),"");var p=q,z=this.getCoords_(a+d,c),w=this.getCoords_(a,c+f),x=this.getCoords_(a+d,c+f);p.x=s.max(p.x,z.x,w.x,x.x);p.y=s.max(p.y,z.y,w.y,x.y);t.push("padding:0 ",j(p.x/k),"px ",j(p.y/k),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",E.join(""),", sizingmethod='clip');")}else t.push("top:",j(q.y/k),"px;left:",j(q.x/k),"px;");t.push(' ">','<g_vml_:image src="',b.src,
'"',' style="width:',k*d,"px;"," height:",k*f,'px;"',' cropleft="',h/n,'"',' croptop="',g/o,'"',' cropright="',(n-h-l)/n,'"',' cropbottom="',(o-g-e)/o,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",t.join(""))};i.stroke=function(b){var a=[],c=P(b?this.fillStyle:this.strokeStyle),d=c.color,f=c.alpha*this.globalAlpha;a.push("<g_vml_:shape",' filled="',!!b,'"',' style="position:absolute;width:',10,"px;height:",10,'px;"',' coordorigin="0 0" coordsize="',k*10," ",k*10,'"',' stroked="',
!b,'"',' path="');var h={x:null,y:null},g={x:null,y:null},l=0;for(;l<this.currentPath_.length;l++){var e=this.currentPath_[l];switch(e.type){case "moveTo":a.push(" m ",j(e.x),",",j(e.y));break;case "lineTo":a.push(" l ",j(e.x),",",j(e.y));break;case "close":a.push(" x ");e=null;break;case "bezierCurveTo":a.push(" c ",j(e.cp1x),",",j(e.cp1y),",",j(e.cp2x),",",j(e.cp2y),",",j(e.x),",",j(e.y));break;case "at":case "wa":a.push(" ",e.type," ",j(e.x-this.arcScaleX_*e.radius),",",j(e.y-this.arcScaleY_*e.radius),
" ",j(e.x+this.arcScaleX_*e.radius),",",j(e.y+this.arcScaleY_*e.radius)," ",j(e.xStart),",",j(e.yStart)," ",j(e.xEnd),",",j(e.yEnd));break}if(e){if(h.x==null||e.x<h.x)h.x=e.x;if(g.x==null||e.x>g.x)g.x=e.x;if(h.y==null||e.y<h.y)h.y=e.y;if(g.y==null||e.y>g.y)g.y=e.y}}a.push(' ">');if(b)if(typeof this.fillStyle=="object"){var m=this.fillStyle,r=0,n={x:0,y:0},o=0,q=1;if(m.type_=="gradient"){var t=m.x1_/this.arcScaleX_,E=m.y1_/this.arcScaleY_,p=this.getCoords_(m.x0_/this.arcScaleX_,m.y0_/this.arcScaleY_),
z=this.getCoords_(t,E);r=Math.atan2(z.x-p.x,z.y-p.y)*180/Math.PI;if(r<0)r+=360;if(r<1.0E-6)r=0}else{var p=this.getCoords_(m.x0_,m.y0_),w=g.x-h.x,x=g.y-h.y;n={x:(p.x-h.x)/w,y:(p.y-h.y)/x};w/=this.arcScaleX_*k;x/=this.arcScaleY_*k;var R=s.max(w,x);o=2*m.r0_/R;q=2*m.r1_/R-o}var u=m.colors_;u.sort(function(ba,ca){return ba.offset-ca.offset});var J=u.length,da=u[0].color,ea=u[J-1].color,fa=u[0].alpha*this.globalAlpha,ga=u[J-1].alpha*this.globalAlpha,S=[],l=0;for(;l<J;l++){var T=u[l];S.push(T.offset*q+
o+" "+T.color)}a.push('<g_vml_:fill type="',m.type_,'"',' method="none" focus="100%"',' color="',da,'"',' color2="',ea,'"',' colors="',S.join(","),'"',' opacity="',ga,'"',' g_o_:opacity2="',fa,'"',' angle="',r,'"',' focusposition="',n.x,",",n.y,'" />')}else a.push('<g_vml_:fill color="',d,'" opacity="',f,'" />');else{var K=this.lineScale_*this.lineWidth;if(K<1)f*=K;a.push("<g_vml_:stroke",' opacity="',f,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',aa(this.lineCap),
'"',' weight="',K,'px"',' color="',d,'" />')}a.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",a.join(""))};i.fill=function(){this.stroke(true)};i.closePath=function(){this.currentPath_.push({type:"close"})};i.getCoords_=function(b,a){var c=this.m_;return{x:k*(b*c[0][0]+a*c[1][0]+c[2][0])-v,y:k*(b*c[0][1]+a*c[1][1]+c[2][1])-v}};i.save=function(){var b={};O(this,b);this.aStack_.push(b);this.mStack_.push(this.m_);this.m_=y(I(),this.m_)};i.restore=function(){O(this.aStack_.pop(),
this);this.m_=this.mStack_.pop()};function ha(b){var a=0;for(;a<3;a++){var c=0;for(;c<2;c++)if(!isFinite(b[a][c])||isNaN(b[a][c]))return false}return true}function A(b,a,c){if(!!ha(a)){b.m_=a;if(c)b.lineScale_=W(V(a[0][0]*a[1][1]-a[0][1]*a[1][0]))}}i.translate=function(b,a){A(this,y([[1,0,0],[0,1,0],[b,a,1]],this.m_),false)};i.rotate=function(b){var a=G(b),c=F(b);A(this,y([[a,c,0],[-c,a,0],[0,0,1]],this.m_),false)};i.scale=function(b,a){this.arcScaleX_*=b;this.arcScaleY_*=a;A(this,y([[b,0,0],[0,a,
0],[0,0,1]],this.m_),true)};i.transform=function(b,a,c,d,f,h){A(this,y([[b,a,0],[c,d,0],[f,h,1]],this.m_),true)};i.setTransform=function(b,a,c,d,f,h){A(this,[[b,a,0],[c,d,0],[f,h,1]],true)};i.clip=function(){};i.arcTo=function(){};i.createPattern=function(){return new U};function D(b){this.type_=b;this.r1_=this.y1_=this.x1_=this.r0_=this.y0_=this.x0_=0;this.colors_=[]}D.prototype.addColorStop=function(b,a){a=P(a);this.colors_.push({offset:b,color:a.color,alpha:a.alpha})};function U(){}G_vmlCanvasManager=
M;CanvasRenderingContext2D=H;CanvasGradient=D;CanvasPattern=U})();
/**
*	Site-specific configuration settings for Highslide JS
*/
hs.graphicsDir = '/assets/img/v2/powerscout/highslide/';
hs.outlineType = 'rounded-white';
hs.wrapperClassName = 'draggable-header';
hs.captionEval = 'this.a.title';
hs.showCredits = false;
hs.marginTop = 20;
hs.marginRight = 20;
hs.marginBottom = 20;
hs.marginLeft = 20;
hs.dragByHeading = false;
hs.dimmingOpacity = 0.000000001;
