// I tried to just extend the  Events class by doing Events.extend, but no joy.
var McEvents = new Class ({
	handlers: null,
	addEvents: function (handlers) { // allow easy adding of groups of event handlers
		for (var handler in handlers) this.addEvent(handler, handlers[handler]);
	},
	removeEvents: function (handlers) {
		for (var handler in handlers) this.removeEvent(handler, handlers [handler]);
	},
	setHandlers: function (handlers) {
		this.handlers =	$merge.apply(null, [this.handlers].extend(arguments));
	}
});

var McGlobals = {
	mcPhone:			null,
	mcPhoneRecorder:	null,
	mcPhoneFaceplate:	null,
	mcEndpointPopup:	null,
	mcTutorialPlayer:	null
}

var McDepends = new Class({
	option: {
		onReady:	Class.empty,
		fnTest:		null,
		refObj:		null
	},
	dependsOn: [],
	initialize: function (dependsOn, options) {
		this.setOptions (options);

		var argType = $type (dependsOn);
		if (argType == 'array') {
			this.dependsOn = dependsOn;
		} else if (argType == 'string') {
			this.dependsOn = new Array();
			this.dependsOn.push (dependsOn);
		}
		this.fnTest = this.options.fnTest || this.isReady.bind (this);
		this.testAll();
	},
	testAll: function () {
		for (var i = 0, len = this.dependsOn.length; i < len; i++) {
			var obj =	window[this.dependsOn[i]];
			if (!this.isReady (this.dependsOn[i])) {
				this.testAll.delay (50, this);
				return;
			}
		}
		this.fireEvent ('onReady');
	},
	isReady: function (varName) {
		var refObj = this.options.refObj || window;
		return refObj[varName] != null;
	}
});
McDepends.implement(new Events, new Options);

var McIESetupForms = new Class ({
	initialize: function () {
		if (window.ie6) {
			window.addEvent('domready', function() {
				var inputs = $$('input')
				for (var input in inputs) {
					input.addClassName (input.type);
				}
			});
		}
	}
});
/*
	Script: McPhone.js

	Dependencies:
		--

	Class: McPhone, McButton, McLcd, McStateCache
*/

/********************* Begin McButtons definition ********************************************/
var McButtons = new Class({
	options: {
		onClick:			Class.empty,
		scale:				1
	},
	initialize: function (mcPhone, elWrapper, kids, options) {
		this.setOptions (options);

		this.elWrapper		= $(elWrapper);

		this.handlers = {
			onRequest:		this.onRequest.bind		(this),
			onResponse:		this.onResponse.bind	(this),
			onNodeButton:	this.onNodeButton.bind	(this)
		}
		var _self = this;
		if ($defined (kids)) {
			$each (kids, function (kid) {
				var index = kid.indexOf('Closed');

				var title = (index == -1 ? kid : kid.substr(0, index));
				var btnId = 'b_' + kid;
				new Element ("a", {	'href':		'#g',
									'class':	"btn",
									'title':	title,
									'id':		btnId,
									'events':	{click: function() {_self.buttonClicked (kid)}}
							}).inject (elWrapper);

			});
		}
		this.enabledButtons = new Array();

		mcPhone.addEvents (this.handlers);
	},
	animationTimeout: null,
	buttonFilter: null,
	buttonClicked:	function (kid) {
		var data = this.enabledButtons[kid];
		if (data != null) {
			this.fireEvent ('onClick', [data.tid, kid, data.tag])
		}
	},
	dragClasses:		{'N': 'north', 'S': 'south', 'E': 'east', 'W': 'west'},
	elTouchWrapper:		null, // only create on demand, if this is a touch screen device.
	getKid:				function (elButton)		{return elButton.className.substr(4);},
	getTid:				function (elButton) 	{return elButton.getAttribute ("t");},
	isTouch:			function (kid)			{return (kid.charAt(1) == ':' && kid.charAt(0) == 't')},
	isDrag:				function (kid)			{return (kid.charAt(1) == ':' && kid.charAt(0) == 'd')},
	isTouchOrDrag:		function (kid)			{return (kid.charAt(1) == ':' && ((kid.charAt(0) == 'd') || (kid.charAt(0) == 't')) ) },

	getNormalButtons:	function ()				{return this.elWrapper.getChildren();},
	getTouchButtons:	function ()				{return ($defined(this.elTouchWrapper) ? this.elTouchWrapper.getChildren() : false);},

	show: function () {
		this.elWrapper.style.display = 'block';
		if ($defined(this.elTouchWrapper)) this.elTouchWrapper.style.display = 'block';
	},
	hide: function () {
		this.elWrapper.style.display='none'
		if ($defined(this.elTouchWrapper)) this.elTouchWrapper.style.display='none';
	},
	clear: function () {
		this.elWrapper.getElements('.enable').removeClass ('enable');
		this.enabledButtons.splice(0);
		if ($defined(this.elTouchWrapper)) this.elTouchWrapper.innerHTML='<span />';
	},
	flash: function () {
		this.elWrapper.toggleClass ('show');
		if ($defined(this.elTouchWrapper)) this.elTouchWrapper.toggleClass ('show');
	},
	animate: function () {
		this.clearAnimation();
		this.doAnimate();
	},
	clearAnimation: function () {
		this.animationTimeout = $clear (this.animationTimeout);
		this.elWrapper.removeClass ('show');
		if ($defined(this.elTouchWrapper)) this.elTouchWrapper.removeClass ('show');
	},
	doAnimate: function () {
		this.flash();
		this.animationTimeout = setTimeout (function () {this.doAnimate()}.bind(this), 750);
	},
	toggleAnimation: function () {
		if ($defined (this.animationTimeout)) {
			this.clearAnimation();
		} else {
			this.doAnimate();
		}
	},
	onRequest:			function ()					{this.clear(); /* this.clearAnimation();*/}, // fired by McPhone
	onResponse:			function ()					{}, // fired by McPhone
	onNodeButton:		function (node, mcPhone)	{this.processNodeButton	(node, mcPhone)},

	processNodeButton:	function (node, mcPhone) {
		if (this.buttonFilter != null) {
			if (!this.buttonFilter(node))
				return;
		}
		var tid	= node.getAttribute("t"); // Transition Id
		var kid	= node.getAttribute("k"); // Key Id
		var tt	= node.getAttribute("g"); // Link tag type

		mcPhone.stateCache.addChildState (tid, node);

		if (kid.charAt(1) == ':') {
			if (!$defined (this.elTouchWrapper)) {
				this.elTouchWrapper = new Element ("div", {'class':	'buttons'}).inject	(mcPhone.getLcd().elWrapper);
			}
			this.createTouch (tid, kid, tt);

		} else {
			this.enableButton (tid, kid, tt);
		}

	},
	setButtonFilter:	function (fn)	{this.buttonFilter = fn},
	removeButtonFilter:	function ()		{this.buttonFilter = null},

	enableButton: function (tid, kid, tt, mcPhone) {

		this.enabledButtons[kid] = {'tid' : tid, 'tag': tt};

		var elButton = $('b_' + kid);
		if (elButton != null) {
			elButton.addClass ('enable');
		}
	},

	createTouch: function (tid, kid, tt) {
		var linkOptions =	{	'href':		'#g',
								't':		tid,
								'events':	{click: function() {this.fireEvent ('onClick', [tid, kid, tt])}.bind(this)}
							}
		var posInfo = kid.substring(2).split (",");
		var left	= Math.round (Number (posInfo[0]) * this.options.scale);
		var top		= Math.round (Number (posInfo[1]) * this.options.scale);

		if (kid.charAt(0) == 't') {
			var width	= Math.round (Number (posInfo[2]) * this.options.scale);
			var height	= Math.round (Number (posInfo[3]) * this.options.scale);
			$extend (linkOptions, {'class': 'touch', 'styles': {'left': left, 'top': top, 'width': width, 'height': height}})

		} else if (kid.charAt(0) == 'd') {
			var dir =  ($defined (posInfo[4]) ? posInfo[4] : 'N');
			var buttonClass = 'drag ' + this.dragClasses[dir] ;

			var styles;
			switch (dir) {
				case 'E':
					styles = {'left': 5, 'top': top}
					break;
				case 'W':
					styles = {'right': 5, 'top': top}
					break;
				case 'S':
					styles = {'left': left, 'top': top}
					break;
				case 'N':
					styles = {'left': left, 'top': top}
					break;
			}
			$extend (linkOptions, {'class': buttonClass, 'styles': styles})
		}
		return new Element ("a", linkOptions).inject (this.elTouchWrapper);
	}
});
McButtons.implement(new McEvents, new Events, new Options);

/********************* Begin McLcd definition ********************************************/
var McLcd = new Class({
	options: {
		animation: {
			speed:			1, // should be a number that divides into the sleep time
			enabled:		true,
			autoplay:		true,
			playLoops:		true
		},
		onBegin:		Class.empty,
		onPause:		Class.empty,
		onFrame:		Class.empty,
		onEnd:			Class.empty
	},
	currentState: null,
	currentImageFile: "",
	isOverlay: false,
	initialize: function(elWrapper, graphRoot, options) {
		this.setOptions(options);

		this.elWrapper						= $(elWrapper);
		this.lastSleepTime					= 0;
		this.graphRoot						= (graphRoot.lastIndexOf("/") == graphRoot.length-1 ? graphRoot : graphRoot + "/");
		this.currentImage					= new Image();
		this.currentImage.onload  			= this.currentImageLoaded.bind(this);

		this.fnForward			= function() {this.playNextFrame()}.bind(this);
		this.fnReverse			= function() {this.playPrevFrame()}.bind(this);

		this.options.animation.speed = Number (this.options.animation.speed);

		this.elOverlayWrapper		= new Element ("div", {'id': 'divOverlayLcd', 'styles': {'display' : 'none', 'background-color' : 'transparent', 'width' : '100%', 'height' : '100%'}}).inject (this.elWrapper);
	},
	playState: function (sNode, options) {
		if (! $defined (sNode)) return false;

		this.isOverlay =  sNode.getAttribute('o') != null;

		var opts = $merge (this.options, options);

		this.autoplay = opts.animation.autoplay; // save this off so it can start and stop
		var anNodes = sNode.getElementsByTagName("an");

		if (opts.animation.enabled) {
			this.playAnimations (anNodes);
		} else {
			this.anNodes = anNodes;
			this.playLastFrame ();
		}
		this.currentState = sNode;

	},
	initAnimation: function  (anNode) {
		var lc			= anNode.getAttribute("l");
		this.loopCount	= (lc == null ? 1 : parseInt(lc));
		this.loopIndex	= 0;
		this.fIndex		= 0;
		this.fNodes		= anNode.childNodes;
	},
	playAnimations: function (anNodes) {
		if (! $defined (anNodes)) return false;

		this.fireEvent ('onBegin', this);

		this.fNodes			= null;
		this.elapsedTime	= this.lastSleepTime = 0;
		this.loopIndex		= this.loopCount = this.anIndex = -1;
		this.anNodes		= anNodes;

		if (this.anNodes.length > 0) {
			this.anIndex = 0;
			this.initAnimation (this.anNodes[0]);

			if (this.fNodes != null && this.fNodes.length > 0) {
				this.playCurrentFrame(true);
			}
		}
	},
	playFirstFrame: function() {this.playAnimations (this.anNodes);},
	playPrevFrame: function () { // this is not animated, we just go 1 frame backwards.
		if (this.fIndex > 0) {
			this.fIndex--;

		} else if (this.options.animation.playLoops && ((this.loopCount == -1) || (this.loopIndex-1 >= 0))) {
			this.loopIndex--;
			this.fIndex = this.fNodes.length-1;

		} else if (this.anIndex > 0) {
			this.anIndex--;
			this.initAnimation(this.anNodes [this.anIndex]);
			this.fIndex = this.fNodes.length-1;
		} else {
			this.fireEvent ('onBegin', this);
			return;	// First frame has been played. Don't call playCurrentFrame again
		}
		this.playCurrentFrame (false);
	},
	playNextFrame: function () { // this will animate if autoplay == true
		if (this.fIndex < this.fNodes.length-1) {
			this.fIndex++;

		} else if (this.options.animation.playLoops && ((this.loopCount == -1) || (this.loopIndex < this.loopCount-1))) {
			if (this.loopCount == -1) this.fireEvent ('onEnd', this); //Alert anyone waiting for an end, even though it's infinite
			this.loopIndex++;
			this.fIndex = 0;

		} else if (this.anIndex < this.anNodes.length-1) {
			this.anIndex++;
			this.initAnimation(this.anNodes [this.anIndex]);

		} else {
			this.autoplay = false;
			this.fireEvent ('onEnd', this);
			return; // don't call playCurrentFrame
		}
		this.playCurrentFrame (true);
	},
	playLastFrame: function() {
		if (this.anNodes.length >= 0) {
			this.initAnimation (this.anNodes[this.anNodes.length-1]);
			this.fIndex = this.fNodes.length-1;
			this.elapsedTime = 0;
			this.playCurrentFrame();
			this.lastSleepTime = 0;
		}
		this.fireEvent ('onEnd', this);
	},
	playCurrentFrame: function (forward) {
		this.playforward	= forward;
		this.curFrameNode	= this.fNodes [this.fIndex];


		if (this.currentImageFile == this.curFrameNode.firstChild.data) {
			this.currentImageLoaded();
		} else {
			this.currentImageFile = this.curFrameNode.firstChild.data; // save it off
			this.currentImage.src	= this.graphRoot + this.curFrameNode.firstChild.data;
		}
	},
	currentImageLoaded: function () {

		if ( !this.isOverlay )	{
			this.elOverlayWrapper.setStyle ('display','none');
			this.elWrapper.setStyle ('background-image', 'url(' + this.graphRoot + this.curFrameNode.firstChild.data + ')');

		} else {
			this.elOverlayWrapper.setStyle ('display','block');
			this.elOverlayWrapper.setStyle ('background-image', 'url(' + this.graphRoot + this.curFrameNode.firstChild.data + ')');
		}

		var sleep = this.curFrameNode.getAttribute("s");

		sleep = Number ($pick (sleep, 0));
		if (this.options.animation.speed != 1) {
			sleep /= this.options.animation.speed;
		}
		var fnAnimate = null;
		if (this.playforward) {
			this.elapsedTime += this.lastSleepTime;
			fnAnimate = this.fnForward;

		} else if (this.playforward != undefined) {
			this.elapsedTime -= sleep;
			fnAnimate = this.fnReverse;
		}
		this.fireEvent ('onFrame', [this.curFrameNode, this]);

		this.lastSleepTime = sleep;
		if (this.autoplay && fnAnimate != null) {
			this.animationTimeout = setTimeout (fnAnimate, sleep);
		}
	},
	play: function () {
		this.autoplay		= true;
		this.lastSleepTime	= 0;
		this.playCurrentFrame (true);
	},
	pause: function () {
		this.autoplay=false;
		if (this.animationTimeout != null) {
			clearTimeout(this.animationTimeout);
			this.animationTimeout = null;
		}
		this.fireEvent ('onPause', this);
	},
	getAnnotation: function (fNode) {
//		var itemCode = fNode.getAttribute("a");
//		return (itemCode ? this.mcItemText.getItemText(itemCode) : null);
	},
	hasPrevFrame:			function () {return (this.fIndex>0 || this.anIndex>0)},
	hasNextFrame:			function () {return ((this.fIndex < (this.fNodes.length-1)) || (this.anIndex < (this.anNodes.length-1))) },
	getCurrentState:		function () {return this.currentState;},
	getAnimationNode:		function () {return this.anNodes [this.anIndex];},
	getFrameNode:			function () {return this.fNodes [this.fIndex];},
	getElapsedTime:			function ()	{return this.elapsedTime;},
	getAnimationIndex:		function ()	{return this.anIndex;},
	getLoopIndex:			function () {return this.loopIndex;},
	getFrameIndex:			function () {return this.fIndex;},
	getFrameCount:			function () {return this.fNodes.length;}
});
McLcd.implement(new McEvents, new Events, new Options);

var McStateCache = new Class ({
	initialize: function (mcPhone, options) {
		this.setOptions (options);

		this.preloadedImages	= new Array();
		this.preloadedStates	= new Array();
		this.cacheStates		= new Array(); // array

		this.handlers = {
			onRequest:		this.onRequest.bind		(this),
			onResponse:		this.onResponse.bind	(this)
		}
	},
	flush: function () {
		this.preloadFlush();
		this.cacheStates.splice (0);
	},
	get:		function (tid)			{ return this.cacheStates[tid]},
	put:		function (tid, sNode)	{ if (sNode != null && sNode.tagName == 's') this.cacheStates[tid] = sNode;	},
	contains: 	function (tid)			{ return this.cacheStates[tid] != null;},

	onRequest: function (mcPhone) { /* fired by McPhone*/
		this.preloadFlush();
	},
	onResponse: function (xml, mcPhone) { /* fired by McPhone*/
		if (this.options.preloadImages) setTimeout (function() {(mcPhone.supportsAnimation() ? this.preloadAll () : this.preloadLast());}.bind(this), 100);
	},
	addChildState: function (tid, parentNode) {
		var sNode = parentNode.firstChild;
		if (sNode == null || sNode.tagName != 's') return false;
		this.cacheStates [tid] = sNode;
		this.preloadedStates.push ({'tid': tid, 'sNode': sNode});
	},
	preloadFlush: function () {
		this.preloadedStates.splice(0);
		this.preloadedImages.splice(0);

		if (this.preloadTimeout != null) {
			clearTimeout (this.preloadTimeout);
			this.preloadTimeout = null;
		}
	},
	preloadAll: function () {
		var obj = this.preloadedStates.pop()
		if (obj != null) {
			var frames = obj.sNode.getElementsByTagName("f");

			for (var i = 0, len=frames.length ; i<len; i++) {
				this.preloadFrame (frames[i]);
			}
		}
		this.preloadTimeout = setTimeout (function() {this.preloadAll()}.bind(this), 50);
	},
	preloadLast: function () {
		$each (this.preloadedStates, function (obj) {
			var anNodes = obj.sNode.getElementsByTagName("an");
			if (anNodes.length > 0) {
				var anNode	= anNodes [anNodes.length-1];
				var fNodes	= anNode.childNodes;
				if (fNodes != null && fNodes.length > 0)
					this.preloadFrame (fNodes[fNodes.length-1]);
			}
		}, this);
	},
	preloadFrame: function (node) {
		var image = new Image();
		image.src = this.graphRoot + node.firstChild.data;
		this.preloadedImages.push(image);
	}
});
McStateCache.implement(new Events, new Options);

