/*
Script: Core.js
	Mootools - My Object Oriented javascript.

License:
	MIT-style license.

MooTools Copyright:
	copyright (c) 2007 Valerio Proietti, <http://mad4milk.net>

MooTools Credits:
	- Class is slightly based on Base.js <http://dean.edwards.name/weblog/2006/03/base/> (c) 2006 Dean Edwards, License <http://creativecommons.org/licenses/LGPL/2.1/>
	- Some functions are inspired by those found in prototype.js <http://prototype.conio.net/> (c) 2005 Sam Stephenson sam [at] conio [dot] net, MIT-style license
	- Documentation by Aaron Newton (aaron.newton [at] cnet [dot] com) and Valerio Proietti.
*/
/**
  * @name: FD 
  * @package: Fairfax Digital Javascript Framework 
  * @version: 0.6 
  * @author: Dave Elkan 
  * @copyright: Copyright 2008. Fairfax Digital. 
  * * * * * * * * * * * * * * * * * * * * * * * * * * 
  *             DO NOT ALTER THIS FILE. 
  *                ---                  
  * If your requirements are not met by this widget 
  * or this widget does not work please ask 
  * Dave Elkan <delkan@fairfaxdigital.com.au>. 
  * This widget is designed to to be used across 
  * the Fairfax Digital Network and contains no  
  * site specific code. 
  * Altering this file will render it unsupportable. 
  * * * * * * * * * * * * * * * * * * * * * * * * * *  
  * Created: 2008-05-08T11:15:44+10:00  
  * Revision: 23  
  * * * * * * * * * * * * * * * * * * * * * * * * * *  
  * This file contains dependancies for:  
  * FD
  * FD.gallery
  * FD.video  
  * * * * * * * * * * * * * * * * * * * * * * * * * *  
  **/



/*
Script: Core.js
	MooTools - My Object Oriented JavaScript Tools.

License:
	MIT-style license.

Copyright:
	Copyright (c) 2006-2007 Valerio Proietti, <http://mad4milk.net/>

Code & Documentation:
	The MooTools production team <http://mootools.net/developers/>.

Inspiration:
	- Class implementation inspired by Base.js <http://dean.edwards.name/weblog/2006/03/base/> Copyright (c) 2006 Dean Edwards, GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
	- Some functionality inspired by Prototype.js <http://prototypejs.org> Copyright (c) 2005-2007 Sam Stephenson, MIT License <http://opensource.org/licenses/mit-license.php>
*/

var MooTools = {
	'version': '1.2b2',
	'build': '%build%'
};

var Native = function(options){
	options = options || {};

	var afterImplement = options.afterImplement || function(){};
	var generics = options.generics;
	generics = (generics !== false);
	var legacy = options.legacy;
	var initialize = options.initialize;
	var protect = options.protect;
	var name = options.name;

	var object = initialize || legacy;

	object.constructor = Native;
	object.$family = {name: 'native'};
	if (legacy && initialize) object.prototype = legacy.prototype;
	object.prototype.constructor = object;

	if (name){
		var family = name.toLowerCase();
		object.prototype.$family = {name: family};
		Native.typize(object, family);
	}

	var add = function(obj, name, method, force){
		if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
		if (generics) Native.genericize(obj, name, protect);
		afterImplement.call(obj, name, method);
		return obj;
	};

	object.implement = function(a1, a2, a3){
		if (typeof a1 == 'string') return add(this, a1, a2, a3);
		for (var p in a1) add(this, p, a1[p], a2);
		return this;
	};

	object.alias = function(existing, property, force){
		existing = this.prototype[existing];
		if (existing) add(this, property, existing, force);
		return this;
	};

	return object;
};

Native.implement = function(objects, properties){
	for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
};

Native.genericize = function(object, property, check){
	if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
		var args = Array.prototype.slice.call(arguments);
		return object.prototype[property].apply(args.shift(), args);
	};
};

Native.typize = function(object, family){
	if (!object.type) object.type = function(item){
		return ($type(item) === family);
	};
};

(function(objects){
	for (var name in objects) Native.typize(objects[name], name.toLowerCase());
})({'Boolean': Boolean, 'Native': Native, 'Object': Object});

(function(objects){
	for (var name in objects) new Native({name: name, initialize: objects[name], protect: true});
})({'String': String, 'Function': Function, 'Number': Number, 'Array': Array, 'RegExp': RegExp, 'Date': Date});

