//alert( "common.js: syntax OK" );



String.prototype.trim = function() {
	return this.replace( String.prototype.trim_reqexp, "" );
};

String.prototype.trim_reqexp = /(^\s+)?(\s+$)?/g;

String.prototype.startsWith = function( theString ) {
	return this.indexOf( theString ) == 0;
};

String.prototype.endsWith = function( theString ) {
	var i = this.lastIndexOf( theString );
	return ( i > 0  &&  (i + theString.length) == this.length );
};

String.prototype.spliced = function( start, len, toInsert ) {
//	var str = this.substring( 0, start );
//	if ( toInsert ) str += toInsert;
//	str += this.substring( start + len );
//	return str;
	if ( !toInsert ) toInsert = "";
	if ( !start ) start = 0;
	if ( !len ) len = 0;
//	site9.debug( "String.spliced: " + start + ", " + len + ", " + toInsert );
	return this.replace( new RegExp( "(.{" + start + "})(.{" + len + "})(.*)" ), "$1" + toInsert + "$3" );
};


Array.prototype.indexOf = function( theObject ) {
	for( var i = 0 ; i < this.length ; i++ )
		if ( theObject == this[i] ) return i;
	return -1;
};

Array.prototype.lastObject = function() {
	return this[ this.length - 1 ];
};

Array.prototype.uniqueCopy = function() {
	var theUniqArray = this.copy();
	theUniqArray.removeDuplicates();
	return theUniqArray;
	/*var theUniqArray = [];
	for( var i = 0 ; i < this.length ; i++ ) {
		var obj = this[i];
		if ( theUniqArray.indexOf( obj ) == -1 )
			theUniqArray.push(obj);
	}
	return theUniqArray;*/
};

Array.prototype.forEach = function( fn, args ) {
	for( var i = 0 ; i < this.length ; i++ )
		this[i] = fn.apply( this[i], args );
	return this;
};

Array.prototype.copy = function() {
	return [].concat( this );
};

Array.prototype.removeDuplicates = function() {
	var removed = [];
	for( var i = 0 ; i < this.length ; i++ ) {
		var obj = this[i];
		for( var j = 0 ; j < i ; j++ ) {
			if ( this[j] == this[i] ) {
				removed.push( this[i] );
				this.splice( i, 1 );
				j = i;
				i--;
			}
		}
	}
	return removed;
};

Array.prototype.pushAll = function( array ) {
	for( var i = 0 ; i < array.length ; i++ )
		this.push( array[i] );
	return this;
};

Array.prototype.unshiftAll = function( array ) {
	for( var i = 0 ; i < array.length ; i++ )
		this.unshift( array[i] );
	return this;
};



/* ================== SITE9 ================== */

site9 = {
//	offscreenImages: [],
	windowHasLoaded: false
};


////  Site 9 Feature Constructor
//
//site9.Feature = function( theFeatureName ) {
//	if ( site9  &&  site9.debug ) site9.debug("Creating Feature: " + theFeatureName);
//	this.featureName = theFeatureName;
//};
//
//site9.Feature.prototype = {
//	installed: false,
//	   loaded: false,
//
//	install: function() {},
//	   load: function() {},
//	prepare: function( node ) {},
//
//	_install: function() {
//		this.install();
//		this.installed = true;
//		return this;
//	},
//
//	_load: function() {
//		this.load();
//		this.loaded = true;
//		return this;
//	},
//
//	toString: function() {
//		return "[Site 9 Feature: " + this.featureName + "]";
//	}
//};
//

/* ================== General ================== */

site9.Arrays = {
	copy: function( theArray ) {
		return Array.prototype.copy.call( theArray );
	},

	indexOf: function( theArray, theObject ) {
		return Array.prototype.indexOf.call( theArray, theObject );
	},

	uniqueCopy: function( theArray ) {
		return Array.prototype.uniqueCopy.call( theArray );
	}
};

site9.Objects = {
	toString: function( obj, prefix, separator, joiner, suffix, ignoreEmpties, ignoredKeys ) {
		if ( !obj  &&  this != site9.Objects ) obj = this;

		if ( typeof(prefix)    != "string" ) prefix    = "{ ";
		if ( typeof(separator) != "string" ) separator = ", ";
		if ( typeof(joiner)    != "string" ) joiner    = ": ";
		if ( typeof(suffix)    != "string" ) suffix    = " }";

		var str = "";

		for( var key in obj ) {
			if ( !ignoredKeys || ignoredKeys.indexOf(key) != -1 ) {
				var value = obj[key];
				if ( !ignoreEmpties || (typeof(value) != "undefined"  &&  value != "") ) {
					if ( str.length ) str += separator;
					str += key + joiner + value;
				}
			}
		}

		return prefix + str + suffix;
	},

	copyTo: function( toObj, fromObj ) {
		if ( !fromObj  &&  this != site9.Objects ) fromObj = this;

		for ( var key in fromObj ) {
			var value = fromObj[key];
			if ( toObj[key] != value )
				toObj[key] = value;
		}
		return toObj;
	}
};


site9.locate = function( identifier ) {
	try {
		return eval( identifier );
	} catch( e ) {
		return false;
	}
	/*var path = identifier.split('.');
	var obj = window;
	while( path.length > 0  &&  obj )
		obj = obj[path.shift()];
	return ( obj  &&  obj.installed );*/
};

/*site9.valueForKeyPath = function( theKeyPath, startingObj ) {
	if ( !startingObj ) startingObj = window;
	var path = theKeyPath.split('.');

	while( path.length > 0  &&  startingObj )
		startingObj = startingObj[path.shift()];
	return startingObj;
};*/

//site9.makeWindow = function( initSrc, winName, keepInFocus, width, height, opts, content ) {
//	if ( !initSrc ) initSrc = '';
//	if ( !opts    ) opts = 'resizable,scrollbars=yes';
//	if (  width   ) opts = 'width=' + width + ',' + opts;
//	if (  height  ) opts = 'height=' + height + ',' + opts;
//
//	var win = window.open( initSrc, winName, opts );
//	if ( !keepInFocus ) window.focus();
//
//	if ( win == null ) return false;
//
//	win.name = winName;
//
//	if ( content ) {
//		win.document.open();
//		win.document.write( content );
//		win.document.close();
//	}
//
//	return win;
//};


/* ================== Debugging ================== */

site9.debug = function( theText, consoleName ) {
	if ( !consoleName ) consoleName = "out";

	site9._getDebugConsole( consoleName, true ).appendln( theText );

	return site9;
};

site9.clearDebug = function( consoleName ) {
	if ( !consoleName ) consoleName = "out";

	var console = site9._getDebugConsole( consoleName );
	if ( console ) console.clear();
};

site9._getDebugConsole = function( consoleName, create ) {
	if ( !consoleName ) consoleName = "out";
	var console = site9.DebugConsole.prototype.static_namedConsoles[consoleName];
	if ( !console && create ) console = new site9.DebugConsole( consoleName )
	return console;
};

site9.DebugConsole = function( name ) {
	this.name = name;
	this.static_namedConsoles[ name ] = this;
};

site9.DebugConsole.prototype = {
	_buffer: null,
	_window: null,
	_popupWarinings: false,
	enabled: true,
	_indent: "",

	_isStartOfLine: true,

	append: function( theText ) {
		if ( this.enabled ) {
			if ( this._isStartOfLine )
				theText = this.prefix() + theText;

			this._appendText( theText );
		}
		return this;
	},

	appendln: function( theText ) {
		this.append( theText + "\r\n" );
		this._isStartOfLine = true;
		return this;
	},

	clear: function() {
		if ( this._hasWindow() ) this._window.document.getElementById("main").value = this.header();
		else if ( this._buffer ) this._buffer = null;
		this._isStartOfLine = true;
	},

	increaseIndent: function() { this._indent += '\t'; },
	decreaseIndent: function() { this._indent = this._indent.replace(/\t$/, ""); },

	prefix: function() { return this._indent; },

	header: function() {
		var str = 'This window acts as a console for your JavaScript.\r\nSimply call site9.debug("sometext"';
		if ( this.name == "out" ) str += '[';
		str += ', "' + this.name + '"'
		if ( this.name == "out" ) str += ']';
		str += '); to have it appear here.\r\n--\r\n\r\n';
		return str;
	},

	title: function() {
		return "Site 9 JavaScript Debug Console [" + this.name + "]";
	},

	_appendText: function( theText ) {
		if ( !this._hasWindow() ) {
			this._window = this._createWindow();
			if ( !this._window ) {
				if ( !this._buffer ) {
					if ( this._popupWarnings ) alert( "S9 Debug Console (" + this.name + ") was not opened (pop-ups blocked?). Debug output will be silently buffered until it can be opened." );
					this._buffer = " ";
				}
				this._buffer += theText;
				return this;
			} else {
				if ( this._buffer ) theText = (this._buffer.substring(1) + theText);
				this.clear();
			}
		}

		this._window.document.getElementById("main").value += theText;
	},
/*
	_bufferText: function( theText ) {
		if ( !this._buffer ) {
			if ( this._popupWarnings ) alert( "S9 Debug Console (" + this.name + ") was not opened (pop-ups blocked?). Debug output will be silently buffered until it can be opened." );
			this._buffer = " ";
		}
		this._buffer += theText;
	},
*/
	_hasWindow: function() {
		return ( this._window  &&  !this._window.closed );
	},

	_createWindow: function() {
		var winName = "s9_debug_" + this.name;

		var win = window.open( "", winName, "resizable,scrollbars=NO,width=500,height=500" );
		if ( win == null ) return false;

		window.focus();

		win.name = winName;
		win.document.open();
		win.document.write( '<html><head><title>' + this.title() + '</title><style>body{margin:0;text-align:right}*{font-family:Monaco;font-size:9px;}textarea{width:99%;height:99.5%;margin:0;padding:0;border:none}div{position:absolute;bottom:0px;right:16px;padding:3px;background:#eee;border:1px solid #ccc;border-bottom:none}</style></head><body><div><a href="javascript:void(window.opener.site9.clearDebug(\''+this.name+'\'))">Clear Console</a></div><textarea id="main"></textarea></body></html>' );
		win.document.close();

		return win;
	},

	toString: function() {
		return '[' + this.title() + ']';
	},

	//  Static

	static_namedConsoles: {}
};

site9.debug = site9.clearDebug = function() {};

/* ================== DOM Helpers ================== */