/********************* Begin McPhone definition ********************************************/
var McPhone = new Class({
	options: {
		fetch:					['toState', 'buttons', 'timeouts'],
		contextRoot:			null,
		deviceRoot: 			null,
		graphRoot: 				null,
		minTimeout:				0,
		preloadImages:			true,
		closedSuffix:			'_C',
		flipType:				'none',
		isPortrait:				true,
		autoFlip:				true,
		size:					'full', //xsmall, small, medium, large, full (null)
		maxWidth:				-1,
		maxHeight:				-1,
		stateOptions:			{preloadImages: true},
		buttonsOptions:			{},
		buttons:				[],
		lcdOptions:				{}
	},
	sizes: {	'xsmall':		0.50,
				'small':		0.67,
				'medium':		0.75,
				'large':		0.87,
				'full':			1},

	initialize: function (elWrapper, tidRoot, options) {
		this.setOptions (options);
		this.mcFetchStrategy	= null;

		this.handlers = {
			onClick:	this.onButtonClicked.bind (this)
		}
		// flipType can be 'R' (rotate) | 'D' (dual) | 'F' (flip) | 'N' (none)
		this.flipType			= this.options.flipType=='rotate' ? 'R' : (this.options.flipType=='dual' ? 'D' : (this.options.flipType=='flip' ? 'F' : 'N'));
		this.flipMode			= this.options.flipMode || 'O';

		this.fetchOptions		= this.options.fetch; // save this off, so we can reset it later
		this.contextRoot		= this.options.contextRoot;
		this.deviceRoot			= this.options.deviceRoot;
		this.graphRoot			= this.options.graphRoot;
		this.tidRoot			= tidRoot;

		this.timeouts			= new Array();

		var phoneClass			= 'phone';
		if (this.flipMode=='C') {
			phoneClass += this.options.closedSuffix;
		}

		this.elPhone			= new Element ("div", {'class': phoneClass});
		this.elButtons			= new Element ("div", {'class':	'buttons'}).inject (this.elPhone);
		this.elLcd				= new Element ("div", {'class':	'lcd'}).inject (this.elPhone);

		this.stateCache			= new McStateCache (this, this.options.stateOptions);

		this.createLcd		();
		this.createButtons	();

		elWrapper.parentNode.replaceChild(this.elPhone, elWrapper); // replaces the wrapper with the actual phone div.
	},
	createLcd: function () {
		this.mcLcd = new McLcd (this.elLcd, this.graphRoot, this.options.lcdOptions);
		this.mcLcd.addEvent ('onEnd', this.onStateEnd.bind(this));
	},

	createButtons: function () {
		var scale = Number (this.sizes[this.options.size] || 1);
		var newOptions = $merge ({'scale' : scale}, this.options.buttonsOptions);

		this.mcButtons = new McButtons (this, this.elButtons, this.options.buttons, newOptions);

		// if no onclick handler was passed in, used our default
		if (! $defined (this.options.buttonsOptions.onClick)) {
			this.mcButtons.addEvent ('onClick', this.handlers.onClick);
		}
	},

	flip: function (tid, fetchOptions) {
		if (this.flipType == 'N') return;

		if (this.flipMode=='O') { this.flipClosed(); } else { this.flipOpen();}

		if ($defined (tid)) {
			this.fetch (tid, fetchOptions);
		} else {
			this.refresh();
		}
	},
	setFlipMode: function (flipMode) {
		if (!$defined (flipMode) || flipMode == this.flipMode) return;
		flipMode=='O' ? this.flipOpen() : this.flipClosed();
	},
	flipOpen: function()	{
		if (this.flipMode == 'O') return;
		this.flipMode='O';
		this.elPhone.id		= this.elPhone.id.replace (this.options.closedSuffix, '');
		this.elPhone.className	= this.elPhone.className.replace (this.options.closedSuffix, '');
		this.fireEvent ('onFlip', this);
	},
	flipClosed:	function () {
		if (this.flipMode == 'C') return;
		this.flipMode = 'C';
		this.elPhone.id			+= this.options.closedSuffix;
		this.elPhone.className	+= this.options.closedSuffix;
		this.fireEvent ('onFlip', this);
	},
	supportsFlip:		function () {return this.flipType != 'N'},
	isDual:				function ()	{return this.flipType == 'D'},
	isRotate:			function ()	{return this.flipType == 'R'},
	isFlipOpen:			function () {return this.flipMode == 'O'},
	isFlipClosed:		function () {return (! this.isFlipOpen()) },

	isPortrait:			function () {return ($pick (this.options.isPortrait, true))},

	isVertical:			function () {return (this.isPortrait() && this.isFlipOpen()) || (!this.isPortrait() && !this.isFlipOpen()) },
	isHorizontal:		function () {return (! this.isVertical ())},

	setRootTid:			function (tid)				{this.tidRoot = tid},
	isCurrentRootTid:	function ()					{return this.tidRoot == this.tidCurrent},
	isCurrentTid:		function (tid)				{return this.tidCurrent == tid;},
	getCurrentTid:		function ()					{return this.tidCurrent;},

	getLcd:				function ()					{return this.mcLcd;},
	getElement:			function ()					{return this.elPhone},
	getWidth:			function ()					{return this.elPhone.offsetWidth},
	getHeight:			function ()					{return this.elPhone.offsetHeight},
	getMaxWidth:		function ()					{return this.options.maxWidth},
	getMaxHeight:		function ()					{return this.options.maxHeight},

	getMcButtons:		function ()					{return this.mcButtons},

	setFetchOptions:	function (value)			{this.fetchOptions = value},
	resetFetchOptions:	function ()					{this.fetchOptions = this.options.fetch;},

	refresh:			function ()					{this.fetch (this.tidCurrent);},
	onButtonClicked:   	function (tid, kid, tag)	{this.fetch (tid)},
	enableAnimation:	function (enable)			{this.mcLcd.options.animation.enabled = enable; return enable;},
	supportsAnimation:	function ()					{return this.mcLcd.options.animation.enabled},

	setMinTimeout:		function (minTimeout) 		{try {this.options.minTimeout = minTimeout.toInt();} catch (ex) {} },
	setAnimationSpeed:	function (speed) 			{this.mcLcd.options.animation.speed = Number (speed);},

	setFetchStrategy: function (mcFetchStrategy) {
		this.mcFetchStrategy = mcFetchStrategy;
		this.mcFetchStrategy.addEvent ('onSuccess', this.onSuccess.bind(this));
		this.mcFetchStrategy.addEvent ('onFailure', this.onFailure.bind(this));
	},
	fetchFirst: function () {
		this.fetch (null, ['toState'].merge(this.fetchOptions));
	},
	fetch: function (tid, fetchOptions) {
		if (this.mcFetchStrategy == null) {
			alert("Fetch Strategy not defined");
			return ;
		}
		this.clearTimeouts();

		this.fireEvent ('onRequest', this); // do this before calling playCached.

		this.tidCurrent	= (tid == null ? this.tidRoot : tid);
		this.playState	(this.tidCurrent, this.stateCache.get (this.tidCurrent));

		this.mcFetchStrategy.fetch ( this.tidCurrent, $pick(fetchOptions, this.fetchOptions));

	},
	onSuccess: function (text, xml) {
		if (xml == null || xml.firstChild == null)
			return;

		var nodes = xml.firstChild.childNodes;

		for (var i=0, len= nodes.length; i < len; i++) {
			var node = nodes[i];

			switch (node.tagName) {
				case 's': // State
					// if it's in the cache, then it was already played
					if (!this.stateCache.contains (this.tidCurrent)) {
						this.stateCache.put	(this.tidCurrent, node);
						this.playState		(this.tidCurrent, node);
					}
					break;
				case 't':	// Timeout
					this.addTimeout (node);
					break;
				case 'm':	// Flip (m is for *m*ode: f was taken)
					this.fireEvent ('onNodeFlip', [node, this]);
					break;
				case 'u':	// Tutorial for the tagged state (u is for t*u*torial: t was taken)
					this.fireEvent ('onNodeTutorial', [node, this]);
					break;
				case 'g':	// Tagged state (g is for ta*g*: t was taken)
					this.fireEvent ('onNodeTag', [node, this]);
					break;
				case 'b':	// Button node
					this.fireEvent ('onNodeButton', [node, this]);
					break;
			}
		}
		this.fireEvent ('onResponse', [xml, this]);
	},
	onFailure: function (transport) {
		var msg = 'Error 404';
	},
	clearTimeouts: function () {
		if (this.fetchTimeout != null) {
			clearTimeout (this.fetchTimeout);
			this.fetchTimeout = null;
		}
	},
	addTimeout: function ( node ) {
		var sleep	= node.getAttribute("s");
		var tid		= node.getAttribute("t");

		sleep = ($defined(sleep) ? sleep.toInt() : this.options.minTimeout);
		sleep = (sleep < this.options.minTimeout ? this.options.minTimeout : sleep);

		this.stateCache.addChildState (tid, node);
		this.timeouts [this.tidCurrent] = {'tid': tid, 'sleep': sleep};
	},
	playState: function (tid, sNode) {
		if (sNode == null) return false;

		dbug.log ("Playing TID: " + tid);

		this.tidPlaying = tid;
		this.mcLcd.playState(sNode);

		if (sNode.getAttribute('e') != null)	this.fireEvent ('onEndpointNode', sNode);
		if (this.options.autoFlip)				this.setFlipMode (sNode.getAttribute('f'));

		return true;
	},
	onStateEnd: function () {
		var timeout = this.timeouts[this.tidPlaying];
		this.tidPlaying = null;
		if ($defined (timeout)) {
			this.timeouts [this.tidPlaying] = null;
			this.fetchTimeout = setTimeout(function () {this.fetch (timeout.tid)}.bind(this), timeout.sleep );
		}
	}
});
McPhone.implement(new McEvents, new Events, new Options);
/*
	Script: McFetchStrategy.js

	Dependencies:
		mootools.js,
		McPhone.js

	Class: McFetchStrategy
*/

/*
	Each fetch strategy must support:
		A fetch method
		2 events: onSuccess and onFailure
*/

var McFetchStrategyFile = new Class ({
	options: {
		xmlRoot: 	'/xml'
	},
	initialize: function (mcPhone, options) {
		this.setOptions (options);

		this.xmlDoc = window.ie ? new ActiveXObject("Microsoft.XMLDOM") :
					  document.implementation.createDocument("", "", null);

		this.contextRoot = ("file://" != this.options.xmlRoot.substring(0,7).toLowerCase() ? "file://" + this.options.xmlRoot : this.options.xmlRoot);

		mcPhone.setFetchStrategy (this);
   },
	xmlLoaded: function () {
		this.fireEvent ('onSuccess', [null, this.xmlDoc]);
	},
	fetch: function (tid, fetchOptions) {
		if (window.location.href.indexOf('http') != -1) {
			return null;
		}
		if (window.ie) {
			this.xmlDoc.onreadystatechange = this.xmlLoaded.bind(this);
		} else {
			this.xmlDoc.onload = this.xmlLoaded.bind(this);
		}
		if ($defined (tid)) {
			var url = this.contextRoot + "/" + tid + ".xml";
			this.xmlDoc.load (url);
		}
	}
});
McFetchStrategyFile.implement(new Events, new Options);

var McFetchStrategyServlet = new Class ({
	options : {
		method:	 'get',
		autoCancel:	true
	},
	initialize: function (mcPhone, options) {
		this.setOptions (options);

		this.contextRoot	= mcPhone.contextRoot;
		this.flipType		= mcPhone.flipType;
		this.flipMode		= mcPhone.flipMode;

		this.xhr			= new XHR({	'method': this.options.method, 'autoCancel': this.options.autoCancel });

		mcPhone.addEvent		('onFlip', this.onFlip.bind(this));
		mcPhone.setFetchStrategy (this); // do this last, since it calls back into our addEvent

  },
	params: {	'toState':		'ts',
				'fromState':	'fs',
				'nextStates':	'ns',
				'buttons':		'bn',
				'endpoints':	'ep',
				'tutorials':	'tu',
				'timeouts':		'to'
	},
	fetch: function (tid, fetchOptions) {
		var url = this.contextRoot + '/ajax/state?id=' + tid;

		for (var i=0, len=fetchOptions.length; i<len; i++) {
			url += "&" + this.params [fetchOptions[i]] + "=1";
		}
		if (this.flipType!='N') {
			url += ("&fm=" + this.flipMode + "&ft=" + this.flipType); //mode (open or closed) and type (flip, open, rotate)
		}
		this.xhr.send(url);
	},
	onFlip:			function (mcPhone)	{this.flipMode = mcPhone.flipMode;},
	addEvent:		function (type, fn)	{if (fn != Class.empty) this.xhr.addEvent (type, fn);},
	removeEvent:	function (type, fn)	{ this.xhr.removeEvent (type, fn);}
});
McFetchStrategyServlet.implement(new Options);

var McFetchStrategyXml = new Class ({
	options : {
		xmlRoot:	'/xml',
		autoCancel:	true
	},
	initialize: function (mcPhone, options) {
		this.setOptions (options);

		this.xhr		= new XHR({	'method': 'get', 'autoCancel': this.options.autoCancel});

		mcPhone.setFetchStrategy (this); // do this last
	},
	fetch: function (tid, fetchOptions) {
		this.xhr.send(this.options.xmlRoot + "/" + tid + ".xml");
	},
	addEvent:		function (type, fn)	{if (fn != Class.empty) this.xhr.addEvent (type, fn);},
	removeEvent:	function (type, fn)	{ this.xhr.removeEvent (type, fn);}

});
McFetchStrategyXml.implement(new Options);