(function(object, methods){
	for (var i = 0, l = methods.length; i < l; i++) Native.genericize(object, methods[i], true);
	return arguments.callee;
})
(Array, ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', 'toString', 'valueOf', 'indexOf', 'lastIndexOf'])
(String, ['charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'replace', 'search', 'slice', 'split', 'substr', 'substring', 'toLowerCase', 'toUpperCase', 'valueOf']);

function $chk(obj){
	return !!(obj || obj === 0);
};

function $clear(timer){
	clearTimeout(timer);
	clearInterval(timer);
	return null;
};

function $defined(obj){
	return (obj != undefined);
};

function $empty(){};

function $arguments(i){
	return function(){
		return arguments[i];
	};
};

function $lambda(value){
	return (typeof value == 'function') ? value : function(){
		return value;
	};
};

function $extend(original, extended){
	for (var key in (extended || {})) original[key] = extended[key];
	return original;
};

function $unlink(object){
	var unlinked = null;
	
	switch ($type(object)){
		case 'object':
			unlinked = {};
			for (var p in object) unlinked[p] = $unlink(object[p]);
		break;
		case 'array':
			unlinked = [];
			for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
		break;
		default: return object;
	}
	
	return unlinked;
};

function $merge(){
	var mix = {};
	for (var i = 0, l = arguments.length; i < l; i++){
		var object = arguments[i];
		if ($type(object) != 'object') continue;
		for (var key in object){
			var op = object[key], mp = mix[key];
			mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $merge(mp, op) : $unlink(op);
		}
	}
	return mix;
};

function $pick(){
	for (var i = 0, l = arguments.length; i < l; i++){
		if ($defined(arguments[i])) return arguments[i];
	}
	return null;
};

function $random(min, max){
	return Math.floor(Math.random() * (max - min + 1) + min);
};

function $splat(obj){
	var type = $type(obj);
	return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
};

var $time = Date.now || function(){
	return new Date().getTime();
};

function $try(fn, bind, args){
	try {
		return fn.apply(bind, $splat(args));
	} catch(e){
		return false;
	}
};

function $type(obj){
	if (obj == undefined) return false;
	if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
	if (obj.nodeName){
		switch (obj.nodeType){
			case 1: return 'element';
			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
		}
	} else if (typeof obj.length == 'number'){
		if (obj.callee) return 'arguments';
		else if (obj.item) return 'collection';
	}
	return typeof obj;
};

var Hash = new Native({

	name: 'Hash',

	initialize: function(object){
		if ($type(object) == 'hash') object = $unlink(object.getClean());
		for (var key in object){
			if (!this[key]) this[key] = object[key];
		}
		return this;
	}

});

Hash.implement({
	
	getLength: function(){
		var length = 0;
		for (var key in this){
			if (this.hasOwnProperty(key)) length++;
		}
		return length;
	},

	forEach: function(fn, bind){
		for (var key in this){
			if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
		}
	},
	
	getClean: function(){
		var clean = {};
		for (var key in this){
			if (this.hasOwnProperty(key)) clean[key] = this[key];
		}
		return clean;
	}

});

Hash.alias('forEach', 'each');

function $H(object){
	return new Hash(object);
};

Array.implement({

	forEach: function(fn, bind){
		for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
	}

});

Array.alias('forEach', 'each');

function $A(iterable){
	if ($type(iterable) == 'collection'){
		var array = [];
		for (var i = 0, l = iterable.length; i < l; i++) array[i] = iterable[i];
		return array;
	}
	return Array.prototype.slice.call(iterable);
};

function $each(iterable, fn, bind){
	var type = $type(iterable);
	((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
};




/*
Script: Browser.js
	The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.

License:
	MIT-style license.
*/

var Browser = new Hash({
	Engine: {name: 'unknown', version: ''},
	Platform: {name: (navigator.platform.match(/mac|win|linux|nix/i) || ['other'])[0].toLowerCase()},
	Features: {xhr: !!(window.XMLHttpRequest), xpath: !!(document.evaluate), air: !!(window.runtime)}
});

if (window.opera) Browser.Engine.name = 'presto';
else if (window.ActiveXObject) Browser.Engine = {name: 'trident', version: (Browser.Features.xhr) ? 5 : 4};
else if (!navigator.taintEnabled) Browser.Engine = {name: 'webkit', version: (Browser.Features.xpath) ? 420 : 419};
else if (document.getBoxObjectFor != null) Browser.Engine.name = 'gecko';
Browser.Engine[Browser.Engine.name] = Browser.Engine[Browser.Engine.name + Browser.Engine.version] = true;
Browser.Platform[Browser.Platform.name] = true;

function $exec(text){
	if (!text) return text;
	if (window.execScript){
		window.execScript(text);
	} else {
		var script = document.createElement('script');
		script.setAttribute('type', 'text/javascript');
		script.text = text;
		document.head.appendChild(script);
		document.head.removeChild(script);
	}
	return text;
};

Native.UID = 0;

var Window = new Native({

	name: 'Window',

	legacy: window.Window,

	initialize: function(win){
		if (!win.Element){
			win.Element = $empty;
			if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
			win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
		}
		win.uid = Native.UID++;
		return $extend(win, Window.Prototype);
	},

	afterImplement: function(property, value){
		window[property] = Window.Prototype[property] = value;
	}

});

Window.Prototype = {$family: {name: 'window'}};

new Window(window);

var Document = new Native({

	name: 'Document',

	legacy: window.Document,

	initialize: function(doc){
		doc.head = doc.getElementsByTagName('head')[0];
		doc.html = doc.getElementsByTagName('html')[0];
		doc.window = doc.defaultView || doc.parentWindow;
		if (Browser.Engine.trident4) $try(function(){
			doc.execCommand("BackgroundImageCache", false, true);
		});
		doc.uid = Native.UID++;
		return $extend(doc, Document.Prototype);
	},

	afterImplement: function(property, value){
		document[property] = Document.Prototype[property] = value;
	}

});

Document.Prototype = {$family: {name: 'document'}};

new Document(document);




/*
Script: Array.js
	Contains Array Prototypes like copy, each, contains, and remove.

License:
	MIT-style license.
*/

Array.implement({

	every: function(fn, bind){
		for (var i = 0, l = this.length; i < l; i++){
			if (!fn.call(bind, this[i], i, this)) return false;
		}
		return true;
	},

	filter: function(fn, bind){
		var results = [];
		for (var i = 0, l = this.length; i < l; i++){
			if (fn.call(bind, this[i], i, this)) results.push(this[i]);
		}
		return results;
	},
	
	clean: function() {
		return this.filter($arguments(0));
	},

	indexOf: function(item, from){
		var len = this.length;
		for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
			if (this[i] === item) return i;
		}
		return -1;
	},

	map: function(fn, bind){
		var results = [];
		for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
		return results;
	},

	some: function(fn, bind){
		for (var i = 0, l = this.length; i < l; i++){
			if (fn.call(bind, this[i], i, this)) return true;
		}
		return false;
	},

	associate: function(keys){
		var obj = {}, length = Math.min(this.length, keys.length);
		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
		return obj;
	},

	link: function(object){
		var result = {};
		for (var i = 0, l = this.length; i < l; i++){
			for (var key in object){
				if (object[key](this[i])){
					result[key] = this[i];
					delete object[key];
					break;
				}
			}
		}
		return result;
	},

	contains: function(item, from){
		return this.indexOf(item, from) != -1;
	},

	extend: function(array){
		for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
		return this;
	},

	getLast: function(){
		return (this.length) ? this[this.length - 1] : null;
	},

	getRandom: function(){
		return (this.length) ? this[$random(0, this.length - 1)] : null;
	},

	include: function(item){
		if (!this.contains(item)) this.push(item);
		return this;
	},

	merge: function(array){
		for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
		return this;
	},

	remove: function(item){
		for (var i = this.length; i--; i){
			if (this[i] === item) this.splice(i, 1);
		}
		return this;
	},

	empty: function(){
		this.length = 0;
		return this;
	},

	flatten: function(){
		var array = [];
		for (var i = 0, l = this.length; i < l; i++){
			var type = $type(this[i]);
			if (!type) continue;
			array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
		}
		return array;
	},

	hexToRgb: function(array){
		if (this.length != 3) return null;
		var rgb = this.map(function(value){
			if (value.length == 1) value += value;
			return value.toInt(16);
		});
		return (array) ? rgb : 'rgb(' + rgb + ')';
	},

	rgbToHex: function(array){
		if (this.length < 3) return null;
		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
		var hex = [];
		for (var i = 0; i < 3; i++){
			var bit = (this[i] - 0).toString(16);
			hex.push((bit.length == 1) ? '0' + bit : bit);
		}
		return (array) ? hex : '#' + hex.join('');
	}

});




/*
Script: Function.js
	Contains Function Prototypes like create, bind, pass, and delay.

License:
	MIT-style license.
*/

Function.implement({

	extend: function(properties){
		for (var property in properties) this[property] = properties[property];
		return this;
	},

	create: function(options){
		var self = this;
		options = options || {};
		return function(event){
			var args = options.arguments;
			args = $defined(args) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
			if (options.event) args = [event || window.event].extend(args);
			var returns = function(){
				return self.apply(options.bind || null, args);
			};
			if (options.delay) return setTimeout(returns, options.delay);
			if (options.periodical) return setInterval(returns, options.periodical);
			if (options.attempt) return $try(returns);
			return returns();
		};
	},

	pass: function(args, bind){
		return this.create({'arguments': args, 'bind': bind});
	},

	attempt: function(args, bind){
		return this.create({'arguments': args, 'bind': bind, 'attempt': true})();
	},

	bind: function(bind, args){
		return this.create({'bind': bind, 'arguments': args});
	},

	bindWithEvent: function(bind, args){
		return this.create({'bind': bind, 'event': true, 'arguments': args});
	},

	delay: function(delay, bind, args){
		return this.create({'delay': delay, 'bind': bind, 'arguments': args})();
	},

	periodical: function(interval, bind, args){
		return this.create({'periodical': interval, 'bind': bind, 'arguments': args})();
	},

	run: function(args, bind){
		return this.apply(bind, $splat(args));
	}

});




/*
Script: Number.js
	Contains Number Prototypes like limit, round, times, and ceil.

License:
	MIT-style license.
*/

Number.implement({

	limit: function(min, max){
		return Math.min(max, Math.max(min, this));
	},

	round: function(precision){
		precision = Math.pow(10, precision || 0);
		return Math.round(this * precision) / precision;
	},

	times: function(fn, bind){
		for (var i = 0; i < this; i++) fn.call(bind, i, this);
	},

	toFloat: function(){
		return parseFloat(this);
	},

	toInt: function(base){
		return parseInt(this, base || 10);
	}

});

Number.alias('times', 'each');

(function(math){
	var methods = {};
	math.each(function(name){
		if (!Number[name]) methods[name] = function(){
			return Math[name].apply(null, [this].concat($A(arguments)));
		};
	});
	Number.implement(methods);
})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);




/*
Script: String.js
	Contains String Prototypes like camelCase, capitalize, test, and toInt.

License:
	MIT-style license.
*/

String.implement({

	test: function(regex, params){
		return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
	},

	contains: function(string, separator){
		return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
	},

	trim: function(){
		return this.replace(/^\s+|\s+$/g, '');
	},

	clean: function(){
		return this.replace(/\s+/g, ' ').trim();
	},

	camelCase: function(){
		return this.replace(/-\D/g, function(match){
			return match.charAt(1).toUpperCase();
		});
	},

	hyphenate: function(){
		return this.replace(/[A-Z]/g, function(match){
			return ('-' + match.charAt(0).toLowerCase());
		});
	},

	capitalize: function(){
		return this.replace(/\b[a-z]/g, function(match){
			return match.toUpperCase();
		});
	},

	escapeRegExp: function(){
		return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
	},

	toInt: function(base){
		return parseInt(this, base || 10);
	},

	toFloat: function(){
		return parseFloat(this);
	},

	hexToRgb: function(array){
		var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
		return (hex) ? hex.slice(1).hexToRgb(array) : null;
	},

	rgbToHex: function(array){
		var rgb = this.match(/\d{1,3}/g);
		return (rgb) ? rgb.rgbToHex(array) : null;
	},

	stripScripts: function(option){
		var scripts = '';
		var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
			scripts += arguments[1] + '\n';
			return '';
		});
		if (option === true) $exec(scripts);
		else if ($type(option) == 'function') option(scripts, text);
		return text;
	}

});




/*
Script: Hash.js
	Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.

License:
	MIT-style license.
*/

Hash.implement({

	has: Object.prototype.hasOwnProperty,

	keyOf: function(value){
		for (var key in this){
			if (this.hasOwnProperty(key) && this[key] === value) return key;
		}
		return null;
	},

	hasValue: function(value){
		return (Hash.keyOf(this, value) !== null);
	},

	extend: function(properties){
		Hash.each(properties, function(value, key){
			Hash.set(this, key, value);
		}, this);
		return this;
	},

	merge: function(properties){
		Hash.each(properties, function(value, key){
			Hash.include(this, key, value);
		}, this);
		return this;
	},

	remove: function(key){
		if (this.hasOwnProperty(key)) delete this[key];
		return this;
	},

	get: function(key){
		return (this.hasOwnProperty(key)) ? this[key] : null;
	},

	set: function(key, value){
		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
		return this;
	},

	empty: function(){
		Hash.each(this, function(value, key){
			delete this[key];
		}, this);
		return this;
	},

	include: function(key, value){
		var k = this[key];
		if (!$defined(k)) this[key] = value;
		return this;
	},

	map: function(fn, bind){
		var results = new Hash;
		Hash.each(this, function(value, key){
			results.set(key, fn.call(bind, value, key, this));
		}, this);
		return results;
	},

	filter: function(fn, bind){
		var results = new Hash;
		Hash.each(this, function(value, key){
			if (fn.call(bind, value, key, this)) results.set(key, value);
		}, this);
		return results;
	},

	every: function(fn, bind){
		for (var key in this){
			if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
		}
		return true;
	},

	some: function(fn, bind){
		for (var key in this){
			if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
		}
		return false;
	},

	getKeys: function(){
		var keys = [];
		Hash.each(this, function(value, key){
			keys.push(key);
		});
		return keys;
	},

	getValues: function(){
		var values = [];
		Hash.each(this, function(value){
			values.push(value);
		});
		return values;
	},

	toQueryString: function(){
		var queryString = [];
		Hash.each(this, function(value, key){
			$splat(value).each(function(val){
				queryString.push(key + '=' + encodeURIComponent(val));
			});
		});
		return queryString.join('&');
	}

});

Hash.alias('keyOf', 'indexOf').alias('hasValue', 'contains').alias('remove', 'erase');




/*
Script: Event.js
	Contains the Event Native, to make the event object completely crossbrowser.

License:
	MIT-style license.
*/

var Event = new Native({

	name: 'Event',

	initialize: function(event, win){
		win = win || window;
		event = event || win.event;
		if (event.$extended) return event;
		this.$extended = true;
		var type = event.type;
		var target = event.target || event.srcElement;
		while (target && target.nodeType == 3) target = target.parentNode;
		
		if (type.match(/DOMMouseScroll|mousewheel/)){
			var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
		} else if (type.test(/key/)){
			var code = event.which || event.keyCode;
			var key = Event.Keys.keyOf(code);
			if (type == 'keydown'){
				var fKey = code - 111;
				if (fKey > 0 && fKey < 13) key = 'f' + fKey;
			}
			key = key || String.fromCharCode(code).toLowerCase();
		} else if (type.match(/(click|mouse|menu)/i)){
			var page = {
				x: event.pageX || event.clientX + win.document.documentElement.scrollLeft,
				y: event.pageY || event.clientY + win.document.documentElement.scrollTop
			};
			var client = {
				x: event.pageX ? event.pageX - win.pageXOffset : event.clientX,
				y: event.pageY ? event.pageY - win.pageYOffset : event.clientY
			};
			var rightClick = (event.which == 3) || (event.button == 2);
			var related = null;
			if (type.match(/over|out/)){
				switch (type){
					case 'mouseover': related = event.relatedTarget || event.fromElement; break;
					case 'mouseout': related = event.relatedTarget || event.toElement;
				}
				if ((function(){
					while (related && related.nodeType == 3) related = related.parentNode;
				}).create({attempt: Browser.Engine.gecko})() === false) related = false;
			}
		}

		return $extend(this, {
			event: event,
			type: type,
			
			page: page,
			client: client,
			rightClick: rightClick,
			
			wheel: wheel,
			
			relatedTarget: related,
			target: target,
			
			code: code,
			key: key,
			
			shift: event.shiftKey,
			control: event.ctrlKey,
			alt: event.altKey,
			meta: event.metaKey
		});
	}

});

Event.Keys = new Hash({
	'enter': 13,
	'up': 38,
	'down': 40,
	'left': 37,
	'right': 39,
	'esc': 27,
	'space': 32,
	'backspace': 8,
	'tab': 9,
	'delete': 46
});

Event.implement({

	stop: function(){
		return this.stopPropagation().preventDefault();
	},

	stopPropagation: function(){
		if (this.event.stopPropagation) this.event.stopPropagation();
		else this.event.cancelBubble = true;
		return this;
	},

	preventDefault: function(){
		if (this.event.preventDefault) this.event.preventDefault();
		else this.event.returnValue = false;
		return this;
	}

});



/*
Script: Class.js
	Contains the Class Function for easily creating, extending, and implementing reusable Classes.

License:
	MIT-style license.
*/

var Class = new Native({

	name: 'Class',

	initialize: function(properties){

		properties = properties || {};

		var klass = function(){
			for (var property in this) this[property] = $unlink(this[property]);
			
			this.parent = null;
			
			['Implements', 'Extends'].each(function(Property){
				if (!this[Property]) return;
				Class[Property](this, this[Property]);
				delete this[Property];
			}, this);

			this.constructor = klass;

			var self = (arguments[0] !== $empty && this.initialize) ? this.initialize.apply(this, arguments) : this;
			if (this.options && this.options.initialize) this.options.initialize.call(this);
			return self;
		};

		$extend(klass, this);
		klass.constructor = Class;
		klass.prototype = properties;
		return klass;
	}

});

Class.implement({

	implement: function(){
		Class.Implements(this.prototype, Array.slice(arguments));
		return this;
	}

});

Class.Implements = function(self, klasses){
	$splat(klasses).each(function(klass){
		$extend(self, ($type(klass) == 'class') ? new klass($empty) : klass);
	});
};

Class.Extends = function(self, klass){
	klass = new klass($empty);
	for (var property in klass){
		var kp = klass[property];
		var sp = self[property];
		self[property] = (function(previous, current){
			if ($defined(current) && previous != current){
				var type = $type(current);
				if (type != $type(previous)) return current;
				switch (type){
					case 'function':
						return function(){
							current.parent = self.parent = previous.bind(this);
							var value = current.apply(this, arguments);
							self.parent = current.parent;
							return value;
						};
					case 'object': return $merge(previous, current);
					default: return current;
				}
			}
			return previous;
		})(kp, sp);
	}
};

//legacy .extend support

Class.prototype.extend = function(properties){
	properties.Extends = this;
	return new Class(properties);
};



/*
Script: Class.Extras.js
	Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.

License:
	MIT-style license.
*/

var Chain = new Class({

	chain: function(){
		this.$chain = (this.$chain || []).extend(arguments);
		return this;
	},

	callChain: function(){
		if (this.$chain && this.$chain.length) this.$chain.shift().apply(this, arguments);
		return this;
	},

	clearChain: function(){
		if (this.$chain) this.$chain.empty();
		return this;
	}

});

var Events = new Class({

	addEvent: function(type, fn, internal){
		if (fn != $empty){
			this.$events = this.$events || {};
			this.$events[type] = this.$events[type] || [];
			this.$events[type].include(fn);
			if (internal) fn.internal = true;
		}
		return this;
	},

	addEvents: function(events){
		for (var type in events) this.addEvent(type, events[type]);
		return this;
	},

	fireEvent: function(type, args, delay){
		if (!this.$events || !this.$events[type]) return this;
		this.$events[type].each(function(fn){
			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
		}, this);
		return this;
	},

	removeEvent: function(type, fn){
		if (!this.$events || !this.$events[type]) return this;
		if (!fn.internal) this.$events[type].remove(fn);
		return this;
	},

	removeEvents: function(type){
		for (var e in this.$events){
			if (type && type != e) continue;
			var fns = this.$events[e];
			for (var i = fns.length; i--; i) this.removeEvent(e, fns[i]);
		}
		return this;
	}

});

var Options = new Class({

	setOptions: function(){
		this.options = $merge.run([this.options].extend(arguments));
		if (!this.addEvent) return this;
		for (var option in this.options){
			if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
			this.addEvent(option, this.options[option]);
			delete this.options[option];
		}
		return this;
	}

});




/*
Script: Element.js
	One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser,
	time-saver methods to let you easily work with HTML Elements.

License:
	MIT-style license.
*/

Document.implement({
	
	newElement: function(tag, props){
		if (Browser.Engine.trident && props){
			['name', 'type', 'checked'].each(function(attribute){
				if (!props[attribute]) return;
				tag += ' ' + attribute + '="' + props[attribute] + '"';
				if (attribute != 'checked') delete props[attribute];
			});
			tag = '<' + tag + '>';
		}
		return $.element(this.createElement(tag)).set(props);
	},
	
	newTextNode: function(text){
		return this.createTextNode(text);
	},
	
	getDocument: function(){
		return this;
	},
	
	getWindow: function(){
		return this.defaultView || this.parentWindow;
	}

});

var Element = new Native({

	name: 'Element',

	legacy: window.Element,

	initialize: function(tag, props){
		var konstructor = Element.Constructors.get(tag);
		if (konstructor) return konstructor(props);
		if (typeof tag == 'string') return document.newElement(tag, props);
		return $(tag).set(props);
	},

	afterImplement: function(key, value){
		if (!Array[key]) Elements.implement(key, Elements.multi(key));
		Element.Prototype[key] = value;
	}

});

Element.Prototype = {$family: {name: 'element'}};

Element.Constructors = new Hash;

var IFrame = new Native({

	name: 'IFrame',

	generics: false,

	initialize: function(){
		Native.UID++;
		var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
		var props = params.properties || {};
		var iframe = $(params.iframe) || false;
		var onload = props.onload || $empty;
		delete props.onload;
		props.id = props.name = $pick(props.id, props.name, iframe.id, iframe.name, 'IFrame_' + Native.UID);
		((iframe = iframe || new Element('iframe'))).set(props);
		var onFrameLoad = function(){
			var host = $try(function(){
				return iframe.contentWindow.location.host;
			});
			if (host && host == window.location.host){
				iframe.window = iframe.contentWindow;
				var win = new Window(iframe.window);
				var doc = new Document(iframe.window.document);
				$extend(win.Element.prototype, Element.Prototype);
			}
			onload.call(iframe.contentWindow);
		};
		(!window.frames[props.id]) ? iframe.addListener('load', onFrameLoad) : onFrameLoad();
		return iframe;
	}

});

var Elements = new Native({

	initialize: function(elements, options){
		options = $extend({ddup: true, cash: true}, options);
		elements = elements || [];
		if (options.ddup || options.cash){
			var uniques = {};
			var returned = [];
			for (var i = 0, l = elements.length; i < l; i++){
				var el = $.element(elements[i], !options.cash);
				if (options.ddup){
					if (uniques[el.uid]) continue;
					uniques[el.uid] = true;
				}
				returned.push(el);
			}
			elements = returned;
		}
		return (options.cash) ? $extend(elements, this) : elements;
	}

});

Elements.implement({

	filterBy: function(filter){
		if (!filter) return this;
		return new Elements(this.filter((typeof filter == 'string') ? function(item){
			return item.match(filter);
		} : filter));
	}

});

Elements.multi = function(property){
	return function(){
		var items = [];
		var elements = true;
		for (var i = 0, j = this.length; i < j; i++){
			var returns = this[i][property].apply(this[i], arguments);
			items.push(returns);
			if (elements) elements = ($type(returns) == 'element');
		}
		return (elements) ? new Elements(items) : items;
	};
};

Window.implement({

	$: function(el, notrash){
		if (el && el.$attributes) return el;
		var type = $type(el);
		return ($[type]) ? $[type](el, notrash, this.document) : null;
	},

	$$: function(selector){
		if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
		var elements = [];
		var args = Array.flatten(arguments);
		for (var i = 0, l = args.length; i < l; i++){
			var item = args[i];
			switch ($type(item)){
				case 'element': item = [item]; break;
				case 'string': item = this.document.getElements(item, true); break;
				default: item = false;
			}
			if (item) elements.extend(item);
		}
		return new Elements(elements);
	},
	
	getDocument: function(){
		return this.document;
	},
	
	getWindow: function(){
		return this;
	}

});

$.string = function(id, notrash, doc){
	id = doc.getElementById(id);
	return (id) ? $.element(id, notrash) : null;
};

$.element = function(el, notrash){
	el.uid = el.uid || [Native.UID++];
	if (!notrash && Garbage.collect(el) && !el.$family) $extend(el, Element.Prototype);
	return el;
};

$.textnode = $.window = $.document = $arguments(0);

$.number = function(uid){
	return Garbage.Elements[uid] || null;
};

Native.implement([Element, Document], {

	getElement: function(selector, notrash){
		return $(this.getElements(selector, true)[0] || null, notrash);
	},

	getElements: function(tags, nocash){
		tags = tags.split(',');
		var elements = [];
		var ddup = (tags.length > 1);
		tags.each(function(tag){
			var partial = this.getElementsByTagName(tag.trim());
			(ddup) ? elements.extend(partial) : elements = partial;
		}, this);
		return new Elements(elements, {ddup: ddup, cash: !nocash});
	}

});

Element.Storage = {

	get: function(uid){
		return (this[uid] = this[uid] || {});
	}

};

Element.Inserters = new Hash({

	before: function(context, element){
		if (element.parentNode) element.parentNode.insertBefore(context, element);
	},

	after: function(context, element){
		if (!element.parentNode) return;
		var next = element.nextSibling;
		(next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
	},

	bottom: function(context, element){
		element.appendChild(context);
	},

	top: function(context, element){
		var first = element.firstChild;
		(first) ? element.insertBefore(context, first) : element.appendChild(context);
	}

});

Element.Inserters.inside = Element.Inserters.bottom;

Element.Inserters.each(function(value, key){
	
	var Key = key.capitalize();
	
	Element.implement('inject' + Key, function(el){
		Element.Inserters[key](this, $(el, true));
		return this;
	});
	
	Element.implement('grab' + Key, function(el){
		Element.Inserters[key]($(el, true), this);
		return this;
	});
	
});

Element.implement({
	
	getDocument: function(){
		return this.ownerDocument;
	},
	
	getWindow: function(){
		return this.ownerDocument.getWindow();
	},

	getElementById: function(id, nocash){
		var el = this.ownerDocument.getElementById(id);
		if (!el) return null;
		for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
			if (!parent) return null;
		}
		return $.element(el, nocash);
	},

	set: function(prop, value){
		switch ($type(prop)){
			case 'object':
				for (var p in prop) this.set(p, prop[p]);
				break;
			case 'string':
				var property = Element.Properties.get(prop);
				(property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
		}
		return this;
	},

	get: function(prop){
		var property = Element.Properties.get(prop);
		return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
	},

	erase: function(prop){
		var property = Element.Properties.get(prop);
		(property && property.erase) ? property.erase.apply(this, Array.slice(arguments, 1)) : this.removeProperty(prop);
		return this;
	},

	match: function(tag){
		return (!tag || Element.get(this, 'tag') == tag);
	},

	inject: function(el, where){
		Element.Inserters.get(where || 'bottom')(this, $(el, true));
		return this;
	},

	wraps: function(el, where){
		el = $(el, true);
		return this.replaces(el).grab(el);
	},

	grab: function(el, where){
		Element.Inserters.get(where || 'bottom')($(el, true), this);
		return this;
	},

	appendText: function(text, where){
		return this.grab(this.getDocument().newTextNode(text), where);
	},

	adopt: function(){
		Array.flatten(arguments).each(function(element){
			this.appendChild($(element, true));
		}, this);
		return this;
	},

	dispose: function(){
		return this.parentNode.removeChild(this);
	},

	clone: function(contents){
		var temp = new Element('div').grab(this.cloneNode(contents !== false));
		Array.each(temp.getElementsByTagName('*'), function(element){
			if (element.id) element.removeAttribute('id');
		});
		return new Element('div').set('html', temp.innerHTML).getFirst();
	},

	replaces: function(el){
		el = $(el, true);
		el.parentNode.replaceChild(this, el);
		return this;
	},

	hasClass: function(className){
		return this.className.contains(className, ' ');
	},

	addClass: function(className){
		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
		return this;
	},

	removeClass: function(className){
		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1').clean();
		return this;
	},

	toggleClass: function(className){
		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
	},

	getComputedStyle: function(property){
		var result = null;
		if (this.currentStyle){
			result = this.currentStyle[property.camelCase()];
		} else {
			var computed = this.getWindow().getComputedStyle(this, null);
			if (computed) result = computed.getPropertyValue([property.hyphenate()]);
		}
		return result;
	},

	empty: function(){
		var elements = $A(this.getElementsByTagName('*'));
		elements.each(function(element){
			$try(Element.prototype.dispose, element);
		});
		Garbage.trash(elements);
		$try(Element.prototype.set, this, ['html', '']);
		return this;
	},

	destroy: function(){
		Garbage.kill(this.empty().dispose());
		return null;
	},

	toQueryString: function(){
		var queryString = [];
		this.getElements('input, select, textarea', true).each(function(el){
			var name = el.name, type = el.type, value = Element.get(el, 'value');
			if (value === false || !name || el.disabled) return;
			$splat(value).each(function(val){
				queryString.push(name + '=' + encodeURIComponent(val));
			});
		});
		return queryString.join('&');
	},

	getProperty: function(attribute){
		var EA = Element.Attributes, key = EA.Props[attribute];
		var value = (key) ? this[key] : this.getAttribute(attribute);
		return (EA.Bools[attribute]) ? !!value : value;
	},

	getProperties: function(){
		var args = $A(arguments);
		return args.map(function(attr){
			return this.getProperty(attr);
		}, this).associate(args);
	},

	setProperty: function(attribute, value){
		var EA = Element.Attributes, key = EA.Props[attribute], hasValue = $defined(value);
		if (key && EA.Bools[attribute]) value = (value || !hasValue) ? true : false;
		else if (!hasValue) return this.removeProperty(attribute);
		(key) ? this[key] = value : this.setAttribute(attribute, value);
		return this;
	},

	setProperties: function(attributes){
		for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
		return this;
	},

	removeProperty: function(attribute){
		var EA = Element.Attributes, key = EA.Props[attribute], isBool = (key && EA.Bools[attribute]);
		(key) ? this[key] = (isBool) ? false : '' : this.removeAttribute(attribute);
		return this;
	},

	removeProperties: function(){
		Array.each(arguments, this.removeProperty, this);
		return this;
	}

});

(function(){

var walk = function(element, walk, start, match, all, nocash){
	var el = element[start || walk];
	var elements = [];
	while (el){
		if (el.nodeType == 1 && Element.match(el, match)){
			elements.push(el);
			if (!all) break;
		}
		el = el[walk];
	}
	return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : $(elements[0], nocash);
};

Element.implement({

	getPrevious: function(match, nocash){
		return walk(this, 'previousSibling', null, match, false, nocash);
	},

	getAllPrevious: function(match, nocash){
		return walk(this, 'previousSibling', null, match, true, nocash);
	},

	getNext: function(match, nocash){
		return walk(this, 'nextSibling', null, match, false, nocash);
	},

	getAllNext: function(match, nocash){
		return walk(this, 'nextSibling', null, match, true, nocash);
	},

	getFirst: function(match, nocash){
		return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
	},

	getLast: function(match, nocash){
		return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
	},

	getParent: function(match, nocash){
		return walk(this, 'parentNode', null, match, false, nocash);
	},

	getParents: function(match, nocash){
		return walk(this, 'parentNode', null, match, true, nocash);
	},

	getChildren: function(match, nocash){
		return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
	},

	hasChild: function(el){
		if (!(el = $(el, true))) return false;
		return Element.getParents(el, this.get('tag'), true).contains(this);
	}

});

})();

Element.alias('dispose', 'remove').alias('getLast', 'getLastChild');

Element.Properties = new Hash;

Element.Properties.style = {

	set: function(style){
		this.style.cssText = style;
	},

	get: function(){
		return this.style.cssText;
	},

	erase: function(){
		this.style.cssText = '';
	}

};

Element.Properties.value = {get: function(){
	switch (Element.get(this, 'tag')){
		case 'select':
			var values = [];
			Array.each(this.options, function(option){
				if (option.selected) values.push(option.value);
			});
			return (this.multiple) ? values : values[0];
		case 'input': if (['checkbox', 'radio'].contains(this.type) && !this.checked) return false;
		default: return $pick(this.value, false);
	}
}};

Element.Properties.tag = {get: function(){
	return this.tagName.toLowerCase();
}};

Element.Properties.html = {set: function(){
	return this.innerHTML = Array.flatten(arguments).join('');
}};

Element.implement({

	getText: function(){
		return this.get('text');
	},

	setText: function(text){
		return this.set('text', text);
	},

	setHTML: function(){
		return this.set('html', arguments);
	},
	
	getHTML: function(){
		return this.get('html');
	},

	getTag: function(){
		return this.get('tag');
	}

});

Native.implement([Element, Window, Document], {

	addListener: function(type, fn){
		if (this.addEventListener) this.addEventListener(type, fn, false);
		else this.attachEvent('on' + type, fn);
		return this;
	},

	removeListener: function(type, fn){
		if (this.removeEventListener) this.removeEventListener(type, fn, false);
		else this.detachEvent('on' + type, fn);
		return this;
	},

	retrieve: function(property, dflt){
		var storage = Element.Storage.get(this.uid);
		var prop = storage[property];
		if ($defined(dflt) && !$defined(prop)) prop = storage[property] = dflt;
		return $pick(prop);
	},

	store: function(property, value){
		var storage = Element.Storage.get(this.uid);
		storage[property] = value;
		return this;
	},

	eliminate: function(property){
		var storage = Element.Storage.get(this.uid);
		delete storage[property];
		return this;
	}

});

Element.Attributes = new Hash({
	Props: {'html': 'innerHTML', 'class': 'className', 'for': 'htmlFor', 'text': (Browser.Engine.trident) ? 'innerText' : 'textContent'},
	Bools: ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'],
	Camels: ['value', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap']
});

(function(EA){

	var EAB = EA.Bools, EAC = EA.Camels;
	EA.Bools = EAB = EAB.associate(EAB);
	Hash.extend(Hash.merge(EA.Props, EAB), EAC.associate(EAC.map(function(v){
		return v.toLowerCase();
	})));
	EA.remove('Camels');

})(Element.Attributes);

var Garbage = {

	Elements: {},

	ignored: {object: 1, embed: 1, OBJECT: 1, EMBED: 1},

	collect: function(el){
		if (el.$attributes) return true;
		if (Garbage.ignored[el.tagName]) return false;
		Garbage.Elements[el.uid] = el;
		el.$attributes = {};
		return true;
	},

	trash: function(elements){
		for (var i = elements.length, el; i--; i) Garbage.kill(elements[i]);
	},

	kill: function(el){
		if (!el || !el.$attributes) return;
		delete Garbage.Elements[el.uid];
		if (el.retrieve('events')) el.removeEvents();
		for (var p in el.$attributes) el.$attributes[p] = null;
		if (Browser.Engine.trident){
			for (var d in Element.Prototype) el[d] = null;
		}
		el.$attributes = el.uid = null;
	},

	empty: function(){
		for (var uid in Garbage.Elements) Garbage.kill(Garbage.Elements[uid]);
	}

};

window.addListener('beforeunload', function(){
	window.addListener('unload', Garbage.empty);
	if (Browser.Engine.trident) window.addListener('unload', CollectGarbage);
});




/*
Script: Element.Event.js
	Contains Element methods for dealing with events, and custom Events.

License:
	MIT-style license.
*/

Element.Properties.events = {set: function(events){
	this.addEvents(events);
}};

Native.implement([Element, Window, Document], {

	addEvent: function(type, fn){
		var events = this.retrieve('events', {});
		events[type] = events[type] || {'keys': [], 'values': []};
		if (events[type].keys.contains(fn)) return this;
		events[type].keys.push(fn);
		var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
		if (custom){
			if (custom.onAdd) custom.onAdd.call(this, fn);
			if (custom.condition){
				condition = function(event){
					if (custom.condition.call(this, event)) return fn.call(this, event);
					return false;
				};
			}
			realType = custom.base || realType;
		}
		var defn = function(){
			return fn.call(self);
		};
		var nativeEvent = Element.NativeEvents[realType] || 0;
		if (nativeEvent){
			if (nativeEvent == 2){
				defn = function(event){
					event = new Event(event, self.getWindow());
					if (condition.call(self, event) === false) event.stop();
				};
			}
			this.addListener(realType, defn);
		}
		events[type].values.push(defn);
		return this;
	},

	removeEvent: function(type, fn){
		var events = this.retrieve('events');
		if (!events || !events[type]) return this;
		var pos = events[type].keys.indexOf(fn);
		if (pos == -1) return this;
		var key = events[type].keys.splice(pos, 1)[0];
		var value = events[type].values.splice(pos, 1)[0];
		var custom = Element.Events.get(type);
		if (custom){
			if (custom.onRemove) custom.onRemove.call(this, fn);
			type = custom.base || type;
		}
		return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
	},

	addEvents: function(events){
		for (var event in events) this.addEvent(event, events[event]);
		return this;
	},

	removeEvents: function(type){
		var events = this.retrieve('events');
		if (!events) return this;
		if (!type){
			for (var evType in events) this.removeEvents(evType);
			events = null;
		} else if (events[type]){
			while (events[type].keys[0]) this.removeEvent(type, events[type].keys[0]);
			events[type] = null;
		}
		return this;
	},

	fireEvent: function(type, args, delay){
		var events = this.retrieve('events');
		if (!events || !events[type]) return this;
		events[type].keys.each(function(fn){
			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
		}, this);
		return this;
	},

	cloneEvents: function(from, type){
		from = $(from);
		var fevents = from.retrieve('events');
		if (!fevents) return this;
		if (!type){
			for (var evType in fevents) this.cloneEvents(from, evType);
		} else if (fevents[type]){
			fevents[type].keys.each(function(fn){
				this.addEvent(type, fn);
			}, this);
		}
		return this;
	}

});

Element.NativeEvents = {
	'click': 2, 'dblclick': 2, 'mouseup': 2, 'mousedown': 2, 'contextmenu': 2, //mouse buttons
	'mousewheel': 2, 'DOMMouseScroll': 2, //mouse wheel
	'mouseover': 2, 'mouseout': 2, 'mousemove': 2, 'selectstart': 2, 'selectend': 2, //mouse movement
	'keydown': 2, 'keypress': 2, 'keyup': 2, //keyboard
	'focus': 2, 'blur': 2, 'change': 2, 'reset': 2, 'select': 2, 'submit': 2, //form elements
	'load': 1, 'unload': 1, 'beforeunload': 1, 'resize': 1, 'move': 1, 'DOMContentLoaded': 1, 'readystatechange': 1, //window
	'error': 1, 'abort': 1, 'scroll': 1 //misc
};

(function(){

var checkRelatedTarget = function(event){
	var related = event.relatedTarget;
	if (!related) return true;
	return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
};

Element.Events = new Hash({

	mouseenter: {

		base: 'mouseover',

		condition: checkRelatedTarget

	},

	mouseleave: {

		base: 'mouseout',

		condition: checkRelatedTarget

	},

	mousewheel: {

		base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'

	}

});

})();



/*
Script: Element.Style.js
	Contains methods for interacting with the styles of Elements in a fashionable way.

License:
	MIT-style license.
*/

Element.Properties.styles = {set: function(styles){
	this.setStyles(styles);
}};

Element.Properties.opacity = {

	set: function(opacity, novisibility){
		if (!novisibility){
			if (opacity == 0){
				if (this.style.visibility != 'hidden') this.style.visibility = 'hidden';
			} else {
				if (this.style.visibility != 'visible') this.style.visibility = 'visible';
			}
		}
		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
		if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')';
		this.style.opacity = opacity;
		this.store('opacity', opacity);
	},

	get: function(){
		return this.retrieve('opacity', 1);
	}

};

Element.implement({
	
	setOpacity: function(value){
		return this.set('opacity', value, true);
	},
	
	getOpacity: function(){
		return this.get('opacity');
	},

	setStyle: function(property, value){
		switch (property){
			case 'opacity': return this.set('opacity', parseFloat(value));
			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
		}
		property = property.camelCase();
		if ($type(value) != 'string'){
			var map = (Element.Styles.get(property) || '@').split(' ');
			value = $splat(value).map(function(val, i){
				if (!map[i]) return '';
				return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
			}).join(' ');
		} else if (value == String(Number(value))){
			value = Math.round(value);
		}
		this.style[property] = value;
		return this;
	},

	getStyle: function(property){
		switch (property){
			case 'opacity': return this.get('opacity');
			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
		}
		property = property.camelCase();
		var result = this.style[property];
		if (!$chk(result)){
			result = [];
			for (var style in Element.ShortStyles){
				if (property != style) continue;
				for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
				return result.join(' ');
			}
			result = this.getComputedStyle(property);
		}
		if (result){
			result = String(result);
			var color = result.match(/rgba?\([\d\s,]+\)/);
			if (color) result = result.replace(color[0], color[0].rgbToHex());
		}
		if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result)))){
			if (property.test(/^(height|width)$/)){
				var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
				values.each(function(value){
					size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
				}, this);
				return this['offset' + property.capitalize()] - size + 'px';
			}
			if (Browser.Engine.presto && String(result).test('px')) return result;
			if (property.test(/(border(.+)Width|margin|padding)/)) return '0px';
		}
		return result;
	},

	setStyles: function(styles){
		for (var style in styles) this.setStyle(style, styles[style]);
		return this;
	},

	getStyles: function(){
		var result = {};
		Array.each(arguments, function(key){
			result[key] = this.getStyle(key);
		}, this);
		return result;
	}

});