try {
	site9.debug( "Using built-in Node constants (ELEMENT_NODE: " + Node.ELEMENT_NODE + ")" );
	site9._NodeConstants = Node;
} catch( e ) {
	site9._NodeConstants = {
					   ELEMENT_NODE:  1,
//					 ATTRIBUTE_NODE:  2,
//						  TEXT_NODE:  3,
//				 CDATA_SECTION_NODE:  4,
//			  ENTITY_REFERENCE_NODE:  5,
//						ENTITY_NODE:  6,
//		PROCESSING_INSTRUCTION_NODE:  7,
//					   COMMENT_NODE:  8,
					  DOCUMENT_NODE:  9,
//				 DOCUMENT_TYPE_NODE: 10,
			 DOCUMENT_FRAGMENT_NODE: 11
//					  NOTATION_NODE: 12
	};
//	site9.debug( "Using artificial Node constants (ELEMENT_NODE: " + site9._NodeConstants.ELEMENT_NODE + ")" );
}

function _( nodeID ) {
	if ( typeof(nodeID) == "string" ) return document.getElementById(nodeID);
	return nodeID;
};

function __( nodeID ) {
	if ( typeof(nodeID) == "string" ) return site9.getElementBySelector(nodeID);
	return nodeID;
};

site9.isStructuralNode = function( theNode ) {
	var ntype = theNode.nodeType;
	return ( ntype == site9._NodeConstants.ELEMENT_NODE	||
			 ntype == site9._NodeConstants.DOCUMENT_NODE	||
			 ntype == site9._NodeConstants.DOCUMENT_FRAGMENT_NODE );
};

site9.nodeAncestry = function( theNode, stoppingBefore ) {
	var list = [];
	if ( theNode )
		while( (theNode = theNode.parentNode) && (theNode != stoppingBefore) )
			list.push(theNode);
	return list;
};

site9.nodePathString = function( theNode ) {
	var pstr = "";
	var path = site9.nodeAncestry(theNode);
	path.reverse();
	path.push(theNode);
	for( var i = 0 ; i < path.length ; i++ ) {
		theNode = path[i];
		if ( i > 0 ) pstr += '/';
		pstr += site9.nodeDescription( theNode );
	}
	return pstr;
};

/**
 *  Returns a description of theNode, like: "TAG#id.class"
 *  Special handling for window, document, text nodes and strings.
 */
site9.nodeDescription = function( theNode ) {
	if ( !theNode ) return "";
	else if ( theNode == window ) return "window";
	else if ( typeof(theNode) == "string" ) return theNode;
	else if ( theNode.nodeType != site9._NodeConstants.ELEMENT_NODE ) return theNode.nodeName;

	var desc = theNode.tagName;
	if ( theNode.id )
		desc += '#' + theNode.id;
	if ( theNode.className )
		desc += '.' + theNode.className.replace( /\s+/g, '.' );
	if ( theNode.parentNode )
		desc += ':nth-child(' + site9.Arrays.indexOf( theNode.parentNode.childNodes, theNode ) + ')';

	return desc;
};



site9.modifyClassNames = function( theNodes, regex, replacements ) {
	for( var i = 0 ; i < theNodes.length ; i++ )
		theNodes[i].className = theNodes[i].className.replace( regex, replacements );
};

site9.alterClassNames = function( theNodes, classToAdd, classToRemove ) {
	if ( classToAdd    ) site9.modifyClassNames( theNodes, /^(.*)\s*$/, "$1 " + classToAdd );
	if ( classToRemove ) site9.modifyClassNames( theNodes, new RegExp("\\b" + classToRemove + "\\b", "g"), "" );
};


site9.getElementByTagName = function( theTag, theNode ) {
	return new site9._BasicCssSelector( theTag ).selectFirst( theNode );
};

site9.getElementBySelector = function( theSelector, theNode ) {
	return site9.getElementsBySelector( theSelector, theNode )[0];
};

site9.getElementsBySelector = function( theSelector, theNode ) {
	return new site9.CssSelector( theSelector ).selectAll( theNode );
};

site9.getParentByTagName = function( theTag, theNode ) {
	return site9.deprecated( "site9.getParentByTagName", site9.getParentBySelector );
};

site9.getParentsByTagName = function( theTag, theNode ) {
	return site9.deprecated( "site9.getParentsByTagName", site9.getParentsBySelector );
};

site9.getParentBySelector = function( theSelector, theNode ) {
	return new site9.CssSelector( theSelector ).selectedNodes( site9.nodeAncestry( theNode ) )[0];
};

site9.getParentsBySelector = function( theSelector, theNode ) {
	return new site9.CssSelector( theSelector ).selectedNodes( site9.nodeAncestry( theNode ) );
};

site9.getChildOfParent = function( childSelector, parentSelector, theNode ) {
	theNode = site9.getParentBySelector( parentSelector, theNode );
	if ( !theNode ) return null;
	return site9.getElementBySelector( childSelector, theNode );
};

site9.getChildrenOfParent = function( childSelector, parentSelector, theNode ) {
	theNode = site9.getParentBySelector( parentSelector, theNode );
	if ( !theNode ) return [];
	return site9.getElementsBySelector( childSelector, theNode );
};

site9.getChildrenOfParents = function( childSelector, parentSelector, theNode ) {
	var theNodes = site9.getParentsBySelector( parentSelector, theNode );
	var selected = [];
	for ( var i = 0 ; i < theNodes.length ; i++ )
		selected = selected.concat( site9.getElementsBySelector( childSelector, theNode ) );
	selected.removeDuplicates();
	return selected;
};


/**
 *  Allows you to navigate the DOM tree in a 'hard-coded', but easy way.
 *  Each character in the instructions moves in a different direction:
 *    ^		The parent node
 *    v		The first child node
 *    >		The next sibling
 *    <		The previous sibling
 *  non-ELEMENT_NODE/DOCUMENT_NODEs are skipped.
 *
 *  NOTE: Moving previous and next is reflexive -- "<>" has no net effect),
 *    but moving parent and child is only reflexive for the 0th child node --
 *    "^v" for a node that is child[0] of it's parent has no net effect, but
 *    for all other nodes it moves to the first sibling.
 */
site9.navigateNodes = function( instructions, theNode ) {
	if ( !theNode ) theNode = document;
	instructions = unescape( instructions ).replace( /\s+/g, "" );

	for( var i = 0 ; i < instructions.length ; i++ ) {
		var instr = instructions.substring(i,i+1);

		do {
				 if ( instr == '^' ) theNode = theNode.parentNode;
			else if ( instr == '>' ) theNode = theNode.nextSibling;
			else if ( instr == 'v' ) theNode = theNode.childNodes[0];
			else if ( instr == '<' ) theNode = theNode.previousSibling;
			else site9.debug("site9.navigateNodes: Unknown instruction '" + instr + "'");

			if ( !theNode ) return null;
		} while( !site9.isStructuralNode(theNode) );
	}

	return theNode;
};

/**
 *  Just like site9.navigateNodes, except the syntax is based on CSS:
 *    <		The parent node
 *    >		The first child node
 *    +		The next sibling
 *    -		The previous sibling
 */
site9.navigateNodesCss = function( instructions, theNode ) {
	instructions = unescape( instructions );
	instructions = instuctions.replace( /</g, '^' ).replace( />/g,  'v' );
	instructions = instuctions.replace( /-/g, '<' ).replace( /\+/g, '>' );

	return site9.navigateNodes( instructions, theNode );
};


/* ================== JavaScript CSS Node Access ==================
 *
 *  Allow you to select nodes from the DOM by using a CSS selector.
 *  Full CSS syntax is allowed, but only these parts are implemented:
 *    *					Universal Selector
 *    E					Type Selector
 *    E F				Descendant Selector
 *    E > F				Child Selector
 *    E + F				Adjacent Selector
 *    E.class			Class Selector
 *    E#id				ID Selector
 *    E:first-child		Pseudo-class Selector
 *    E:last-child		Pseudo-class Selector
 *    E, F				Union Selector
 *
 *  These non-standard selectors are also supported:
 *    E - F				Adjacent (previous) Selector
 *    E < F				Parent Selector
 *    E ^ F				Ascendant Selector
 *
 *  The parser specially handles these non-standard selectors:
 *    1					Treated as Nth-child pseudo-class (only positive numbers)
 *    E:1				Treated as Nth-child pseudo-class
 *    E:-1				Treated as Nth-last-child pseudo-class
 *
 *  See: <http://www.w3.org/TR/REC-CSS2/selector.html> for more information.
 */

/*
site9.parseCssSelector = function( selectorText ) {
	var selector = new site9.CssSelector( selectorText );

	if ( selector.simpleSels.length == 1 ) {
		selector = selector.simpleSels[0];

		if ( selector.basicSels.length != 1 ) {
			selector = selector.basicSels[0];
		}
	}

	return selector;
};
*/

//  A comma-separated list of simple selectors, like: "A, B > C, D E F > *"

site9.CssSelector = function( selectorText ) {
	selectorText = selectorText.trim();
//	site9.debug( "Creating CssSelector from: " + selectorText );

	if ( !selectorText ) {
		this.simpleSels = [];
		return;
	}

	this.simpleSels = selectorText.split( /\s*,\s*/ );
//	site9.debug( "simple selectors: " + this.simpleSels );

	for ( var i = 0 ; i < this.simpleSels.length ; i++ ) {
//		site9.debug( "simple selector: " + this.simpleSels[i] );
		this.simpleSels[i] = new site9._SimpleCssSelector( this.simpleSels[i] );
	}
};