var McTutorial = new Class({
	options: {
		tutorial:		null,
		playNow:		false,
		onPlayStep:		Class.empty,
		onPlayAction:	Class.empty,
		onPlayStart:	Class.empty,
		onPlayEnd:		Class.empty,
		onPlayMedia:	Class.empty

	},
	indexStep:		null,
	indexAction:	null,
	tutorial:		null,
	isPlaying:		false,
	hasPlayed:		false, // true if isPlaying has ever been true

	initialize: function (mcPhone, options) {
		this.setOptions(options);

		this.handlers = {
			onClick:		this.onClick.bind(this),
			onRequest:		this.onRequest.bind(this),
			onResponse:		this.onResponse.bind(this)
		}
		this.filters = {
			buttonFilter: this.buttonFilter.bind(this)
		}
		this.mcPhone	= mcPhone;
		this.tutorial	= this.options.tutorial;

		this.mcPhone.getLcd().options.animation.playLoops = false; /* turn off looping */
		if (this.options.playNow) this.playTutorial (); /* if no tutorial passed in options, this will do nothing.*/
	},
	replayTutorial: function () {
		this.playTutorial (this.tutorial);
	},
	playTutorial: function (tutorial) {
		this.tutorial = $pick (tutorial, this.tutorial);
		if (!$defined (this.tutorial)) return false;

		if ($defined (this.tutorial.rootTid)) this.mcPhone.tidRoot = this.tutorial.rootTid; //nice naming consistency

		this.mcPhone.getMcButtons().clear();
		this.mcPhone.setFetchOptions(['toState','buttons', 'timeouts']);

		this.mcPhone.getMcButtons().addEvent ('onClick', this.handlers.onClick)
		this.mcPhone.getMcButtons().setButtonFilter (this.filters.buttonFilter);

		this.mcPhone.addEvent ('onRequest', this.handlers.onRequest);
		this.mcPhone.addEvent ('onResponse', this.handlers.onResponse);

		this.fireEvent ('onPlayTutorial', this);

		this.hasPlayed = this.isPlaying = true;
		return true;
	},
	stopTutorial: function () {
		if (!$defined (this.tutorial)) return false;
		this.mcPhone.resetFetchOptions();

		this.mcPhone.getMcButtons().removeEvent ('onClick', this.handlers.onClick);
		this.mcPhone.getMcButtons().removeButtonFilter ();

		this.mcPhone.removeEvent ('onRequest', this.handlers.onRequest);
		this.mcPhone.removeEvent ('onResponse', this.handlers.onResponse);

		this.fireEvent ('onStopTutorial', this);

		this.isPlaying = false;

	},
	firstStep:	function() {
		if (!$defined (this.tutorial)) return false;

		this.indexAction = null;
		this.indexStep = (this.hasSteps() && this.isInteractive () ? null : 0);
		this.fireEvent ('onPlayStart');

		return this.playCurrentStep ();
	},
	prevStep:	function() {
		if (!$defined (this.tutorial) || !$defined(this.indexStep)) return false;
		if (this.indexStep == 0) {
			this.firstStep()
		} else {
			this.indexStep--;
			return this.playCurrentStep ();
		}
	},
	nextStep:	function() {
		if (!$defined (this.tutorial) || this.tutorial.steps.length == 0) return false;
		var testIndex = this.indexStep;

		if (!$defined(this.indexStep)) {
			this.indexStep = 0;
		} else if (this.indexStep < this.tutorial.steps.length-1) {
			this.indexStep++;
		}
		return (testIndex != this.indexStep ? this.playCurrentStep () : false);
	},
	lastStep:	function() {
		if (! $defined (this.tutorial)) return;
		if ($defined (this.indexStep) && (this.indexStep >=  this.tutorial.steps.length-1)) return;
		this.indexStep = (this.tutorial.steps.length > 0 ? this.tutorial.steps.length-1 : null);
		return this.playCurrentStep ();
	},
	playStep: function (indexStep) { //
		if (indexStep == this.tutorial.steps.length ) {
			this.indexStep = indexStep-1;
		} else {
			this.indexStep = (!this.isInteractive () ? indexStep : indexStep-1);
		}
		return this.playCurrentStep ();
	},
	playCurrentStep: function () {
		if (!$defined (this.tutorial)) return false;
		this.setCurrentActionIndex();

		var info = this.getActionInfo();

		if ($defined (info.step)) {
			if (this.isInteractive ()) {
				this.playAction (info.step, info.action, 'onPlayStep');
			} else {
				this.fireEvent ('onPlayMedia', info);
			}
		} else {
			this.mcPhone.fetch (this.tutorial.firstTid);
		}
		return true;
	},
	getCurrentStepIndex:	function () {return this.indexStep;},
	getCurrentStep:			function () {return this.getStep (this.indexStep)},
	getLastStep:			function () {return this.getStep (this.getStepCount()-1)},
	getNextStep:			function () {
		if ($defined (this.tutorial) && this.indexStep < this.tutorial.steps.length-1) {
			return this.tutorial.steps [this.indexStep+1];
		}
		return null;
	},
	getStep: function (index) {
		return ($defined (this.tutorial) && $defined (index) && index < this.tutorial.steps.length ? this.tutorial.steps [index] : null);
	},
	getStepCount:	function () {return ($defined (this.tutorial) ? this.tutorial.steps.length : 0);},
	hasPrevStep:	function () {return $defined (this.indexStep) && this.indexStep >= 0 },
	hasNextStep:	function () {return this.indexStep < this.getStepCount()-1},
	hasSteps:		function () {return this.getStepCount() > 0;},

	playAction: function (step, action, eventName) {
		if ($defined (action)) {
			// fire the event before calling fetch. this allows autoplayers to work correctly
			this.fireEvent (eventName,  {'indexStep':this.indexStep, 'indexAction':this.indexAction, 'step': step, 'action': action});
			this.mcPhone.fetch (action.tid);
		}
	},
	setCurrentActionIndex: function () {
		this.indexAction = null;
		if ($defined (this.indexStep)) {
			var step = this.tutorial.steps[this.indexStep];
			if (this.isInteractive() && $defined (step.actions) && step.actions.length > 0 ) {
				this.indexAction = step.actions.length-1;
			}
		}
	},
	getActionInfo: function () {
		var info = new Object();
		if ($defined (this.indexStep)) {
			var step = this.tutorial.steps [this.indexStep];
			info.indexStep	= this.indexStep;
			info.step		= step;

			if ( $defined (this.indexAction)) {
				info.indexAction	= this.indexAction;
				info.action			= step.actions [this.indexAction];
			}
		}
		return info;
	},
	getNextActionInfo: function (info) {

		var aIndex=0, sIndex=0;

		if ($defined (info)) {
			aIndex = ($defined (info.indexAction) ? info.indexAction + 1 : 0)
			if ($defined (info.indexStep)) {
				sIndex = ($defined (info.indexAction) ? info.indexStep : info.indexStep+1);
			}
		} else {
			aIndex = ($defined (this.indexAction) ? this.indexAction + 1 : 0);
			if ($defined (this.indexStep)) {
				sIndex = ($defined (this.indexAction) ? this.indexStep : this.indexStep+1);
			}
		}
		for (var i = sIndex, sLen = this.tutorial.steps.length; i < sLen; i++) {
			var step = this.tutorial.steps[i];
			if ($defined (step.actions)) {
				var len = step.actions.length;

				if (aIndex >= len) {
					aIndex = 0;
				} else {
					for (; aIndex < len; aIndex++) {
						if (step.actions[aIndex].kid.indexOf ('Timeout:', 0) == -1) {
							return {'indexStep':i, 'indexAction':aIndex, 'action': step.actions[aIndex], 'step': step};
						}
					}
					if (aIndex >= len) aIndex=0;
				}
			}
		}
		return {};
	},
	playActionInfo: function (info) { // if info is null, play the next action
		if (!$defined (info)) {
			info = this.getNextActionInfo ();
		}
		if (!$defined (info.action)) return;

		this.indexStep		= info.indexStep;
		this.indexAction	= info.indexAction;

		this.playAction (info.step,  info.action, 'onPlayAction');
	},
	onClick: function () {
		this.playActionInfo (null);
	},
	onRequest: function () { /*fired by McPhone*/
		this.currentButton = null;
		this.nextAction = this.getNextActionInfo ().action; /* cache it until onResponse */
	},
	onResponse: function () { /*fired by McPhone*/
		this.nextAction		= null;
		this.mcPhone.getMcButtons().animate();
	},
	buttonFilter: function (node) { /* called by McButtons return false to prevent button creation*/
		if (node.tagName != 'b' || $defined (this.currentButton) || !$defined(this.nextAction)) return false;

		var tid = this.mcPhone.getMcButtons().getTid(node);
		return (tid == this.nextAction.tid);
	},
	createButton: function (node) {
//		var kid						= node.getAttribute("k");
//		this.currentButton			= this.mcPhone.createButton (node);

	},
	doPlayNow:		function ()		{return this.options.playNow},
	hasTutorial:	function ()		{return this.tutorial != null;},
	getPhone:		function ()		{return this.mcPhone},
	getName:		function ()		{return this.tutorial.name},
	isImage:		function ()		{return this.tutorial.type =='image'},
	isFlash:		function ()		{return this.tutorial.type =='flash'},
	isInteractive:	function ()		{return this.tutorial.type =='interactive'},
	getFlashFile:	function (step)	{return $defined (step.media) ? step.media.flash : null},
	getImageFile:	function (step)	{return $defined (step.media) ? step.media.image : null}


});
McTutorial.implement(new Events, new Options, new McEvents);
/*
    Script: McPhoneRecorder.js

    Dependencies:
        --

    Class: McPhoneRecorder
*/

var McPhoneRecorder = new Class({
	tagType: {
		'menu'		: 'm',
		'group'		: 'g',
		'subGroup'	: 's'
	},
	mcPhone:		null,
	isRecording:	false,
	actionsUndo: 	new Array(),
	actionsRedo:	new Array(),

	initialize: function () {
		this.buttonHandlers = {
			onClick: this.onButtonClicked.bind(this)
		}
	},
	startRecording: function (mcPhone) {
		if (this.isRecording) return;

		this.mcPhone = mcPhone;
		this.mcPhone.getMcButtons().addEvent ('onClick', this.buttonHandlers.onClick);
		this.isRecording = true;
	},
	stopRecording: function () {
		if (!this.isRecording) return;
		this.getMcPhone().getMcButtons().removeEvent ('onClick', this.buttonHandlers.onClick);
		this.isRecording = false;
	},
	onButtonClicked: function (tid, kid, tagType) {
		var targetTid = null;
		var ix = this.actionsUndo.length - 1;

		if ( tagType == this.tagType.group || tagType == this.tagType.subGroup ) { // Group returns

			if ( ix >= 0 ) {
				targetTid		= this.actionsUndo[ix].tid;
				var curState	= this.getMcPhone().stateCache.get (targetTid );
				var curTag		= curState.getAttribute (tagType);

				for (ix--; ix >= 0; ix-- ) {
					var prevTid		= this.actionsUndo[ix].tid;
					var prevState	= this.getMcPhone().stateCache.get (prevTid);

					if ($defined( prevState ) ) {
						var prevTag = prevState.getAttribute (tagType);

						if ( prevTag != curTag ) {
							targetTid = prevTid;
							break;
						}
					}
				}
				if (ix < 0) {
					targetTid =  this.getMcPhone().tidRoot;
				}
			}
		} else {
			targetTid = tid;
		}
		if ( targetTid != null ){
			this.mcPhone.fetch (targetTid);
			this.actionsUndo.push ({"tid":targetTid, "kid":kid});
			this.actionsRedo.splice(0);
			this.fireEvent ("onNavigate", targetTid);
		}
	},
	undoAction: function () {
		if (!this.canUndo()) return;

		var action = this.actionsUndo.pop();
		this.actionsRedo.push (action);

		if (this.canUndo ()) {
			var tid = this.actionsUndo[this.actionsUndo.length-1].tid;
			this.getMcPhone().fetch (tid);
			this.fireEvent ("onNavigate", tid);

		} else {
			this.getMcPhone().fetchFirst ();
			this.fireEvent ("onNavigate", this.getMcPhone().tidRoot);
		}
	},
	redoAction: function () {
		if (!this.canRedo()) return;

		var action = this.actionsRedo.pop();
		this.actionsUndo.push (action);
		this.getMcPhone().fetch (action.tid);

		this.fireEvent ("onNavigate", action.tid);
	},
	goHome: function () {
		if (this.atHome()) return;
		this.actionsRedo.splice(0);
		if ($defined (this.getMcPhone().tidRoot)) {
			this.actionsUndo.push ({"tid":this.getMcPhone().tidRoot, "kid":"RootKey"});
		}
		this.getMcPhone().flipOpen ();
		this.getMcPhone().fetchFirst ();
		this.fireEvent ("onNavigate", this.getMcPhone().tidRoot);
	},
	canUndo:	function ()	{return this.actionsUndo.length > 0;},
	canRedo:	function ()	{return this.actionsRedo.length > 0;},
	atHome:		function () {return !this.canUndo()},
	getActions:	function () {return concat (this.actionsUndo, this.actionsRedo)},

	getMcPhone:	function ()	{return ($defined (this.mcPhone) ? this.mcPhone : (this.fetchFirst ? this : null));}

});
McPhoneRecorder.implement(new Events);

/********************* Begin McTutorialPlayer definition ********************************************/

var McTutorialPlayer = new Class({
	options: {
		elCurrentStep:	null,
		elPrevButton:	null,
		elNextButton:	null,
		elMediaWrapper:	null,
		onPlayStep:		Class.empty,
		onPlayTutorial:	Class.empty,
		onStopTutorial:	Class.empty,
		restoreFreeNav:	false,
		tutorialRoot:	null,
		swfobject:		null,
		defaultImage:	null
	},
	initialize: function (mcPhone, tutorial, elSteps, options) {
		this.setOptions(options);
		this.mcTutorial	= new McTutorial (mcPhone,  {
							'onPlayTutorial':	this.onPlayTutorial.bind (this),
							'onStopTutorial':	this.onStopTutorial.bind (this),
							'onPlayAction':		function(action)	{this.onPlayAction(action)}.bind(this),
							'onPlayStep':		function(step)		{this.onPlayStep(step)}.bind(this),
							'onPlayMedia':		function(step)		{this.onPlayMedia(step)}.bind(this),
							'onPlayStart':		function()			{this.onPlayStart()}.bind(this)
							} );
		if (this.options.onPlayTutorial != Class.empty) this.mcTutorial.addEvent ('onPlayTutorial', this.options.onPlayTutorial);
		if (this.options.onStopTutorial != Class.empty) this.mcTutorial.addEvent ('onStopTutorial', this.options.onStopTutorial);

		this.elSteps = $pick (elSteps,  []);

		this.elMediaWrapper	= $(this.options.elMediaWrapper);
		this.elPhoneWrapper	= $(mcPhone.getElement());

		this.tutorialRoot	= this.options.tutorialRoot || "";
		this.tutorialRoot	= (this.tutorialRoot.lastIndexOf("/") == this.tutorialRoot.length-1 ? this.tutorialRoot : this.tutorialRoot + "/");
		this.swfobject		= this.options.swfobject;

		this.mcTutorial.playTutorial (tutorial);

		if (this.elSteps.length == 1) {
			elSteps[0].setStyle ('cursor', 'auto');
			elSteps[0].removeClass ("select")
		}

	},
	elStepSelected: null,
	doHover: function (elStep) {
		if (this.elSteps.length > 1)
			$(elStep).addClass ("hover");
	},
	unHover: function (elStep) {
		if (this.elSteps.length > 1)
			$(elStep).removeClass ("hover");
	},
	doSelectStep: function (index) {
		var elStep = this.elSteps[index];
		if (!$defined (elStep)) return false;

		if ($defined (this.elStepSelected)) {
			if (elStep.id == this.elStepSelected.id) return;
			this.elStepSelected.removeClass("select");
		}
		this.indexStep		= index;
		this.elStepSelected = $(elStep);

		this.elStepSelected.addClass("select");
		this.elStepSelected.addClass("visited");

		this.fireEvent ('onPlayStep');


//		this.elStepSelected.parentNode.scrollTo(0, this.elStepSelected.scrollTop);

	},

	playStep: function (elStep) {
		var index = elStep.id.substr(5);

		if (0 > index || index > this.mcTutorial.tutorial.steps.length)	return;

		if (0 == index) {
			this.mcTutorial.firstStep();
		} else {
			this.mcTutorial.playStep (index);
		}
	},
	playPrevStep:	function ()	{this.mcTutorial.prevStep();},
	playFirstStep:	function ()	{this.mcTutorial.firstStep ();},
	playLastStep :	function ()	{this.mcTutorial.lastStep();},
	playNextStep:	function ()	{this.mcTutorial.nextStep();},
	play:			function () {this.mcTutorial.playTutorial();},
	stop:			function () {this.mcTutorial.stopTutorial();},

	onPlayAction: function () {
		var next	= this.mcTutorial.getNextActionInfo();
		var index	= ($defined (next.action) ? next.indexStep : this.mcTutorial.tutorial.steps.length);
		this.doSelectStep	(index);
		this.updateElements	(index);
	},

	onPlayStep: function (current) {
		var index = ($defined (current.indexStep) ? current.indexStep + 1 : 0);
		this.doSelectStep	(index);
		this.updateElements	(index);
	},

	onPlayMedia: function (current) {
		var index = Number(current.indexStep);
		this.doSelectStep	(index);
		this.updateElements	(index);

		if ($defined (this.elMediaWrapper)) {
			this.elMediaWrapper.empty()

			var flashFile = this.mcTutorial.getFlashFile (current.step);
			var imageFile = this.mcTutorial.getImageFile (current.step);

			if ($defined (this.swfobject) && $defined (flashFile)) { // inject flash if available
				var url = this.tutorialRoot + flashFile;

				var el		= new Element ("div",{'id': 'divFlashObj'}).inject (this.elMediaWrapper);
				var elTest	= this.elMediaWrapper;
				var width	= elTest.offsetWidth;

				while ((width == null || width == 0) && elTest != null) {
					elTest		= elTest.parentNode;
					width		= elTest.offsetWidth;
				}
				if (width == 0) width = 600;
				this.swfobject.embedSWF (url, el.id, width, "600", "9.0.0", false, false, {'wmode': 'transparent'});

			} else if ($defined (imageFile)) { // else, inject the defined image
				var url = this.tutorialRoot + imageFile;
				new Element ("img",{'src': url, 'alt': 'Tutorial Image'}).inject (this.elMediaWrapper);

			} else if ($defined (this.options.defaultImage)) { // else inject the default image
				new Element ("img",{'src': this.options.defaultImage, 'alt': 'Tutorial Image'}).inject (this.elMediaWrapper);
			}
			this.elPhoneWrapper.setStyle	('display', 'none');
			this.elMediaWrapper.setStyle	('display', 'block');
		}
	},
	onPlayStart: function () {
		this.doSelectStep	(0);
		this.updateElements (0);
	},
	onPlayTutorial: function () { /* fired by McTutorial */
		this.tidFreeNav = this.mcTutorial.mcPhone.getCurrentTid();
		this.mcTutorial.firstStep();
	},
	onStopTutorial: function () { /* fired by McTutorial */
		if (this.options.restoreFreeNav) this.mcTutorial.mcPhone.fetch (this.tidFreeNav);
	},
	updateElements:	function (index) {
		if ($defined (this.options.elCurrentStep)) this.options.elCurrentStep.innerHTML = index+1;
		if ($defined (this.options.elPrevButton)) {
			if (this.hasPrevStep()) {
				this.options.elPrevButton.removeClass ('disabled');
			} else {
				this.options.elPrevButton.addClass ('disabled');
			}
		}
		if ($defined (this.options.elNextButton)) {
			if (this.hasNextStep()) {
				this.options.elNextButton.removeClass ('disabled');
			} else {
				this.options.elNextButton.addClass ('disabled');
			}
		}
	},
	hasPrevStep:	function () {return this.indexStep > 0},
	hasNextStep:	function () {return this.indexStep < this.elSteps.length-1},
	getTutorial:	function () {return this.mcTutorial},
	isInteractive:	function ()	{return this.mcTutorial.isInteractive()}
});
McTutorialPlayer.implement(new Options, new Events);
/*
    Script: McTaggedTutorials.js

    Dependencies:
        mootools.js

    Class: McTaggedTutorials
*/