Element.Styles = new Hash({
	width: '@px', height: '@px', left: '@px', top: '@px', bottom: '@px', right: '@px', maxWidth: '@px', maxHeight: '@px',
	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
	zIndex: '@', 'zoom': '@', fontWeight: '@',
	textIndent: '@px', opacity: '@'
});

Element.ShortStyles = {'margin': {}, 'padding': {}, 'border': {}, 'borderWidth': {}, 'borderStyle': {}, 'borderColor': {}};

['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
	var Short = Element.ShortStyles;
	var All = Element.Styles;
	['margin', 'padding'].each(function(style){
		var sd = style + direction;
		Short[style][sd] = All[sd] = '@px';
	});
	var bd = 'border' + direction;
	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
	Short[bd] = {};
	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
});




/*
Script: Element.Dimensions.js
	Contains methods to work with size, scroll, or positioning of Elements and the window object.

License:
	MIT-style license.

Note:
	Dimensions requires an XHTML doctype.
*/

(function(){

function $body(el){
	return el.tagName.toLowerCase() == 'body';
};

Element.implement({
	
	positioned: function(){
		if ($body(this)) return true;
		return (Element.getComputedStyle(this, 'position') != 'static');
	},
	
	getOffsetParent: function(){
		if ($body(this)) return null;
		if (!Browser.Engine.trident) return $(this.offsetParent);
		var el = this;
		while ((el = el.parentNode)){
			if (Element.positioned(el)) return $(el);
		}
		return null;
	},
	
	getSize: function(){
		if ($body(this)) return this.getWindow().getSize();
		return {x: this.offsetWidth, y: this.offsetHeight};
	},
	
	getScrollSize: function(){
		if ($body(this)) return this.getWindow().getScrollSize();
		return {x: this.scrollWidth, y: this.scrollHeight};
	},
	
	getScroll: function(){
		if ($body(this)) return this.getWindow().getScroll();
		return {x: this.scrollLeft, y: this.scrollTop};
	},
	
	scrollTo: function(x, y){
		if ($body(this)) return this.getWindow().scrollTo(x, y);
		this.scrollLeft = x;
		this.scrollTop = y;
		return this;
	},
	
	getPosition: function(relative){
		if ($body(this)) return {x: 0, y: 0};
		var el = this, position = {x: 0, y: 0};
		while (el){
			position.x += el.offsetLeft;
			position.y += el.offsetTop;
			el = el.offsetParent;
		}
		var rpos = (relative) ? $(relative).getPosition() : {x: 0, y: 0};
		return {x: position.x - rpos.x, y: position.y - rpos.y};
	},
	
	getCoordinates: function(element){
		if ($body(this)) return this.getWindow().getCoordinates();
		var position = this.getPosition(element), size = this.getSize();
		var obj = {'top': position.y, 'left': position.x, 'width': size.x, 'height': size.y};
		obj.right = obj.left + obj.width;
		obj.bottom = obj.top + obj.height;
		return obj;
	},
	
	getRelativePosition: function(){
		return this.getPosition(this.getOffsetParent());
	},
	
	computePosition: function(obj){
		return {
			left: obj.x - (this.getComputedStyle('margin-left').toInt() || 0),
			top: obj.y - (this.getComputedStyle('margin-top').toInt() || 0)
		};
	},

	position: function(obj){
		return this.setStyles(this.computePosition(obj));
	}
	
});

})();