site9.CssSelector.prototype = {
	simpleSels: null,

	selectAll: function( theNode ) {
		if ( !theNode ) theNode = document;
//		site9.debug( "CssSelector [" + this + "].selectAll(): " + site9.nodePathString(theNode) );
//
		var possible = [];

		for( var i = 0 ; i < this.simpleSels.length ; i++ )
			possible = possible.concat( this.simpleSels[i].selectAll( theNode ) );

		possible.removeDuplicates();
//		site9.debug( "CssSelector [" + this + "].selectAll(): selected " + possible.length + " nodes" );
		return possible;
	},

	selectReverse: function( theNode ) {
		if ( !theNode ) theNode = document;
//		site9.debug( "CssSelector [" + this + "].selectReverse(): " + site9.nodePathString(theNode) );
//
		var possible = [];

		for( var i = 0 ; i < this.simpleSels.length ; i++ )
			possible = possible.concat( this.simpleSels[i].selectReverse( theNode ) );

		possible.removeDuplicates();
//		site9.debug( "CssSelector [" + this + "].selectReverse(): selected " + possible.length + " nodes" );
		return possible;
	},

	selectFirst: function( theNode ) {
		return this.selectAll( theNode )[0];
	},

	selectLast: function( theNode ) {
		return this.selectAll( theNode ).reverse()[0];
	},

	selectsNode: function( theNode, fromNode ) {
//		site9.debug( "CssSelector [" + this + "].selectsNode(): " + site9.nodePathString(theNode) );
//
		var selectsFrom = this.selectReverse( theNode );

		if ( !fromNode ) return selectsFrom.length > 0;

		for( var i = 0 ; i < selectsFrom.length ; i++ )
			if ( selectsFrom[i] == fromNode )
				return true;
		return false;
	},

	selectedNodes: function( possible, fromNode ) {
//		site9.debug( "CssSelector [" + this + "].selectedNodes(): " + site9.nodePathString(fromNode) );
//
		var selected = [];
		for( var i = 0 ; i < possible.length ; i++ )
			if ( this.selectsNode( possible[i], fromNode ) )
				selected.push( possible[i] );
		return selected;
	},

	toString: function() {
		return this.simpleSels.join(", ");
	},

	copy: function() {
		var sel = new site9.CssSelector("");
		for( var i = 0 ; i < this.simpleSels.length ; i++ )
			sel.simpleSels[i] = this.simpleSels[i].copy();
		return sel;
	}
};

//  A selector consisting of chained basic selectors, like: "A > B C + D"

site9._SimpleCssSelector = function( selectorText ) {
	selectorText = selectorText.replace( /\s*([>+])\s*/g, " $1" );
//	site9.debug( "creating _SimpleCssSelector from: " + selectorText );

	if ( !selectorText ) {
		this.basicSels = [];
		return;
	}

	this.basicSels = selectorText.split( /\s+/ );
//	site9.debug( "basic selectors: " + this.basicSels );

	for( var i = 0 ; i < this.basicSels.length ; i++ ) {
//		site9.debug( "basic selector: " + this.basicSels[i] );
		this.basicSels[i] = new site9._BasicCssSelector( this.basicSels[i] );
	}
};

site9._SimpleCssSelector.prototype = {
	basicSels: null,

	selectAll: function( theNode ) {
//		site9.debug( "SimpleCssSelector [" + this + "].selectAll(): " + site9.nodePathString(theNode) );
		var selected = [theNode];

		for( var i1 = 0 ; i1 < this.basicSels.length ; i1++ ) {
			var possible = [];

			for( var i2 = 0 ; i2 < selected.length ; i2++ )
				possible = possible.concat( this.basicSels[i1].selectAll( selected[i2] ) );

			possible.removeDuplicates();
			selected = possible;
		}

//		site9.debug( "SimpleCssSelector [" + this + "].selectAll(): selected " + selected.length + " nodes" );
		return selected;
	},

	selectReverse: function( theNode ) {
//		site9.debug( "SimpleCssSelector [" + this + "].selectReverse(): " + site9.nodePathString(theNode) );
		var selected = [theNode];

		for( var i1 = this.basicSels.length - 1 ; i1 >= 0  ; i1-- ) {
			var possible = [];

			for( var i2 = 0 ; i2 < selected.length ; i2++ )
				possible = possible.concat( this.basicSels[i1].selectReverse( selected[i2] ) );

			possible.removeDuplicates();
			selected = possible;
		}

//		site9.debug( "SimpleCssSelector [" + this + "].selectReverse(): selected " + selected.length + " nodes" );
		return selected;
	},

	selectFirst:   site9.CssSelector.prototype.selectFirst,
	selectLast:    site9.CssSelector.prototype.selectLast,
	selectsNode:   site9.CssSelector.prototype.selectsNode,
	selectedNodes: site9.CssSelector.prototype.selectedNodes,

	toString: function() {
		return this.basicSels.join(' ');
	},

	copy: function() {
		var sel = new site9._SimpleCssSelector("");
		for( var i = 0 ; i < this.basicSels.length ; i++ )
			sel.basicSels[i] = this.basicSels[i].copy();
		return sel;
	}
};

//  The most basic selector, no more complex than "> TAG#id.class1.class2:first-child"

site9._BasicCssSelector = function( selectorText ) {
	selectorText = selectorText.trim();
//	site9.debug( "creating _BasicCssSelector from: " + selectorText );
	this.classNames = [];

	if ( !selectorText ) return;

//	  Joiner:

	if ( !/^[<>^+-]/.test(selectorText) ) selectorText = ' ' + selectorText;
	this.joiner  = selectorText.charAt(0);
	selectorText = selectorText.substring(1).trim();

//	  Pseudo-class Fix:

	if ( /^-?\d/.test(selectorText) )  selectorText = ':' + selectorText;
	selectorText = selectorText.replace( /:first-child/, ":0"  );
	selectorText = selectorText.replace( /:last-child/,  ":-1" );

//	  Tag Name:

	this.tagName = selectorText.match( /^(\w*)/ )[1].toUpperCase();
	if ( this.tagName == "" )
		this.tagName = '*';
	else
		selectorText = selectorText.substring( this.tagName.length );

//	 Everything Else:

	for( var start = 0, end = 1 ; end <= selectorText.length ; end++ ) {
		if ( end == selectorText.length  ||  ".:#]".indexOf( selectorText.charAt(end) ) != -1 ) {
			var content = selectorText.substring( start + 1, end );
			switch( selectorText.charAt(start) ) {
				case '.':
					this.classNames.push( new RegExp("\\b" + content + "\\b") );
					break;
				case ':':
					if ( /^-?\d/.test(content) )
						this.childNum = parseInt( content );
					else {
						this.unsupported += ':' + content;
						site9.debug( "site9._BasicCssSelector: Pseudo-class '" + content + "' is not supported" );
					}
					break;
				case '#':
					this.id = content;
					break;
				case '[':
					this.unsupported += '[' + content + ']';
					site9.debug( "site9._BasicCssSelector: Attribute selector '" + content + "' is not supported" );
					break;
			}
			start = end;
		}
	}
};

site9._BasicCssSelector.prototype = {
		 tagName: '*',
			  id: null,
	  classNames: null,
	attributeMap: null,
		nthChild: null,
		  joiner: null,
	 unsupported: "",

	selectAll: function( theNode ) {
//		site9.debug( "BasicCssSelector [" + this + "].selectAll(): " + site9.nodePathString(theNode) );
		var possible = this._join( theNode, this.joiner );
		var selected = [];

		for( var i = 0 ; i < possible.length ; i++ ) {
			if ( this._matchesNode( possible[i] ) )
				selected.push( possible[i] );
		}

//		site9.debug( "BasicCssSelector [" + this + "].selectAll(): selected " + selected.length + " nodes" );
		return selected;
	},

	selectReverse: function( theNode ) {
//		site9.debug( "BasicCssSelector [" + this + "].selectReverse(): " + site9.nodePathString(theNode) );
		var selected = [];

		if ( this._matchesNode( theNode ) )
			selected = this._join( theNode, this._inverseJoiner() );

//		site9.debug( "BasicCssSelector [" + this + "].selectReverse(): selected " + selected.length + " nodes" );
		return selected;
	},

	selectFirst:   site9.CssSelector.prototype.selectFirst,
	selectLast:    site9.CssSelector.prototype.selectLast,
	selectsNode:   site9.CssSelector.prototype.selectsNode,
	selectedNodes: site9.CssSelector.prototype.selectedNodes,

	_matchesNode: function( theNode ) {
//		site9.debug( "BasicCssSelector [" + this + "]._matchesNode(): checking node: " + site9.nodePathString(theNode) );
//
		var noTagName = ( this.tagName == null );
		var isDocument = ( theNode == document );
		if ( noTagName || isDocument )
			return ( noTagName && isDocument );

		if ( !site9.isStructuralNode(theNode) )
			return false;

		if ( this.tagName != "*"  &&  theNode.tagName != this.tagName )
			return false;

		if ( this.id  &&  theNode.id != this.id )
			return false;

		for( var i = 0 ; i < this.classNames.length ; i++ )
			if ( !this.classNames[i].test( theNode.className ) )
				return false;

		if ( this.nthChild != null ) {
			var n = this.nthChild;
			if ( n < 0 )
				n += theNode.parentNode.childNodes.length;
			if ( theNode != theNode.parentNode.childNodes[n] );
				return false;
		}

		return true;
	},

	_join: function( theNode, theJoiner ) {
		switch ( theJoiner ) {
			case ' ':
				if ( this.id ) {
					// Without this, selectors like "#foo" are (needlessly) very expensive ... that is a Bad Thing.
					var node = document.getElementById( this.id );
					return ( node  &&  site9.nodeAncestry( node, theNode.parentNode ).lastObject() == theNode ) ? [node] : [];
				} else
					return theNode.getElementsByTagName( this.tagName );
			case '^': return site9.nodeAncestry( theNode );
			case '>': return theNode.childNodes;
			case '<': return [ theNode.parentNode ];
			case '+': return [ theNode.nextSibling ];
			case '-': return [ theNode.previousSibling ];

			default:
				site9.debug( "site9._BasicCssSelector: Unsupported joiner: " + theJoiner );
				return [];
		}
	},

	_inverseJoiner: function() {
		switch( this.joiner ) {
			case ' ': return '^';
			case '^': return ' ';
			case '>': return '<';
			case '<': return '>';
			case '+': return '-';
			case '-': return '+';
		}
	},

	toString: function() {
		var str = "";

		if ( this.joiner != null  &&  this.joiner != ' ' )
			str += this.joiner + ' ';

		if ( this.tagName == null )
			return str + "document";

		str += this.tagName;

		if ( this.id )
			str += "#" + this.id;

		for( var i = 0 ; i < this.classNames.length ; i++ )
			str += "." + this.classNames[i].source.replace( /\\b/g, "" );

		if ( this.nthChild == null ) {
			//noop
		} else if ( this.nthChild == 0 )
			str += ":first-child";
		else if ( this.nthChild == -1 )
			str += ":last-child";
		else if ( this.nthChild > 0 )
			str += ":nth-child(" + this.nthChild + ")";
		else
			str += ":nth-last-child(" + this.nthChild + ")";
//
//		for( var i in this.attributeMap ) {
//			var attrPatterns = this.attributeMap[i];
//			for( var i2 = 0 ; i2 < attrPatterns.length ; i2++ )
//				str += "[" + i + " like " + attrPatterns[i].source + "]";
//		}

		str += this.unsupported;

		return str;
	},

	copy: function() {
		var sel = new site9._BasicCssSelector("");

		tagName		= this.tagName;
		id			= this.id;
		nthChild	= this.nthChild;
		joiner		= this.joiner;
		unsupported	= this.unsupported;
//		attributeMap = this.attributeMap;

		for( var i = 0 ; i < this.classNames.length ; i++ )
			sel.classNames[i] = this.classNames[i];

		return sel;
	}
};