var McTaggedTutorials = new Class({
	options: {
		contextRoot:	'',
		elMsgNone:		null,
		elMsgSome:		null,
		elLoading:		null
	},
	changed:		false,
	tagId:			-1,
	firstRequest:	true,
	viewUrl:		null,

	initialize: function(mcPhone, elWrapper, deviceId, options) {
		this.setOptions(options);

		this.elWrapper		= $(elWrapper);
		this.contextRoot	= this.options.contextRoot || '';
		this.deviceId		= deviceId;

		this.elList			= new Element ('ul').inject (this.elWrapper);

		this.xhr		= new XHR ({'method': 'get', 'autoCancel' : true, 'onSuccess': this.onSuccess.bind(this), 'onFailure' : this.onFailure.bind(this)});
		this.url		= this.contextRoot + '/ajax/tutorial?deviceId=' + deviceId + '&tagId=';

		if ($defined (this.options.view)) {
			var queryString	= $defined (this.options.view.params) ?  '?' + Object.toQueryString (this.options.view.params) + '&tutorialId=' : '?tutorialId=';
			this.viewUrl	= this.contextRoot + this.options.view.page + queryString;
		}
        this.mcPhone        = mcPhone;
		mcPhone.addEvent ('onNodeTag',	this.onNodeTag.bind(this));
		mcPhone.addEvent ('onRequest',	this.onRequest.bind(this));
		mcPhone.addEvent ('onResponse',	this.onResponse.bind(this));
	},
	onNodeTag: function (node) { /* fired by McPhone*/
		if (node.tagName != 'g') return;

		var id = node.getAttribute("i");
		if (id != this.tagId) {
			this.tagId = (id == null ? -1 : id);
			this.changed = true;
		}
	},
	onRequest: function () {
		this.changed = false;
	},
	onResponse: function () {
		if (this.changed) this.load (this.tagId);
	},
	load: function (tagId) {

		if (this.elWrapper)			this.elWrapper.setStyle	('display', 'none');
		if (this.options.elMsgNone)	this.options.elMsgNone.setStyle	('display', 'none');
		if (this.options.elMsgSome)	this.options.elMsgSome.setStyle	('display', 'none');
		if (this.options.elLoading)	this.options.elLoading.setStyle	('display', 'block');
        if (this.mcPhone.fetchStrategy != 'html') {  // Check McPhone.js for valid fetchStrategies...
            this.xhr.send (this.url + tagId);
        }
	},
	onFailure: function () {
		if (this.options.elLoading)	this.options.elLoading.setStyle	('display', 'none');
		if (this.options.elMsgNone)	this.options.elMsgNone.setStyle	('display', 'block');
	},
	onSuccess: function (text, xml) {
		if (this.options.elLoading)	this.options.elLoading.setStyle	('display', 'none');

		if (xml == null || xml.firstChild == null)
			return;

		this.elWrapper.removeChild (this.elList);
		this.elList.empty();

		var nodes = xml.firstChild.childNodes;
		if (nodes.length == 0) {
			if (this.options.elMsgNone)	this.options.elMsgNone.setStyle	('display', 'block');

		} else {
			for (var i=0, len= nodes.length; i < len; i++) {
				var node = $(nodes[i]);
				new Element ("a", {'href': this.viewUrl + node.getAttribute ('id')}).appendText (node.getText()).inject (new Element ("li").inject (this.elList));
			};
			this.elWrapper.setStyle ('display', 'block');
			if (this.options.elMsgSome)	this.options.elMsgSome.setStyle	('display', 'block');
		}
		this.elList.inject (this.elWrapper);
	}
});
McTaggedTutorials.implement(new Options, new Events);/*
    Script: McAjaxWinModal.js

    Dependencies:
        mootools.js, cnet.js

    Class: McAjaxWinModal

*/

var McAjaxWinModal = new Class({
	options: {
		fnInit:	Class.empty,
		fnSend:		Class.empty,
		fnDone:		Class.empty
	},
	initialize: function (elPopup, idForm, idSending, options) {
		this.setOptions(options);

		this.idForm		= idForm; // just keep the ids, since the elements disappear on re-render
		this.idSending	= idSending;

		this.win = new StickyWinFxModal({
						position:			'center',
						allowMultiple:		false,
						content:			elPopup,
						draggable:			true,
						showNow:			false,
						dragHandleSelector:	'h1.moduleTitle',
						useIframeShim:		true,
						fadeDuration:		300,
						modalOptions: {
							hideOnClick: false,
							modalStyle: {'position':'absolute'}
						}
					});
		this.evKeydown = this.keydown.bind (this);
	},

	init: function () {
		this.options.fnInit();
		if (this.options.fnInit == Class.empty) {
			this.onInit();
		}
	},
	onInit: function () {
		this.status = 'init';
		this.win.show ();
		document.addEvent('keydown', this.evKeydown );

		var inputs = this.win.options.content.getElementsByTagName('input');
		for (var i=0; i<inputs.length; i++) {
			if (inputs[i].type=='text') {
				this.setFocus(inputs[i].id);
				break;
			}
		}

	},
	send: function () { // deprecated, call execute instead.
		this.status = 'send';
		this.showHide (this.idSending, this.idForm);
		this.options.fnSend();
	},
	onSend: function () {
		this.showHide (this.idForm, this.idSending);
		var errors = this.win.options.content.getElementsByClassName ('error');

		if ($defined (errors) && errors.length > 0) {
			this.status = 'init';
			for (var i=0; i<errors.length; i++) {
				var error = errors[i];
				if (error.tagName=='label') {
					this.setFocus (error.htmlFor);
					break;
				}
			}
		} else {
			this.status = 'sent';
		}

	},
	done: function () {
		if (status == 'done') return;
		document.removeEvent (this.evKeydown);
		this.status = 'done';
		this.options.fnDone();
		this.win.hide();
	},
	onDone: function () {
	},
	showHide: function (idShow, idHide) {
		var elShow	= $(idShow);
		var elHide	= $(idHide);
		if ($defined(elHide))	elHide.setStyle	('display', 'none');
		if ($defined(elShow))	elShow.setStyle	('display', 'block');
	},
	execute: function (target, fn) {
		if (!$defined (target)) return;
		while ($defined(target) && target != this.win.options.content) target = target.getParent();
		if (target == this.win.options.content) fn();
	},
	keydown: function (arg) {
		if (arg.keyCode == 13) { //enter key
			if (this.status=='init') this.execute (arg.target, this.send.bind(this));

		} else if (arg.keyCode == 27) { //escape key
			if (this.status != 'done') this.execute (arg.target,  this.done.bind (this));
		}
	},
	setFocus: function (idFocus) {
		var elFocus = $(idFocus);
		if ($defined (elFocus)) elFocus.focus();
	}
});
McAjaxWinModal.implement(new Options);/*
    Script: McDynamicLayout.js

    Dependencies:
        --

    Class: McPhoneLayout

*/

var McDynamicLayout = new Class ({
	options: {
		fudgeWidth:				30,
		fudgeHeight:			20,
		minWidth:				0,	// min width for elTarget.
		minHeight:				0,	// min height for elTarget.
		maxWidth:				0,
		maxHeight:				0,
		doWidth:				true,
		doHeight:				true,
		adjustIfBigger:			true, // only do the adjustment if the target is bigger
		adjustIfSmaller:		false  // only do the adjustment if the target is smaller

	},
	initialize : function (elSource, elTarget,  options) {
		this.setOptions(options);

		this.elSource	= $(elSource);
		this.elTarget	= $(elTarget);
		this.elWrapper	= this.elTarget.getParent();
		this.siblings	= null;
		this.adjust ();
	},
	calculatedWidth:-1,
	calculatedHeight:-1,
	initialSize: null,

	adjust: function () {
		if (this.options.doWidth)	this.adjustWidth();
		if (this.options.doHeight)	this.adjustHeight();

	},
	adjustWidth :	function () {
		var sourceWidth	= this.getBoundedWidth (this.getSourceWidth());
		var totalWidth	= sourceWidth;

		if (sourceWidth == 0) {
			setTimeout (this.adjustWidth.bind(this), 250);
			return;
		}
		this.getSiblings().each ( function(elSibling) {
			totalWidth += this.getPaneWidth (elSibling);
		}.bind(this));

		var paneMargins = this.getPaneWidth (this.getTarget()) - this.getTarget().offsetWidth;

		totalWidth	+= (paneMargins + this.options.fudgeWidth); //fudge factor

		this.getTarget().setStyle 	('width', sourceWidth);
		this.getWrapper().setStyle	('width', totalWidth);

		this.calculatedWidth = totalWidth;
	},
	adjustHeight:	function () {
		var targetHeight	= this.getTarget().offsetHeight;
		if (this.isBoundedHeight (targetHeight)) return;

		var wrapperHeight		= this.getWrapper().offsetHeight;
		var sourceHeight		= this.getBoundedHeight (this.getSourceHeight ());

		var wrapperDelta		= wrapperHeight	- targetHeight;
		var newTargetHeight		= sourceHeight	- wrapperDelta;
		var newWrapperHeight	= wrapperHeight	- (targetHeight - newTargetHeight)

		this.setStyles ('height', this.getTarget(), newTargetHeight, sourceHeight);
		this.setStyles ('height', this.getWrapper(), newWrapperHeight, wrapperHeight);

		this.calculatedHeight	= newWrapperHeight;

	},
	setStyles: function (style, elTarget, targetValue, sourceValue){
		var adjust = (targetValue < sourceValue && this.options.adjustIfSmaller) ||
					 (targetValue > sourceValue && this.options.adjustIfBigger);
		if (adjust) {
			elTarget.setStyle 	(style, targetValue);
		}
	},
	getSource:		function () {return this.elSource},
	getTarget:		function () {return this.elTarget},
	getWrapper:		function () {return this.elTarget.getParent()},
	getSiblings:	function () {
		if ($defined (this.siblings)) return this.siblings;

		this.siblings	= new Array();
		var elChildren	= this.getWrapper().getChildren();
		elChildren.each (function (el) {
			if (el != this.elTarget && $type(el) == 'element') this.siblings.push (el);
		}.bind (this));
		return this.siblings;
	},
	getPaneWidth:	function (el) {
		var width		= el.offsetWidth;
		var marginLeft	= el.getStyle ('margin-left').toInt();
		var marginRight	= el.getStyle ('margin-right').toInt();
		if (marginLeft != 'NaN') width += marginLeft;
		if (marginRight != 'NaN') width += marginRight;
		return width;
	},
	/* return the greater of the phone width, or the minWidth option */
	getSourceWidth:		function () {return (this.elSource ? this.elSource.offsetWidth : 0)},
	getSourceHeight:	function () {return (this.elSource ? this.elSource.offsetHeight : 0)},

	getBoundedWidth:	function (width) {
		if (this.options.minWidth > width)
			width =  this.options.minWidth;
		if (this.options.maxWidth > 0 && width > this.options.maxWidth)
			width = this.options.maxWidth;
		return width;
	},
	getBoundedHeight: function (height) {
		if (this.options.minHeight > height)
			height =  this.options.minHeight;
		if (this.options.maxHeight > 0 && height > this.options.maxHeight)
			height = this.options.maxHeight;
		return height;
	},
	isBoundedWidth:		function (width)	{return width >= this.options.minWidth && width <= this.options.maxWidth},
	isBoundedHeight:	function (height)	{return height >= this.options.minHeight && height <= this.options.maxHeight}
});

McDynamicLayout.implement(new Options);

var McDynamicWidthLayout = McDynamicLayout.extend ({
	initialize: function (elSource, elTarget,  options) {
		this.parent (elSource, elTarget, $merge (options, {'doHeight':false}) ); /* will call the previous initialize; */
	}
});

// All this one does is adjust the width of the parent of elSource
var McWrapperWidthLayout = McDynamicLayout.extend ({
	initialize: function (elSource, options) {
		this.parent (elSource, elSource, $merge (options, {'doHeight':false}) ); /* will call the previous initialize; */
	}
});

var McScrollHeightLayout = McDynamicLayout.extend ({
	initialize: function (elSource, elTarget, options) {
		this.parent (elSource, elTarget, $merge (options, {'doWidth':false}) ); /* will call the previous initialize; */
	},
	adjustHeight: function () {
		var wrapperHeight	= this.getWrapper().offsetHeight;
		var sourceHeight	= this.getBoundedHeight (this.getSourceHeight ());
		var targetHeight	= this.getTarget().offsetHeight;

		if (sourceHeight < targetHeight) {
			var wrapperDelta		= wrapperHeight	- targetHeight;
			var newTargetHeight		= sourceHeight	- wrapperDelta;
			var newWrapperHeight	= wrapperHeight	- (targetHeight - newTargetHeight)

			this.getTarget().setStyle 	('height', newTargetHeight);
			this.getWrapper().setStyle  ('height', newWrapperHeight);
			this.getTarget().setStyle	('overflow-y', 'scroll');

		} else {
			this.getTarget().setStyle ('overflow-y', 'hidden');
		}
	}
});
/*
    Script: McPhonePageLayout.js

    Dependencies:
        McDynamicLayout.js

    Class: McPhoneWidthLayout

*/

var McPhoneLayout = McDynamicLayout.extend ({
	mcPhone: null,
	initialize: function (mcPhone, elTarget, options) {
		this.mcPhone = mcPhone;

		this.parent (mcPhone.getElement(), elTarget, options); /* will call the previous initialize; */

		if (this.options.adjustOnFlip) this.mcPhone.addEvent ('onFlip', this.adjust.bind (this));
	}
});

var McPhoneWidthLayout = McPhoneLayout.extend ({
	initialize: function (mcPhone, elTarget, options) {
		this.parent (mcPhone, elTarget, $merge (options, {'doHeight':false}) ); /* will call the previous initialize; */
	}
});

var McPhoneHeightLayout = McPhoneLayout.extend ({
	initialize: function (mcPhone, elTarget, options) {
		this.parent (mcPhone, elTarget, $merge (options, {'doWidth':false}) ); /* will call the previous initialize; */
	}
});
/*
    Script: McEndpointPopup.js

    Dependencies:
        --

    Class: McPhoneRecorder.js
*/
var McEndpointPopup = new Class({
	initialize: function (mcPhone, mcPhoneRecorder, elPopup) {
		this.mcPhoneRecorder = mcPhoneRecorder;

		this.mcEndpointPopup = new StickyWinFx({	position:			'center',
													edge:				'center',
													relativeTo:			this.mcPhoneRecorder.mcPhone.getElement(),
													offset:				{x:0, y:0},
													content:			$(elPopup),
													showNow:			false,
													useIframeShim:		false,
													fadeDuration:		1000,
													fade: 				false
												});
		mcPhone.addEvent ('onEndpointNode',	this.showPopup.bind(this));
		mcPhone.addEvent ('onRequest', 		this.hidePopup.bind(this));
	},
	goingBack: false,
	showPopup: function () {
		if (!this.goingBack) this.mcEndpointPopup.show();
		this.goingBack = false;
	},
	hidePopup: function () {
		if (this.mcEndpointPopup.visible)
			this.mcEndpointPopup.hide();
	},
	goBack: function () {
		if ($defined (this.mcPhoneRecorder)) {
			this.goingBack = true;
			this.mcPhoneRecorder.undoAction();
			this.hidePopup();
		}
	}
});
var McSlidingDialog = new Class({
	options: {
		left: 200,
		top: 200
	},
	initialize: function (mcPhone, elWrapper, options) {
		this.setOptions (options);

		this.elWrapper	= $(elWrapper);
		this.hideEffect	= this.elWrapper.effect('opacity',	{duration: 1000, transition: Fx.Transitions.linear});
		this.showEffect	= new Fx.Styles(this.elWrapper,		{duration: 2000, transition: Fx.Transitions.Back.easeOut});

		this.handlers	= {onRequest: this.onRequest.bind (this)}
		mcPhone.addEvents (this.handlers);
	},
	onRequest:	function (mcPhone)	{mcPhone.removeEvents (this.handlers); this.hide();},
	hide:		function () {
		if (this.elWrapper.getStyle('display') != 'none') {
			this.hideEffect.start (1,0).chain (function() {this.elWrapper.setStyle('display', 'none')}.bind(this));
		}
	},
	show: function () {this.showEffect.start ({"top": this.options.top, "left": this.options.left});}
});
McSlidingDialog.implement(new Options);/* The default styles for this toolbar are defined in /css/styles.css */
var McToolbar = new Class({
	options: {
		className:		'toolbar',
		hasEndCaps:		false,
		buttons:		[]
	},
	initialize: function(elWrapper, options) {
		this.setOptions(options);

		this.elWrapper			= $(elWrapper);

		var ulToolbar			= new Element ("ul");
		ulToolbar.className		= this.options.className + ' clearfix';

		if (this.options.hasEndCaps) {
			new Element ("li", {'class': 'btnEndCap btnLeft'} ).inject(ulToolbar);
			this.buttons = this.createButtons (ulToolbar);
			new Element ("li", {'class': 'btnEndCap btnRight'} ).inject(ulToolbar);

		} else {
			this.buttons = this.createButtons (ulToolbar);
		}
		ulToolbar.inject (this.elWrapper);
	},
	createButtons: function (ulToolbar) {
		var buttons = new Array();

		for (var buttonName in this.options.buttons) {
			var optButton = this.options.buttons[buttonName];

			if ($defined (optButton.onclick) && optButton.onclick != Class.empty) {
				var className = 'btn' + buttonName;
				if ($defined (optButton.separate)) {
					className = "separate " + className;
				}
				var button = new Element ("li").inject(ulToolbar);
				button.className	= className;
				button.title		= $defined (optButton.title) ? optButton.title : buttonName;
				button.onclick		= optButton.onclick;

				if (window.ActiveXObject) { // fake out IE hover
					button.onmouseover	= this.onmouseover;
					button.onmouseout	= this.onmouseout;
				}
				buttons[buttonName] = button;
			}
		}
		return buttons;
	},
	disableButton: function (button) {
		var el = this.getButton (button);
		if (!$defined (el)) return;

		if (el.className.search (new RegExp ("Disabled\\b")) == -1) { // if not already disabled
			el.className+="Disabled";
		}
	},
	enableButton: function (button) {
		var el = this.getButton (button);
		if (!$defined (el)) return;

		el.className = el.className.replace(new RegExp ("Disabled\\b"), "");
	},
	setButtonEnabled: function (button, enabled) {
		if (enabled) {this.enableButton (button)} else {this.disableButton (button)}
	},
	onmouseover: function () {
		if (!this.className.search ( new RegExp ("Disabled\\b")) == -1) { // if not disabled
			this.className+="Hover";
		}
	},
	onmouseout: function () {
		this.className = this.className.replace( new RegExp ("Hover\\b"), "");
	},
	getButton: function (button) {
		var elType = $type (button);
		if (elType=='string') {
			return this.buttons[button];
		} else {
			return button;
		}
	}


});
McToolbar.implement(new Events, new Options);/*
    Script: McPhoneToolbar.js

    Dependencies:
        McToolbar.js

    Class: McPhoneToolbar

    The default styles for this toolbar are defined in /css/styles.css
*/

