/******************************************************************************
 The SI Object v2.4
 
 Stores a variety of functions localized to modules. Any module that requires
 initialization onload should have an onload handler. The SI.onload method will
 loop through all modules and run their respective onload handler. SI.onload
 can then be called in the window.onload event handler and all modules requiring
 initialization will be initialized. Does the same for onbeforeload and onresize.
 
 v2.1 : Added check for base function requirements, and Flash
 v2.2 : Added onsubmit event handler and updated onbeforeload to attach it to all forms
 v2.3 : Added onCSSload event handler and releated CSSattach/CSSwatch
 v2.4 : Changed `onCSSload` to `oncssload` for consistency
 
 ******************************************************************************/
if (!SI) { var SI = new Object(); };
SI.hasRequired 	= function() { 
	if (document.getElementById && document.getElementsByTagName) {
		var html = document.getElementsByTagName('html')[0];
		html.className += ((html.className=='')?'':' ')+'has-dom';
		return true;
		};
	return false;
	}();
SI.onbeforeload	= function() {
	if (this.hasRequired) {
		for (var module in this) { 
			if (this[module].onbeforeload) { 
				this[module].onbeforeload();
				};
			};
		};
	SI.Debug.output('Onbeforeload complete.',1);
	};
SI.onload		= function() { SI.Debug.output('Onload fired.',1); if (this.hasRequired) { for (var module in this) { if (this[module].onload) { this[module].onload(); };};};};
SI.onresize		= function() { SI.Debug.output('Onresize fired.',1); if (this.hasRequired) { for (var module in this) { if (this[module].onresize) { this[module].onresize(); };};};}; eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[(function(e){return d[e]})];e=(function(){return'\\w+'});c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('4 F=\'2n\';4 C=\'2a\';4 2=\'\';4 D=0;9 n(){2=\'\'}c.2b=9(e){e=(e)?e:((c.15)?15:2g);f(e){2i(D);2d(e.2e){8 2w:2+=\'l\';7;8 1Y:2+=\'u\';7;8 2x:2+=\'r\';7;8 2l:2+=\'d\';7;8 2v:2+=\'A\';7;8 2o:2+=\'B\';7;8 2p:2+=\'R\';7;8 2q:2+=\'L\';7;8 2r:2+=\'X\';7;8 2t:2+=\'Y\';7;2u:2+=\'-\';7};f(2==F){T();n()}m f(2==C){G();n()}m f(F.14(0,2.1c)!=2&&C.14(0,2.1c)!=2){n()}m{D=p(\'n()\',1k);1l k}}};9 o(e,3){3=(3==a)?I.J:3;e.5.27="1m(3:"+3+")";e.5.25=3/a;e.5.1n=3/a;e.5.3=3/a;e.3=3};9 y(b){4 1=i.M(b);f(1.3<I.J){o(1,1.3+10);c.p(\'y("\'+1.b+\'")\',a)}m{c.p(\'w("\'+1.b+\'")\',1q)}};9 w(b){4 1=i.M(b);f(1){f(1.3>0){o(1,1X.1t(1.3-10));c.p(\'w("\'+1.b+\'")\',a)}m{1.1Q.1A(1)}}};9 T(){4 1=i.1N(\'1\');1.b=\'1M-1D\';1.5.19=\'1E\';1.5.1I=\'1J\';1.5.1L=\'P\';1.5.1O=\'P\';1.5.1S=1U;1.5.1V=\'1Z\';o(1,0);1.13=\'1b/16/1g/1d/20-21-23.24\';1.26=9(){28.5.19=\'2c\';y(1.b)};1.29=9(){c.18.1a=\'q://1f.2h.z/2j/\'};i.t.2m(1)};9 G(){4 11=c.18.1a;4 v=\'#2s\';4 E=\'1b/16/1g/1d/O.1i\';4 s=\'1j=\'+11;4 V=\'<Q 1o="1p:1r-1s-1u-1v-1w" 1z="q://1B.H.z/1C/12/1F/17/1K.1P#1T=6,0,0,0" S="a%" U="a%"><j h="1h" g="\'+s+\'" /><j h="22" g="\'+E+\'" /><j h="N" g="k" /><j h="1e" g="k" /><j h="Z" g="K" /><j h="W" g="\'+v+\'" /><2f 13="\'+E+\'" 1h="\'+s+\'" N="k" 1e="k" Z="K" W="\'+v+\'" S="a%" U="a%" 1R="1W/x-12-17" 2k="q://1f.H.z/1x/1G" /></Q>\';i.t.b=\'O\';i.t.1y=V;c.1H=9(){}};',62,158,'|img|sequence|opacity|var|style||break|case|function|100|id|window|||if|value|name|document|param|false||else|clearSequence|setOpacity|setTimeout|http||flashVars|body||swfBGColor|fadeOut||fadeIn|com|||code2|allowanceID|swfPath|code1|easterEgg2|macromedia|99|999|high||getElementById|loop|mm2|32px|object||width|easterEgg1|height|swfHTML|bgcolor|||quality||swfReset|shockwave|src|substring|event|paths|flash|location|visibility|href|app|length|assets|menu|www|util|flashvars|swf|mint_location|1500|return|alpha|MozOpacity|classid|clsid|2000|d27cdb6e|ae6d|ceil|11cf|96b8|444553540000|go|innerHTML|codebase|removeChild|fpdownload|pub|approved|hidden|cabs|getflashplayer|onresize|position|fixed|swflash|bottom|stan|createElement|right|cab|parentNode|type|zIndex|version|1000|cursor|application|Math|38|pointer|bg|ee|movie|tested|png|KHTMLOpacity|onload|filter|this|onclick|dRuLYB|onkeyup|visible|switch|keyCode|embed|null|robweychert|clearTimeout|virtualstan|pluginspage|40|appendChild|uuddlrlrBA|66|82|76|88|191919|89|default|65|37|39'.split('|'),0,{}));

/******************************************************************************
 SI.Debug module v1.4
 
 Creates a div and writes to it. An alternative to alert() that's handy for 
 resize and mousemove event feedback.
 
 v1.1 : Added boolean debug to simplify toggling debugging on and off
 v1.2 : Added counter and rules. Elimated output length limit
 v1.3 : Added "Clear" button
 v1.4 : Add a second boolean argument to SI.Debug.output() that bolds the output
 NOTE: Setting debug to true will crash IE PC as any output during an onresize
 triggers another resize event which drops IE into an interminable loop
 
 ******************************************************************************/