Native.implement([Window, Document], {
	
	getSize: function(){
		var body = this.getDocument().body, html = this.getDocument().documentElement;
		if (Browser.Engine.webkit419) return {x: this.innerWidth, y: this.innerHeight};
		return {x: html.clientWidth, y: html.clientHeight};
	},

	getScroll: function(){
		var html = this.getDocument().documentElement;
		return {x: $pick(this.pageXOffset, html.scrollLeft), y: $pick(this.pageYOffset, html.scrollTop)};
	},

	getScrollSize: function(){
		var html = this.getDocument().documentElement, body = this.getDocument().body;
		if (Browser.Engine.trident) return {x: Math.max(html.clientWidth, html.scrollWidth), y: Math.max(html.clientHeight, html.scrollHeight)};
		if (Browser.Engine.webkit) return {x: body.scrollWidth, y: body.scrollHeight};
		return {x: html.scrollWidth, y: html.scrollHeight};
	},
	
	getPosition: function(){
		return {x: 0, y: 0};
	},
	
	getCoordinates: function(){
		var size = this.getSize();
		return {top: 0, left: 0, height: size.y, width: size.x, bottom: size.y, right: size.x};
	}
	
});

Native.implement([Window, Document, Element], {
	
	getHeight: function(){
		return this.getSize().y;
	},
	
	getWidth: function(){
		return this.getSize().x;
	},
	
	getScrollTop: function(){
		return this.getScroll().y;
	},
	
	getScrollLeft: function(){
		return this.getScroll().x;
	},
	
	getScrollHeight: function(){
		return this.getScrollSize().y;
	},
	
	getScrollWidth: function(){
		return this.getScrollSize().x;
	},
	
	getTop: function(){
		return this.getPosition().y;
	},
	
	getLeft: function(){
		return this.getPosition().x;
	}
	
});