var McPhoneToolbar = McToolbar.extend({
	initialize: function (mcPhoneRecorder, elWrapper, options) {

		this.handlers = {
			phone: {	onRequest:	this.onRequest.bind		(this),
						onNodeFlip:	this.onNodeFlip.bind	(this)
					},
			button: {	onPrev: this.onPrev.bind	(this),
						onNext: this.onNext.bind	(this),
						onHome: this.onHome.bind	(this),
						onShow: this.onShow.bind	(this),
						onFlip: this.onFlip.bind	(this)
					},
			record:	{	onNav: this.onNavigate.bind(this)}
		}

		var buttonCount = 4;
		options = $merge ({	className:	'phoneToolbar clearfix',
							hasEndCaps:	true,
							allowFlip:	true,
							buttons: {
								'Prev':	{'onclick': this.handlers.button.onPrev, 'title': 'Go to the previous screen' },
								'Next':	{'onclick': this.handlers.button.onNext, 'title': 'Go to the next screen' },
								'Home':	{'onclick': this.handlers.button.onHome, 'title': 'Go to the Home page for this phone' },
								'Show':	{'onclick': this.handlers.button.onShow, 'title': 'Show all active buttons on this phone'}
						}}, options);

		if (options.allowFlip && mcPhoneRecorder.mcPhone.supportsFlip()) {
			options.buttons = $merge (options.buttons,	{
					'Flip':	{'onclick': this.handlers.button.onFlip,	'title': 'Open or close this phone.'}
				});
			buttonCount++;
		}
		options.className		= (options.className + " btnCount_" + buttonCount);
		this.mcPhoneRecorder	= mcPhoneRecorder;
		mcPhoneRecorder.addEvent ('onNavigate', this.handlers.record.onNav);

		this.parent (elWrapper, options);

		this.onNavigate (); // disables all nav buttons to begin with (except the Flip button).

		if (this.options.allowFlip) {
			if (mcPhoneRecorder.mcPhone.isDual() || mcPhoneRecorder.mcPhone.isRotate()) {
				this.enableFlip (false);
				mcPhoneRecorder.mcPhone.addEvents (this.handlers.phone);

			} else if (mcPhoneRecorder.mcPhone.supportsFlip()) {
				this.enableFlip (true);
			}
		}
	},
	tidFlip:	null,
	onNodeFlip: function (node) { /* fired by McPhone*/
		if (node.tagName != 'm') return;

		var kid = node.getAttribute("k");
		var tid = node.getAttribute("t");
		this.tidFlip = tid;
		this.enableFlip (true);
	},
	onNavigate: function (tid) {
		if (! this.mcPhoneRecorder.canUndo())	{ this.disableButton (this.buttons['Prev'])	} else { this.enableButton (this.buttons['Prev']) };
		if (! this.mcPhoneRecorder.canRedo())	{ this.disableButton (this.buttons['Next'])	} else { this.enableButton (this.buttons['Next']) };
		if (  this.mcPhoneRecorder.atHome ())	{ this.disableButton (this.buttons['Home'])	} else { this.enableButton (this.buttons['Home']) };
	},
	enableFlip: function (enable) {
		var btnFlip = this.buttons['Flip'];
		if (btnFlip != null && this.mcPhoneRecorder.mcPhone.supportsFlip()) {
			enable ? this.enableButton (btnFlip) : this.disableButton (btnFlip);
		}
	},
	onRequest: function () {this.enableFlip (false);},
	onPrev: function () {this.mcPhoneRecorder.undoAction();},
	onNext: function () {this.mcPhoneRecorder.redoAction();},
	onHome: function () {this.mcPhoneRecorder.goHome();},
	onFlip: function () {this.mcPhoneRecorder.mcPhone.flip(this.tidFlip, ['toState', 'buttons', 'timeouts', 'nextStates'])},
	onShow: function () {this.mcPhoneRecorder.mcPhone.getMcButtons().toggleAnimation();}
});
/*!	SWFObject v2.0 <http://code.google.com/p/swfobject/>
	Copyright (c) 2007 Geoff Stearns, Michael Williams, and Bobby van der Sluis
	This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
*/

var swfobject = function() {
	
	var UNDEF = "undefined",
		OBJECT = "object",
		SHOCKWAVE_FLASH = "Shockwave Flash",
		SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
		FLASH_MIME_TYPE = "application/x-shockwave-flash",
		EXPRESS_INSTALL_ID = "SWFObjectExprInst",
		
		win = window,
		doc = document,
		nav = navigator,
		
		domLoadFnArr = [],
		regObjArr = [],
		timer = null,
		storedAltContent = null,
		storedAltContentId = null,
		isDomLoaded = false,
		isExpressInstallActive = false;
	
	/* Centralized function for browser feature detection
		- Proprietary feature detection (conditional compiling) is used to detect Internet Explorer's features
		- User agent string detection is only used when no alternative is possible
		- Is executed directly for optimal performance
	*/	
	var ua = function() {
		var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF && typeof doc.appendChild != UNDEF && typeof doc.replaceChild != UNDEF && typeof doc.removeChild != UNDEF && typeof doc.cloneNode != UNDEF,
			playerVersion = [0,0,0],
			d = null;
		if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
			d = nav.plugins[SHOCKWAVE_FLASH].description;
			if (d) {
				d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
				playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
				playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
				playerVersion[2] = /r/.test(d) ? parseInt(d.replace(/^.*r(.*)$/, "$1"), 10) : 0;
			}
		}
		else if (typeof win.ActiveXObject != UNDEF) {
			var a = null, fp6Crash = false;
			try {
				a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".7");
			}
			catch(e) {
				try { 
					a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".6");
					playerVersion = [6,0,21];
					a.AllowScriptAccess = "always";  // Introduced in fp6.0.47
				}
				catch(e) {
					if (playerVersion[0] == 6) {
						fp6Crash = true;
					}
				}
				if (!fp6Crash) {
					try {
						a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
					}
					catch(e) {}
				}
			}
			if (!fp6Crash && a) { // a will return null when ActiveX is disabled
				try {
					d = a.GetVariable("$version");  // Will crash fp6.0.21/23/29
					if (d) {
						d = d.split(" ")[1].split(",");
						playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
					}
				}
				catch(e) {}
			}
		}
		var u = nav.userAgent.toLowerCase(),
			p = nav.platform.toLowerCase(),
			webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
			ie = false,
			windows = p ? /win/.test(p) : /win/.test(u),
			mac = p ? /mac/.test(p) : /mac/.test(u);
		/*@cc_on
			ie = true;
			@if (@_win32)
				windows = true;
			@elif (@_mac)
				mac = true;
			@end
		@*/
		return { w3cdom:w3cdom, pv:playerVersion, webkit:webkit, ie:ie, win:windows, mac:mac };
	}();

	/* Cross-browser onDomLoad
		- Based on Dean Edwards' solution: http://dean.edwards.name/weblog/2006/06/again/
		- Will fire an event as soon as the DOM of a page is loaded (supported by Gecko based browsers - like Firefox -, IE, Opera9+, Safari)
	*/ 
	var onDomLoad = function() {
		if (!ua.w3cdom) {
			return;
		}
		addDomLoadEvent(main);
		if (ua.ie && ua.win) {
			try {  // Avoid a possible Operation Aborted error
				doc.write("<scr" + "ipt id=__ie_ondomload defer=true src=//:></scr" + "ipt>"); // String is split into pieces to avoid Norton AV to add code that can cause errors 
				var s = getElementById("__ie_ondomload");
				if (s) {
					s.onreadystatechange = function() {
						if (this.readyState == "complete") {
							this.parentNode.removeChild(this);
							callDomLoadFunctions();
						}
					};
				}
			}
			catch(e) {}
		}
		if (ua.webkit && typeof doc.readyState != UNDEF) {
			timer = setInterval(function() { if (/loaded|complete/.test(doc.readyState)) { callDomLoadFunctions(); }}, 10);
		}
		if (typeof doc.addEventListener != UNDEF) {
			doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, null);
		}
		addLoadEvent(callDomLoadFunctions);
	}();
	
	function callDomLoadFunctions() {
		if (isDomLoaded) {
			return;
		}
		if (ua.ie && ua.win) { // Test if we can really add elements to the DOM; we don't want to fire it too early
			var s = createElement("span");
			try { // Avoid a possible Operation Aborted error
				var t = doc.getElementsByTagName("body")[0].appendChild(s);
				t.parentNode.removeChild(t);
			}
			catch (e) {
				return;
			}
		}
		isDomLoaded = true;
		if (timer) {
			clearInterval(timer);
			timer = null;
		}
		var dl = domLoadFnArr.length;
		for (var i = 0; i < dl; i++) {
			domLoadFnArr[i]();
		}
	}
	
	function addDomLoadEvent(fn) {
		if (isDomLoaded) {
			fn();
		}
		else { 
			domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
		}
	}
	
	/* Cross-browser onload
		- Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
		- Will fire an event as soon as a web page including all of its assets are loaded 
	 */
	function addLoadEvent(fn) {
		if (typeof win.addEventListener != UNDEF) {
			win.addEventListener("load", fn, false);
		}
		else if (typeof doc.addEventListener != UNDEF) {
			doc.addEventListener("load", fn, false);
		}
		else if (typeof win.attachEvent != UNDEF) {
			win.attachEvent("onload", fn);
		}
		else if (typeof win.onload == "function") {
			var fnOld = win.onload;
			win.onload = function() {
				fnOld();
				fn();
			};
		}
		else {
			win.onload = fn;
		}
	}
	
	/* Main function
		- Will preferably execute onDomLoad, otherwise onload (as a fallback)
	*/
	function main() { // Static publishing only
		var rl = regObjArr.length;
		for (var i = 0; i < rl; i++) { // For each registered object element
			var id = regObjArr[i].id;
			if (ua.pv[0] > 0) {
				var obj = getElementById(id);
				if (obj) {
					regObjArr[i].width = obj.getAttribute("width") ? obj.getAttribute("width") : "0";
					regObjArr[i].height = obj.getAttribute("height") ? obj.getAttribute("height") : "0";
					if (hasPlayerVersion(regObjArr[i].swfVersion)) { // Flash plug-in version >= Flash content version: Houston, we have a match!
						if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements
							fixParams(obj);
						}
						setVisibility(id, true);
					}
					else if (regObjArr[i].expressInstall && !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac)) { // Show the Adobe Express Install dialog if set by the web page author and if supported (fp6.0.65+ on Win/Mac OS only)
						showExpressInstall(regObjArr[i]);
					}
					else { // Flash plug-in and Flash content version mismatch: display alternative content instead of Flash content
						displayAltContent(obj);
					}
				}
			}
			else {  // If no fp is installed, we let the object element do its job (show alternative content)
				setVisibility(id, true);
			}
		}
	}
	
	/* Fix nested param elements, which are ignored by older webkit engines
		- This includes Safari up to and including version 1.2.2 on Mac OS 10.3
		- Fall back to the proprietary embed element
	*/
	function fixParams(obj) {
		var nestedObj = obj.getElementsByTagName(OBJECT)[0];
		if (nestedObj) {
			var e = createElement("embed"), a = nestedObj.attributes;
			if (a) {
				var al = a.length;
				for (var i = 0; i < al; i++) {
					if (a[i].nodeName.toLowerCase() == "data") {
						e.setAttribute("src", a[i].nodeValue);
					}
					else {
						e.setAttribute(a[i].nodeName, a[i].nodeValue);
					}
				}
			}
			var c = nestedObj.childNodes;
			if (c) {
				var cl = c.length;
				for (var j = 0; j < cl; j++) {
					if (c[j].nodeType == 1 && c[j].nodeName.toLowerCase() == "param") {
						e.setAttribute(c[j].getAttribute("name"), c[j].getAttribute("value"));
					}
				}
			}
			obj.parentNode.replaceChild(e, obj);
		}
	}
	
	/* Fix hanging audio/video threads and force open sockets and NetConnections to disconnect
		- Occurs when unloading a web page in IE using fp8+ and innerHTML/outerHTML
		- Dynamic publishing only
	*/
	function fixObjectLeaks(id) {
		if (ua.ie && ua.win && hasPlayerVersion("8.0.0")) {
			win.attachEvent("onunload", function () {
				var obj = getElementById(id);
				if (obj) {
					for (var i in obj) {
						if (typeof obj[i] == "function") {
							obj[i] = function() {};
						}
					}
					obj.parentNode.removeChild(obj);
				}
			});
		}
	}
	
	/* Show the Adobe Express Install dialog
		- Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
	*/
	function showExpressInstall(regObj) {
		isExpressInstallActive = true;
		var obj = getElementById(regObj.id);
		if (obj) {
			if (regObj.altContentId) {
				var ac = getElementById(regObj.altContentId);
				if (ac) {
					storedAltContent = ac;
					storedAltContentId = regObj.altContentId;
				}
			}
			else {
				storedAltContent = abstractAltContent(obj);
			}
			if (!(/%$/.test(regObj.width)) && parseInt(regObj.width, 10) < 310) {
				regObj.width = "310";
			}
			if (!(/%$/.test(regObj.height)) && parseInt(regObj.height, 10) < 137) {
				regObj.height = "137";
			}
			doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
			var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
				dt = doc.title,
				fv = "MMredirectURL=" + win.location + "&MMplayerType=" + pt + "&MMdoctitle=" + dt,
				replaceId = regObj.id;
			// For IE when a SWF is loading (AND: not available in cache) wait for the onload event to fire to remove the original object element
			// In IE you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
			if (ua.ie && ua.win && obj.readyState != 4) {
				var newObj = createElement("div");
				replaceId += "SWFObjectNew";
				newObj.setAttribute("id", replaceId);
				obj.parentNode.insertBefore(newObj, obj); // Insert placeholder div that will be replaced by the object element that loads expressinstall.swf
				obj.style.display = "none";
				win.attachEvent("onload", function() { obj.parentNode.removeChild(obj); });
			}
			createSWF({ data:regObj.expressInstall, id:EXPRESS_INSTALL_ID, width:regObj.width, height:regObj.height }, { flashvars:fv }, replaceId);
		}
	}
	
	/* Functions to abstract and display alternative content
	*/
	function displayAltContent(obj) {
		if (ua.ie && ua.win && obj.readyState != 4) {
			// For IE when a SWF is loading (AND: not available in cache) wait for the onload event to fire to remove the original object element
			// In IE you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
			var el = createElement("div");
			obj.parentNode.insertBefore(el, obj); // Insert placeholder div that will be replaced by the alternative content
			el.parentNode.replaceChild(abstractAltContent(obj), el);
			obj.style.display = "none";
			win.attachEvent("onload", function() { obj.parentNode.removeChild(obj); });
		}
		else {
			obj.parentNode.replaceChild(abstractAltContent(obj), obj);
		}
	}	

	function abstractAltContent(obj) {
		var ac = createElement("div");
		if (ua.win && ua.ie) {
			ac.innerHTML = obj.innerHTML;
		}
		else {
			var nestedObj = obj.getElementsByTagName(OBJECT)[0];
			if (nestedObj) {
				var c = nestedObj.childNodes;
				if (c) {
					var cl = c.length;
					for (var i = 0; i < cl; i++) {
						if (!(c[i].nodeType == 1 && c[i].nodeName.toLowerCase() == "param") && !(c[i].nodeType == 8)) {
							ac.appendChild(c[i].cloneNode(true));
						}
					}
				}
			}
		}
		return ac;
	}
	
	/* Cross-browser dynamic SWF creation
	*/
	function createSWF(attObj, parObj, id) {
		var r, el = getElementById(id);
		if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
			attObj.id = id;
		}
		if (ua.ie && ua.win) { // IE, the object element and W3C DOM methods do not combine: fall back to outerHTML
			var att = "";
			for (var i in attObj) {
				if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries, like Object.prototype.toJSONString = function() {}
					if (i == "data") {
						parObj.movie = attObj[i];
					}
					else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
						att += ' class="' + attObj[i] + '"';
					}
					else if (i != "classid") {
						att += ' ' + i + '="' + attObj[i] + '"';
					}
				}
			}
			var par = "";
			for (var j in parObj) {
				if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
					par += '<param name="' + j + '" value="' + parObj[j] + '" />';
				}
			}
			el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
			fixObjectLeaks(attObj.id); // This bug affects dynamic publishing only
			r = getElementById(attObj.id);	
		}
		else if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements: fall back to the proprietary embed element
			var e = createElement("embed");
			e.setAttribute("type", FLASH_MIME_TYPE);
			for (var k in attObj) {
				if (attObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
					if (k == "data") {
						e.setAttribute("src", attObj[k]);
					}
					else if (k.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
						e.setAttribute("class", attObj[k]);
					}
					else if (k != "classid") { // Filter out IE specific attribute
						e.setAttribute(k, attObj[k]);
					}
				}
			}
			for (var l in parObj) {
				if (parObj[l] != Object.prototype[l]) { // Filter out prototype additions from other potential libraries
					if (l != "movie") { // Filter out IE specific param element
						e.setAttribute(l, parObj[l]);
					}
				}
			}
			el.parentNode.replaceChild(e, el);
			r = e;
		}
		else { // Well-behaving browsers
			var o = createElement(OBJECT);
			o.setAttribute("type", FLASH_MIME_TYPE);
			for (var m in attObj) {
				if (attObj[m] != Object.prototype[m]) { // Filter out prototype additions from other potential libraries
					if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
						o.setAttribute("class", attObj[m]);
					}
					else if (m != "classid") { // Filter out IE specific attribute
						o.setAttribute(m, attObj[m]);
					}
				}
			}
			for (var n in parObj) {
				if (parObj[n] != Object.prototype[n] && n != "movie") { // Filter out prototype additions from other potential libraries and IE specific param element
					createObjParam(o, n, parObj[n]);
				}
			}
			el.parentNode.replaceChild(o, el);
			r = o;
		}
		return r;
	}
	
	function createObjParam(el, pName, pValue) {
		var p = createElement("param");
		p.setAttribute("name", pName);	
		p.setAttribute("value", pValue);
		el.appendChild(p);
	}
	
	function getElementById(id) {
		return doc.getElementById(id);
	}
	
	function createElement(el) {
		return doc.createElement(el);
	}
	
	function hasPlayerVersion(rv) {
		var pv = ua.pv, v = rv.split(".");
		v[0] = parseInt(v[0], 10);
		v[1] = parseInt(v[1], 10);
		v[2] = parseInt(v[2], 10);
		return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
	}
	
	/* Cross-browser dynamic CSS creation
		- Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
	*/	
	function createCSS(sel, decl) {
		if (ua.ie && ua.mac) {
			return;
		}
		var h = doc.getElementsByTagName("head")[0], s = createElement("style");
		s.setAttribute("type", "text/css");
		s.setAttribute("media", "screen");
		if (!(ua.ie && ua.win) && typeof doc.createTextNode != UNDEF) {
			s.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
		}
		h.appendChild(s);
		if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
			var ls = doc.styleSheets[doc.styleSheets.length - 1];
			if (typeof ls.addRule == OBJECT) {
				ls.addRule(sel, decl);
			}
		}
	}
	
	function setVisibility(id, isVisible) {
		var v = isVisible ? "visible" : "hidden";
		if (isDomLoaded) {
			getElementById(id).style.visibility = v;
		}
		else {
			createCSS("#" + id, "visibility:" + v);
		}
	}
	
	return {
		/* Public API
			- Reference: http://code.google.com/p/swfobject/wiki/SWFObject_2_0_documentation
		*/ 
		registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr) {
			if (!ua.w3cdom || !objectIdStr || !swfVersionStr) {
				return;
			}
			var regObj = {};
			regObj.id = objectIdStr;
			regObj.swfVersion = swfVersionStr;
			regObj.expressInstall = xiSwfUrlStr ? xiSwfUrlStr : false;
			regObjArr[regObjArr.length] = regObj;
			setVisibility(objectIdStr, false);
		},
		
		getObjectById: function(objectIdStr) {
			var r = null;
			if (ua.w3cdom && isDomLoaded) {
				var o = getElementById(objectIdStr);
				if (o) {
					var n = o.getElementsByTagName(OBJECT)[0];
					if (!n || (n && typeof o.SetVariable != UNDEF)) {
				    	r = o;
					}
					else if (typeof n.SetVariable != UNDEF) {
						r = n;
					}
				}
			}
			return r;
		},
		
		embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj) {
			if (!ua.w3cdom || !swfUrlStr || !replaceElemIdStr || !widthStr || !heightStr || !swfVersionStr) {
				return;
			}
			widthStr += ""; // Auto-convert to string to make it idiot proof
			heightStr += "";
			if (hasPlayerVersion(swfVersionStr)) {
				setVisibility(replaceElemIdStr, false);
				var att = (typeof attObj == OBJECT) ? attObj : {};
				att.data = swfUrlStr;
				att.width = widthStr;
				att.height = heightStr;
				var par = (typeof parObj == OBJECT) ? parObj : {};
				if (typeof flashvarsObj == OBJECT) {
					for (var i in flashvarsObj) {
						if (flashvarsObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries
							if (typeof par.flashvars != UNDEF) {
								par.flashvars += "&" + i + "=" + flashvarsObj[i];
							}
							else {
								par.flashvars = i + "=" + flashvarsObj[i];
							}
						}
					}
				}
				addDomLoadEvent(function() {
					createSWF(att, par, replaceElemIdStr);
					if (att.id == replaceElemIdStr) {
						setVisibility(replaceElemIdStr, true);
					}
				});
			}
			else if (xiSwfUrlStr && !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac)) {
				setVisibility(replaceElemIdStr, false);
				addDomLoadEvent(function() {
					var regObj = {};
					regObj.id = regObj.altContentId = replaceElemIdStr;
					regObj.width = widthStr;
					regObj.height = heightStr;
					regObj.expressInstall = xiSwfUrlStr;
					showExpressInstall(regObj);
				});
			}
		},
		
		getFlashPlayerVersion: function() {
			return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
		},
		
		hasFlashPlayerVersion:hasPlayerVersion,
		
		createSWF: function(attObj, parObj, replaceElemIdStr) {
			if (ua.w3cdom && isDomLoaded) {
				return createSWF(attObj, parObj, replaceElemIdStr);
			}
			else {
				return undefined;
			}
		},
		
		createCSS: function(sel, decl) {
			if (ua.w3cdom) {
				createCSS(sel, decl);
			}
		},
		
		addDomLoadEvent:addDomLoadEvent,
		
		addLoadEvent:addLoadEvent,
		
		getQueryParamValue: function(param) {
			var q = doc.location.search || doc.location.hash;
			if (param == null) {
				return q;
			}
		 	if(q) {
				var pairs = q.substring(1).split("&");
				for (var i = 0; i < pairs.length; i++) {
					if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
						return pairs[i].substring((pairs[i].indexOf("=") + 1));
					}
				}
			}
			return "";
		},
		
		// For internal usage only
		expressInstallCallback: function() {
			if (isExpressInstallActive && storedAltContent) {
				var obj = getElementById(EXPRESS_INSTALL_ID);
				if (obj) {
					obj.parentNode.replaceChild(storedAltContent, obj);
					if (storedAltContentId) {
						setVisibility(storedAltContentId, true);
						if (ua.ie && ua.win) {
							storedAltContent.style.display = "block";
						}
					}
					storedAltContent = null;
					storedAltContentId = null;
					isExpressInstallActive = false;
				}
			} 
		}
		
	};

}();
/*
Script: dbug.js
Wrapper for the firebug console.log() function.

Dependancies:
	 no dependencies

Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>

Class: dbug
		dbug is a wrapper for the firebug console plugin for
		firefox. The syntax for logging is the same as documented
		at http://getfirebug.com, though only the .log() command
		is supported.

		You can leave dbug.log() statements in your code and
		they will not be echoed out to the screen in any way.

		To display the dbug statements, you have two options:
		include *"jsdebug=true"* in the query string of the page
		and all your dbug statements will be printed as they
		occur OR *type into the firebug console dbug.enable()*
		and the debug statements that have occurred up until
		that point will be echoed, and all others from that
		point will be printed as they occur. You can also
		*put dbug.enable() in your javascript* to turn it on.

		dbug.disable() will turn it back off.

Arguments:
	args - collection of things to log to the console.

Examples:
	(start code)
	dbug.log("message");
	> message
	dbug.log("my var is %s", myVar)
	> my var is x
	dbug.log($('myelement'));
	> <div id="myelement"></div>
	dbug.log("myelement: %s, some value: %s", $('myelement'), somevalue);
	> myelement: <div id="myelement"></div>, some value: blah
	(end)

	more at <http://getfirebug.com>
	*/