SI.Debug = {
	debug			: false,
	e				: null,
	count			: 0,
	onbeforeload	: function() { 
		if (this.debug) {
			this.e = document.createElement('div');
			document.body.appendChild(this.e); 
			this.e.style.position 	= 'fixed';
			this.e.style.top 		= '16px';
			this.e.style.right 		= '16px';
			this.e.style.width 		= '360px';
			this.e.style.backgroundColor = '#EEE';
			this.e.style.border 	= '1px solid #DDD';
			this.e.style.padding 	= '12px';
			this.e.style.zIndex		= 10000;
			this.e.style.opacity 	= .8;
			
			var a = document.createElement('a');
			a.innerHTML = 'Clear Debug Output';
			a.href = '#Clear';
			a.e = document.createElement('div');
			a.onclick = function() {
				this.e.innerHTML='';
				return false;
				};
			this.e.appendChild(a);
			// e is now the inner div
			this.e = this.e.appendChild(a.e);
			};
		},
	output 			: function() { 
		if (this.debug && this.e!=null) {
			html = arguments[0];
			if (arguments.length==2) {
				html = '<strong>'+html+'</strong>';
				}
			var c = ++this.count;
			c = ((c<100)?'0':'')+((c<10)?'0':'')+c;
			
			this.e.innerHTML = '<hr />' + c + ': &nbsp; ' + html + this.e.innerHTML;
			};
		}
	};