/*
Script: Selectors.js
	Adds advanced CSS Querying capabilities for selecting elements.

License:
	MIT-style license.
*/

Native.implement([Element, Document], {

	getElements: function(selectors, nocash){
		var Local = {};
		selectors = selectors.split(',');
		var elements = [], j = selectors.length;
		var ddup = (j > 1);
		for (var i = 0; i < j; i++){
			var selector = selectors[i], items = [], separators = [];
			selector = selector.trim().replace(Selectors.sRegExp, function(match){
				if (match.charAt(2)) match = match.trim();
				separators.push(match.charAt(0));
				return ':)' + match.charAt(1);
			}).split(':)');
			for (var k = 0, l = selector.length; k < l; k++){
				var sel = Selectors.parse(selector[k]);
				if (!sel) return [];
				var temp = Selectors.Method.getParam(items, separators[k - 1] || false, this, sel, Local);
				if (!temp) break;
				items = temp;
			}
			var partial = Selectors.Method.getItems(items, this);
			elements = (ddup) ? elements.concat(partial) : partial;
		}
		return new Elements(elements, {ddup: ddup, cash: !nocash});
	}

});

Window.implement({

	$E: function(selector){
		return this.document.getElement(selector);
	}

});

var Selectors = {

	regExp: (/:([^-:(]+)[^:(]*(?:\((["']?)(.*?)\2\))?|\[(\w+)(?:([!*^$~|]?=)(["']?)(.*?)\6)?\]|\.[\w-]+|#[\w-]+|\w+|\*/g),

	sRegExp: (/\s*([+>~\s])[a-zA-Z#.*\s]/g)

};

Selectors.parse = function(selector){
	var params = {tag: '*', id: null, classes: [], attributes: [], pseudos: []};
	selector = selector.replace(Selectors.regExp, function(bit){
		switch (bit.charAt(0)){
			case '.': params.classes.push(bit.slice(1)); break;
			case '#': params.id = bit.slice(1); break;
			case '[': params.attributes.push([arguments[4], arguments[5], arguments[7]]); break;
			case ':':
				var xparser = Selectors.Pseudo.get(arguments[1]);
				if (!xparser){
					params.attributes.push([arguments[1], arguments[3] ? '=' : '', arguments[3]]);
					break;
				}
				var pseudo = {'name': arguments[1], 'parser': xparser, 'argument': (xparser.parser) ? xparser.parser(arguments[3]) : arguments[3]};
				params.pseudos.push(pseudo);
			break;
			default: params.tag = bit;
		}
		return '';
	});
	return params;
};

Selectors.Pseudo = new Hash;

Selectors.XPath = {

	getParam: function(items, separator, context, params){
		var temp = '';
		switch (separator){
			case ' ': temp += '//'; break;
			case '>': temp += '/'; break;
			case '+': temp += '/following-sibling::*[1]/self::'; break;
			case '~': temp += '/following-sibling::'; break;
		}
		temp += (context.namespaceURI) ? 'xhtml:' + params.tag : params.tag;
		var i;
		for (i = params.pseudos.length; i--; i){
			var pseudo = params.pseudos[i];
			if (pseudo.parser && pseudo.parser.xpath) temp += pseudo.parser.xpath(pseudo.argument);
			else temp += ($chk(pseudo.argument)) ? '[@' + pseudo.name + '="' + pseudo.argument + '"]' : '[@' + pseudo.name + ']';
		}
		if (params.id) temp += '[@id="' + params.id + '"]';
		for (i = params.classes.length; i--; i) temp += '[contains(concat(" ", @class, " "), " ' + params.classes[i] + ' ")]';
		for (i = params.attributes.length; i--; i){
			var bits = params.attributes[i];
			switch (bits[1]){
				case '=': temp += '[@' + bits[0] + '="' + bits[2] + '"]'; break;
				case '*=': temp += '[contains(@' + bits[0] + ', "' + bits[2] + '")]'; break;
				case '^=': temp += '[starts-with(@' + bits[0] + ', "' + bits[2] + '")]'; break;
				case '$=': temp += '[substring(@' + bits[0] + ', string-length(@' + bits[0] + ') - ' + bits[2].length + ' + 1) = "' + bits[2] + '"]'; break;
				case '!=': temp += '[@' + bits[0] + '!="' + bits[2] + '"]'; break;
				case '~=': temp += '[contains(concat(" ", @' + bits[0] + ', " "), " ' + bits[2] + ' ")]'; break;
				case '|=': temp += '[contains(concat("-", @' + bits[0] + ', "-"), "-' + bits[2] + '-")]'; break;
				default: temp += '[@' + bits[0] + ']';
			}
		}
		items.push(temp);
		return items;
	},

	getItems: function(items, context){
		var elements = [];
		var doc = context.getDocument();
		var xpath = doc.evaluate('.//' + items.join(''), context, Selectors.XPath.resolver, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
		for (var i = 0, j = xpath.snapshotLength; i < j; i++) elements[i] = xpath.snapshotItem(i);
		return elements;
	},

	resolver: function(prefix){
		return (prefix == 'xhtml') ? 'http://www.w3.org/1999/xhtml' : false;
	}

};

Selectors.Filter = {

	getParam: function(items, separator, context, params, Local){
		var found = [];
		var tag = params.tag;
		if (separator){
			var uniques = {}, child, children, item, k, l;
			var add = function(child){
				child.uid = child.uid || [Native.UID++];
				if (!uniques[child.uid] && Selectors.Filter.match(child, params, Local)){
					uniques[child.uid] = true;
					found.push(child);
					return true;
				}
				return false;
			};
			for (var i = 0, j = items.length; i < j; i++){
				item = items[i];	
				switch(separator){
					case ' ':
						children = item.getElementsByTagName(tag);
						params.tag = false;
						for (k = 0, l = children.length; k < l; k++) add(children[k]);
					break;
					case '>':
						children = item.childNodes;
						for (k = 0, l = children.length; k < l; k++){
							if (children[k].nodeType == 1) add(children[k]);
						}
					break;
					case '+':
						while ((item = item.nextSibling)){
							if (item.nodeType == 1){
								add(item);
								break;
							}
						}
					break;
					case '~':
						while ((item = item.nextSibling)){
							if (item.nodeType == 1 && add(item)) break;
						}
					break;
				}
			}	
			return found;
		}
		if (params.id){
			el = context.getElementById(params.id, true);
			params.id = false;
			return (el && Selectors.Filter.match(el, params, Local)) ? [el] : false;
		} else {
			items = context.getElementsByTagName(tag);
			params.tag = false;
			for (var m = 0, n = items.length; m < n; m++){
				if (Selectors.Filter.match(items[m], params, Local)) found.push(items[m]);
			}
		}
		return found;
	},

	getItems: $arguments(0)

};

Selectors.Filter.match = function(el, params, Local){
	Local = Local || {};

	if (params.id && params.id != el.id) return false;
	if (params.tag && params.tag != '*' && params.tag != el.tagName.toLowerCase()) return false;

	var i;

	for (i = params.classes.length; i--; i){
		if (!el.className || !el.className.contains(params.classes[i], ' ')) return false;
	}

	for (i = params.attributes.length; i--; i){
		var bits = params.attributes[i];
		var result = Element.prototype.getProperty.call(el, bits[0]);
		if (!result) return false;
		if (!bits[1]) continue;
		var condition;
		switch (bits[1]){
			case '=': condition = (result == bits[2]); break;
			case '*=': condition = (result.contains(bits[2])); break;
			case '^=': condition = (result.substr(0, bits[2].length) == bits[2]); break;
			case '$=': condition = (result.substr(result.length - bits[2].length) == bits[2]); break;
			case '!=': condition = (result != bits[2]); break;
			case '~=': condition = result.contains(bits[2], ' '); break;
			case '|=': condition = result.contains(bits[2], '-');
		}

		if (!condition) return false;
	}

	for (i = params.pseudos.length; i--; i){
		if (!params.pseudos[i].parser.filter.call(el, params.pseudos[i].argument, Local)) return false;
	}

	return true;
};

Selectors.Method = (Browser.Features.xpath) ? Selectors.XPath : Selectors.Filter;

Element.implement({

	match: function(selector){
		return (!selector || Selectors.Filter.match(this, Selectors.parse(selector)));
	}

});




/*
Script: Domready.js
	Contains the domready custom event.

License:
	MIT-style license.
*/

Element.Events.domready = {

	onAdd: function(fn){
		
		if (Browser.loaded) return fn.call(this);
		
		var self = this, win = this.getWindow(), doc = this.getDocument();
		
		var domready = function(){
			if (!arguments.callee.done){
				arguments.callee.done = true;
				fn.call(self);
			};
			return true;
		};
		
		var states = (Browser.Engine.webkit) ? ['loaded', 'complete'] : 'complete';
		
		var check = function(context){
			if (states.contains(context.readyState)) return domready();
			return false;
		};
		
		if (doc.readyState && Browser.Engine.webkit){
			
			(function(){
				if (!check(doc)) arguments.callee.delay(50);
			})();
			
		} else if (doc.readyState && Browser.Engine.trident){
			
			var script = $('ie_domready');
			if (!script){
				var src = (win.location.protocol == 'https:') ? '//:' : 'javascript:void(0)';
				doc.write('<script id="ie_domready" defer src="' + src + '"></script>');
				script = $('ie_domready');
			}
			if (!check(script)) script.addEvent('readystatechange', check.pass(script));
			
		} else {
			
			win.addEvent('load', domready);
			doc.addEvent('DOMContentLoaded', domready);
			
		}
		
		return null;
	}

};

window.addEvent('domready', function(){
	Browser.loaded = true;
});



/*
Script: JSON.js
	JSON encoder and decoder.

License:
	MIT-style license.

See Also:
	<http://www.json.org/>
*/

var JSON = new Hash({

	encode: function(obj){
		switch ($type(obj)){
			case 'string':
				return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
			case 'array':
				return '[' + String(obj.map(JSON.encode).filter($defined)) + ']';
			case 'object': case 'hash':
				var string = [];
				Hash.each(obj, function(value, key){
					var json = JSON.encode(value);
					if (json) string.push(JSON.encode(key) + ':' + json);
				});
				return '{' + String(string) + '}';
			case 'number': case 'boolean': return String(obj);
			case false: return 'null';
		}
		return null;
	},

	$specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'},

	$replaceChars: function(chr){
		return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
	},

	decode: function(string, secure){
		if ($type(string) != 'string' || !string.length) return null;
		if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
		return eval('(' + string + ')');
	}

});

Native.implement([Hash, Array, String, Number], {

	toJSON: function(){
		return JSON.encode(this);
	}

});




/*
Script: Cookie.js
	Class for creating, loading, and saving browser Cookies.

License:
	MIT-style license.

Credits:
	Based on the functions by Peter-Paul Koch (http://quirksmode.org).
*/

var Cookie = new Class({
	
	Implements: Options,
	
	options: {
		path: false,
		domain: false,
		duration: false,
		secure: false,
		document: document
	},
	
	initialize: function(key, options){
		this.key = key;
		this.setOptions(options);
	},
	
	write: function(value){
		value = encodeURIComponent(value);
		if (this.options.domain) value += '; domain=' + this.options.domain;
		if (this.options.path) value += '; path=' + this.options.path;
		if (this.options.duration){
			var date = new Date();
			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
			value += '; expires=' + date.toGMTString();
		}
		if (this.options.secure) value += '; secure';
		this.options.document.cookie = this.key + '=' + value;
		return this;
	},
	
	read: function(){
		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
		return value ? decodeURIComponent(value[1]) : null;
	},

	erase: function(){
		new Cookie(this.key, $merge(this.options, {duration: -1})).write('');
		return this;
	}
	
});

Cookie.set = function(key, value, options){
	return new Cookie(key, options).write(value);
};

Cookie.get = function(key){
	return new Cookie(key).read();
};

Cookie.remove = function(key, options){
	return new Cookie(key, options).erase();
};



/*
Script: Swiff.js
	Wrapper for embedding SWF movies. Supports (and fixes) External Interface Communication.

License:
	MIT-style license.

Credits:
	Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
*/

var Swiff = function(path, options){
	if (!Swiff.fixed) Swiff.fix();
	var instance = 'Swiff_' + Native.UID++;
	options = $merge({
		id: instance,
		height: 1,
		width: 1,
		container: null,
		properties: {},
		params: {
			quality: 'high',
			allowScriptAccess: 'always',
			wMode: 'transparent',
			swLiveConnect: true
		},
		events: {},
		vars: {}
	}, options);
	var params = options.params, vars = options.vars, id = options.id;
	var properties = $extend({height: options.height, width: options.width}, options.properties);
	Swiff.Events[instance] = {};
	for (var event in options.events){
		Swiff.Events[instance][event] = function(){
			options.events[event].call($(options.id));
		};
		vars[event] = 'Swiff.Events.' + instance + '.' + event;
	}
	params.flashVars = Hash.toQueryString(vars);
	if (Browser.Engine.trident){
		properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
		params.movie = path;
	} else {
		properties.type = 'application/x-shockwave-flash';
		properties.data = path;
	}
	var build = '<object id="' + options.id + '"';
	for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
	build += '>';
	for (var param in params) build += '<param name="' + param + '" value="' + params[param] + '" />';
	build += '</object>';
	return ($(options.container) || new Element('div')).set('html', build).firstChild;
};

Swiff.extend({

	Events: {},

	remote: function(obj, fn){
		var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
		return eval(rs);
	},

	getVersion: function(){
		if (!$defined(Swiff.pluginVersion)){
			var version;
			if (navigator.plugins && navigator.mimeTypes.length){
				version = navigator.plugins["Shockwave Flash"];
				if (version && version.description) version = version.description;
			} else if (Browser.Engine.trident){
				version = $try(function(){
					return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version");
				});
			}
			Swiff.pluginVersion = (typeof version == 'string') ? parseInt(version.match(/\d+/)[0]) : 0;
		}
		return Swiff.pluginVersion;
	},

	fix: function(){
		Swiff.fixed = true;
		window.addEvent('beforeunload', function(){
			__flash_unloadHandler = __flash_savedUnloadHandler = $empty;
		});
		if (!Browser.Engine.trident) return;
		window.addEvent('unload', function(){
			Array.each(document.getElementsByTagName('object'), function(obj){
				obj.style.display = 'none';
				for (var p in obj){
					if (typeof obj[p] == 'function') obj[p] = $empty;
				}
			});
		});
	}

});




/*
Script: Fx.js
	Contains the basic animation logic to be extended by all other Fx Classes.

License:
	MIT-style license.
*/

var Fx = new Class({

	Implements: [Chain, Events, Options],

	options: {
		/*
		onStart: $empty,
		onCancel: $empty,
		onComplete: $empty,
		*/
		fps: 50,
		unit: false,
		duration: 500,
		link: 'ignore',
		transition: function(p){
			return -(Math.cos(Math.PI * p) - 1) / 2;
		}
	},

	initialize: function(options){
		this.pass = this.pass || this;
		this.setOptions(options);
		this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
		var wait = this.options.wait;
		if (wait === false) this.options.link = 'cancel';
	},

	step: function(){
		var time = $time();
		if (time < this.time + this.options.duration){
			var delta = this.options.transition((time - this.time) / this.options.duration);
			this.set(this.compute(this.from, this.to, delta));
		} else {
			this.set(this.compute(this.from, this.to, 1));
			this.complete();
		}
	},

	set: function(now){
		return now;
	},

	compute: function(from, to, delta){
		return Fx.compute(from, to, delta);
	},

	check: function(){
		if (!this.timer) return true;
		switch (this.options.link){
			case 'cancel': this.cancel(); return true;
			case 'chain': this.chain(this.start.bind(this, arguments)); return false;
		}
		return false;
	},

	start: function(from, to){
		if (!this.check(from, to)) return this;
		this.from = from;
		this.to = to;
		this.time = 0;
		this.startTimer();
		this.onStart();
		return this;
	},

	complete: function(){
		return (!this.stopTimer()) ? this : this.onComplete();
	},

	cancel: function(){
		return (!this.stopTimer()) ? this : this.onCancel();
	},

	onStart: function(){
		return this.fireEvent('onStart', this.pass);
	},

	onComplete: function(){
		return this.fireEvent('onComplete', this.pass).callChain();
	},

	onCancel: function(){
		return this.fireEvent('onCancel', this.pass).clearChain();
	},

	pause: function(){
		this.stopTimer();
		return this;
	},

	resume: function(){
		this.startTimer();
		return this;
	},

	stopTimer: function(){
		if (!this.timer) return false;
		this.time = $time() - this.time;
		this.timer = $clear(this.timer);
		return true;
	},

	startTimer: function(){
		if (this.timer) return false;
		this.time = $time() - this.time;
		this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
		return true;
	}

});

Fx.compute = function(from, to, delta){
	return (to - from) * delta + from;
};

Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};




/*
Script: Fx.CSS.js
	Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.

License:
	MIT-style license.
*/

Fx.CSS = new Class({

	Extends: Fx,

	//prepares the base from/to object

	prepare: function(element, property, values){
		values = $splat(values);
		var values1 = values[1];
		if (!$chk(values1)){
			values[1] = values[0];
			values[0] = element.getStyle(property);
		}
		var parsed = values.map(this.parse);
		return {from: parsed[0], to: parsed[1]};
	},

	//parses a value into an array

	parse: function(value){
		value = $lambda(value)();
		value = (typeof value == 'string') ? value.split(' ') : $splat(value);
		return value.map(function(val){
			val = String(val);
			var found = false;
			Fx.CSS.Parsers.each(function(parser, key){
				if (found) return;
				var parsed = parser.parse(val);
				if ($chk(parsed)) found = {'value': parsed, 'parser': parser};
			});
			found = found || {value: val, parser: Fx.CSS.Parsers.String};
			return found;
		});
	},

	//computes by a from and to prepared objects, using their parsers.

	compute: function(from, to, delta){
		var computed = [];
		(Math.min(from.length, to.length)).times(function(i){
			computed.push({'value': from[i].parser.compute(from[i].value, to[i].value, delta), 'parser': from[i].parser});
		});
		computed.$family = {name: 'fx:css:value'};
		return computed;
	},

	//serves the value as settable

	serve: function(value, unit){
		if ($type(value) != 'fx:css:value') value = this.parse(value);
		var returned = [];
		value.each(function(bit){
			returned = returned.concat(bit.parser.serve(bit.value, unit));
		});
		return returned;
	},

	//renders the change to an element

	render: function(element, property, value){
		element.setStyle(property, this.serve(value, this.options.unit));
	},

	//searches inside the page css to find the values for a selector

	search: function(selector){
		var to = {};
		Array.each(document.styleSheets, function(sheet, j){
			var rules = sheet.rules || sheet.cssRules;
			Array.each(rules, function(rule, i){
				if (!rule.style || !rule.selectorText || !rule.selectorText.test('^' + selector + '$')) return;
				Element.Styles.each(function(value, style){
					if (!rule.style[style] || Element.ShortStyles[style]) return;
					value = rule.style[style];
					to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value;
				});
			});
		});
		return to;
	}

});

Fx.CSS.Parsers = new Hash({

	Color: {

		parse: function(value){
			if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
			return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
		},

		compute: function(from, to, delta){
			return from.map(function(value, i){
				return Math.round(Fx.compute(from[i], to[i], delta));
			});
		},

		serve: function(value){
			return value.map(Number);
		}

	},

	Number: {

		parse: function(value){
			return parseFloat(value);
		},

		compute: function(from, to, delta){
			return Fx.compute(from, to, delta);
		},

		serve: function(value, unit){
			return (unit) ? value + unit : value;
		}

	},

	String: {

		parse: $lambda(false),

		compute: $arguments(1),

		serve: $arguments(0)

	}

});




/*
Script: Fx.Morph.js
	Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.

License:
	MIT-style license.
*/

Fx.Morph = new Class({

	Extends: Fx.CSS,

	initialize: function(element, options){
		this.element = this.pass = $(element);
		arguments.callee.parent(options);
	},

	set: function(now){
		if (typeof now == 'string') now = this.search(now);
		for (var p in now) this.render(this.element, p, now[p]);
		return this;
	},

	compute: function(from, to, delta){
		var now = {};
		for (var p in from) now[p] = arguments.callee.parent(from[p], to[p], delta);
		return now;
	},

	start: function(properties){
		if (!this.check(properties)) return this;
		if (typeof properties == 'string') properties = this.search(properties);
		var from = {}, to = {};
		for (var p in properties){
			var parsed = this.prepare(this.element, p, properties[p]);
			from[p] = parsed.from;
			to[p] = parsed.to;
		}
		return arguments.callee.parent(from, to);
	}

});

Element.Properties.morph = {

	set: function(options){
		var morph = this.retrieve('morph');
		if (morph) morph.cancel();
		return this.store('morph', new Fx.Morph(this, $extend({link: 'cancel'}, options)));
	},

	get: function(options){
		if (options || !this.retrieve('morph')) this.set('morph', options);
		return this.retrieve('morph');
	}

};

Element.implement({

	morph: function(props){
		this.get('morph').start(props);
		return this;
	},
	
	effects: function(options){
		return new Fx.Morph(this, options);
	}

});




/*
Script: Request.js
	Powerful all purpose Request Class. Uses XMLHTTPRequest.

License:
	MIT-style license.
*/

var Request = new Class({

	Implements: [Chain, Events, Options],

	options: {/*
		onRequest: $empty,
		onSuccess: $empty,
		onFailure: $empty,
		onException: $empty,*/
		url: '',
		data: '',
		headers: {},
		async: true,
		method: 'post',
		link: 'ignore',
		isSuccess: null,
		emulation: true,
		urlEncoded: true,
		encoding: 'utf-8',
		evalScripts: false,
		evalResponse: false
	},

	getXHR: function(){
		return (window.XMLHttpRequest) ? new XMLHttpRequest() : ((window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') : false);
	},

	initialize: function(options){
		if (!(this.xhr = this.getXHR())) return;
		this.setOptions(options);
		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
		this.headers = new Hash(this.options.headers).extend({
			'X-Requested-With': 'XMLHttpRequest',
			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
		});
	},

	onStateChange: function(){
		if (this.xhr.readyState != 4 || !this.running) return;
		this.running = false;
		this.status = 0;
		$try(function(){
			this.status = this.xhr.status;
		}, this);
		if (this.options.isSuccess.call(this, this.status)){
			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
			this.success(this.response.text, this.response.xml);
		} else {
			this.response = {text: null, xml: null};
			this.failure();
		}
		this.xhr.onreadystatechange = $empty;
	},

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

	processScripts: function(text){
		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
		return text.stripScripts(this.options.evalScripts);
	},

	success: function(text, xml){
		this.onSuccess(this.processScripts(text), xml);
	},
	
	onSuccess: function(){
		this.fireEvent('onComplete', arguments).fireEvent('onSuccess', arguments).callChain();
	},
	
	failure: function(){
		this.onFailure();
	},

	onFailure: function(){
		this.fireEvent('onComplete').fireEvent('onFailure', this.xhr);
	},

	setHeader: function(name, value){
		this.headers.set(name, value);
		return this;
	},

	getHeader: function(name){
		return $try(function(){
			return this.getResponseHeader(name);
		}, this.xhr) || null;
	},

	check: function(){
		if (!this.running) return true;
		switch (this.options.link){
			case 'cancel': this.cancel(); return true;
			case 'chain': this.chain(this.send.bind(this, arguments)); return false;
		}
		return false;
	},

	send: function(options){
		if (!this.check(options)) return this;
		this.running = true;

		var type = $type(options);
		if (type == 'string' || type == 'element') options = {data: options};

		var old = this.options;
		options = $extend({data: old.data, url: old.url, method: old.method}, options);
		var data = options.data, url = options.url, method = options.method;

		switch($type(data)){
			case 'element': data = $(data).toQueryString(); break;
			case 'object': case 'hash': data = Hash.toQueryString(data);
		}

		if (this.options.emulation && ['put', 'delete'].contains(method)){
			var _method = '_method=' + method;
			data = (data) ? _method + '&' + data : _method;
			method = 'post';
		}

		if (this.options.urlEncoded && method == 'post'){
			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
		}

		if (data && method == 'get'){
			url = url + (url.contains('?') ? '&' : '?') + data;
			data = null;
		}

		this.xhr.open(method.toUpperCase(), url, this.options.async);

		this.xhr.onreadystatechange = this.onStateChange.bind(this);

		this.headers.each(function(value, key){
			try{
				this.xhr.setRequestHeader(key, value);
			} catch(e){
				this.fireEvent('onException', [e, key, value]);
			}
		}, this);

		this.fireEvent('onRequest');
		this.xhr.send(data);
		if (!this.options.async) this.onStateChange();
		return this;
	},

	cancel: function(){
		if (!this.running) return this;
		this.running = false;
		this.xhr.abort();
		this.xhr.onreadystatechange = $empty;
		this.xhr = this.getXHR();
		this.fireEvent('onCancel');
		return this;
	}

});

(function(){

var methods = {};
['get', 'post', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
	methods[method] = function(){
		var params = Array.link(arguments, {url: String.type, data: $defined});
		return this.send($extend(params, {method: method.toLowerCase()}));
	};
});

Request.implement(methods);

})();

Element.Properties.send = {

	get: function(options){
		if (options || !this.retrieve('send')) this.set('send', options);
		return this.retrieve('send');
	},

	set: function(options){
		var send = this.retrieve('send');
		if (send) send.cancel();
		return this.store('send', new Request($extend({
			data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
		}, options)));
	}

};

Element.implement({

	send: function(url){
		var sender = this.get('send');
		sender.send({data: this, url: url || sender.options.url});
		return this;
	}

});




/*
Script: Assets.js
	Provides methods to dynamically load JavaScript, CSS, and Image files into the document.

License:
	MIT-style license.
*/

var Asset = new Hash({

	javascript: function(source, properties){
		properties = $extend({
			onload: $empty,
			document: document,
			check: $lambda(true)
		}, properties);
		
		var script = new Element('script', {'src': source, 'type': 'text/javascript'});
		
		var load = properties.onload.bind(script), check = properties.check, doc = properties.document;
		delete properties.onload; delete properties.check; delete properties.document;
		
		script.addEvents({
			load: load,
			readystatechange: function(){
				if (this.readyState == 'complete') load();
			}
		}).setProperties(properties);
		
		
		if (Browser.Engine.webkit419) var checker = (function(){
			if (!$try(check)) return;
			$clear(checker);
			load();
		}).periodical(50);
		
		return script.inject(doc.head);
	},

	css: function(source, properties){
		return new Element('link', $merge({
			'rel': 'stylesheet', 'media': 'screen', 'type': 'text/css', 'href': source
		}, properties)).inject(document.head);
	},

	image: function(source, properties){
		properties = $merge({
			'onload': $empty,
			'onabort': $empty,
			'onerror': $empty
		}, properties);
		var image = new Image();
		var element = $(image) || new Element('img');
		['load', 'abort', 'error'].each(function(name){
			var type = 'on' + name;
			var event = properties[type];
			delete properties[type];
			image[type] = function(){
				if (!image) return;
				if (!element.parentNode){
					element.width = image.width;
					element.height = image.height;
				}
				image = image.onload = image.onabort = image.onerror = null;
				event.delay(1, element, element);
				element.fireEvent(name, element, 1);
			};
		});
		image.src = element.src = source;
		if (image && image.complete) image.onload.delay(1);
		return element.setProperties(properties);
	},

	images: function(sources, options){
		options = $merge({
			onComplete: $empty,
			onProgress: $empty
		}, options);
		if (!sources.push) sources = [sources];
		var images = [];
		var counter = 0;
		sources.each(function(source){
			var img = new Asset.image(source, {
				'onload': function(){
					options.onProgress.call(this, counter, sources.indexOf(source));
					counter++;
					if (counter == sources.length) options.onComplete();
				}
			});
			images.push(img);
		});
		return new Elements(images);
	}

});




/** 
  CHANGELOG:
    2008/01/11 - Updated FD.addAd and FD._spawn to 
                 allow for the new ad server.
                 This update will determine whether the page has been
                 updated for the new ad server, if not, it uses the old method.
                 
    2008/01/17 - Updated FD.getSiteAdvertDesc function which can be overridden 
                 to offer site-specific business logic.
                 
    2008/02/06 - Updated to version 0.6:
                  - Added Atlas functionality to FD.addAd
                  - Removed need for a params object when adding an ad.
                  
    2008/02/08 - Changed refreshAd:
                  - Build 868's garbage collector is causing trouble
                    Used ad.src = ad.src method until upgrading to v1.2
                    
    2008/03/17 - Changed addAd:
                  - Changed usage of !src.contains("ffxcam") to
                    src.contains("direct.fairfax.com.au")
                  - Didn't know URL of Ad Server when doing initial release.
                  
    2008/04/29 - Changed addAd:
                  - Added functionality to accept site and area directly from baseAd params
                    rather than through the translator.
 **/
/*
   Class: FD
   
   *Version: 0.6*
  
     Base class of the Fairfax Digital Javascript Framework.
     This class will allow the registration of FD widgets and
     allows a simple API for adding scripts, advertisements
     and tracking images.
     The class will also automatically initialise the Nielson
     tracking script.
*/ 

var FDBase = new Class({
  _remAds: false,
  _registeredWidgets: [],
  _widgets: [],
  _ads: [],
  _adLU: [], // Ad Lookup.
  _scripts: [],
  _images: [],
  _trackImg: [],
  _usePriority: false,
  /*
    Group: INITIALISATION */
  
  initialize: function() {
    // Create a random number for this impression
    this.pageID = Math.floor(1000000*Math.random());
    window.addEvent("domready", this._init.bind(this));
  },
  
  /*
    Function: initialize
    
      Run onDomReady to execute all registered functions,
      spawn Advertisements, inject script tags and images.
   */
  _init: function() {
    // Init Widgets
    if(typeof(window['initPost']) == "function") {
      this._registeredWidgets.push("initPost");
      initPost();
    }
    
    // Check for excluded ads
    this._excludeAds();
    // Spawn the ads
    this._spawnWidgets();
  },
    
  /*
    Group: WIDGETS
    
    Function: _sortByPriority
   
      Helper function to sort widgets and ads by priority.
   */
  _sortByPriority: function(a, b) {
    return a.priority - b.priority;
  }, 
  
  /*
    Function: _spawnWidgets

      Initialises the registered widgets by priority (if any)
   */
  _spawnWidgets: function() {
    if(this._widgets.length > 1 && this._usePriority) {
      this._widgets.sort(this._sortByPriority);
    }
    
    for(var a = 0, i = this._widgets.length; a < i; a++) {  
      var thisWidget = this._widgets[a];
      // Call each widgets init function
      if(thisWidget.type == "ad" && !this._remAds && !thisWidget.exclude) {
        this._spawnAd(thisWidget);
      } else {
        if(!thisWidget.exclude && thisWidget.name) {
          window.fireEvent("init"+thisWidget.name);
        }
      }
    }
  },
  
  /*
    Function: exclude
   
     Marks a function as excluded which prevents it from being executed.
      
    Parameters: 
   
      widgetName - The name of the widget to exclude from initialisation
     
   */
  exclude: function(widgetName) {
    for(var a = 0; a < this._widgets.length; a++) {
      var thisWidget = this._widgets[a];
      if(thisWidget.name == widgetName) {
        thisWidget.exclude = true;
      }
    }
  }, 
  
  /*
  
    Function: register
    
      Registers a function to be executed by priority onDomReady.
  */
  register: function(name, priority) {
    if(name) {
      var initF = "init"+name;
      var wEvents = window.retrieve('events');
      if(wEvents && !wEvents[initF] && $type(window[initF]) == "function") {
        FD.log("Registering function '"+initF+"'");
        window.addEvent(initF, window[initF]);
      }
      // Backwards combatibility
      if(!isNaN(arguments[2])) {
        priority = arguments[2];
      }
      var w = {
        name: name,
        priority: priority
      };
      if(!isNaN(w.priority)) {
        this._usePriority = true;
      } else {
        w.priority = 99;
      }
      this._widgets.push(w);
    }
  },
  
  /*
   Function: setWidgetPriority
   
    Manually sets the priority of a widget
    
   Parameters:
   
     widgetName - The name of the widget.
     priority - The new priority of the named widget.
     
   */
  setWidgetPriority: function(widgetName, priority) {
    var thisWidget, i = 0;
    while(thisWidget = this._widgets[i]) {
      if(thisWidget.name == widgetName) {
        thisWidget.priority = priority;
      }
      i++;
    }
  },

  /*
    Group: ADVERTISEMENTS
  
    Function: _spawnAd
    
      Takes an Advertisement Object and spawns a new iFrame for it if it's div is located.
      If the advertisement's src property is an array of URL's then an iFrame is 
      created for each one and injected randomly into the ads div.

    Parameters:
    
      thisAd - The advertisement object created by <FD.addAd>.
      
    See Also:
    
      <FD.addAd>
      
   */
  _spawnAd: function(thisAd) {
    var thisAdHolder = $(thisAd.id);
    if(thisAdHolder) {
      if(thisAd.addSmall) {
        var thisSmall = new Element("small");
        thisSmall.setText(thisAd.smallText);
        if(thisAd.addSmall == "top" || thisAd.addSmall == "both") {
          thisSmall.injectInside(thisAdHolder);
        }
      }
      if($type(thisAd.src) == "string") {
        thisAd.src = [thisAd.src];
      }
      var iFrameAttribs = {
        frameBorder: "0",
        marginHeight: "0",
        marginWidth: "0",
        hspace: "0",
        vspace: "0",
        scrolling: "no",
        allowtransparency: "true"
      };
      
      if(thisAd.src.length > 1) {
        for (var i = thisAd.src.length -1; i >= 0; --i) {
          var j =  Math.floor(Math.random() * (i + 1));
          if (i == j) continue;
          var temp = thisAd.src[i];
          thisAd.src[i] = thisAd.src[j];
          thisAd.src[j] = temp;
        }
      }
      for(var a = 0, i = thisAd.src.length; a < i; a++) {
        var src = thisAd.src[a];
        var props = {};
        var width = thisAd.width;
        var height = thisAd.height;
        if(isNaN(width) || isNaN(height)) {
          // Get the width and height of this ad
          var dim = (src.toLowerCase().indexOf("adspace") > -1) ? this._getAdDimensions(src, "adspace") : this._getAdDimensions(src, "AAMSZ");
          if(dim) {
            width = dim[1];
            height = dim[2];
          }
        }
        props.src = src;
        props.id = thisAd.iframeId;
        if(a > 0) {
          props.id += a;
        }
        props.name = props.id;
        props.styles = {};
        if(width) {
          props.styles.width = width+"px";
        }
        if(height) {
          props.styles.height = height+"px";
        }
        props = $merge(props, iFrameAttribs);
        var thisIframe = new Element("iframe", props);
        thisAdHolder.adopt(thisIframe);
      }
      if(thisAd.addSmall == "bottom" || thisAd.addSmall == "both") {
        thisSmall.clone().injectInside(thisAdHolder);
      }
    }
  },
  
  _getAdDimensions: function(src, tag) {
    if(src && tag) {
      if(src.indexOf(tag+"=") > -1) {
        var thisWH = new RegExp(tag+"=([0-9]+)x([0-9]+)", "i").exec(src);
        if(thisWH) {
          return thisWH;
        }
      }
    }
    return false;
  },
  
  /*
    Function: _excludeAds
    
      Checks to see if the ffxAdExclusionList variable has been set
      and excludes ads based on it's content.
      
   */
  _excludeAds: function() {
    if (typeof(ffxAdExclusionList) != "undefined") {
      var thisPage = window.location.pathname;
      for (var i = 0; i < ffxAdExclusionList.length; i++) {
        var current = ffxAdExclusionList[i];
        if (current == 'remove_all_ads') {
          this._remAds = true;
        } else if (current == 'remove_article_ads') {
          // News Specific
          if (thisPage.match(/\/news\/[a-zA-Z0-9-]+\/[a-zA-Z0-9-]+\/[0-9]{4}\/[0-9]{2}\/[0-9]{2}\/[0-9]+.html/) || thisPage.match(/\/articles\/.*?/)){
            this._remAds = true;
          }
        } else if (current == 'remove_index_ads') {
          if  (thisPage.match(/\/index.html$/) || !thisPage.match(/.html$/)) {
            this._remAds = true;
          }
        } else if (current == thisPage) {
          this._remAds = true;
        }
      }
    }
  },
  
  /*
    Function: addAd
    
      Registers an Advertisement to be initilised onDomReady.
      Advertisements can be registered using the parameters below (depreciated) or by using 
      an object to define the properties (encouraged). See the Examples below for more information.
      
      The Advertisements are added to the _widgets array and initialised inline with the widgets 
      to allow for a visually tailored load process. (i.e: Ads and widgets at the top of the page
      load before those at the bottom).
          
    Parameters:
    
      id - The ID of the Div to insert the Advertisement into.
      iframeId - The ID to use for the iFrame that is inserted into the Advertisement Div.
      src - Either a single URL or an array of URL's. See <FD._spawnAd> for more details.
      width - Optional. The final width of the Advertisement Div.
      height - Optional. The final height of the Advertisement Div.
      addSmall - Optional. Adds &lt;small&gt;Advertisement&lt;/small&gt; before and after the advertisement.
      priority - Optional. Sets the priority of the advertisement in the loading sequence.
      
    Examples: 
      
      (start code)
      
        function initPost() {
          var bust = Math.floor(1000000*Math.random());
          var baseAd = {
            src: "http://ffxcam.smh.com.au/html.ng/",
            params: {
              site: "smh",
              domain: "smh.com.au",
              isiframe: "yes",
              cat: "news"
            }
          };
          
          // Creating an ad for a single 300x250 island ad with priority 1
          FD.addAd($merge(baseAd, {
            id: "adSpotIsland",
            iframeId: "AdPlaceholder-2",
            params: $merge(baseAd.params, {
              adspace: "300x250",
              loc: "search",
              ctype: "page",
              adtype: "doubleisland",
              bust: bust
            }),
            priority: 1,
            addSmall: true
          }));
          
          // Creating an array of text ads each 300x28px to be inserted into the same div
          FD.addAd($merge(baseAd, {
            id: "adSpot-textBox",
            iframeId: "AdPlaceholder-hptextad1",
            src: ["http://ffxcam.smh.com.au/html.ng/loc=1&", 
                  "http://ffxcam.smh.com.au/html.ng/loc=2&",
                  "http://ffxcam.smh.com.au/html.ng/loc=3&",
                  "http://ffxcam.smh.com.au/html.ng/loc=4&",
                  "http://ffxcam.smh.com.au/html.ng/loc=5&"],
            params: $merge(baseAd.params, {
              adspace: "textad",
              ctype: "page",
              fontsize: ".83em"
            }),
            width: 300,
            height: 28
          }));
        }
      
      (end code)
      
   */
  addAd: function(id, iframeId, src, width, height, addSmall, priority) {
    var fields = ['id', 'iframeId', 'src', 'width', 'height', 'addSmall', 'priority'];
    if(arguments.length > 0) {
      var obj = {};
      if($type(arguments[0]) == "object") {
        obj = arguments[0];
      } else {
        for(var a = 0; a < arguments.length; a++) {
          obj[fields[a]] = arguments[a];
        }
      }
      // If this ad doesnt already exists in the array 
      // and if it's holder div exists
      if(!this._ads[obj.iframeId] && $(obj.id)) {
        obj.type = "ad";
        if(obj.addSmall) {
          if(obj.addSmall == true) {
            obj.addSmall = "top";
          }
          if(!obj.smallText) {
            obj.smallText = "Advertisement";
          }
        }
        
        if($type(obj.src) != "array") {
          obj.src = [obj.src];
        }
        var i = 0, src, args;
        while(src = obj.src[i]) {
          // Create a copy of the original params
          // so as not to over-ride original settings
          var siteDesc;
          if(src.contains("direct.fairfax.com.au") && (siteDesc = this.getSiteAdvertDesc(obj))) {
            var params = this.copyAdParams(obj);
            // Support for new ad server mode
            if(src.indexOf("/hserver/") > -1) {
              src = src.replace(/(\/hserver\/)/i, "/jserver/");
            }
            if(params.site && params.area) {
              args = "SITE="+params.site+"/AREA="+params.area; 
              delete params.site;
              delete params.area;
            } else if(siteDesc.site && siteDesc.area) {
              args = "SITE="+siteDesc.site+"/AREA="+siteDesc.area; 
            }
            if(params.loc) {
              var pos = params.loc;
              params.pos = pos;
              delete params.loc;
            }
            var ctype = params.ctype;
            if(ctype) {
              if(ctype == "page") {
                ctype = "index";
              } else if(ctype != "index") {
                ctype = "story";
              }
              args += "/CTYPE="+ctype;
              delete params.ctype;
            }
            if(params.adspace) {
              args += "/AAMSZ="+params.adspace;
              delete params.adspace;
            }
            args += "/pageid="+this.pageID;
            args += "/acc_random="+Math.floor(1000000*Math.random());
            for(var p in params) {
              args += "/"+p+"="+params[p];
            }
            if(params.adtype == "doubleisland" || params.adtype == "panorama") {
              // Adredirect call via adredirect.html
              // Ensure JS is returned
              if(src.indexOf("/hserver/") > -1) {
                src = src.replace(/(\/hserver\/)/i, "/jserver/");
              }
              src = siteDesc.redir + src + args.toUpperCase();
            } else {
              // Direct call to adserver
              // Ensure HTML is returned
              if(src.indexOf("/jserver/") > -1) {
                src = src.replace(/(\/jserver\/)/i, "/hserver/");
              }
              src = src + args.toUpperCase();
            }
          } else {
            var params = this.copyAdParams(obj);
            // Support for old ad server mode
            if(src.indexOf("html.ng") == -1 && 
               src.indexOf("js.ng") == -1 && 
               src[src.length - 1] != "/" &&
               src[src.length - 1] != "?") {
              src += "?";
            }
            for(var param in params) {
              src += param+'='+params[param]+'&';
            }
          }
          obj.src[i] = src;
FD.log("Ad URL for '"+obj.id+"':\n "+src);
          i++;
        }
        var thisAd = $(obj.id);
        if(isNaN(obj.priority)) {
          obj.priority = 99;
        } else {
          this._usePriority = true;
        }
        
        this._ads[obj.iframeId] = obj;
        this._widgets.push(obj);
        this._adLU.push(obj.iframeId);
      }
    }
  },
  
  getSiteAdvertDesc: function() {
    return window['siteAdvertDesc'];
  },
  
  copyAdParams: function(obj) {
    var params = {};
    for(var param in obj.params) {
      params[param] = obj.params[param];
    }
    return params;
  },
 
  /*
    Function: refreshAd
    
      Refreshes an ad iFrame to (hopefully) display a new ad
      
   */
  refreshAd: function(id) {
    if(id) {
      if(id == "*") {
        // Refresh all ads. 
        for(var a = 0; a < this._adLU.length; a++) {
          this.refreshAd(this._adLU[a]);
        }
      } else {
        var ad = $(id);
        /* Temporarily commented out until upgrading to v1.2
        if(ad && this._ads[id]) {
          var adHolder = $(this._ads[id]["id"]);
          if(adHolder) {
            adHolder.empty();
            this._spawnAd(this._ads[id]);
          }
        }*/
        if(ad) {
          ad.src = ad.src;
        }
      }
    }
  }, 

  /*
    Group: TRACKING
  
    Function: _getNielsonURL
      
      Returns the current Nielson URL.
   */
  _getNielsonURL: function() {
    return this.getProtocol()+'//secure-au.imrworldwide.com/';
  },
  
  /*
    Function: _nnRecordFactory
    
      Gathers variables to track a content impression with Nielson
   */
  _nnRecordFactory: function(_nnClickURL) {
    if(_nnClickURL.indexOf("http") == -1) {
      var location = window.location;
      var appendLoc = "";
      if(_nnClickURL.charAt(0) != "/") { // Relative URL
        appendLoc = "/";
      }
      _nnClickURL = location.protocol+"//"+location.host+appendLoc+_nnClickURL;
    }
    var _nnCI = _rsCI; // client id no.
    var _nnCY = "au"; // country code
    var _nnCC = 0;    // cookie check
    if (typeof(_nnCG) == "undefined") {
      if(typeof(_rsCG) != "undefined") {
        _nnCG = _rsCG;
      } else {
        _nnCG = 0;
      }
    }
  	var _nnND = this._getNielsonURL();
  	var _nnPixelSrc = _nnND + 'cgi-bin/m?rnd=' + (new Date()).getTime();
  	_nnPixelSrc += '&ci=' + _nnCI;
  	_nnPixelSrc += '&cg=' + escape(_nnCG); // Site ID picked up globally.
  	_nnPixelSrc += '&si=' + escape(_nnClickURL);
  	//_nnPixelSrc += '&si=' + _nnCI + '-ctgw-' + escape(_nnClickURL);
  	_nnPixelSrc += '&cc=' + _nnCC;
  	_nnPixelSrc += '&rp=' + escape(window.location);
    return _nnPixelSrc;
  },

  /*
    Function: _trackingImageLoaded
    
      Removes a tracking image from the image buffer 
      when it has successfully loaded. 
   */
  _trackingImageLoaded: function(id) {
    if(this._trackImg[id]) {
      this._trackImg[id].shift();
    }
  },
  
  /*  
    Function: doOmnitureImpression
    
      Tracks a click with Omniture
   */
   
  doOmnitureImpression: function(id, anchor) {
    if(anchor && id) {
      // Omniture
      if(typeof(window['s_gs']) == "function") {
        // Track with Omniture G code
        s_linkType='o';
        s_linkName = id;
        s_pageName = "reqdavesTestGallery";//"+id;
        s_lnk = s_co(anchor);
        s_gs(s_account);
      } else if(typeof(window['s']) == "object") {
        // Track with Omniture H code
        
      }
    }
  },
  
  /*  
    Function: doNielsonImpression
    
      Tracks a click with Nielson
   */
   
  doNielsonImpression: function(id, anchor) {
    var url = anchor;
    if($type(anchor) == "element") {
      url = anchor.href;
    }
    // Request Buffering
    if(!this._trackImg[id]) {
      this._trackImg[id] = [];
    }
    // Nielson
    if(window['_rsCI']) {
      var src = this._nnRecordFactory(url);
      var img = new Element("img");
      img.src = src;
      this._trackImg[id].push(url);
  		img.addEvent("load", this._trackingImageLoaded.bind(this, id));
    }
  },
  
  /*
    Function: doContentImpression
      
      Tracks a URL with Nielson and Omniture
   */
  doContentImpression: function(id, anchor) {
    if(id && anchor) {
      this.doNielsonImpression(id, anchor);
      this.doOmnitureImpression(id, anchor);
    }
  },
 
  /*
    Function: getProtocol
      
      Returns the current Protocol.
   */
  getProtocol: function() {
    return location.protocol.indexOf('https') >- 1 ? 'https:' : 'http:';
  },
  
  /*
    Function: inArray
    
      Returns true if the passed value is found in the passed array
   */
  inArray: function(array, value) {
    for(var x = 0; x < array.length; x++) {
      if(array[x] == value) {
        return true;
      }
    }
  },
  
  /*
    Function: setBatchStyle
    
      Sets a style to an array of elements.
   */
  setBatchStyle: function(elements, style, value) {
    for(var a = 0; a < elements.length; a++) {
      elements[a].setStyle(style, value);
    }
  }, 
  
  /*
    Function: log
    
      Output a message to firebug (if it exists)
   */
   log: function(message) {
     if(window['console']) {
       console.info(message);
     }
   }, 
   
  /*
    Function: error
    
      Output an error message to firebug (if it exists)
   */
   error: function(message) {
     if(window['console']) {
       console.error(message);
     }
   }
});
var FD = new FDBase();