var dbug = {
/*	Property: logged

		Array with any messages logged that have not been sent to the console;
		happens when dbug is not enabled. when you enable it again,
		these messages will be dumped to the console.
	*/
	logged: [],
	timers: {},
/*	property: debug
		boolean; whether or not the debugger is enabled.
	*/
	firebug: false,
	debug: false,

/*	property: log

		sends a message to the console if dbug is enabled, otherwise
		it stores this info until dbug is enabled.

		Parameters:
			message - the message to log, includes various substition options, see <http://www.getFirebug.com>

		Syntax:
		> dbug.log("message");
		> > message
		> dbug.log("my var is %s", myVar)
		> > my var is x

		for more examples, see <http://www.getFirebug.com>
	*/
	log: function() {
		dbug.logged.push(arguments);
	},
	nolog: function(msg) {
		dbug.logged.push(arguments);
	},
/*	Property: time
		Starts a console timer with the given name if dbug is enabled.
		See <http://www.getFirebug.com> for details.
	*/
	time: function(name){
		dbug.timers[name] = new Date().getTime();
	},
/*	Property: timeEnd
		Ends a console timer with the given name if dbug is enabled.
		See <http://www.getFirebug.com> for details.
	*/
	timeEnd: function(name){
		if (dbug.timers[name]) {
			var end = new Date().getTime() - dbug.timers[name];
			dbug.timers[name] = false;
			dbug.log('%s: %s', name, end);
		} else dbug.log('no such timer: %s', name);
	},
/*	Property: enable

		turns on the dbug functionality so that messages will show up
		in the firebug console. any messages sent to dbug.log()
		previously will be displayed in the console immediately and
		all future logging statements will echo to the console.

		See also:
		<dbug.log>, <dbug.disable>

		Example:
		>dbug.enable()
		> > enabling dbug

	*/
	enable: function() {
		if(dbug.firebug) {
			try {
				dbug.debug = true;
				dbug.log = console.debug || console.log;
				dbug.time = console.time;
				dbug.timeEnd = console.timeEnd;
				dbug.log('enabling dbug');
				for(var i=0;i<dbug.logged.length;i++){ dbug.log.apply(console, dbug.logged[i]); }
				dbug.logged=[];
			} catch(e) {
				dbug.enable.delay(400);
			}
		}
	},
/*	Property: disable

		turns the dbug functionality off. all future logging calls
		will be stored in the logged array until dbug is enabled again.

		See also:
		<dbug.log>, <dbug.enable>, <dbug.logged>

		Example:
		>dbug.disable()
	*/
	disable: function(){
		if(dbug.firebug) dbug.debug = false;
		dbug.log = dbug.nolog;
		dbug.time = function(){};
		dbug.timeEnd = function(){};
	},
/*	Property: cookie
		dbug.cookie turns debugging on for the rest of the day for that domain. This lets you click around and use the debugging version of libraries without having to add jsdebug=true to each new page's url and reload the page.

		Calling dbug.cookie() when the cookie is already present will disable it (toggle).

		Arguments:
		set - (boolean; optional); if true sets the cookie even if it's already set (overrides toggle),
					if false overrides to disable the cookie (same as <dbug.disableCookie>);
	*/
	cookie: function(set){
		var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
		var debugCookie = value ? unescape(value[1]) : false;
		if((debugCookie != 'true' || set) && !set) {
			dbug.enable();
			dbug.log('setting debugging cookie');
			var date = new Date();
			date.setTime(date.getTime()+(24*60*60*1000));
			document.cookie = 'jsdebug=true;expires='+date.toGMTString();
		} else dbug.disableCookie();
	},
/*	Property: disableCookie
		This removes the cookie set by <dbug.cookie> and turns off debugging for subsequent page loads.
	*/
	disableCookie: function(){
		dbug.log('disabling debugging cookie');
		document.cookie = 'jsdebug=false';
	}
};

if (typeof console != "undefined" && console.warn){
	dbug.firebug = true;
	var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
	var debugCookie = value ? unescape(value[1]) : false;
	if(window.location.href.indexOf("jsdebug=true")>0 || debugCookie=='true') dbug.enable();
	if(debugCookie=='true')dbug.log('debugging cookie enabled');
	if(window.location.href.indexOf("jsdebugCookie=true")>0){
		dbug.cookie();
		if(!dbug.debug)dbug.enable();
	}
	if(window.location.href.indexOf("jsdebugCookie=false")>0)dbug.disableCookie();
}


/*	Script: element.shortcuts.js
Extends the <Element> object with some basic shortcuts (like .hide and .show).

Dependancies:
	 mootools - <Moo.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>

Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>

Class: Element
		This extends the <Element> prototype.
	*/
Element.extend({

/*	Property: isVisible
		Returns a boolean; true = visible, false = not visible.

		Example:
		>$(id).isVisible()
		> > true | false	*/
	isVisible: function() {
		return this.getStyle('display') != 'none';
	},
/*	Property: toggle
		Toggles the state of an element from hidden (display = none) to
		visible (display = what it was previously or else display = block)

		Example:
		> $(id).toggle()
	*/
	toggle: function() {
		return this[this.isVisible() ? 'hide' : 'show']();
	},
/*	Property: hide
		Hides an element (display = none)

		Example:
		> $(id).hide()
		*/
	hide: function() {
		this.originalDisplay = this.getStyle('display');
		this.setStyle('display','none');
		return this;
	},
/*	Property: show
		Shows an element (display = what it was previously or else display = block)

		Example:
		>$(id).show() */
	show: function(display) {
		this.originalDisplay = (this.originalDisplay=="none")?'block':this.originalDisplay;
		this.setStyle('display',(display || this.originalDisplay || 'block'));
		return this;
	},
/*	Property: tidy
		Uses <String.tidy> to clean up common special characters with their ASCII counterparts (smart quotes, elipse characters, stuff from MS Word, etc.).
	*/
	tidy: function(){
		try {
			if(this.getValue().tidy())this.value = this.getValue().tidy();
		}catch(e){dbug.log('element.tidy error: %o', e);}
	},
	//DO NOT USE THIS METHOD
	//it is temporary, as Mootools 1.1 will negate its requirement
	fxOpacityOk: function(){
		if (!window.ie6)return true;
		var isColor = false;
		try {
			if (new Color(this.getStyle('backgroundColor'))) isColor = true;
		}catch(e){}
		return isColor;
	}
});
//legacy namespace
Element.visible = Element.isVisible;


if(!Element.empty) {
	Element.extend({
		/*
		Property: empty
			Empties an element of all its children (overridden by Mootools 1.1 <Element.empty> if present).

		Example:
			>$('myDiv').empty() // empties the Div and returns it

		Returns:
			The element.
		*/
		empty: function(){
			//Garbage.trash(this.getElementsByTagName('*'));
			return this.setHTML('');
		}
	});
}
/*	legacy support for $S	*/
var $S = $$;

/*	Script: element.dimensions.js
Extends the <Element> object.

Dependancies:
	 mootools - <Moo.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>

Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>

Class: Element
		This extends the <Element> prototype.
	*/