/******************************************************************************
 SI.CSS module v2.3m

 Includes functions to add and remove CSS classes as well as functions to add
 relationship (first-, only-, and last-child) and alt classes. Also has a simple,
 single CSS selector element grabber.

 v1.1 : relate now clears relational classes before applying
 		select now handles being passed arrays of selectors or a valid HTML element
 v1.2 : added $() and $CSS() because bling is the new DHTML (watch out global 
		namespace!)
		select() now handles strings with multiple comma delimited selectors, when 
		an element type is specified before a unique id, it differentiates between 
		a valid and invalid match, and the returned array contains only unique 
		elements
		removed all methods but select()--will revisit when necessary
 v2.0 : added attribute selector support to select() (completely rewrote select())
 v2.1 : rewrote $() to not use select();
 v2.2 : reintroduced addClassName() and removeClassName()
 v2.3 : added toCSS(), creates a CSS ancestor selector for the given element 
		(stopping at the first element with an id)
 v2.3m:	included Mint-specific relate() and alt() methods
******************************************************************************/
SI.CSS = {
	// operates on the children of the given element
	// adds appropriate class to first, last and only child as well as alt
	relate		: function()
	{
		if (!SI.hasRequired)
		{
			return;
		};
		
		var elems = this.select(arguments);
		for (var i=0; i<elems.length; i++)
		{
			var elem 	= elems[i];
			var alt 	= false;
			
			var children = [];
			// make sure we're dealing with real HTML elements
			if (elem.nodeName=='TABLE')
			{
				children = elem.getElementsByTagName('tr'); // won't work with nested tables
			}
			// else if (elem.nodeName=='UL' || elem.nodeName=='OL') { children = elem.getElementsByTagName('li'); }
			else if (elem.nodeName == 'TBODY') // this is a folder-table, every other row is inside a content
			{
				var parentSelector = this.toCSS(elem.parentNode);
				children = this.select(parentSelector + ' tr.folder, '+ parentSelector + ' tr.folder-open');
			}
			else 
			{
				SI.Debug.output('Not a table: '+elem.nodeName+' (children: '+elem.childNodes.length+')',1);
				for (var j=0; j<elem.childNodes.length; j++)
				{
					if (elem.childNodes[j].nodeType==1)
					{
						children[children.length] = elem.childNodes[j];
					};
				};
			};
			
			for (var j=0; j<children.length; j++)
			{
				var child = children[j];
				child.className = this.removeClassName(child.className, 'first-child only-child last-child alt');
				
				if (children.length == 1)
				{
					child.className = this.addClassName(child.className,'only-child');
					break;
				}
				else if (j == 0)
				{
					child.className = this.addClassName(child.className,'first-child');
				}
				else if (j == children.length-1)
				{
					child.className = this.addClassName(child.className,'last-child');
				};
				if (alt)
				{
					child.className = this.addClassName(child.className,'alt');
				};
				alt =! alt;
			};
		};
	},
	
	// operates on the children of the given element
	// pass any number of simple, single element CSS selectors
	alt		: function()
	{
		if (!SI.hasRequired)
		{
			return;
		};
		
		var elems = this.select(arguments);
		for (var i=0; i<elems.length; i++)
		{
			var elem 	= elems[i];
			var alt 	= false;
			
			var children = elem.childNodes
			if (elem.nodeName=='TABLE')
			{
				children = elem.getElementsByTagName('tr');
			};
			
			for (var j=0; j<children.length; j++)
			{
				var child = children[j];
				if (child.nodeType==1) // make sure we're dealing with an HTML element
				{
					if (alt)
					{
						child.className = this.addClassName(child.className,'alt');
					};
					
					alt =! alt;
				};
			};
		};
	},
		
	addClassName : function(existingClassName, classNameAdditions)
	{
		var existing	= existingClassName.split(/\s+/);
		var additions	= classNameAdditions.split(/\s+/);

		return existing.concat(additions).unique().join(' ');
	},

	removeClassName : function(existingClassName, classNameRemovals)
	{
		var existing	= existingClassName.split(/\s+/);
		var removing	= classNameRemovals.split(/\s+/);
		var remaining	= new Array();

		for (var i = 0; i < existing.length; i++)
		{
			if (removing.search(existing[i]) == -1)
			{
				remaining.push(existing[i]);
			}
		}

		return remaining.join(' ');
	},

	replaceClassName : function (existingClassName, removeClassName, replaceClassName)
	{
		var existing	= existingClassName.split(/\s+/);
		var remove		= removeClassName;
		var add			= replaceClassName;

		for (var i = 0; i < existing.length; i++)
		{
			if (existing[i] == remove)
			{
				existing[i] = add;
			}
		}

		return existing.join(' ');
	},

	select		: function()
	{
		if (!SI.hasRequired) { return; };
		var selected = new Array();

		var parser	= function(fullSelector, parents) 
		{
			var selected	= Array();
			var selectors	= fullSelector.split(/,\s*/);

			for (var i = 0; i < selectors.length; i++)
			{
				var tmpSelected = Array();
				var split		= selectors[i].match(/([^ ]+) ?(.*)?/);
				var selector	= (typeof split[1] == 'undefined' || split[1] == '') ? '' : split[1];
				var remainder	= (typeof split[2] == 'undefined' || split[2] == '') ? '' : split[2];
				var breakdown	= selector.match(/^([a-z0-9]+)?(#([-_a-z0-9]+))?((\.[-_a-z0-9]+)*)?((\[[a-z]+(=[^\]]+)?\])*)?((:[-_a-z0-9]+)*)?$/i);

				var tag			= (typeof breakdown[1] == 'undefined' || breakdown[1] == '') ? '*'	: breakdown[1].toUpperCase();
				var id			= (typeof breakdown[3] == 'undefined' || breakdown[3] == '') ? '' 	: breakdown[3];
				var classes		= (typeof breakdown[4] == 'undefined' || breakdown[4] == '') ? '.' 	: breakdown[4];
				var attributes	= (typeof breakdown[6] == 'undefined' || breakdown[6] == '') ? '[]' : breakdown[6];
				var psuedoes	= (typeof breakdown[9] == 'undefined' || breakdown[9] == '') ? ':' 	: breakdown[9];

				var attributeValues = new Array();
				var attributeNames	= new Array();
				classes 	= classes.substring(1, classes.length).split('.');
				attributes	= attributes.substring(1, attributes.length -1).split('][');
				psuedoes 	= psuedoes.substring(1, psuedoes.length).split(':');

				// cleanup
				if (classes[0] == '')		{ classes.length = 0; };
				if (attributes[0] == '')	{ attributes.length = 0; };
				if (psuedoes[0] == '')		{ psuedoes.length = 0; };

				for (var h = 0; h < attributes.length; h++)
				{
					var attributeSplit 	= attributes[h].match(/([a-z]+)(=([^\]]+))?/i);
					var attrName	= (typeof attributeSplit[1] == 'undefined'|| attributeSplit[1] == '') ? '' : attributeSplit[1];
					var attrValue	= (typeof attributeSplit[3] == 'undefined'|| attributeSplit[3] == '') ? '' : attributeSplit[3];

					attributeNames.push(attrName);
					attributeValues.push(attrValue);
				};

				/** /
				alert
				(
					tag				+ ' | ' +
					id				+ ' | ' +
					classes			+ ' | ' +
					attributes		+ ' | ' +
					attributeValues	+ ' | ' +
					psuedoes
				);
				/**/

				for (var j = 0; j < parents.length; j++)
				{
					// element and id selectors
					var elems = (id != '') ? [document.getElementById(id)] : parents[j].getElementsByTagName(tag);

					validationLoop:
					for (var k = 0; k < elems.length; k++) 
					{
						var elem = elems[k];
						if (elem == null) { continue; }; // failed getElementById()

						// class selectors
						var elemClasses = elem.className.split(/\s+/);
						if
						(
							(tag != '*' && elem.nodeName != tag) ||
							(classes.length && !classes.foundIn(elemClasses))
						)
						{
							continue validationLoop;
						};

						// attribute selectors
						for (var l = 0; l < attributeNames.length; l++)
						{
							var attribute	= attributeNames[l];
							var attrValue	= attributeValues[l];
							var value		= elem.getAttribute(attribute);
							var match		= (new Boolean(value.match((new RegExp('^(' + attrValue + ')$')))));

							if (value == null || (attrValue != '' && match == false))
							{
								continue validationLoop;
							};
						};

						// haven't implemented pseudo selectors yet
						tmpSelected.push(elem);
					};
				};

				if (remainder != '')
				{
					tmpSelected = parser(remainder, tmpSelected);
				}
				selected = selected.concat(tmpSelected);
			};
			return selected;
		};

		// Make sure we haven't been passed another array (arguments from another function)
 		var args = (arguments.length === 1 && typeof arguments[0] != 'string') ? arguments[0] : arguments;
		for (var i = 0; i < args.length; i++) 
		{
			selected = selected.concat(((typeof args[i] == 'object') ? args[i] : parser(args[i], [document])));
		}
		return selected.unique();
	},

	toCSS : function(e)
	{
		var selector = e.nodeName.toLowerCase();
		if (e.id)
		{
			selector += '#' + e.id;
		}
		else if (e.parentNode && e.parentNode.nodeName != 'HTML')
		{
			selector = this.toCSS(e.parentNode) + ' ' + selector;
		}
		selector += (e.className) ? '.' + e.className.replace(/\s+/, '.') : '';
		return selector;
	}
};
/*-----------------------------------------------------------------------------
 $()
 
 Shorthand for document.getElementById(), accepts multiple ids (without # prefix) 
 as separate arguments or a comma/space delimited list of ids or both. Pass an 
 array and you'll recieve an array

 Sample usage and results:
 
	$('test')				: returns a single element
	$(['test'])				: returns a single element *array*
	$('test, another')		: returns a two element array
	$('test', 'another')	: returns a two element array
	$('test', 'another')	: returns a two element array
	$(['test', 'another'])	: returns a two element array
 
 ******************************************************************************/
function $()
{
	var args		= (arguments[0].constructor == Array) ? arguments[0] : toArray(arguments);
	var ids			= new Array();
	var selected	= new Array();
	
	// parse out ids
	for (var i = 0; i < args.length; i++)
	{
		ids = ids.concat(args[i].split(/[,\s]+/));
	}
	
	// grab all the elements we can
	for (var j = 0; j < ids.length; j++)
	{
		if (e = document.getElementById(ids[j]))
		{
			selected.push(e);
		}
	}
	
	// returns all unique elements unless only one id was provided
	// always returns an array when an array is provided as an argument
	selected = (ids.length == 1 && arguments[0].constructor != Array) ? selected[0] : selected.unique();

	return selected;
};
/*-----------------------------------------------------------------------------
 $CSS()											  Shorthand for SI.CSS.select()
 ******************************************************************************/
function $CSS() { return SI.CSS.select(arguments) };
	

/******************************************************************************
 SI.Tabs module v1.1
 
 ******************************************************************************/
SI.Tabs =
{
	className 	: 'tabs',
	container	: 'ul',
	onload		: function()
	{
		if (!document.getElementsByTagName) { return; }
		
		var elems	= document.getElementsByTagName(this.container);
		for (var i=0; i<elems.length; i++)
		{
			var e = elems[i];
			if (e.className==this.className)
			{
				var tabs = e.getElementsByTagName('a');
				for (var j=0; j<tabs.length; j++)
				{
					var lnk		= tabs[j];
					lnk.tabs	= tabs;
					lnk.tab		= document.getElementById(lnk.href.replace(/^([^#]*#)/,''));
					lnk.tab.lnk	= lnk;
					// Hide inactive links
					if (lnk.parentNode.className.indexOf('active') == -1) { lnk.tab.style.display = 'none'; }
					else { SI.Tabs.autofocus(lnk.tab); }
					
					lnk.onclick = function()
					{
						return SI.Tabs.select(this);
					};
				};
			};
		};
	},
	select		: function(tab) // an element
	{
		// disable all tabs
		for (var i=0; i<tab.tabs.length; i++)
		{
			var lnk = tab.tabs[i];
			lnk.parentNode.className = lnk.parentNode.className.replace(/ ?active/, '');
			lnk.tab.style.display = 'none';
		};
		tab.parentNode.className += ((tab.parentNode.className == '') ? '': ' ') + 'active';
		tab.tab.style.display = 'block';
		SI.Tabs.autofocus(tab.tab);
		return false;
	},
	autofocus	: function(e)
	{
		var inputs = e.getElementsByTagName('input');
		for (var i=0; i<inputs.length; i++)
		{
			var input = inputs[i];
			if (input.type=='text')
			{
				input.focus();
				input.select();
				break;
				}
			}
		}
	};


/******************************************************************************
 SI.Cookie module v1.0
 
 ******************************************************************************/
SI.Cookie = {
	domain	: function() {
		var domain = '.'+location.hostname.replace(/^www\./, '');
		// the following conditionals do nothing, JavaScript adds the . back when setting the cookie
		if (domain == '.localhost') { domain = 'localhost.local'; }
		else if (domain == '.127.0.0.1') { domain = '127.0.0.1'; };
		return domain;
		}(),
	set		: function(name,value) {
		var expires = new Date();
		var base 	= new Date(0);
		var diff 	= base.getTime();
		if (diff>0) { expires.setTime(expires.getTime()-diff); }
		expires.setTime(expires.getTime() + 365 * 24 * 60 * 60 * 1000);
		document.cookie = name + "=" + value + ";expires=" + expires.toGMTString() + ";path=/;domain=" + this.domain;
		},
	get		: function(name) {
		var p = name+"="; 
		var c=document.cookie; 
		var i=c.indexOf(p);
		if (i==-1) { return; };
		var e=c.indexOf(";",i+p.length);
		if (e==-1) {e = c.length; };
		return unescape(c.substring(i+p.length,e));
		},
	toss	: function(name) {
		document.cookie = name + "=;expires=Thu, 01-Jan-70 00:00:01 GMT;path=/;domain=" + this.domain;
		}
	}


/******************************************************************************
 SI.Request module v1.5m
 
 Asynchronous scripting, Inman-style baby! Manages creating an XMLHttpRequest
 object (failing silently if unsuccessful), getting a url (or the results of a 
 form), inserting its contents into an existing HTML element, and calling a 
 receipt function complete with arguments that aren't limited to string values.
 
 v1.1	: Now supports both GET and POST
 v1.2	: Added formToQuery() which takes a form and returns a complete url
 v1.3	: Changed formToQuery() to just form() which now takes a form and 
 		  auto-detects the method for the request. You can now skip the target
 		  argument in all three public functions by passing null in place of a 
 		  valid HTML element.
 v1.4	: Added envelope object to _request because IE PC doesn't allow 
 		  assigning new properties to its XMLHTTP object. Added support for 
 		  TEXTAREAs in form()
 v1.5	: Added branching for setting innerHTML of table and tbody elements 
 		  in IE PC
 v1.5m	: customized for Mint (recalcs offset height of panes after insert)
 ******************************************************************************/
SI.Request = 
{
	get		: function(url) // [target[,callback[,args]]]
	{
		this._request('GET',arguments);
	},
	
	post	: function(url)  //  [target[,callback[,args]]]
	{
		this._request('POST',arguments);
	},
	
	form	: function(form) //  [target[,callback[,args]]]
	{
		if (form.onsubmit)
		{
			if (!form.onsubmit())
			{
				return false;
			};
		};
		var method = (form.method && form.method.toUpperCase()=='POST')?'POST':'GET';
		var url = form.action;
		url += (url.indexOf('?')!=-1)?'&':'?';
		var query = [];
		
		for (var i=0; i<form.elements.length;i++)
		{
			var e = form.elements[i];
			if (e.name!='')
			{ 
				switch(e.nodeName)
				{
					case 'INPUT':
						if 
						(
							e.type.match(/(submit|image|cancel|reset)/) || 
							(e.type.match(/(checkbox|radio)/) && !e.checked)
						)
						{
							continue;
						};
						query[query.length] = escape(e.name) + '=' + escape(e.value);
					break;
					
					case 'TEXTAREA':
						query[query.length] = escape(e.name) + '=' + escape(e.value);
					break;
					
					case 'SELECT':
						query[query.length] = escape(e.name) + '=' + escape(e.options[e.selectedIndex].value);
					break;
				};
			};
		};
		arguments[0] = url + query.join('&');
		this._request(method,arguments);
	},
	
	_request	: function(type,args) // PRIVATE: Use get(), post() or form() instead
	{
		var envelope = {};
		var request = false;
		
		/*@cc_on @*/
		/*@if (@_jscript_version >= 5)
		try { request = new ActiveXObject("Msxml2.XMLHTTP"); } 
		catch (e) {
			try { request = new ActiveXObject("Microsoft.XMLHTTP"); }
			catch (E) { request = false; };
			};
		@end @*/
		if (!request && typeof XMLHttpRequest!='undefined')
		{
			request = new XMLHttpRequest();
		};
		if (!request)
		{
			return;
		};
		
		envelope.request = request;
		
		var url = args[0] + ((args[0].indexOf('?')!=-1)?'&':'?')+(new Date()).getTime();
		var query = null;
		
		if (type=='POST')
		{
			var uri = url.split('?');
			url = uri[0];
			query = uri[1];
		}
		
		envelope.ram = {};
		if (args[1] && args[1]!=null) { envelope.ram.target = args[1]; };
		if (args[2]) { envelope.ram.callback	= args[2]; };
		if (args[3]) { envelope.ram.args		= args[3]; };
		
		envelope.request.open(type,url,true);
		if (type=='POST')
		{
			envelope.request.setRequestHeader("Method","POST " + url + " HTTP/1.1");
			envelope.request.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
		}
		envelope.request.send(query);
		
		if (envelope.ram.target || envelope.ram.callback)
		{
			envelope.request.onreadystatechange = function()
			{
				if (envelope.request.readyState==4 && envelope.request.status==200)
				{
					if (envelope.ram.target)
					{
						var target = envelope.ram.target;
						var content = envelope.request.responseText;
						if (SI.IE && (target.nodeName == 'TBODY' || target.nodeName == 'TABLE'))
						{
							SI.IE.fixInnerHTML(target, content);
						}
						else
						{
							target.innerHTML = content;
						}
						
						// process any transparent PNGS loaded by the request
						if (SI.IE)
						{
							SI.IE.onbeforeload();
						};
						
						SI.Mint.onRequestLoaded(target);
					};
					if (envelope.ram.callback)
					{
						if (envelope.ram.args)
						{
							envelope.ram.callback(envelope.ram.args);
						}
						else
						{
							envelope.ram.callback();
						};
					};
					if (SI.Mint.collapse && !SI.Mint.singleCol && !SI.IE)
					{
						SI.Mint.sizePanes();
					};
				};
			};
		};
	}
};


/******************************************************************************
 SI.Mint module v1.0
 
 ******************************************************************************/
SI.Mint = {
	url				: '',
	singleCol		: false,
	maxCol			: 100, // you so crazy joe
	minColWidth		: 348,
	collapse		: false,
	collapseCols	: 0,
	panes			: [], // pane ids indexed by order
	postRequest		: [],
	onRequestLoaded	: function(target)
	{
		for (var i = 0; i < this.postRequest.length; i++)
		{
			this.postRequest[i](target);
		};
	},
	staggerPaneLoading	: function(stagger)
	{
		if (!stagger) { return; };
		this.loadPane(0);
	},
	loadPane	: function(paneIndex)
	{
		var pane_id		= this.panes[paneIndex];
		var nextPane	= paneIndex + 1;
		var tab			= $CSS('#pane-' + pane_id + ' li.active a')[0];
		this.loadTab(pane_id, tab);
		
		if (nextPane < this.panes.length)
		{
			this.loadPane(nextPane);
		};
	},
	loadFilter	: function(tab_name, filter)
	{
		var pane 	= filter.parentNode.parentNode.parentNode.parentNode;
		if (pane.className.indexOf('content-container') != -1)
		{
			pane = pane.parentNode; // scroll div is interfering
		}
		var pane_id = pane.id.replace(/[^0-9]+/g, '');
		var url		= this.url+'?pane_id='+pane_id+'&tab='+tab_name+'&filter='+filter.innerHTML;
		
		filter.parentNode.className = SI.CSS.removeClassName(filter.parentNode.className, 'active');
		filter.className = 'loading';
		
		SI.Request.get(url, pane);
	},
	loadTab		: function(pane_id, tab) 
	{
		var url			= this.url+'?pane_id='+pane_id+'&tab='+tab.innerHTML;
		var pane 		= document.getElementById('pane-'+pane_id+'-content');
		tab.parentNode.className = SI.CSS.removeClassName(tab.parentNode.className,'active');
		tab.className = SI.CSS.addClassName(tab.className, 'loading');
		
		// Load url into pane, onsuccess call this.onTabLoaded with tab as an argument
		SI.Request.get(url,pane,this.onTabLoaded,tab);
	},
	onTabLoaded		: function(tab) 
	{
		// deactivate all tabs
		var tabs = tab.parentNode.parentNode.getElementsByTagName('a');
		for (var j=0; j<tabs.length; j++)
		{
			var a_tab = tabs[j];
			a_tab.parentNode.className = SI.CSS.removeClassName(a_tab.parentNode.className,'active');
		};
		// activate current tab and load content
		// tab.innerHTML	= tab.defaultHTML;
		
		tab.className = SI.CSS.removeClassName(tab.className, 'loading');
		tab.parentNode.className = SI.CSS.addClassName(tab.parentNode.className,'active');
	},
	toggleFolder		: function(e, url) 
	{
		var folder	= e;
		var content = e.parentNode.nextSibling;
		
		if (folder.className.indexOf('folder-open') != -1) 
		{
			folder.className	= SI.CSS.replaceClassName(folder.className,'folder-open','folder');
			content.className	= SI.CSS.replaceClassName(content.className,'folder-contents-open','folder-contents');
			if (this.collapse && !this.singleCol && !SI.IE)
			{
				this.sizePanes();
			}
		}
		else
		{
			folder.className	= SI.CSS.addClassName(folder.className,'loading');
			// Load url into content, onsuccess call this.onFolderLoaded
			SI.Request.post(url, content, this.onFolderLoaded, [folder, content]);
		};
	},
	onFolderLoaded	: function(args)
	{
		var folder = args[0];
		var content = args[1];
		folder.className	= SI.CSS.removeClassName(folder.className, 'loading');
		folder.className	= SI.CSS.replaceClassName(folder.className, 'folder', 'folder-open');
		content.className	= SI.CSS.replaceClassName(content.className, 'folder-contents', 'folder-contents-open');
	},
	iPadPanesSized : false,
	sizePanes		: function()
	{
		if (this.iPadPanesSized) return;
		
		var c = document.getElementById('container');
		
		if (this.singleCol)
		{
			this.sizePaneNav();
			return;
		};
		
		if (this.maxCol > this.panes.length)
		{
			this.maxCol = this.panes.length;
		};
		
		// Updated for IE PC compatiblity, the window's inner width minus #container's left and right (pseudo-)margins
		var w = document.body.parentNode.clientWidth - 36; // c.offsetWidth;
		var width, columns;
		
		for (var i = this.maxCol - 1; i >= 0; i--)
		{
			width = Math.floor((w - (i * 18)) / (i + 1));
			if (width < this.minColWidth)
			{
				continue;
			};
			columns = i + 1;
			break;
		};
		
		if (this.collapse && !SI.IE)
		{
			var paneContainer = document.getElementById('pane-container');
						
			// not our first time through so panes have already been moved to column divs
			if (this.collapseCols > 0)
			{
				// move panes back into the pane container
				for (var j = 0; j < this.panes.length; j++)
				{
					var e = document.getElementById('pane-' + this.panes[j]);
					e.parentNode.removeChild(e); // iPad null
					paneContainer.appendChild(e);
				};
				// remove existing columns
				for (var k = 0; k < this.collapseCols; k++)
				{
					var e = document.getElementById('column-' + k);
					e.parentNode.removeChild(e); // iPad null
				};
			};
			this.collapseCols = columns;
			
			theColumns			= []; // html elements
			theColumnHeights	= [];
			
			// add columns
			for (var l = 0; l < this.collapseCols; l++)
			{
				var column = document.createElement('div');
				column.id = 'column-' + l;
				column.className = 'pane-column';
				column.style.width = width + 'px';
				paneContainer.appendChild(column);
				column_id = theColumns.length;
				theColumns[column_id] = column;
				theColumnHeights[column_id] = column.offsetHeight;
			};

			for (var m = 0; m < this.panes.length; m++)
			{
				var e = document.getElementById('pane-' + this.panes[m]);
				var column_id;
				
				// place the first (number of columns) panes in order
				if (m < theColumns.length)
				{
					column_id = m;
				}
				// then just fill which ever is the shortest column with the rest
				else
				{
					var startColumn = 0;
					var minHeight = 0;
					for (var n = 0; n < theColumnHeights.length; n++)
					{
						if (n == 0 || theColumnHeights[n] < minHeight)
						{
							minHeight = theColumnHeights[n];
							column_id = n;
						}
					}
				};
				
				if (theColumns[column_id])
				{
					// pane order is preserved reading left-right, top-down
					e.parentNode.removeChild(e); // iPad null
					theColumns[column_id].appendChild(e);
					theColumnHeights[column_id] = theColumns[column_id].offsetHeight;
				};
			};
		}
		else
		{
			var clear	= -1;
			for (var j = 0; j < this.panes.length; j++)
			{
				var r =  (j + 1) / columns;
				var e = document.getElementById('pane-' + this.panes[j]);
				e.style.clear = 'none';
				e.style.width = width + 'px';

				// if last in row
				if (r != 0 && r == Math.floor(r))
				{
					clear = j + 1;
				};
				// if first in row after first row
				if (j == clear)
				{
					e.style.clear = 'left';
					clear = -1;
				};
			};
		};
		
		this.sizePaneNav();
		// a stopgap until I can determine why lines 877, 884 & 933 don't work on the iPad
		if (navigator.userAgent.match(/iPad/)) this.iPadPanesSized = true;
	},
	paneList		: '',
	paneMenu		: '',
	paneUsesMenu	: false,
	paneListWidth	: 0,
	sizePaneNav		: function()
	{
		var l = 184;
		var r = 124;
		var pl = document.getElementById('pane-list');
		var hw = document.getElementById('header').offsetWidth;
		
		// store the current pane list HTML and create the menu HTML
		if (this.paneList == '')
		{			
			this.paneList		= pl.innerHTML;
			this.paneListWidth	= pl.parentNode.offsetWidth;
			
			var panes = pl.getElementsByTagName('a');
			
			var menu = '';
			menu += '<select id="pane-list-select" onchange="SI.Scroll.to(this.options[this.selectedIndex].value.replace(/^[^#]*#/,\'\'));">';
			for (var i = 0; i < panes.length; i++)
			{
				menu += '<option value="' + panes[i].href + '">' + panes[i].innerHTML + '</option>';
			}
			menu += '</select>';
			
			this.paneMenu = menu;
		};
		
		if ((this.paneListWidth + l + r) > hw)
		{
			if (!this.paneUsesMenu)
			{
				this.paneUsesMenu = true;
				pl.innerHTML = this.paneMenu;
			}
		}
		else
		{
			this.paneUsesMenu = false;
			pl.innerHTML = this.paneList;
		};
	},
	onloadScrolls	: function() {
		
		var h1s = SI.CSS.select('div.pane h1');
		for (var i=0;i<h1s.length;i++)
		{
			var h1 = h1s[i];
			h1.onclick = function()
			{
				SI.Scroll.to(this.parentNode.id);
			};
		};
		if (document.body.addEventListener) {
			
			function enableScrollWheel(e) {
				var s = e.currentTarget.scrollTop + (e.detail * 12);
				e.currentTarget.scrollTop = (s<0)?0:s;
				e.preventDefault();
				}
			
			var scrolls = SI.CSS.select('div.scroll','div.scroll-inline');
			for (var i=0; i<scrolls.length; i++) {
				// Mozilla allows you to enable scroll on overflow elements, Safari does not. 
				try { scrolls[i].addEventListener('DOMMouseScroll',enableScrollWheel,false); } catch (ex) { };
				}
			}
		},
	installPepper	: function(src) {
		var args = {
			MintPath	: 'Preferences',
			action	: 'Install Pepper',
			src			: src
			};
		this.clickForm(window.location,'post',args);
		},
	uninstallPepper	: function(name,id) {
		if (!window.confirm('Uninstall the '+name+' Pepper? (Doing so will delete all associated data. The Pepper may be reinstalled but this data cannot be recovered.)')) { return; }
		var args = {
			MintPath	: 'Preferences',
			action	: 'Uninstall Pepper',
			pepperID	: id
			};
		this.clickForm(window.location,'post',args);
		},
	clickForm		: function(url,method,args) {
		var form = document.createElement('form');
		form.action = url;
		form.method = method;
		
		for (var key in args) {
			var input = document.createElement('input');
			input.setAttribute('type','hidden');
			input.setAttribute('name',key);
			input.setAttribute('value',args[key]);
			form.appendChild(input);
			}
		
		document.body.appendChild(form);
		form.submit();
		},
	savePreferences	: function()
		{
			if (!document.getElementById('btn-done-bottom'))
			{
				alert('The Preferences form must be completely loaded before saving.');
				return false;
			}
			else
			{
				return true;
			};
		}
	};

/******************************************************************************
 SI.Fade module v1.0

 ******************************************************************************/
SI.Fade = {
	setOpacity : function(e, opacity)
	{
		opacity 				= (opacity == 100) ? 99.999 : opacity;
		e.style.filter 			= "alpha(opacity:" + opacity + ")";
		e.style.KHTMLOpacity 	= opacity / 100;
		e.style.MozOpacity 		= opacity / 100;
		e.style.opacity 		= opacity / 100;
		e.opacity 				= opacity;
	},

	up	: function(id)
	{
		var e = document.getElementById(id);
		if (e.opacity < 99.999)
		{
			this.setOpacity(e, e.opacity + 10);
			window.setTimeout('SI.Fade.up("' + id + '")', 100);
		};
	},

	down : function(id)
	{
		var e = document.getElementById(id);
		if (e)
		{
			if (e.opacity > 0)
			{
				this.setOpacity(e, Math.ceil(e.opacity - 10));
				window.setTimeout('SI.Fade.down("' + id + '")', 100);
			}
			else
			{
				e.parentNode.removeChild(e);
			};
		};
	},
	
	delayedDown : function(id)
	{
		var e = document.getElementById(id);
		if (e)
		{
			SI.Fade.setOpacity(e, 100);
			window.setTimeout('SI.Fade.down("' + id + '")', 2500);
		};
	}
};

/******************************************************************************
 SI.Scroll module v1.0
 
 Based on and including code originally created by Travis Beckam of 
 http://www.squidfingers.com | http://www.podlob.com
 
 ******************************************************************************/
SI.Scroll = {
	yOffset			: 53,
	scrollLoop 		: false, 
	scrollInterval	: null,
	getWindowHeight	: function() {
		if (document.all) {  return (document.documentElement.clientHeight) ? document.documentElement.clientHeight : document.body.clientHeight; }
		else { return window.innerHeight; }
		},
	getScrollLeft	: function() {
		if (document.all) { return (document.documentElement.scrollLeft) ? document.documentElement.scrollLeft : document.body.scrollLeft; }
		else { return window.pageXOffset; }
		},
	getScrollTop	: function() {
		if (document.all) { return (document.documentElement.scrollTop) ? document.documentElement.scrollTop : document.body.scrollTop; }
		else { return window.pageYOffset; }
		},
	getElementYpos	: function(el) {
		var y = 0;
		while(el.offsetParent){
			y += el.offsetTop
			el = el.offsetParent;
			}
		return y;
		},
	to 				: function(id){
		if(this.scrollLoop){
			clearInterval(this.scrollInterval);
			this.scrollLoop = false;
			this.scrollInterval = null;
			};
		if (document.getElementById('pane-list-select'))
		{
			var select = document.getElementById('pane-list-select');
			for (var i=0;i<select.options.length;i++)
			{
				if (select.options[i].value.replace(/^[^#]*#/,'') == id)
				{
					select.options[i].selected = true;
				};
			};
		};
		var container = document.getElementById('container');
		var documentHeight = this.getElementYpos(container) + container.offsetHeight;
		var windowHeight = this.getWindowHeight()-this.yOffset;
		var ypos = this.getElementYpos(document.getElementById(id));
		if(ypos > documentHeight - windowHeight) ypos = documentHeight - windowHeight;
		this.scrollTo(0,ypos-this.yOffset);
		},
	scrollTo 		: function(x,y) {
		if(this.scrollLoop) {
			var left = this.getScrollLeft();
			var top = this.getScrollTop();
			if(Math.abs(left-x) <= 1 && Math.abs(top-y) <= 1) {
				window.scrollTo(x,y);
				clearInterval(this.scrollInterval);
				this.scrollLoop = false;
				this.scrollInterval = null;
				}
			else {
				window.scrollTo(left+(x-left)/2, top+(y-top)/2);
				}
			}
		else {
			if (SI.IE) y -= this.yOffset;
			this.scrollInterval = setInterval("SI.Scroll.scrollTo("+x+","+y+")",100);
			this.scrollLoop = true;
			}
		}
	};


/******************************************************************************
 SI.Sortable module v1.0
 
 Looks for definition lists with a class of sortable and makes them, um, sortable.
 Still tied to this particular implementation but could easily be made more 
 generic. Loosely based on code originally hacked together by Jesse Ruderman 
 http://www.squarefree.com/
 
 ******************************************************************************/
SI.Sortable = {
	elems 	: new Array(),
	refreshElems 	: function() {
		var dls	= document.getElementsByTagName('dl');
		for (var i=0; i<dls.length; i++) {
			var dl = dls[i];
			if (dl.className=='sortable') {
				var k=0;
				for (var j=0; j<dl.childNodes.length; j++) {
					e = dl.childNodes[j];
					if (e.nodeName=='DT' || e.nodeName=='DD') {
						e.order = k;
						e.dl = dl;
						this.elems[k] = e;
						k++;
						}
					}
				}
			dl.elems = this.elems;
			}
		},
	getOffsets	: function(e) {
		return  {
			top			: e.offsetTop,
			bottom		: e.offsetTop+e.offsetHeight,
			halfHeight	: e.offsetTop+Math.round(e.offsetHeight/2),
			left		: e.offsetLeft,
			right		: e.offsetLeft+e.offsetWidth,
			halfWidth	: e.offsetLeft+Math.round(e.offsetWidth/2)
			};
		},
	getBounds		: function(e) { 
		return {
			top		: (0-e.offsetTop),
			bottom	: (0-e.offsetTop-e.offsetHeight+e.parentNode.offsetHeight),
			left	: (0-e.offsetLeft),
			right	: (0-e.offsetLeft-e.offsetWidth+e.parentNode.offsetWidth)
			};
		},
	onload			: function() {
		if (!document.getElementsByTagName) { return; }
		this.refreshElems();
		//SI.Debug.output('Sortable.onload');

		for (var i=0; i<this.elems.length; i++) {
			var dd = this.elems[i];
			if (dd.nodeName=='DT') { continue; }
			
			dd.style.cursor = 'move';
			dd.bounds = this.getBounds(dd);
			
			//SI.Debug.output('Initializing: '+dd.innerHTML.replace(/(<[^>]*>)*/,'')+' ('+dd.bounds.top+','+dd.bounds.bottom+')');
			
			Drag.init(dd,null,0,0,dd.bounds.top,dd.bounds.bottom);
			
			dd.onDragStart = function() {
				
				//SI.Debug.output('DragStart: '+Drag.obj.innerHTML.replace(/(<[^>]*>)*/,''));
				Drag.obj.className = 'drag';
				var bounds = SI.Sortable.getBounds(Drag.obj);
				Drag.obj.minY = bounds.top;
				Drag.obj.maxY = bounds.bottom;
				
				var tab = document.getElementById(this.id.replace(/-pane-\d+$/, ''));
				SI.Tabs.select(tab.lnk);
				}
			
			dd.onDrag = function(x,y,e) {
				e.offsets = SI.Sortable.getOffsets(e);
				var order = e.order;
				
				//SI.Debug.output('Dragging: '+e.innerHTML.replace(/(<[^>]*>)*/,'')+' (y:'+e.offsets.top+')');
				
				if (e.order!=0 && y<0) { // Free to move up and heading in that direction
					var b = e.dl.elems[e.order-1]; // The element before
					b.offsets =  SI.Sortable.getOffsets(b);
					if (e.offsets.top<=b.offsets.halfHeight && b.order!=0) {
						e.dl.removeChild(e);
						SI.Sortable.refreshElems();
						
						e.dl.insertBefore(e,b);
						SI.Sortable.refreshElems();
						
						//SI.Debug.output('Swap up ('+e.order+') '+e.innerHTML.replace(/(<[^>]*>)*/,'')+' with '+b.innerHTML.replace(/(<[^>]*>)*/,'')+' ('+e.offsets.top+' <= '+b.offsets.halfHeight+')');
						}
					}
				else if (e.order!=e.dl.elems.length-1 && y>0) { // Free to move down and heading in that direction
					var a = e.dl.elems[e.order+1]; // The element after
					a.offsets =  SI.Sortable.getOffsets(a);
					if (e.offsets.bottom>a.offsets.halfHeight) {
						e.dl.removeChild(e);
						SI.Sortable.refreshElems();
						
						if ((order+1)==e.dl.elems.length-1) { e.dl.appendChild(e); }
						else { e.dl.insertBefore(e,e.dl.elems[order+1]); }
						SI.Sortable.refreshElems();
						
						//SI.Debug.output('Swap down ('+e.order+') '+e.innerHTML.replace(/(<[^>]*>)*/,'')+' with '+a.innerHTML.replace(/(<[^>]*>)*/,'')+' ('+e.offsets.bottom+' >= '+a.offsets.halfHeight+')');
						}
					}
				}
			
			dd.onDragEnd = function(x,y,e) { 
				e.style.top = '0';
				e.className = '';
				e.innerHTML += '';
				SI.Sortable.refreshElems();
				SI.Sortable.updateInputs();
				}
			}
		},
	updateInputs		: function() {
		// had to do this after the fact because Safari eats the updated values
		// after using `innerHTML += ''` to force a window redraw
		var order = '';
		var disabled = false;
		for (var i=0; i<this.elems.length; i++) {
			var e = this.elems[i];
			if (e.id=='disable') { disabled = true; }
			
			var inputs = e.getElementsByTagName('input');
			if (inputs.length) { 
				order += inputs[0].value+',';
				inputs[1].value = (disabled)?0:1;
				e.className = (disabled)?'disabled':'';
				//SI.Debug.output(e.innerHTML.replace(/(<[^>]*>)*/,'')+((disabled)?' disabled':' enabled'));
				}
			}
		order = order.replace(/,$/,'');
		document.getElementById('pane_order').value = order;
		//SI.Debug.output('New Order: '+order);
		}
	};



/**************************************************
 * dom-drag.js
 * 09.25.2001
 * www.youngpup.net
 **************************************************
 * 2001-10-28 - fixed minor bug where events
 * sometimes fired off the handle, not the root.
 *
 * 2005-04-29 Jesse Ruderman - mangled so it probably
 * only works for reordering lists; made it keep
 * hold of the item better when onDrag moves
 * the element within the DOM or when the user
 * scrolls.
 **************************************************/

var Drag = {
	obj : null, 
	init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper) {
		o.onmousedown = Drag.start;
		
		o.hmode     = bSwapHorzRef ? false : true ;
		o.vmode     = bSwapVertRef ? false : true ;
		
		o.root = (oRoot && oRoot!=null)?oRoot:o;
		
		if (o.hmode  && isNaN(parseInt(o.root.style.left  ))) o.root.style.left   = "0px";
		if (o.vmode  && isNaN(parseInt(o.root.style.top   ))) o.root.style.top    = "0px";
		if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right  = "0px";
		if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px";
		
		o.minX  = typeof minX != 'undefined' ? minX : null;
		o.minY  = typeof minY != 'undefined' ? minY : null;
		o.maxX  = typeof maxX != 'undefined' ? maxX : null;
		o.maxY  = typeof maxY != 'undefined' ? maxY : null;
		
		o.xMapper = fXMapper ? fXMapper : null;
		o.yMapper = fYMapper ? fYMapper : null;
		
		o.root.onDragStart  = new Function();
		o.root.onDragEnd  = new Function();
		o.root.onDrag   = new Function();
		},
	start : function(e)  {
		var o = Drag.obj = this;
		e = Drag.fixE(e);
		var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
		var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
		o.root.onDragStart(x, y);
		
		o.grabX = e.pageX - x;
		o.grabY = e.pageY - y;
		
		if (o.hmode) {
			if (o.minX != null) o.minMouseX = e.pageX - x + o.minX;
			if (o.maxX != null) o.maxMouseX = o.minMouseX + o.maxX - o.minX;
			} 
		else {
			if (o.minX != null) o.maxMouseX = -o.minX + e.pageX + x;
			if (o.maxX != null) o.minMouseX = -o.maxX + e.pageX + x;
			}
		
		if (o.vmode) {
			if (o.minY != null) o.minMouseY = e.pageY - y + o.minY;
			if (o.maxY != null) o.maxMouseY = o.minMouseY + o.maxY - o.minY;
			}
		else {
			if (o.minY != null) o.maxMouseY = -o.minY + e.pageY + y;
			if (o.maxY != null) o.minMouseY = -o.maxY + e.pageY + y;
			}
		
		document.onmousemove  = Drag.drag;
		document.onmouseup    = Drag.end;
		
		return false;
		},
	drag : function(e) {
		e = Drag.fixE(e);
		var o = Drag.obj;
		
		var ey  = e.pageY;
		var ex  = e.pageX;
		var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom);
		var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
		var nx, ny;
		
		if (o.minX != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
		if (o.maxX != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
		if (o.minY != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
		if (o.maxY != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);
		
		// Goal: keep (topleft - grab) constant
		// To know where to place it, we need to know its natural position.
		
		var errorY;
		
		do {
			nx = -o.grabX + ex //((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
			ny = -o.grabY + ey //((ey - o.lastMouseY) * (o.vmode ? 1 : -1));
			
			if (o.xMapper)    nx = o.xMapper(y)
			else if (o.yMapper) ny = o.yMapper(x)
			
			Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
			Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
			oldOffsetTop = o.offsetTop;
			Drag.obj.root.onDrag(nx, ny, Drag.obj.root);
			
			// onDrag may have modified the DOM.  Catch up. (Idea from toolman / tim taylor)
			errorY = o.offsetTop - oldOffsetTop;
			o.grabY += errorY;
			} while(errorY);
		return false;
		},
	end : function() {
		document.onmousemove = null;
		document.onmouseup   = null;
		Drag.obj.root.onDragEnd(parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]), 
								parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"]), 
								Drag.obj.root);
		Drag.obj = null;
		},

	fixE : function(e) {
		if (typeof e == 'undefined') e = window.event;
		if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
		if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
		if (typeof e.pageY == 'undefined' &&  typeof e.clientX == 'number' && document.documentElement)
		{
			e.pageX = e.clientX + document.documentElement.scrollLeft;
			e.pageY = e.clientY + document.documentElement.scrollTop;
		}
		return e;
		}
	};

// Implement missing modern methods
if (Array.prototype.push == null) 		{ Array.prototype.push = function(){ for(var i = 0; i < arguments.length; i++){ this[this.length] = arguments[i]; }; return this.length; };};
if (Array.prototype.shift == null)		{ Array.prototype.shift = function(){ var item = null; if (this.length) { item = this[0]; var shifted = this.slice(1); this.length = 0; for (var i = 0; i < shifted.length; i++) { this[i] = shifted[i]; };}; return item; };};
if (Array.prototype.unshift == null)	{ Array.prototype.unshift = function(){ var original = this.slice(0); this.length = 0; for (var i = 0; i < arguments.length; i++) { this.push(arguments[i]); }; for (var j = 0; j < original.length; j++) { this.push(original[j]); }; return this.length; };};
if (Array.prototype.splice == null)		{ Array.prototype.splice = function(){ var start = arguments[0]; var resume = start + arguments[1]; var original = this.slice(0); this.length = 0; for (var i = 0; i < start; i++) { this.push(original[i]); }; if (arguments.length > 2) { for (var j = 2; j < arguments.length; j++) { this.push(arguments[j]); }; }; for (var k = resume; k < original.length; k++) { this.push(original[k]); }; return original.slice(start, resume); };};

// removes duplicate values from an array 
Array.prototype.unique = function()
{
	var original = this.slice(0);
	this.length	 = 0;

	for (var i = 0; i < original.length; i++)
	{
		var unique = true;
		for (var j = 0; j < this.length; j++)
		{
			if (original[i] == this[j])
			{
				unique = false;
				break;
			};
		};
		if (unique)
		{
			this.push(original[i]);
		};
	};
	return this;
};

// if the needle is found in the array its index is returned, if not -1 is returned
Array.prototype.search = function(needle)
{
	var index = -1;

	for (var i = 0; i < this.length; i++)
	{
		if (this[i] == needle)
		{
			index = i;
			break;
		};
	};

	return index;
};

// Returns true if all elements of the array can be found in the otherArray
Array.prototype.foundIn = function(otherArray)
{
	var found = true;

	for (var i = 0; i < this.length; i++)
	{
		if (otherArray.search(this[i]) == -1)
		{
			found = false;
			break;
		};
	};

	return found;
};

// Returns true if the string contains only whitespace
String.prototype.isEmpty = function()
{
	return this.match(/^\s*$/);
}

// Used to convert a function's arguments object into a true array
function toArray(argumentsObject)
{
	var returnArray = new Array();
	for (var i = 0; i < argumentsObject.length; i++)
	{
		returnArray[i] = argumentsObject[i];
	};
	return returnArray;
};