site9.Point = function( x, y ) {
	this.x = parseInt( x || 0 );
	this.y = parseInt( y || 0 );
};

site9.Point.prototype = {
	toString: function() {
		return "(" + this.x + "," + this.y + ")";
	}
};

site9.Size = function( w, h ) {
	this.w = parseInt( w || 0 );
	this.h = parseInt( h || 0 );
};

site9.Size.prototype = {
	toString: function() {
		return this.w + " x " + this.h;
	}
};

site9.Rectangle = function( x, y, w, h ) {
	this.location = new site9.Point( x, y );
	this.size = new site9.Size( w, h );
};

site9.Rectangle.prototype = {
	getExtents: function() {
		return new site9.Point( this.location.x + this.size.w, this.location.y + this.size.h )
	},

	toString: function() {
		return "[ " + this.location + ", " + this.getExtents() + " ]";
	}
};

site9.nodeBounds = function( theNode ) {
	var bounds = new site9.Rectangle( 0, 0, theNode.offsetWidth, theNode.offsetHeight );
	var loc = bounds.location;
	while( theNode ) {
		loc.x += theNode.offsetLeft;
		loc.y += theNode.offsetTop;
		theNode = theNode.offsetParent;
	}
	return bounds;
};


/* ================== User Interface Helpers ================== */

site9.validEmail = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