Element.extend({
/*	Property: getDimensions
		Returns width and height for element; if element is not visible the element is
		cloned off screen, shown, measured, and then removed.

		Arguments:
		options - a key/value set of options

		Options:
		computeSize - (boolean; optional) use <Element.getComputedSize> or not; defaults to false
		styles - (array; optional) see <Element.getComputedSize>
		plains - (array; optional) see <Element.getComputedSize>

		Returns:
		An object with .width and .height defined as integers. If options.computeSize is true, returns
		all the values that <Element.getComputedSize> returns.

		Example:
		>$(id).getDimensions()
		> > {width: #, height: #}
	*/
	getDimensions: function(options) {
		options = $merge({computeSize: false},options);
		var dim = {};
		function getSize(el, options){
			if(options.computeSize) dim = el.getComputedSize(options);
			else {
				dim.width = el.getSize().size.x;
				dim.height = el.getSize().size.y;
			}
			return dim;
		}
		try { //safari sometimes crashes here, so catch it
			dim = getSize(this, options);
		}catch(e){}
		if((dim.x == 0 || $type(dim.x) != 'number')||(dim.y == 0 || $type(dim.y) != 'number')){
			var holder = new Element('div').setStyles({
				'position':'absolute',
				'top':'-1000px',
				'left':'-1000px',
				'display':'block'
			}).injectAfter(this);
			var clone = this.clone().injectInside(holder).setStyle('display','block');
			dim = getSize(clone, options);
			holder.remove();
		}
		return $merge(dim, {x: dim.width, y: dim.height});
	},
/*	Property: getComputedSize
		Calculates the size of an element including the width, border, padding, etc.

		Arguments:
		options - an object with key/value options

		Options:
		styles - (array) the styles to include in the calculation; defaults to ['padding','border']
		plains - (object) an object with height and width properties, each of which is an
							array including the edges to include in that plain.
							defaults to {height: ['top','bottom'], width: ['left','right']}
		mode - (string; optional) limit the plain to 'vertical' or 'horizontal'; defaults to 'both'

		Returns:
		size - an object that contans dimension values (integers); see list below


		Dimension Values Returned:
		width - the actual width of the object (not including borders or padding)
		height - the actual height of the object (not including borders or padding)
		border-*-width - (where * is top, right, bottom, and left) the width of the border on that edge
		padding-* - (where * is top, right, bottom, and left) the width of the padding on that edge
		computed* - (where * is Top, Right, Bottom, and Left; e.g. computedRight) the width of all the
			styles on that edge computed (so if options.styles is left to the default padding and border,
			computedRight is the sum of border-right-width and padding-right)
		totalHeight - the total sum of the height plus all the computed styles on the top or bottom. by
			default this is just padding and border, but if you were to specify in the styles option
			margin, for instance, the totalHeight calculated would include the margin.
		totalWidth - same as totalHeight, only using width, left, and right

		Example:
(start code)
$(el).getComputedSize();
returns:
{
	padding-top:0,
	border-top-width:1,
	padding-bottom:0,
	border-bottom-width:1,
	padding-left:0,
	border-left-width:1,
	padding-right:0,
	border-right-width:1,
	width:100,
	height:100,
	totalHeight:102,
	computedTop:1,
	computedBottom:1,
	totalWidth:102,
	computedLeft:1,
	computedRight:1
}
(end)
	*/
	getComputedSize: function(options){
		options = $merge({
			styles: ['padding','border'],
			plains: {height: ['top','bottom'], width: ['left','right']},
			mode: 'both'
		}, options);
		var size = {width: 0,height: 0};
		switch (options.mode){
			case 'vertical':
				delete size.width;
				delete options.plains.width;
				break;
			case 'horizontal':
				delete size.height;
				delete options.plains.height;
				break;
		}
		var getStyles = [];
		//this function might be useful in other places; perhaps it should be outside this function?
		$each(options.plains, function(plain, key){
			plain.each(function(edge){
				options.styles.each(function(style){
					getStyles.push((style=="border")?style+'-'+edge+'-'+'width':style+'-'+edge);
				});
			});
		});
		var styles = this.getStyles.apply(this, getStyles);
		var subtracted = [];
		$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left','right'], ['top','bottom']
			size['total'+key.capitalize()] = 0;
			size['computed'+key.capitalize()] = 0;
			plain.each(function(edge){ //top, left, right, bottom
				size['computed'+edge.capitalize()] = 0;
				getStyles.each(function(style,i){ //padding, border, etc.
					//'padding-left'.test('left') size['totalWidth'] = size['width']+[padding-left]
					if(style.test(edge)) {
						styles[style] = styles[style].toInt(); //styles['padding-left'] = 5;
						if(isNaN(styles[style]))styles[style]=0;
						size['total'+key.capitalize()] = size['total'+key.capitalize()]+styles[style];
						size['computed'+edge.capitalize()] = size['computed'+edge.capitalize()]+styles[style];
					}
					//if width != width (so, padding-left, for instance), then subtract that from the total
					if(style.test(edge) && key!=style &&
						(style.test('border') || style.test('padding')) && !subtracted.test(style)) {
						subtracted.push(style);
						size['computed'+key.capitalize()] = size['computed'+key.capitalize()]-styles[style];
					}
				});
			});
		});
		if($chk(size.width)) {
			size.width = size.width+this.offsetWidth+size.computedWidth;
			size.totalWidth = size.width + size.totalWidth;
			delete size.computedWidth;
		}
		if($chk(size.height)) {
			size.height = size.height+this.offsetHeight+size.computedHeight;
			size.totalHeight = size.height + size.totalHeight;
			delete size.computedHeight;
		}
		return $merge(styles, size);
	}
});

/*	Script: element.pin.js
		Extends the Mootools <Window> and <Element> classes to allow fixed positioning for an element.

		Dependencies:
		mootools = <Element.js>, <Window.js> and all their dependencies

		Class: window
		This extends the <window> class from the <http://mootools.net> library.
	*/
window.extend({
/*	Property: supportsPositionFixed
		Returns true if the browser supports fixed positioning; must be called after DomReady (or it returns null);
	*/
	supportsPositionFixed: function(){
		if(!window.loaded) return null;
		var test = new Element('div').setStyles({
			position: 'fixed',
			top: '0px',
			right: '0px'
		}).injectInside(document.body);
		var supported = (test.offsetTop === 0);
		test.remove();
		return supported;
	}
});

/*	Class: Element
		Extends the <Element> class from the <http://mootools.net> library.
	*/
Element.extend({
/*	Property: pin
		Affixes an element at its current position, even if the window is scrolled.

		Arguments:
		pin - (boolean) true: pin, false: release pin. See also <Element.unpin>.
	*/
	pin: function(enable){
		var p = this.getPosition();
		if(enable!==false) {
			if(!this.pinned) {
				var pos = {
					top: (p.y - window.getScrollTop())+'px',
					left: (p.x - window.getScrollLeft())+'px'
				};
				if(window.supportsPositionFixed()) {
					this.setStyle('position','fixed').setStyles(pos);
				} else {
					this.setStyles({
						position: 'absolute',
						top: p.y+'px',
						left: p.x+'px'
					});
					window.addEvent('scroll', function(){
						var to = {
							top: (pos.top.toInt() + window.getScrollTop())+'px',
							left: (pos.left.toInt() + window.getScrollLeft())+'px'
						};
						this.setStyles(to);
					}.bind(this));
				}
				this.pinned = true;
			}
		} else {
			this.pinned = false;
			this.setStyles({
				top: (p.y+window.getScrollTop())+'px',
				left: (p.x+window.getScrollLeft())+'px',
				position: 'absolute'
			});
		}
		return this;
	},
/*	Property: unpin
		Un-pins an element at its current position (see <Element.pin>).
	*/
	unpin: function(){
		return this.pin(false);
	},
/*	Property: togglepin
		Toggles the pin state of the element.
	*/
	togglepin: function(){
		this.pin(!this.pinned);
	}
});

/*	Script: element.position.js
Extends the <Element> object.

Dependancies:
	 mootools - <Moo.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>
	 cnet - <element.dimensions.js>

Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>

Class: Element
		This extends the <Element> prototype.
	*/
Element.extend({
/*	Property: setPosition
		Sets the location of an element relative to another (defaults to the document body).

		Note:
		The element must be absolutely positioned (if it isn't, this method will set it to be);

		Arguments:
		options - a key/value object with options

		Options:
		relativeTo - (element) the element relative to which to position this one; defaults to document.body.
		
		position - (string) the aspect of the relativeTo element that this element should be positioned.
					Options are 'upperRight', 'upperLeft', 'bottomLeft', 'bottomRight', and 'center' (the default).
					With the exception of center, all other options will make the upper right corner of the
					positioned element = the specified corner of the relativeTo element. 'center' will make the center point of the
					positioned element = the center point of the relativeTo element.

		edge - (string; optional) the edge of the element to set relative to the relative elements corner;
					this way you can specify to position this element's upper right corner to the bottom left corner of the relative element.
					this is optional; the default behavior positions the element's upper left corner to the relative element
					unless position == center, in which case it positions the center of the element to the center of the relative element.

		offset - (object) x/y coordinates for the offset (i.e. {x: 10, y:100} will move it down 100 and to the right 10). Negative values are allowed.
		smoothMove - (boolean) move the element to the new position using <Fx.Styles>; defaults to false.
		effectOptions - (object) options object for <Fx.Styles>, optional
		returnPos - (boolean) don't move the element, but instead just return the position object ({top: '#', left: '#'}); defaults to false

	*/
	setPosition: function(options){
		options = $merge({
			relativeTo: document.body,
			position: 'center',
			edge: false,
			offset: {x:0,y:0},
			smoothMove: false,
			effectOptions: {},
			returnPos: false
		}, options);
		this.setStyle('position', 'absolute');
		var rel = $(options.relativeTo) || document.body;
		var top = (rel == document.body)?window.getScrollTop():rel.getTop();
		if (top < 0) top = 0;
		var left = (rel == document.body)?window.getScrollLeft():rel.getLeft();
		if (left < 0) left = 0;
		var dim = this.getDimensions({computeSize: true});
		var pos;
		var prefY = options.offset.y.toInt();
		var prefX = options.offset.x.toInt();
		switch(options.position) {
			case 'upperLeft':
				pos = {
					x:(left + prefX),
					y:(top + prefY)
				};
				break;
			case 'upperRight':
				pos = {
					x:(left + prefX + rel.offsetWidth),
					y:(top + prefY)
				};
				break;
			case 'bottomLeft':
				pos = {
					x:(left + prefX),
					y:(top + prefY + rel.offsetHeight)
				};
				break;
			case 'bottomRight':
				pos = {
					y:(left + prefX + rel.offsetWidth),
					x:(top + prefY + rel.offsetHeight)
				};
				break;
			default: //center
				pos = {
					x: left + (((rel == document.body)?window.getWidth():rel.offsetWidth)/2) + prefX,
					y: top + (((rel == document.body)?window.getHeight():rel.offsetHeight)/2) + prefY
				};
				options.edge = "center";
				break;
		}
		if(options.edge){
			var edgeOffset;
			switch(options.edge){
				case 'upperLeft':
					edgeOffset = {
						x: 0,
						y: 0
					};
					break;
				case 'upperRight':
					edgeOffset = {
						x: -dim.x-dim.computedRight-dim.computedLeft,
						y: 0
					};
					break;
				case 'bottomLeft':
					edgeOffset = {
						x: 0,
						y: -dim.y-dim.computedTop-dim.computedBottom
					};
					break;
				case 'bottomRight':
					edgeOffset = {
						x: -dim.x-dim.computedRight-dim.computedLeft,
						y: -dim.y-dim.computedTop-dim.computedBottom
					};
					break;
				default: //center
					edgeOffset = {
						x: -(dim.x/2),
						y: -(dim.y/2)
					};
					break;
			}
			pos.x = pos.x+edgeOffset.x;
			pos.y = pos.y+edgeOffset.y;
		}
		pos = {
			left: ((pos.x >= 0)?pos.x:0).toInt()+'px',
			top: ((pos.y >= 0)?pos.y:0).toInt()+'px'
		};
		if(options.returnPos) return pos;
		if(options.smoothMove && this.effects) this.effects(options.effectOptions).start(pos);
		else this.setStyles(pos);
		return this;
	}
});

/*
Script: IframeShim.js
Iframe shim class for hiding elements below a floating DOM element.

Dependancies:
	 mootools - <Moo.js>, <Utility.js>, <Common.js>, <String.js>, <Array.js>, <Function.js>, <Element.js>, <Dom.js>

Author:
	Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>


Class: IframeShim
		There are two types of elements that (sometimes) prohibit you from
		positioning a DOM element over them: some form elements and some
		flash elements. The two options you have are:
			- to hide these elements when your dom is going to be over them;
			this works if you know your DOM element is going to completely
			obscure that element

			- an iframe shim - where you put an iframe below your element but
			ABOVE the form/flash element. more details here:
			http://www.macridesweb.com/oltest/IframeShim.html

		The IframeShim class handles a lot of the dirty work for you.
Arguments:
			element -  (required; DOM element or its id) the element you want to put this shim under
			display -  (boolean; optional) display the shim on instantiation; defaults to false
			name -  (string; optional) the id you want to give the new DOM element of the iframe shim; gets "_shim" added to it
			zindex -  (integer; optional) the index of the shim; optional, default is 1 less than the element
			margin -  (integer; optional) make the iframe smaller than the element to give a buffer (for
							things like shadows)
			offset -  (object: {x:#, y:#}; optional) move the iframe up/down, left/right relative to
							the element
			className - (string; optional) className for the shim; defaults to "iframeShim"
			browsers - (boolean; optional) allows you to specify the browsers that the iframe should show up for;
							defaults to ie6 or gecko on a mac (window.ie6 || (window.gecko && navigator.userAgent.test('mac', 'i'))).
							Example usage: *browsers: window.ie6 || window.khtml* //will show for safari, konqueror, and ie6

		then, when you make your floating DOM show up you just execute .hide() or .show()
		to make the shim do its magic. You can also call .position() if the element the
		shim is supposed to be under happens to move.

		example:

		> <div id="myFloatingDiv">stuff</div>
		> <script>
		> 	var myFloatingDivShim = new IframeShim({
		> 		element: 'myFloatingDiv',
		> 		display: false,
		> 		name: 'myFloatingDivShimId'
		> 	});
		> 	function showMyFloatingDiv(){
		> 		$('myFloatingDiv').show();
		> 		myFloatingDivShim.show();
		> 	}
		> </script>

		See also <hide>, <show>, <position>
	*/

var IframeShim = new Class({
	options: {
		element: false,
		name: '',
		className:'iframeShim',
		display:false,
		name: '',
		zindex: false,
		margin: 0,
		offset: {
			x: 0,
			y: 0
		},
		browsers: (window.ie6 || (window.gecko && navigator.userAgent.test('mac', 'i')))
	},
	initialize: function (options){
		this.setOptions(options);
		//legacy
		if(this.options.offset && this.options.offset.top) this.options.offset.y = this.options.offset.top;
		if(this.options.offset && this.options.offset.left) this.options.offset.x = this.options.offset.left;
		this.element = $(this.options.element);
		if(!this.element) return;
		else this.makeShim();
		return;
	},
	makeShim: function(){
		this.shim = new Element('iframe');
		this.id = (this.options.name || new Date().getTime()) + "_shim";
		if(this.element.getStyle('z-Index').toInt()<1 || isNaN(this.element.getStyle('z-Index').toInt()))
			this.element.setStyle('z-Index',5);
		var z = this.element.getStyle('z-Index')-1;

		if($chk(this.options.zindex) &&
			 this.element.getStyle('z-Index').toInt() > this.options.zindex)
			 z = this.options.zindex;

 		this.shim.setStyles({
			'position': 'absolute',
			'zIndex': z,
			'border': 'none',
			'filter': 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
		}).setProperties({
			'src':'javascript:void(0);',
			'frameborder':'0',
			'scrolling':'no',
			'id':this.id
		}).addClass(this.options.className);

		var inject = function(){
			this.shim.injectInside(document.body);
			if(this.options.display) this.show();
			else this.hide();
		};
		if(this.options.browsers){
			if(window.ie && !IframeShim.ready) {
				window.addEvent('onload', inject.bind(this));
			} else {
				inject.bind(this)();
			}
		}
	},

/*
		Property: position
		This will reposition the iframe element. Call this when you move or resize
		the iframe element.
	*/
	position: function(shim){
		if(!this.options.browsers) return;
		var wasVis = this.element.getStyle('display')!='none';
		if(!wasVis) this.element.setStyle('display','block');
		var size = this.element.getSize().size;
		var pos = this.element.getPosition();
		if(! wasVis) this.element.setStyle('display','none');
		if($type(this.options.margin)){
			size.x = size.x-(this.options.margin*2);
			size.y = size.y-(this.options.margin*2);
			this.options.offset.x += this.options.margin;
			this.options.offset.y += this.options.margin;
		}
		//offset.x+=100;// ******* This is my change ********
 		this.shim.setStyles({
			'width': size.x + 'px',
			'height': size.y + 'px'
		}).setPosition({
			relativeTo: this.element,
			offset: this.options.offset
		});
	},
/*
		Property: hide
		This will hide the IframeShim object. If you don't call this when you
		hide the element that's over the flash or select list, then that thing
		will still be hidden.
	*/
	hide: function(){
		if(!this.options.browsers) return;
		this.shim.setStyle('display','none');
	},

/*
		Property: show
		This will obscure any form elements or flash elements below the iframe
		shim element. Call this when you show your floating element.
	*/
	show: function(){
		if(!this.options.browsers) return;
		this.shim.setStyle('display','block');
		this.position();
	},
/*
		Property: remove
		This will remove the iframe from the DOM.
	*/
	remove: function(){
		if(!this.options.browsers) return;
		this.shim.remove();
	}
});
IframeShim.implement(new Options);

window.addEvent('load', function(){
	IframeShim.ready = true;
});


/*	Script: modalizer.js
		Provides functionality to overlay the window contents with a semi-transparent layer that prevents interaction with page content until it is removed.

		Dependencies:
		Mootools - <Moo.js>, <Array.js>, <String.js>, <Function.js>, <Utility.js>, <Dom.js>, <Element.js>, <Window.Size.js>, <Event.js>, <Window.Base.js>

		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Class: Modalizer
		Provides functionality to overlay the window contents with a semi-transparent layer that prevents interaction with page content until it is removed. This class is intended to be implemented into other classes to provide them access to this functionality.
	*/