site9.validURL = /^https?:\/\/[a-zA-Z0-9.\-]+\/[a-zA-Z0-9._\/\?\&;=#\-]*$/;

site9.verifyEmail = function( emailAddress ) {
	site9.deprecated( "site9.verifyEmail", null, "Use site9.validEmail.test(...) instead" );
	return site9.validEmail.test(emailAddress);
};

site9.verifyEmailWithAlert = function( emailAddress ) {
	if ( site9.validEmail.test(emailAddress) ) return true;

	alert( "'" + emailAddress + "' does not appear to be a valid e-mail address. Please double-check it.\r\n\r\nE-mail addresses are usually something like 'name@example.com'" );
	return false;
};

site9.verifyURL = function( theURL ) {
	site9.deprecated( "site9.verifyURL", null, "Use site9.validURL.test(...) instead" );
	return site9.validURL.test(emailAddress);
};

site9.verifyURLWithAlert = function( theURL ) {
	if ( site9.validURL.test(theURL) ) return true;

	alert( "'" + theURL + "' does not appear to be a valid URL. Please double-check it.\r\n\r\nComplete URLs must start with 'http://' or 'https://' and have at least a forward-slash after the host name, like 'http://www.caravantours.com/'." );
	return false;
};

site9.verifyText = function( theText, maxLength ) {
	return (theText.length <= maxLength);
};

site9.verifyTextWithAlert = function( theText, maxLength ) {
	if ( verifyText(theText, maxLength) ) return true;

	theText = theText.substring( maxLength - 20, maxLength );
	alert( "The text you provided is too long. Everything after '..." + theText + "' will be cut off." );
	return false;
};


site9.paginatedConfirm = function( pageHeader, text, linesPP ) {
	var lines = text.split("\r\n").join("\n").split("\r").join("\n").split("\n");
	if ( lines.length < linesPP ) return confirm( pageHeader + text );

	var page;
	var pi = 1;
	var pn = parseInt( "" + (lines.length / linesPP) ) + 1; // <- NOTE: this might not be accurate...

	for( var i = 0 ; i < lines.length ; i++ ) {
		page = pageHeader;
		var eop = i+linesPP;
		for( var j = i ; j < eop ; j++ ) {
			if ( j < lines.length ) page += lines[j];
			page += "\r\n";
		}
		page += "\r\nPage " + pi + " of " + pn + ".\r\nClick OK to contiue, or Cancel to exit.";
		if ( !confirm(page) ) return false;
		i = j;
		pi++;
	}
	return true;
};


/* ================== URL Helpers ================== */

site9.replaceHash = function( theUrl, newHash ) {
	if ( !/^(#.*)?$/.test(newHash) )
		newHash = '#' + newHash;
	return theUrl.replace( /(#.*)?$/, newHash );
};

//site9.URL = function( theUrl ) {
////	site9.debug( theUrl );
//	var regex = new RegExp("^((https?:)(//)?(([^@:]*)(:([^@]*))?@)?(([^/:]*)(:(\\d+))?))?((/([^#?]*))(#([^?]*))?(\\?(.*))?)$" );
//	var bits = theUrl.match( regex );
//	if ( bits != null ) {
////		site9.debug( "did the url match..." );
//		this.href = bits[0];
//		this.protocol = bits[2];
//		this._protocol = this.protocol.replace( /:$/, "" ); // Read only raw protocol
//		this.protocolSep = bits[3];
//		this.username = bits[5];
//		this.password = bits[7];
//		this.hostname = bits[8];
//		this.host = bits[9];
//		this.port = bits[11];
//		this.path = bits[13];
//		this.hash = bits[15];
//		this._hash = this.hash.replace( /^#/, "" ); // Read only raw hash
//		this._search = bits[18];
//
//		this.search = new this._Search( this._search );
//	}
//};
//
//site9.URL.prototype = {
//	toString: function() {
//		var _userInfo = "";
//		if ( this.username  &&  this.username.length > 0 )
//			_userInfo = this.username;
//		if ( this.password  &&  this.password.length > 0 )
//			_userInfo += ':' + this.password;
//		if ( _userInfo.length > 0 )
//			_userInfo += '@';
//		return this.protocol + this.protocolSep + _userInfo + this.hostname + this.path + this.hash + this.search.toString();
//	},
//
//	_Search: function( searchText ) {
//		bits = searchText.split('&');
////		site9.debug( "pairs: " + bits );
//		for ( var i = 0 ; i < bits.length ; i++ ) {
//			if ( bits[i] != "" ) {
//				var pair = bits[i].split('=');
////				site9.debug( "pairs[" + i + "]: " + pair );
//				if ( pair.length == 1 ) pair[1] = "";
//				this[ unescape(pair[0]) ] = unescape(pair[1]);
//			}
//		}
//	}
//};
//
//site9.URL.prototype._Search.prototype = {
//	toString: function() {
//		var s = "";
//		for( var k in this ) {
//			var v = this[k];
//			if ( v != null ) s += '&' + escape(k) + '=' + escape(v);
//		}
//		return s.replace( /^\&/, '?' );
//	}
//};
//

/* ================== Form Utilities ================== */

//site9.formUtil = new site9.Feature( "site9.formUtil" )._install()._load();
site9.formUtil = {};

site9.formUtil.ensureHiddenFieldValue = function( theForm, fieldName, fieldValue ) {
	var field = theForm[fieldName];
	if ( !field ) {
//		site9.debug( "Adding " + fieldName + "=" + fieldValue + " to form: " + theForm.name );
		field = document.createElement("input");
		field.setAttribute("type",  "hidden"  );
		field.setAttribute("name",  fieldName );
		theForm.appendChild( field );
	}
//	field.setAttribute("value", fieldValue );
	field.value = fieldValue;
};

site9.formUtil.replaceUrlField = function( theUrl, fieldName, fieldValue ) {
	if ( fieldValue ==     0 ) fieldValue = "0px";
	if ( fieldValue == false ) fieldValue = "false";

	//  Find the old value:
	var a = theUrl.search( new RegExp("[?&]" + fieldName + "([=&].*)?$") ) + 1;
	if ( a > 0 ) {
		//  Remove the old value:
		var b = theUrl.indexOf('&', a);
		if ( b == -1 ) b = theUrl.length - 1;
		theUrl = theUrl.substring( 0, a ) + theUrl.substring( b + 1 );
	}
	//  Add the new value (if there is one)
	if ( fieldValue )
		theUrl += (theUrl.indexOf('?') >= 0 ? '&' : '?') + fieldName + '=' + escape(fieldValue);
	return theUrl;
};

site9.formUtil.getUrlField = function( theUrl, fieldName ) {
//	site9.debug( "theUrl: " + theUrl );
	theUrl = theUrl.replace( /^[^?]*\?/, "" );
//	site9.debug( "theUrl: " + theUrl );
	if ( theUrl.length > 0 ) {
		var pairs = theUrl.split('&');
//		site9.debug( "pairs: " + pairs );
		for( var i = 0 ; i < pairs.length ; i++ ) {
			var pair = pairs[i];
//			site9.debug( "pair[" + i + "]: " + pair );
			if ( pair == fieldName  ||  pair.indexOf(fieldName+'=') == 0 )
				return pair.substring( pair.indexOf('=')+1 );
		}
	}
	return null;
};

site9.formUtil.addCurrentDateTime = function( theFormOrUrl, fieldName ) {
	if ( !fieldName ) fieldName = "z";
	var fieldValue = (new Date()).getTime();
	if ( theFormOrUrl.tagName == "form" )
		return site9.formUtil.ensureHiddenFieldValue( theFormOrUrl, fieldName, fieldValue );
	else
		return site9.formUtil.replaceUrlField( theFormOrUrl, fieldName, fieldValue );
};


/* ================== HTML Parsing ==================
 *
 *  This was created primarily to help out with AJAX. It's currently not really used.
 */


////site9.htmlParser = new S9Feature( "site9.htmlParser" )._install()._load();
//site9.htmlParser = {};
//
//site9.htmlParser.startOfElementWithId = function( theHTML, theID ) {
//	var start;
//	var thehtml = theHTML.toLowerCase();
//	var theid   = theID.toLowerCase();
//
//	start = thehtml.indexOf('id="' + theid + '"');
//	if ( start == -1 ) start = thehtml.indexOf('id=' + theid);
//	if ( start == -1 ) { site9.debug("No Element! (1)"); return false; }
//
//	start = thehtml.substring( 0, start ).lastIndexOf('<');
//
////	site9.debug( "start of #" + theID + ": " + start );
////	site9.debug( "around the tag: " + theHTML.substring(start).substring(0, 50) );
////	site9.debug( "-----------------------------------------------------------" );
//
//	return start;
//};
//
//site9.htmlParser.endOfElementWithId = function( theHTML, theID, start ) {
//	if ( start == -1 ) return -1;
//
//	var end, clippedOff = 0;
//	var thehtml, theid = theID.toLowerCase();
//
//	start++;
//	theHTML = theHTML.substring(start);
//	clippedOff += start;
//	thehtml = theHTML.toLowerCase();
//
//	var tag = thehtml.replace(/\s+/gim, ' ').replace( /^(\w+)(>|\s).*$/, "$1" );
////	site9.debug( "tag: [[" + tag + "]]" );
//	if ( tag.match(/^\w+$/) == null ) { site9.debug("No Element! (3)"); return false; }
//
//	start = thehtml.indexOf('>');
//	if ( start == -1 ) { site9.debug("No Element! (4)"); return false; }
//
//	start++;
//	theHTML = theHTML.substring(start);
//	clippedOff += start;
//	thehtml = theHTML.toLowerCase();
//
////	site9.debug( "html.length: " + thehtml.length );
//	start = -1;
//	end = -1;
//	while( start <= end ) {
////		 ### NOTE: These are not very robust. They can't tell that <iframe> is not <i>
//
//		start = thehtml.indexOf( "<"  + tag, start + 1 );
//		if ( start == -1 ) start = thehtml.length + 1;
//
//		end = thehtml.indexOf( "</" + tag, end + 1 );
//		if ( end == -1 ) end = thehtml.length;
//
////		site9.debug( "start: " + start + ", end: " + end );
//	}
//
//	end = thehtml.indexOf( '>', end ) + 1;
//	if ( end == 0 ) end = thehtml.length;
//
////	site9.debug( "html around end: " + theHTML.substring(end-3-tag.length).replace(/^(.{0,50}).*$/, "$1") );
//	end = clippedOff + end;
////	site9.debug( "end of #" + theID + ": " + end );
//	return end;
//};
//
//site9.htmlParser.getInnerHTML = function ( theHTML ) {
////	site9.debug( "getting innerHTML of: " + theHTML );
//	theHTML = theHTML.replace( /^<[^\/>][^>]*>/m, "" );
////	site9.debug( "innerHTML: " + theHTML );
//	var end = theHTML.lastIndexOf( "</" );
//	if ( end != -1 ) theHTML = theHTML.substring( 0, end );
////	theHTML = theHTML.replace( /<\/[^>]+>$/m, "" );
////	site9.debug( "innerHTML: " + theHTML );
//	return theHTML;
//};
//
//site9.htmlParser.splitAroundElementWithId = function( theHTML, theID ) {
//	var theResult = new Array();
//	var start, end;
//	start = site9.htmlParser.startOfElementWithId( theHTML, theID );
//	if ( start >= 0 ) {
//		end = site9.htmlParser.endOfElementWithId( theHTML, theID, start );
//		if ( end >= 0 ) {
//			theResult.push( theHTML.substring( 0, start ) );
//			theResult.push( theHTML.substring( start, end ) );
//			theResult.push( theHTML.substring( end ) );
//			return theResult;
//		}
//	}
//	return null;
//};
//
//site9.htmlParser.getAttributes = function( theTag ) {
//	var attr, attrs = {}, name, value;
//	theTag = theTag.replace( /^[^<]*<\S+/, '' ).trim(); // Trim of the tag name.
////	site9.debug( "parsing attribute list: " + theTag );
//	var maxattrs = 10000; // Just in case ... to avoid an infinite loop.
//	while ( theTag.length > 0 && theTag.charAt(0) != '>' && maxattrs-- > 0 ) {
//		var parsed = theTag.match( /\s*(\w[^\s=>]*)(=(("([^"]*)"?)|([^\s">]*)))?\s*(.*)/ );
//		if ( !parsed )
//			theTag = "";
//		else {
//			name = parsed[1];
//			if ( name == "class" ) name = "className";
//			else if ( name == "for" ) name = "htmlFor";
//
//			value = parsed[5];
//			if ( value == "" ) value = parsed[6];
//			if ( value == "" ) value = name;
//
//			theTag = parsed[7];
//		}
//		attrs[name] = value;
//	}
////	for( var i in attrs )
////		site9.debug( "attrs[" + i + "] = " + attrs[i] );
//	return attrs;
//};
//

/* ================== Site 9 Events ================== */


//site9.events = new S9Feature( "site9.events" )._install()._load();
site9.events = {
	_pending:	 [],
	_dispatcher: {},
	_nextID:	 0
};

/**
 *  Attaches handler as a new handler for eventName on node. It is added to the current handler(s).
 *  You can pass a css selector for node to attach to the node that it selects. If the window had not yet loaded,
 *  the attachment is delayed until it is.
 *
 *  The first handler always has a priority of 0. Negative priorities are called earlier, and positive priorities later.
 *
 *  A handler can be executed conditionally, based on the result(s) of the previous handler(s) by passing a function
 *  (or script string) as 'conditional'. The handler is called only if conditional evaluates to true. The method signature
 *  for a conditional should be function( results ) where results is an array of the results of preceeding handlers
 *  (with results[0] being the most recent result).
 *
 *  Returns an opaque Attachment object that can be passed to dettach() later to remove this handler.
 *
 *  When handler is invoked, "this" = node, and arguments is the same as the arguments to the original action.
 *
 *  This would be a good form.onsubmit addition:
 *  	conditional: site9.events.conditionals.isNotFalse
 *
 *  	function( event ) {
 *  		if ( form should not be submitted )
 *  			return false;
 *  		else
 *  			return true;
 *  	}
 */
site9.events.attach = function( node, eventName, handler, priority, conditional ) {
	var attachment = new this._Attachement( node, eventName, handler, priority, conditional );

	if ( typeof(node) == "string"  &&  this._pending != null )
		this._pending.push( attachment );
	else
		this._attach( attachment );

	return attachment;
};

/**
 *  Removes the Attachment designated by attachment. The attachment should be the value returned from the attach() call.
 */
site9.events.dettach = function( attachment ) {
//	site9.debug( "dettaching: " + attachment );
	var list;

	if ( typeof(attachment.node) == "string" ) {
//		site9.debug( "dettaching: attachment was still pending" );
		//  Check for defferred attachment:
		list = this._pending;
		for( var i = 0 ; i < list.length ; i++ ) {
			if ( list[i] == attachment ) {
				list.splice( i, 1 );
				return true;
			}
		}
	} else if ( (list = this._eventQ( attachment.node, attachment.eventName, false )) != null ) {
//		site9.debug( "dettaching: attachment was still live" );
		//  Check for current attachment:
		for( var i = 0 ; i < list.length ; i++ ) {
			if ( list[i] == attachment ) {
				if ( list.length > 1 )
					list = list.splice( i, 1 );
				else {
					attachment.node[attachment.eventName] = null;
					this._dispatcher[attachment.node][attachment.eventName] = null;
				}
				return true;
			}
		}
	}
	site9.debug( "Could not dettach the attachment, perhaps it was already dettached?" );
	return false;
};

site9.events._attach = function( attachment ) {
	if ( typeof(attachment.node)        == "string"    ) attachment.node        = site9.getElementBySelector( attachment.node );
	if ( typeof(attachment.handler)     == "string"    ) attachment.handler     = new Function( attachment.handler );
	if ( typeof(attachment.priority)    == "undefined" ) attachment.priority    = 0;
	if ( typeof(attachment.conditional) == "string"    ) attachment.conditional = new Function( "results", attachment.conditional );

	var q = this._eventQ( attachment.node, attachment.eventName, true );
//	site9.debug( "Attaching: " + attachment );

	var notAttached = true;

	for( var i = 0 ; ( notAttached && i < q.length ) ; i++ ) {
		if ( q[i].priority > attachment.priority ) {
			q.splice( i, 0, attachment );
			notAttached = false;
		}
	}
	if ( notAttached ) q.push( attachment );
};

site9.events._Attachement = function( node, eventName, handler, priority, conditional ) {
	this._id = site9.events._nextID++;

	this.node		 = node;
	this.eventName	 = "on" + eventName;
	this.handler	 = handler;
	this.priority	 = priority;
	this.conditional = conditional;
};

site9.events._Attachement.prototype = {
	toString: function( attachment ) {
		if ( !attachment ) attachment = this;
		return "[" + attachment._id + "]: " + site9.nodeDescription(attachment.node) +
			"." + attachment.eventName + "+=" + attachment.handler + "(" + attachment.priority + ")";
	}
};


site9.events._loadPending = function() {
	var list = this._pending;
	this._pending = null;

	for( var i = 0 ; i < list.length ; i++ )
		this._attach( list[i] );
};

site9.events._dispatch = function( node, eventName, args ) {
//	site9.debug( "dispatching: " + node + "." + eventName );
	var q = this._eventQ( node, eventName, false );
	if ( q == null ) return;

	var results = [];
	for( var i = 0 ; i < q.length ; i++ ) {
		var qi = q[i];
		try {
			if ( !qi.conditional  ||  qi.conditional(results) )
				results.unshift( qi.handler.apply(node, args) );
		} catch(e) {
			site9.debug( "Exception while executing event: " + node + "[" + eventName + "][" + i + "]:\r\n" +
						 site9.Objects.toString(e) );
		}
	}
	return results[0];
};

site9.events._eventQ = function( node, eventName, create ) {
	var qs4Obj = this._dispatcher[node];
	if ( !qs4Obj ) {
		if ( create ) this._dispatcher[node] = qs4Obj = new Object();
		else return null;			
	}

	var q4Event = qs4Obj[eventName];
	if ( !q4Event ) {
		if ( create ) {
			qs4Obj[eventName] = q4Event = [];

			var defaultHandler = node[eventName];
			if ( typeof(defaultHandler) == "function" )
//			{
//				site9.debug( "Node has a default handler!" );
				q4Event.push( new this._Attachement( node, eventName, defaultHandler, 0 ) );
//			}
//			else site9.debug( "Node did not have a default handler!" );

			node[eventName] = new Function("return site9.events._dispatch(this,'" + eventName + "',arguments);");
		} else return null;
	}

	return q4Event;
};

site9.events.conditionals = {
	isTrue:     function( results ) { return results[0] == true;  },
	isFalse:    function( results ) { return results[0] == false; },
	isNotTrue:  function( results ) { return results[0] != true;  },
	isNotFalse: function( results ) { return results[0] != false; }
};

site9.events.currentEvent = function( pEvent ) {
	if ( pEvent ) {
//		site9.debug( "currentEvent was parameter" );
		return pEvent;
	}

	if ( window.event ) {
//		site9.debug( "currentEvent must be window.event" );
		return window.event;
	}

	var x;
	if ( x = site9.events.currentEvent.caller ) {
//		site9.debug( "currentEvent.caller: " + x );
		if ( x = site9.events.currentEvent.caller.arguments ) {
//			site9.debug( "currentEvent.caller.arguments: " + x );
			if ( x.length > 0 ) {
//				site9.debug( "currentEvent.caller.arguments.length: " + x.length );
				return site9.events.currentEvent.caller.arguments[0];
			}
		}
	}

	return null;
};

site9.events.attach( window, "load", "site9.events._loadPending();", -1 );




/* ================== Site 9 XMLHttpRequest Stuff ================== */


site9.HttpConnection = function( async, allowsQueuing, onload, onerror ) {
	this._id = "id:" + site9.HttpConnection.prototype._static_nextId++;

	this._async = async;
	this._usesQ = allowsQueuing;
	this._q = [];
	this._current = null;

	this._loadAttachment  = null;
	this._errorAttachment = null;

	this.xmlHttp = null;
	this._reusable = 0;

		 if ( onload ) this.onload = onload;
	else if ( async  ) this.onload = function() { site9.debug( "site9.HttpConnection taking no action onload!" ); };

		 if ( onerror ) this.onerror = onerror;
	else if ( async   ) this.onerror = function() { site9.debug( "site9.HttpConnection taking no action onerror." ); };
};

site9.HttpConnection.prototype = {
	get: function( url ) {
		this.push( this.queuedGET( url ) );
		return this;
	},

	post: function( url, data ) {
		this.push( this.queuedPOST( url, data ) );
		return this;
	},

	onload:  function() {},
	onerror: function() {},

	succeeded: function() {
		return ( 200 <= this.xmlHttp.status && this.xmlHttp.status < 300 );
	},

	getCurrentRequest: function() {
		return this._current;
	},

	createGET: function( url ) {
		return {
			method: "GET",
			url:	url,
			data:	null
		};
	},

	createPOST: function( url, data ) {
		return {
			method: "POST",
			url:	url,
			data:	data
		};
	},

	push: function( request ) {
		if ( !this._usesQ ) this.stop(true);
//		else site9.debug( "Queuing up GET " + request.url );
		this._q.push( request );
		if ( this._current == null ) this._resume();
	},

	unshift: function( request ) {
		if ( !this._usesQ ) this.stop(true);
//		else site9.debug( "Queuing up GET " + theUrl + " for immediate dispatch" );
		this._q.unshift( request );
		if ( this._current == null ) this._resume();
	},

	stop: function( all ) {
		if ( this.xmlHttp ) this.xmlHttp.abort();
		if ( all ) this._q = [];
		this._resume();

		return this;
	},

	_resume: function() {
		this._checkAttachmentTo("load");
		this._checkAttachmentTo("error");

		this._current = null;

		if ( this._q.length > 0 ) {
			var request = this._q.shift();
			this._current = request;

//			site9.debug( "Sending " + request.method + " " + request.url );

			switch ( this._reusable ) {
				case 0:
					//  WinIE cannot reuse an XMLHttpRequest
//					site9.debug( "Creating a new xmlHttp object" );
					try { this.xmlHttp = new XMLHttpRequest();					 this._reusable = 1; } catch(e1) {
					try { this.xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");	 this._reusable = 0; } catch(e2) {
					try { this.xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); this._reusable = 0; } catch(e3) {
						site9.debug("No XML HTTP found.");
						return;
					} } }
					//nobreak;
				case 1:
					//  Firefox can reuse an XMLHttpRequest by re-assigning its onreadystatechange
//					site9.debug( "Setting onreadystatechange" );
					this.xmlHttp.onreadystatechange =
						new Function("site9.HttpConnection.prototype._static_onreadystatechange('" + this._id + "');");
					//nobreak;
				case 2:
					//  Safari can reuse an XMLHttpRequest with no change.
//					site9.debug( "Placing this connection into the active table" );
					site9.HttpConnection.prototype._static_active[ this._id ] = this;
			}
//			if ( IS_SAFARI ) this._reusable = 2;

			this.xmlHttp.open( request.method, request.url, this._async );
			this.xmlHttp.send( request.data );

			if ( !this._async )
				site9.HttpConnection.prototype._static_onreadystatechange(this._id);
		}
//		else site9.debug( "No more requests in the q, sleeping." );
	},

	_checkAttachmentTo: function( eventName ) {
		var checkName = "_" + eventName + "Attachment";
		var fnName = "on" + eventName;
		if ( this[checkName] != this[fnName] ) {
//			site9.debug( "(re)attaching to my " + eventName );
			site9.events.attach( this, eventName, "this._resume();",  10 );
			this[checkName] = this[fnName];
		}
	},

	toString: function() {
		return '[site9.HttpConnection (' + this._id + ')]';
	},

	//  Static

	_static_nextId: 0,
	_static_active: {},

	_static_onreadystatechange: function( id ) {
//		site9.debug( "Ready state of a site9.HttpConnection.xmlHttp changed" );
		var context = site9.HttpConnection.prototype._static_active[id];
//		site9.debug( "context for id: " + id + " => " + context );
		if ( !context ) return;

		if ( context.xmlHttp.readyState == 4 ) {
			if ( context.succeeded() )
				context.onload( context, 1, 2, 3, 4, 5 );
			else
				context.onerror( context, 1, 2, 3, 4, 5 );

			site9.HttpConnection.prototype._static_active[id] = null;
		}
	}
};


/* ================== WinIE Hover fix ================== */

site9.pseudoHover = {
//	_regexp: /:(hover|active)/g,
	_pseudoHoverBehavior: "behavior:url(/scripts/pseudo_hover.htc);",
	_specificityFix: "pseudo_hover_fix",

	loaded: function( theNode ) {
		var sheets = [];
		sheets.pushAll( document.styleSheets );

		if ( sheets.length == 0  ||  typeof(sheets[0].imports) == "undefined" ) {
//			site9.debug( "Not needed, bailing out." );
			return;
		}

		if ( !theNode ) {
			theNode = document;
//			site9.debug( "Adding specificityFix to HTML" );
			document.body.parentNode.className += " " + this._specificityFix;
//			site9.debug( "Adding specificityFix to BODY" );
			document.body.className += " " + this._specificityFix;
		}

		var _mediaCleaner = /\bnot\s+/g, _mediaTester = /\b(screen|all)\b/i;
		var _selectorSplitter = /\s+/, _selectorParser = /^(([^.#:]*))?(\.([^#:]*))?(#([^:]*))?(:(.*))?$/;

//		site9.debug( "Number of style sheets: " + sheets.length );
		var behaviors = {};

//		site9._getDebugConsole('out').increaseIndent();
		for( var i = 0 ; i < sheets.length ; i++ ) {
			var sheet = sheets[i];
//			site9.debug( "Looking at sheets[" + i + "] ... media: " + sheet.media + ", href: " + sheet.href );
			if ( !sheet.media || _mediaTester.test( sheet.media.replace(_mediaCleaner,"$1") ) ) {
				if ( sheet.imports ) {
//					site9.debug( "Adding " + sheet.imports.length + " imported style sheet(s)" );
					sheets.pushAll( sheet.imports );
				}

				var rules = sheet.rules;

//				site9._getDebugConsole('out').increaseIndent();
				for( var j = 0 ; j < rules.length ; j++ ) {
					var rule = rules[j];
//					site9.debug( "rules[" + j + "]: " + rule.selectorText );

					var selectorText = "", behaviorSelText = "", ruleStyle;
					var stopNow = false, shouldDelete = false, shouldAdd = false;

					var lostSpecificity = 0;
					var endOfHTML = 0, endOfBODY = 0;

					var basicSels = rule.selectorText.split( _selectorSplitter );

//					site9._getDebugConsole('out').increaseIndent();
					for( var k = 0 ; !stopNow  &&  k < basicSels.length ; k++ ) {
//						site9.debug( "basicSels[" + k + "]: " + basicSels[k] );
						//  IE always reformats the selector as TAG.class#id:pseudo
						var bits = basicSels[k].match( _selectorParser );
						var foundHTML = false;

						if ( !bits ) {
							site9.debug( "This basic selector text is unrecognizable: " + basicSels[k] );
							shouldDelete = shouldAdd = false;
							stopNow = true;
						} else if ( bits[2] == "UNKNOWN" ) {
//							site9.debug( "Basic selector does not work here, will remove this rule" );
							shouldAdd = false;
							shouldDelete = true;
							stopNow = true;
						} else {
							if ( behaviorSelText.length ) behaviorSelText += ' ';
							behaviorSelText += ( bits[1] + bits[3] + bits[5] );

							switch( bits[2] ) {
								case "HTML":
//									site9.debug( "Selects HTML" );
									if ( bits[3] ) endOfHTML = selectorText.length + 4;
									else endOfHTML = -1;
									foundHTML = true;
									break;
								case "BODY":
//									site9.debug( "Selects BODY" );
									if ( bits[3] ) endOfBODY = selectorText.length + 4;
									else endOfBODY = -1;
									break;
								case "A":
//									site9.debug( "Basic selector is for hyperlinks, ignoring it" );
									//behaviorSelText += bits[7]; // <-- Add the :hover when it's a link?
									if ( bits[7] ) lostSpecificity++;
									break;
								default:
									if ( bits[8] == "hover" || bits[8] == "active" ) {
//										site9.debug( "Original basic selector: " + bits[1] + bits[3] + bits[5] + bits[7] );
										if ( !bits[3] )
											bits[3] = ('.' + (bits[2] = 'x'));
										else {
//											site9.debug( "Rule lost specificity (10) during hover-conversion: " + rule.selectorText );
											lostSpecificity++;
										}

										bits[3] = bits[3] + "_pseudo_" + bits[8];
										bits[7] = bits[8] = "";
//										site9.debug( "Modified basic selector: " + bits[1] + bits[3] + bits[5] + bits[7] );

										//  We will need to add a behavior for this part of the selector:
										behaviors[ behaviorSelText ] = true;

										shouldDelete = shouldAdd = true;
									}
//									else site9.debug( "Rule is not a :hover or :active rule" );
							}

							if ( selectorText.length ) selectorText += ' ';
							selectorText += ( bits[1] + bits[3] + bits[5] + bits[7] );
							if ( foundHTML ) endOfHTML = selectorText.length + 1;
						}
					}
//					site9._getDebugConsole('out').decreaseIndent();

					if ( shouldAdd ) ruleStyle = site9.Objects.copyTo( {}, rule.style );

					if ( shouldDelete ) {
//						site9.debug( "Removing Rule: " + rule.selectorText );
						sheet.removeRule(j--);
					}

					if ( shouldAdd ) {
//						site9.debug( "Adding Rule: " + selectorText );
						while( lostSpecificity > 0 ) {
							if ( endOfHTML >= 0 ) {
//								site9.debug( "Replacing specificity (10) by attaching to [HTML]" );
								selectorText = selectorText.spliced( endOfHTML, 0, '.'+this._specificityFix+' ' );
								endOfHTML = -1;
							} else if ( endOfBODY >= 0 ) {
//								site9.debug( "Replacing specificity (10) by attaching to [BODY]" );
								selectorText = selectorText.spliced( endOfBODY, 0, '.'+this._specificityFix+' ' );
								endOfBODY = -1;
							} else {
								site9.debug( "WARNING: The modified rule's specificity is " + (lostSpecificity*10) + " lower than the original rule" );
								lostSpecificity = 0;
							}
							lostSpecificity--;
						}
						sheet.addRule( selectorText, "not-a-property: not-a-value", ++j );
						site9.Objects.copyTo( sheet.rules[j].style, ruleStyle );
					}
//					site9.debug( "rules[" + j + "] .. Finished" );
				}
//				site9._getDebugConsole('out').decreaseIndent();
			}
//			site9.debug( "sheets[" + i + "] .. Finished" );
		}
//		site9._getDebugConsole('out').decreaseIndent();

		sheet = document.styleSheets[ document.styleSheets.length - 1 ];
//		site9.debug( "Adding Behaviors to: " + sheet.href );
//		site9._getDebugConsole('out').increaseIndent();
		for( var selectorText in behaviors ) {
//			site9.debug( "Adding rule: " + selectorText + " { " + this._pseudoHoverBehavior + " }" );
			sheet.addRule( selectorText, this._pseudoHoverBehavior );
		}
//		site9._getDebugConsole('out').decreaseIndent();
//		site9.debug( "Added Behaviors." );
	},

//	_printStyleSheet: function( sheet ) {
//		site9.debug( "Location: " + sheet.href );
//		if ( sheet.imports ) {
//			site9.debug( "Imported Style Sheet(s):" );
//			for( var i = 0 ; i < sheet.imports.length ; i++ ) {
//				site9._getDebugConsole('out').increaseIndent();
//				this._printStyleSheet( sheet.imports[i] );
//				site9._getDebugConsole('out').decreaseIndent();
//			}
//		}
//		site9.debug( sheet.cssText );
////		var rules = ( sheet.rules ? sheet.rules : sheet.cssRules );
////
////		for( var i = 0 ; i < rules.length ; i++ ) {
////			var rule = rules[i];
////
////			if ( rule.type == 1  ||  ( rule.type != 0  &&  !rule.type ) ) {
////				site9.debug( rule.selectorText + " { " + this.styleToString(rule.style) + " }" );
////			} else if ( rule.type == 3 ) {
////				site9.debug( "Imported Style Sheet:" );
////				site9._getDebugConsole('out').increaseIndent();
////				this._printStyleSheet( rule.styleSheet );
////				site9._getDebugConsole('out').decreaseIndent();
////			} else {
////				site9.debug( "Other rule: " + rule.type + " " + rule.cssText );
////			}
////		}
//	},
//
//	styleToString: function( style ) {
//		if ( style.cssText ) return style.cssText;
//		else return site9.Objects.toString( style, "", "; ", ": ", "", true, [ "accelerator" ] );
//	},


	_turnOn:  /\b(\w+)\b/g,
	_turnOff: /\s*\w+_pseudo_hover\b/g,

	setState: function( theNode, on ) {
		var cn = theNode.className;
//		site9.debug( "pseudo-hover: classname before: " + cn );
		if ( on ) cn = cn.replace( this._turnOn,  "$1 $1_pseudo_hover" ) + " x_pseudo_hover";
		else	  cn = cn.replace( this._turnOff, "" );
//		site9.debug( "pseudo-hover: classname after:  " + cn );
		theNode.className = cn;
	}
};

site9.events.attach( window, "load", "site9.pseudoHover.loaded();" );


/*site9.events.attach( window, "load", function() {
	site9.debug( "\r\n\r\n\r\n\r\n\r\nlisting style sheets" );
	for( var i = 0 ; i < document.styleSheets.length ; i++ )
		site9.pseudoHover._printStyleSheet( document.styleSheets[i] );
} );*/


/* ================== Deprecated Methods ================== */


site9.deprecated = function( theName, replacedBy, message ) {
	var theCaller = site9.deprecated.caller;
	if ( !theCaller ) theCaller = eval(theName);

	var theCallerCaller = theCaller.caller;
	if ( !theCallerCaller ) theCallerCaller = theCaller.arguments.caller;

	var theCallerArgs = theCaller.arguments;
	var args = "";
	for( var i = 0 ; i < theCallerArgs.length ; i++ ) {
		if ( i > 0 ) args += ", ";
		args += typeof(theCallerArgs[i]);
	}

	site9.debug( "\r\n\r\n---\r\nWarning: Deprecated function: " + theName + "( " + args + " ) called from " +
				 (theCallerCaller ? theCallerCaller : "?") +
				 ( message ? "\r\n" + message : "" ) +
				 "\r\n---\r\n\r\n" );

	if ( typeof(replacedBy) == "function" )
		return replacedBy.apply( this, theCaller.arguments );
};


site9._firstInList = function( theList ) {
	site9.deprecated( "site9._firstInList", null, "Use theList[0] instead" );
	return theList[0];
};

site9._getElementsByAttributes = function( theNode, isDirect, theTag, theIds, theClasses ) {
	site9.deprecated( "site9._getElementsByAttributes", null, "Use new site9.CssSelector( selectorText ).selectAll( theNode ) instead" );

	var selectorText = theTag;

	if ( isDirect ) selectorText = ">" + selectorText;

	for( var i = 0 ; i < theIds.length ; i++ )
		selectorText += '#' + theIds[i];

	for( var i = 0 ; i < theClasses.length ; i++ )
		selectorText += '.' + theClasses[i];

	return new site9._BasicCssSelector( selectorText ).selectAll( theNode );
};

site9._isChildOf = function( node, parent ) {
	site9.deprecated( "site9._isChildOf", null, "Use site9.indexOfObjectInArray( parent, site9.nodeAncestry(node) ) != -1 instead" );
	return site9.indexOfObjectInArray( parent, site9.nodeAncestry(node) ) != -1;
};

site9.trim = function( theString ) {
	site9.deprecated( "site9.trim", null, "Use theString.trim() instead" );
	return theString.trim();
};


site9._unique = function( theArray ) {
	site9.deprecated( "site9._unique", null, "Use site9.Arrays.uniqueCopy( theArray ) or theArray.uniqueCopy() instead. Also, you may want to use theArray.removeDuplicates()" );
	return theArray.uniqueCopy();
};

site9.indexOfObjectInArray = function( theObject, theArray ) {
	site9.deprecated( "site9.indexOfObjectInArray", null, "Use site9.Arrays.indexOf( theArray, theObject ) or theArray.indexOf( theObject ) instead" );
	return theArray.indexOf( theObject );
};


function S9debug( theText ) {
	return site9.deprecated( "S9debug", site9.debug );
};

function S9Feature( theFeatureName ) {
	site9.deprecated( "S9Feature", null, "Use new Object() or {} instead" );
	return Object();
};

function S9modifyClassNames( theNodes, regex, replacements ) {
	return site9.deprecated( "S9modifyClassNames", site9.modifyClassNames );
};

function S9getElementsBySelector( theSelector, theNode ) {
	return site9.deprecated( "S9getElementsBySelector", site9.getElementsBySelector );
};

function S9getElementsByAttributes( theNode, isDirect, theTag, theIds, theClasses ) {
	return site9.deprecated( "S9getElementsByAttributes", site9._getElementsByAttributes );
};

function S9isChildOf( node, parent ) {
	return site9.deprecated( "S9isChildOf", site9._isChildOf );
};

function S9unique( theArray ) {
	return site9.deprecated( "S9unique", site9._unique );
};



function trim( theString ) {
	return site9.deprecated( "trim", site9.trim );
};

function verifyEmail( emailAddress ) {
	return site9.deprecated( "verifyEmail", site9.verifyEmail );
};

function verifyEmailWithAlert( emailAddress ) {
	return site9.deprecated( "verifyEmailWithAlert", site9.verifyEmailWithAlert );
};

function verifyURL( theURL ) {
	return site9.deprecated( "verifyURL", site9.verifyURL );
};

function verifyURLWithAlert( theURL ) {
	return site9.deprecated( "verifyURLWithAlert", site9.verifyURLWithAlert );
};

function verifyText( theText, maxLength ) {
	return site9.deprecated( "verifyText", site9.verifyText );
};

function verifyTextWithAlert( theText, maxLength ) {
	return site9.deprecated( "verifyTextWithAlert", site9.verifyTextWithAlert );
};



function S9startOfElementWithId( theHTML, theID ) {
	return site9.deprecated( "S9startOfElementWithId", site9.htmlParser.startOfElementWithId );
};

function S9endOfElementWithId( theHTML, theID, start ) {
	return site9.deprecated( "S9endOfElementWithId", site9.htmlParser.endOfElementWithId );
};

function S9getInnerHTML( theHTML ) {
	return site9.deprecated( "S9getInnerHTML", site9.htmlParser.getInnerHTML );
};

function S9splitAroundElementWithId( theHTML, theID ) {
	return site9.deprecated( "S9splitAroundElementWithId", site9.htmlParser.splitAroundElementWithId );
};






site9.Collection = function( collectionName, min, defaultState, max ) {
	this.name = collectionName;

	this._activateActions = [];
	this._deactivateActions = [];

	this._offscreenImages = [];

	this.min = ( min || 1 );
	this.max = ( max || 0 );
	this.defaultState = defaultState;

	if ( collectionName ) {
		site9.Collections[ collectionName ] = this;
		this._locator = new RegExp('^#'+this.name+'=(.+)$');
		if ( this._locator.test(window.location.hash) )
			this.changeTo( window.location.hash );
	}

	if ( this.activeState == null  &&  this.defaultState != null )
		this.changeTo( this.defaultState );
};

site9.Collections = {};

site9.Collection.prototype = {
	activeState: null,
	_locator: null,
	timeout: null,
	wrap: true,
	cachesNodes: true,

	/**
	 *  Records this collection's active state in the hash of the given object (or window.location if none is given)
	 */
	recordStateInHash: function( ofObjectWithHash ) {
		if ( this.name  &&  site9.windowHasLoaded ) {
			if ( !ofObjectWithHash ) ofObjectWithHash = window.location;
			ofObjectWithHash.hash = this.name + '=' + this.activeState;
			return true;
		}
		else return false;
	},

	changeTo: function( newState ) {
		if ( this.timeout ) {
//			site9.debug( "Aborting delayed change" );
			clearTimeout(this.timeout);
			this.timeout = null;
		}
//		site9.debug( this.name + ".changeTo: " + newState );

		if ( newState == "" ) newState = null;

		if ( newState == null ) {
			newState = this.defaultState;
		} else {
			if ( typeof(newState) == "string"  &&  this._locator  &&  this._locator.test(newState) ) {
//				site9.debug( "looks like it is a hash location..." );
//				site9.debug( "" + this._locator );
//				site9.debug( "" + newState.match( this._locator ) );
				newState = newState.match( this._locator )[1];
//				site9.debug( "looks like it is a hash location ... interpretted as: " + newState );
			}
//			else site9.debug( "NOT A HASH location ... interpretted as: " + newState );

			var newStateNum = parseInt(newState);

			if ( !isNaN(newStateNum) ) {
				newState = newStateNum;
//				site9.debug( "looks like it is a number ... interpretted as: " + newState );

				if ( this.max  &&  newState > this.max )
					newState = ( this.wrap ? this.min : this.max );

				if ( newState < this.min )
					newState = ( this.wrap ? this.max : this.min );
//				site9.debug( "after bounds checks, we have: " + newState );
			}
		}

		if ( this.activeState != newState ) {
			this.ondeactivate( this.activeState );
			this.activeState = newState;
			this.onactivate( this.activeState );
		}

		//if ( this.name  &&  site9.windowHasLoaded )
		//	window.location.hash = this.name + '=' + this.activeState;

		return true;
	},

	changeToRelative: function( delta ) {
		this.changeTo( (this.activeState||this.min) + delta );
	},

	changeToNext: function() {
		this.changeToRelative( 1 );
	},

	changeToPrevious: function() {
		this.changeToRelative( -1 );
	},

	onactivate: function( state ) {
//		site9.debug( "activating state: " + state );
		if ( this.onWillActivate ) this.onWillActivate( state );

		this._notifyListeners( state, this._activateActions );

		if ( this.onDidActivate ) this.onDidActivate( state );
	},

	ondeactivate: function( state ) {
//		site9.debug( "deactivating state: " + state );
		if ( this.onWillDectivate ) this.onWillDectivate( state );

		this._notifyListeners( state, this._deactivateActions );

		if ( this.onDidDectivate ) this.onDidDectivate( state );
	},

	_notifyListeners: function( state, listeners ) {
		for( var i = 0 ; i < listeners.length ; i++ ) {
//			site9.debug( this.name + " invoking (de)activation action " + i );
			listeners[i].invoke( state );
		}
	},

	/*onchange: function( from, to ) {
		site9.debug( "Collection '" + this.name + "' changed from " + from + " to " + to );
		this.activeState = to;

		var list = this._actions;
		for( var i = 0 ; i < list.length ; i++ ) {
			site9.debug( this.name + " invoking action " + i );
			this._actions[i].invoke( from, to );
		}
		//if ( this.name )
		//	window.location.hash = '#' + this.name + '=' + this.activeState;
		site9.debug( "Collection '" + this.name + "' finished changed from " + from + " to " + to );
	},*/

	/*displayNthChild: function( selectorText, valueWhenDisplayed ) {
		//if ( arguments.length < 2 ) valueWhenDisplayed = "block";
		//this._addAction( new site9._CollectionAction( this, selectorText, null, ["none", valueWhenDisplayed] ) );
		this.display( selectorText, "none", "deactivate" );
		this.display( selectorText, valueWhenDisplayed, "activate" );
	},*/

	display: function( selectorText, display, trigger ) {
		return this._addAction( new site9._CollectionAction( selectorText, null, display ), trigger );
	},

	/*displayNone: function( selectorText, trigger ) {
		return this.display( selectorText, "none", trigger );
	},

	displayBlock: function( selectorText, trigger ) {
		return this.display( selectorText, "block", trigger );
	},

	displayInline: function( selectorText, trigger ) {
		return this.display( selectorText, "inline", trigger );
	},*/

	swap: function( selectorText, attr, list, trigger ) {
		return this._addAction( new site9._CollectionAction( selectorText, attr, list, null, true ), trigger );
	},

	/*swapSrc: function( selectorText, list, trigger ) {
		return this.swap( selectorText, "src", list, trigger );
	},

	swapHref: function( selectorText, list, trigger ) {
		return this.swap( selectorText, "href", list, trigger );
	},

	swapInnerHTML: function( selectorText, list, trigger ) {
		return this.swap( selectorText, "innerHTML", list, trigger );
	},*/

	replace: function( selectorText, attr, regexp, replacements, trigger ) {
		return this._addAction( new site9._CollectionAction( selectorText, attr, regexp, replacements ), trigger );
	},

	replaceSrc: function( selectorText, regexp, replacements, trigger ) {
		return this.replace( selectorText, "src", regexp, replacements, trigger );
	},


	/**
	 *  This class aggressively caches the nodes that each action targets. If you make
	 *  changes to these nodes, you must call invalidate() to get new node lists.
	 */
	invalidate: function() {
		for( var i = 0 ; i < this._activateActions.length ; i++ ) {
//			site9.debug( "Invalidating action: " + i );
			this._activateActions[i].invalidate();
		}
		for( var i = 0 ; i < this._deactivateActions.length ; i++ ) {
//			site9.debug( "Invalidating action: " + i );
			this._deactivateActions[i].invalidate();
		}
	},


	_addAction: function( theAction, trigger ) {
		if ( trigger == "activate" )
			trigger = true;
		else if ( trigger == "deactivate" )
			trigger = false;
		else {
			site9.debug( "unknown trigger: " + trigger );
			return;
		}

		( trigger ? this._activateActions : this._deactivateActions ).push( theAction );

		if ( this.activeState != null ) {
			if ( trigger ) {
//				site9.debug( "Artificial activation of state: " + this.activeState );
				theAction.invoke( this.activeState );
			} else if ( this.max ) {
				for( var i = this.min ; i <= this.max ; i++ ) {
//					site9.debug( "Artificial deactivation of state: " + i );
//					theAction.invoke(i);
				}
			}
		}
//		else site9.debug( "No artificial (de)activation required because activeState is: " + this.activeState );

		return theAction;
	},

	_retainImage: function( src ) {
		var img = new Image();
		img.src = src;
		this._offscreenImages.push( img );
	}
};

site9._CollectionAction = function( selectorText, attr, options, replacements, stateless ) {
//	site9.debug( "Creating new Collection Action ... " );
	this._selector = new site9.CssSelector( selectorText );
	this._nodes = null;
	this._attr = attr;
	this._options = options;
	this._replacements = replacements;
	this._stateless = stateless;
//	site9.debug( "Created " + site9.Objects.toString(this) );
};

site9._CollectionAction.prototype = {
	invoke: function( state ) {
//		site9.debug( "action(" + this._selector + ", " + this._attr + ", " + this._options + ", " + this._replacements + ").invoke(" + state + ")" );

		var list = this.nodes();
		var node = this._stateless ? list[0] : list[state-1];

//		site9.debug( "Applying action to node: " + site9.nodePathString( node ) );
		if ( !node ) {
			site9.debug( "Ignoring missing node!" );
		} else if ( this._replacements ) {
//			site9.debug( "BEFORE: node[this._attr]: " + node[this._attr] );
			node[this._attr] = node[this._attr].replace( this._options, this._replacements );
//			site9.debug( "AFTER:  node[this._attr]: " + node[this._attr] );
		} else if ( this._attr ) {
//			site9.debug( "BEFORE: node[this._attr]: " + node[this._attr] );
			node[this._attr] = this._options[ state ];
//			site9.debug( "AFTER:  node[this._attr]: " + node[this._attr] );
		} else {
//			site9.debug( "BEFORE: node.style.display: " + node.style.display );
			if ( node.style.display != this._options )
				node.style.display = this._options;
//			site9.debug( "AFTER:  node.style.display: " + node.style.display );
		}
	},

	invalidate: function() {
		this._nodes = null;
	},

	nodes: function() {
		var list = this._nodes;
		if ( list == null ) {
//			site9.debug( "Fetching nodes for selector: " + this._selector );
			list = this._selector.selectAll();
			if ( this.cachesNodes ) this._nodes = list;
		}
		return list;
	}/*,

	writeStatesIntoHyperlinks: function( collectionName ) {
		var list = this.nodes();
		for( var i = 0 ; i < list.length ; i++ ) {
			var node = list[i];
			site9.debug( "Looking for hyperlink around: " + site9.nodePathString(node) );
			node = node.parentNode;
			//if ( node.tagName.toLowerCase() != "a" )
			//	node = site9.getParentBySelector("a");
			if ( !node ) {
				site9.debug( "Couldn't find a hyperlink around target node " + i );
			} else {
				site9.debug( "Setting href of " + site9.nodePathString(node) + " to #" + collectionName + '=' + (i+1) );
				node.href = '#' + collectionName + '=' + (i+1);
			}
		}
	}*/
};



site9.linkTBA = function() {
	alert( "The page or service to which this link refers is unfinished. Please check back later" );
	return false;
};



site9.events.attach( window, "load", "site9.windowHasLoaded=true" );

//site9.debug("common.js finished");