var Modalizer = new Class({
	defaultModalStyle: {
		'display':'block',
		'position':'fixed',
		'top':'0px',
		'left':'0px',
		'z-index':5000,
		'background-color':'#333',
		'opacity':.8
	},
/*	Property: setOptions
		Sets the options for the modal overlay.

		Arguments:
		options - an object with name/value definitions

		See <modalShow> for options list.
	*/
	setModalOptions: function(options){
		this.modalOptions = $merge({
			'width':(window.getScrollWidth()+300)+'px',
			'height':(window.getScrollHeight()+300)+'px',
			elementsToHide: 'select',
			onModalHide: Class.empty,
			onModalShow: Class.empty,
			hideOnClick: true,
			modalStyle: {}
		}, this.modalOptions, options || {});
	},
/*	Property: setModalStyle
		Sets the style of the modal overlay to those in the object passed in.

		Arguments:
		styleObject - object with key/value css properties

		Default styleObject:
(start code){
	'display':'block',
	'position':'fixed',
	'top':'0px',
	'left':'0px',
	'width':'100%',
	'height':'100%',
	'z-index':this.modalOptions.zIndex,
	'background-color':this.modalOptions.color,
	'opacity':this.modalOptions.opacity
}(end)

	The object you pass in can contain any portion of this object, and the options you specify will overwrite the defaults; any option you do not specify will remain.
	*/
	setModalStyle: function (styleObject){
		this.modalOptions.modalStyle = styleObject;
		this.modalStyle = $merge(this.defaultModalStyle, {
			'width':this.modalOptions.width,
			'height':this.modalOptions.height
		}, styleObject);
		if($('modalOverlay'))$('modalOverlay').setStyles(this.modalStyle);
		return(this.modalStyle);
	},
/*	Property: modalShow
		Shows the modal window.

		Arguments:
		options - key/value options object

		Options:
		elementsToHide - comma seperated string of selectors to hide when the overlay is applied;
			example: 'select, input, img.someClass'; defaults to 'select'
		modalHide - the funciton that hides the modal window; defaults to
			"function(){if($('modalOverlay'))$('modalOverlay').hide();}"
		modalShow - the function that shows the modal window; defaults to
			"function(){$('modalOverlay').setStyle('display','block');}"
		onModalHide - function to execute when the modal window is removed
		onModalShow - function to execute when the modal window appears
		hideOnClick - allow the user to click anywhere on the modal layer to close it; defaults to true.
		modalStyle - a css style object to apply to the modal overlay. See <setModalStyle>.
	*/
	modalShow: function(options){
		this.setModalOptions(options||{});
		var overlay = null;
		if($('modalOverlay')) overlay = $('modalOverlay');
		if(!overlay) overlay = new Element('div').setProperty('id','modalOverlay').injectInside(document.body);
		overlay.setStyles(this.setModalStyle(this.modalOptions.modalStyle));
		if(window.ie6) overlay.setStyle('position','absolute');
		if(this.modalOptions.hideOnClick) {
			$('modalOverlay').removeEvents('click').addEvent('click', function(){
				this.modalHide();
			}.bind(this));
		}
		this.modalOptions.onModalShow();
		this.togglePopThroughElements(0);
		overlay.setStyle('display','block');
		return this;
	},
/*	Property: modalHide
		Hides the modal layer.
	*/
	modalHide: function(){
		this.togglePopThroughElements(1);
		this.modalOptions.onModalHide();
		if($('modalOverlay'))$('modalOverlay').setStyle('display','none');
		return this;
	},
	togglePopThroughElements: function(opacity){
		if((window.ie6 || (window.gecko && navigator.userAgent.test('mac', 'i')))) {
			$$(this.modalOptions.elementsToHide).each(function(sel){
				sel.setStyle('opacity', opacity);
			});
		}
	}
});
/*	Script: stickyWin.js
		Creates a div within the page with the specified contents at the location relative to the element you specify; basically an in-page popup maker.

		Dependencies:
		Moo - <Moo.js>, <Common.js>, <Utility.js>, <Element.js>, <Function.js>, <Dom.js>, <Array.js>, <Window.Base.js>, <Window.Size.js>, <Events.js>
		CNET - <element.shortcuts.js>, <element.dimensions.js>, <element.position.js>, <element.pin.js>, <IframeShim.js>

		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Class: StickyWin
		Creates a div within the page with the specified contents at the location relative to the element you specify; basically an in-page popup maker.

		Arguments:
		options - an object with key/value options

		Options:
			onDisplay - function to execute when the popup is shown
			onClose - function to execute when the popup is closed
			closeClassName - class name of the element(s) in your popup content that,
					when clicked, should close the window; defaults to "closeSticky"
			pinClassName - class name of the elements(s) in your popup content that,
					when clicked, should pin the sticky in place; defaults to "pinSticky"
			content - the content of your popup; this should be layout html and your message or a dom element
			zIndex - the zIndex of the popup; defaults to 10000
			id - the id of the wrapper div that gets created that will contain your content;
					defaults to 'StickyWin' + the date (so it's unique)
			className - optional class name for the wrapper dive that gets created that will
					contain your content
			position - "center", "upperRight", "bottomRight", "upperLeft", "bottomLeft"; the point in the popup that is positioned;
					defaults to 'center'
			edge - same options as position (center, upperRight, etc.) but specifies the edge of the stickyWin to position
				to the point specified in position. see <Element.setPosition> for details. Optional; defaults to the
				<Element.setPosition> default state.
			offset - object containing {x: # and y: #} (integers) the top and left offset from the element in the
					page that the popup is relative to; this offset is applied to the center of the popup
					or the corner, depending on  the value you specify in the 'position' option.
			relativeTo - a dom element to position the popup relative to; defaults to document.body (i.e. the window)
			width - an optional width for the wrapper div for your popup
			height - an optional height for the wrapper div for your popup
			timeout - (integer) an optional timeout interval to hide the popup after a specified time
			allowMultiple - (boolean) allow multiple instance of StickyWin on the page; defaults to true
			allowMultipleByClass - (boolean) allow multiple popups that share the same className as specified in
				the className option; defaults to false
			showNow - display the popup on instantiation; defaults to true
			useIframeShim - use an <IframeShim> to mask content below the element; defaults to true.
			iframeShimSelector - the css selector to find the element within your popup under which
				the iframe shim should be placed to obscure select lists and the like (see <IframeShim>)

	Example:
(start code)
var myWin = new StickyWin({
	content: '<div id="myWin">hi there!<br><a href="javascript:void(0);" class="closeSticky">close</a></div>'
});
//popups up a box in the middle of the window with "hi there" and a close link(end)
	*/
var StickyWin = new Class({
	options: {
		onDisplay: Class.empty,
		onClose: Class.empty,
		closeClassName: 'closeSticky',
		pinClassName: 'pinSticky',
		content: '',
		zIndex: 10000,
		className: '',
		//id: ... set above in initialize function
		edge: false, //see Element.setPosition in element.cnet.js
		position: 'center', //center, corner == upperLeft, upperRight, bottomLeft, bottomRight
		offset: {x:0,y:0},
		relativeTo: document.body,
		width: false,
		height: false,
		timeout: -1,
		allowMultipleByClass: false,
		allowMultiple: true,
		showNow: true,
		useIframeShim: true,
		iframeShimSelector: ''
	},
	css: '.SWclearfix:after {content: "."; display: block; height: 0; clear: both; visibility: hidden;}'+
			 '.SWclearfix {display: inline-table;}'+
			 '* html .SWclearfix {height: 1%;}'+
			 '.SWclearfix {display: block;}',
	initialize: function(options){
		options.id = options.id || 'StickyWin_'+new Date().getTime();
		this.setOptions(options);
		this.makeWindow();
		if(this.options.content) this.setContent(this.options.content);
		if(this.options.showNow) this.show();
		//add css for clearfix
		window.addEvent('domready', function(){
			try {
				if(!$('StickyWinClearfix')) {
					new Element('style').setProperty('id','StickyWinClearfix').injectInside($$('head')[0]).appendText(this.css);
				}
			}catch(e){dbug.log('error: %s',e);}
		}.bind(this));
	},
	makeWindow: function(){
		this.destroyOthers();
		if(!$(this.options.id)) {
			this.win = new Element('div').setProperty('id',			this.options.id).addClass(this.options.className).addClass('StickyWinInstance').addClass('SWclearfix').setStyles({
				 	'display':'none',
					'position':'absolute',
					'zIndex':this.options.zIndex
				}).injectInside(document.body);
		} else this.win = $(this.options.id);
		if(this.options.width && $type(this.options.width.toInt())=="number") this.win.setStyle('width', this.options.width.toInt() + 'px');
		if(this.options.height && $type(this.options.height.toInt())=="number") this.win.setStyle('height', this.options.height.toInt() + 'px');
		return this;
	},
/*	Property: show
		Shows the popup.
	*/
	show: function(){
		this.fireEvent('onDisplay');
		if(!this.positioned) this.position();
		this.showWin();
		if(this.options.useIframeShim) this.showIframeShim();
		this.visible = true;
		return this;
	},
	showWin: function(){
		this.win.setStyle('display','block');
	},
/*	Property: hide
		Hides the popup.
	*/
	hide: function(){
		this.fireEvent('onClose');
		this.hideWin();
		if(this.options.useIframeShim) this.hideIframeShim();
		this.visible = false;
		return this;
	},
	hideWin: function(){
		this.win.setStyle('display','none');
	},
	destroyOthers: function() {
		if(!this.options.allowMultipleByClass || !this.options.allowMultiple) {
			$$('div.StickyWinInstance').each(function(sw) {
				if(!this.options.allowMultiple || (!this.options.allowMultipleByClass && sw.hasClass(this.options.className)))
					sw.remove();
			}, this);
		}
	},
/*	Property: setContent
		Replaces the content of the popup with the content passed in.

		Arguments:
		html - the new content
	*/
	setContent: function(html) {
		if(this.win.getChildren().length>0) this.win.empty();
		if($type(html) == "string") this.win.setHTML(html);
		else if ($(html)) this.win.adopt(html);
		this.win.getElements('.'+this.options.closeClassName).each(function(el){
			el.addEvent('click', this.hide.bind(this));
		}, this);
		this.win.getElements('.'+this.options.pinClassName).each(function(el){
			el.addEvent('click', this.togglepin.bind(this));
		}, this);
		return this;
	},

	position: function(){
		this.positioned = true;
		this.win.setPosition({
			relativeTo: this.options.relativeTo,
			position: this.options.position,
			offset: this.options.offset,
			edge: this.options.edge
		});
		if(this.shim) this.shim.position();
		return this;
	},
/*	Property: pin
		Affixes the stickywin to a fixed position, even if the window is scrolled. See <Element.pin>.
	*/
	pin: function(pin) {
		if(!this.win.pin) {
			dbug.log('you must include element.pin.js!');
			return false;
		}
		this.pinned = $pick(pin, true);
		return this.win.pin(pin);
	},
/*	Property: unpin
		Affixes the stickywin to a fixed position, even if the window is scrolled. See <Element.unpin>.
	*/
	unpin: function(){
		this.pin(false);
	},
/*	Property: togglepin
		Toggle the pinned state of the Sticky;
	*/
	togglepin: function(){
		this.pin(!this.pinned);
	},
	makeIframeShim: function(){
		if(!this.shim){
			this.shim = new IframeShim({
				element: (this.options.iframeShimSelector)?this.win.getElement(this.options.iframeShimSelector) : $('StickyWinOverlay') || this.win,
				display: false,
				name: 'StickyWinShim'
			});
		}
	},
	showIframeShim: function(){
		if(this.options.useIframeShim) {
			this.makeIframeShim();
			this.shim.show();
		}
	},
	hideIframeShim: function(){
		if(this.options.useIframeShim)
			this.shim.hide();
	},
/*	Property: destroy
		Removes the popup element.
	*/
	destroy: function(){
		this.win.remove();
		if(this.options.useIframeShim) this.shim.remove();
		if($('StickyWinOverlay'))$('StickyWinOverlay').remove();
	}
});
StickyWin.implement(new Options);
StickyWin.implement(new Events);

var stickyWin = StickyWin;

/*	Script: stickyWinFx.js
		Extends <StickyWin> to create popups that fade in and out and can be dragged and resized (requires <stickyWinFx.Drag.js>).

		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Dependencies:
		mootools - <Fx.Base.js>
		cnet - <stickyWin.js> and all its dependencies.
		optional - <stickyWinFx.Drag.js> and <Drag.Base.js>

		Class: StickyWinFx
		Creates a <StickyWin> that optionally fades in and out, is draggable, and is resizable (requires <stickyWinFx.Drag.js>).

		Arguments:
		options - an object with key/value options

		Options:
		see <StickyWin>; inherits all those options in addition to the following.

			fade - (boolean) fade in and out; defaults to true
			fadeDuration - (integer) the duration of the fade effect; defaults to 150
			fadeTransition - an <Fx.Transitions> to use for the fade effect; defaults to <Fx.Transitions.sineInOut>

		Additional Options:
		These options depend on <stickyWinFx.Drag.js> and <Drag.Base.js>; so they don't do anything if those
		files are not included in your environment.

			draggable - (boolean) make the popup draggable, defaults to false
			dragOptions - (object) optional options to pass to the drag effect
			dragHandleSelector - optional css selector to select the element(s) within in
				your popup to use as a drag handle.
			resizable - (boolean) make the popup resizable or not; defaults to false
			resizeOptions - (object) optional options to pass to the resize effect
			resizeHandleSelector - optional css selector to select the element(s) within in
				your popup to use as a resize handle.

		Example:
(start code)
var myWin = new StickyWinFx({
	content: '<div id="myWin">hi there!<br><a href="javascript:void(0);" class="closeSticky">close</a></div>',
	fadeDuration: 500,  //slow it down
	draggable: true, //make it draggable
	dragHandleSelector: 'img.handle' //get the img with the class "handle" for the handle
});
//fades in a box in the middle of the window with "hi there" and a close link(end)
//window is draggable using the image(s) with the class "handle"
(end)
	*/
var StickyWinFx = StickyWin.extend({
	initialize: function(options){
		this.parent($merge({
			fade: true,
			fadeDuration: 150,
			fadeTransition: Fx.Transitions.sineInOut,
			draggable: false,
			dragOptions: {},
			dragHandleSelector: 'h1.caption',
			resizable: false,
			resizeOptions: {},
			resizeHandleSelector: ''
		}, options));
	},
	setContent: function(html){
		this.parent(html);
		if(this.options.draggable) this.makeDraggable();
		if(this.options.resizable) this.makeResizable();
		return this;
	},
	hideWin: function(){
		if(this.options.fade) {
			this.stopFade();
			this.fade(1,0);
		}
		else this.win.hide();
	},
	showWin: function(){
		if(this.options.fade) {
			this.stopFade();
			this.fade(0,1);
		} else this.win.show();
	},
	stopFade: function () {
		if (this.fxFade) this.fadeFx.stop()
	},
	fade: function(from,to){
		if(!this.fadeFx) {
			this.win.setStyles({
				'opacity':'0',
				'display':'block'
			});
			this.fadeFx = this.win.effect('opacity', {
				duration: this.options.fadeDuration,
				transition: this.options.fadeTransition
			});
		}
		if (to > 0) this.win.setStyle('display','block');
		this.fadeFx.custom(from,to).chain(function(){
			if(to == 0) this.win.setStyle('display', 'none');
		}.bind(this));

		return this;
	},
	makeDraggable: function(){
		dbug.log('you must include Drag.js, cannot make draggable');
	},
	makeResizable: function(){
		dbug.log('you must include Drag.js, cannot make resizable');
	}
});

/*	Script: stickyWinFx.Drag.js
		Implements drag and resize functionaity into <StickyWinFx>. See <StickyWinFx> for the options.

		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Dependencies:
		<stickyWin.js>, <stickyWinFx.js>, and all their dependencies plus <Drag.Base.js>.
*/
if(typeof Drag != "undefined"){
	StickyWinFx.implement({
		makeDraggable: function(){
			var toggled = this.toggleVisible(true);
			if(this.options.useIframeShim) {
				this.makeIframeShim();
				var dragComplete = this.options.dragOptions.onComplete || Class.empty;
				this.options.dragOptions.onComplete = function(){
					dragComplete();
					this.shim.position();
				}.bind(this);
			}
			if(this.options.dragHandleSelector) {
				var handle = this.win.getElement(this.options.dragHandleSelector);
				if (handle) {
					handle.setStyle('cursor','move');
					this.options.dragOptions.handle = handle;
				}
			}
			this.win.makeDraggable(this.options.dragOptions);
			if (toggled) this.toggleVisible(false);
		},
		makeResizable: function(){
			var toggled = this.toggleVisible(true);
			if(this.options.useIframeShim) {
				this.makeIframeShim();
				var resizeComplete = this.options.resizeOptions.onComplete || Class.empty;
				this.options.resizeOptions.onComplete = function(){
					resizeComplete();
					this.shim.position();
				}.bind(this);
			}
			if(this.options.resizeHandleSelector) {
				var handle = this.win.getElement(this.options.resizeHandleSelector);
				if(handle) this.options.resizeOptions.handle = this.win.getElement(this.options.resizeHandleSelector);
			}
			this.win.makeResizable(this.options.resizeOptions);
			if (toggled) this.toggleVisible(false);
		},
		toggleVisible: function(show){
			if(!this.visible && window.khtml && $pick(show, true)) {
				this.win.setStyles({
					display: 'block',
					opacity: 0
				});
				return true;
			} else if(!$pick(show, false)){
				this.win.setStyles({
					display: 'none',
					opacity: 1
				});
				return false;
			}
			return false;
		}
	});
}

/*	Script: stickyWin.Modal.js
		This script extends <StickyWin> and <StickyWinFx> classes to add <Modalizer> functionality.

		Author:
		Aaron Newton (aaron [dot] newton [at] cnet [dot] com)

		Dependencies:
		cnet - <stickyWin.js>, and all their dependencies plus <modalizer.js>.
		optional - <stickyWinFx.js>
	*/
var modalWinBase = {
	initialize: function(options){
		options = options||{};
		this.setModalOptions($merge(options.modalOptions||{}, {
			onModalHide: function(){
					this.hide(false);
				}.bind(this)
			}));
		this.parent(options);
	},
	show: function(showModal){
		if($pick(showModal, true))this.modalShow();
		this.parent();
	},
	hide: function(hideModal){
		if($pick(hideModal, true))this.modalHide();
		this.parent();
	}
};

/*	Class: StickyWinModal
		Creates a <StickyWin> that uses the functionality in <Modalizer> to overlay the document.

		Argument:
		options - an object with key/value options defined in <StickyWin> and <Modalizer>

		Options:
		inherits all the options of <StickyWin>
		modalOptions - options object for <Modalizer>
	*/
var StickyWinModal = StickyWin.extend(modalWinBase);
StickyWinModal.implement(new Modalizer);

/*	Class: StickyWinFxModal
		Creates a <StickyWinFx> that uses the functionality in <Modalizer> to overlay the document.

		Argument:
		options - an object with key/value options defined in <StickyWin>, <StickyWinFx> and <Modalizer>

		Argument:
		options - an object with key/value options defined in <StickyWin> and <Modalizer>

		Options:
		inherits all the options of <StickyWinFx>
		modalOptions - options object for <Modalizer>

	*/
var StickyWinFxModal = (typeof StickyWinFx != "undefined")?StickyWinFx.extend(modalWinBase):Class.empty;
try { StickyWinFxModal.implement(new Modalizer()); }catch(e){}

