if (!this.__proto__) {
	var fix = [Object, Function, Number, Boolean, String, Array, Date, RegExp];
	for (var i in fix)
		fix[i].prototype.__proto__ = fix[i].prototype;
}

undefined = window.undefined;

(function() { 

	function inject(dest, src, base, hide) {

		"";

		if (src) for (var name in src)

			if (!src.has || src.has(name)) (function(val, name) {

				var res = val, baseVal = base && base[name];
				if (typeof val == 'function' && baseVal && val !== baseVal &&
					/\$super\b/.test(val)) {
					res = function() {
						var prev = this.$super;
						this.$super = base != dest ? base[name] : baseVal;
						try { return val.apply(this, arguments); }
						finally { this.$super = prev; }
					};
					res.toString = function() {
						return val.toString();
					};
				}
				dest[name] = res;
				if (hide && dest.dontEnum != null)
					dest.dontEnum(name);
			})(src[name], name);
		return dest;
	}

	function extend(obj) {
		var ctor = function() {
			this.__proto__ = obj;
			if (this.$constructor && arguments[0] !== ctor.dont)
				return this.$constructor.apply(this, arguments);
		};
		ctor.prototype = obj;
		return ctor;
	}

	Function.prototype.inject = function(src, hide, base) {
		src = typeof src == 'function' ? src() : src;
		inject(this.prototype, src, base ? base.prototype : this.prototype, hide);
		inject(this, base);
		return inject(this, src.$static, base);
	};

	Function.prototype.extend = function(src, hide) {
		var ctor = extend(new this(this.dont));
		ctor.prototype.constructor = ctor;
		ctor.dont = {};
		return this.inject.call(ctor, src, hide, this);
	};

	Object.prototype.dontEnum = function(force) {
		var d = this._dontEnum = !(d = this._dontEnum) ? {} :
				d._object != this ? new (extend(d)) : d;
		d._object = this;
		for (var i = force == true ? 1 : 0; i < arguments.length; i++)
			d[arguments[i]] = { object: this, allow: force != true };
	};

	Object.prototype.dontEnum(true, "dontEnum", "_dontEnum", "__proto__",
		"constructor", "$static");

	Object.inject({
		has: function(name) {
			var val = this[name], entry;
			return val !== undefined && (!(entry = this._dontEnum[name]) ||
				entry.allow && entry.object[name] !== val)
		},

		inject: function(src, hide) {
			return inject(this, src, this, hide);
		},

		extend: function(src, hide) {
			return (new (extend(this))).inject(src, hide);
		}
	}, true);
})();

function $typeof(obj) {
	return obj && ((obj._type || obj.nodeName && obj.nodeType == 1 && 'element') || typeof obj) || undefined;
}

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

Function.inject(function() {
	Function.timers = {};
	var timerId = 0;

	function timer(that, type, args, ms) {
		var fn = that.bind.apply(that, $A(args, 1));
		var id = timerId++;
		Function.timers[id] = fn;
		var f = "Function.timers[" + id + "]", call = f + "();";
		if (type[0] == 'T') call += " delete " + f;
		var timer = window['set' + type](call, ms);
		fn.clear = function() {
			clearTimeout(timer);
			clearInterval(timer);
			delete Function.timers[id];
		};
		return fn;
	}

	return {
		parameters: function() {
			return this.toString().match(/^\s*function[^\(]*\(([^\)]*)/)[1].split(/\s*,\s*/);
		},

		body: function() {
			return this.toString().match(/^\s*function[^\{]*\{([\s\S]*)\}\s*$/)[1];
		},

		delay: function(ms) {
			return timer(this, "Timeout", arguments, ms);
		},

		periodic: function(ms) {
			return timer(this, "Interval", arguments, ms);
		},

		bind: function(obj) {
			var that = this, args = $A(arguments, 1);
			return function() {
				return that.apply(obj, args.concat($A(arguments)));
			}
		},

		attempt: function(obj) {
			var that = this, args = $A(arguments, 1);
			return function() {
				try { return that.apply(obj, args.concat($A(arguments))); }
				catch(e) { return e; }
			}
		}
	}
}, true);

if (!Function.prototype.apply) {
	Object.prototype.dontEnum(true, "__f");
	var cache = {};

	Function.inject({
		apply: function(obj, args, start) {
			if (!start) start = 0;
			var count = args ? args.length : 0, index = start * 64 + count;
			var fn = cache[index];
			if (!fn) {
				fn = [];
				for (var i = start; i < count; i++)
					fn[i - start] = 'args[' + i + ']';
				fn = cache[index] = new Function('obj, args',
					'return obj.__f(' + fn + ');');
			}
			if (obj) {
				obj.__f = this;
				try { return fn(obj, args); }
				finally { obj.__f = undefined; }
			} else {
				return fn({ __f: this }, args);
			}
		},

		call: function(obj) {
			return this.apply(obj, arguments, 1);
		}
	}, true);
}

$break = {};

function $each(obj, iter, bind) {
	return obj ? Enumerable.each.call(obj, iter, bind) : bind;
};

Enumerable = (function() {

	function iterator(iter) {
		if (!iter) return function(val) { return val };
		switch ($typeof(iter)) {
			case 'function': return iter;
			case 'regexp': return function(val) { return iter.test(val) };
		}
		return function(val) { return val == iter };
	}

	function iterate(fn, name, convert, start) {
		Object.prototype.dontEnum(true, name);
		return function(iter, bind) {
			if (convert) iter = iterator(iter);
			if (!bind) bind = this;
			var prev = bind[name];
			bind[name] = iter;
			try { return fn.call(this, iter, bind, this); }
			finally { bind[name] = prev; }
		};
	}

	var each_Array = Array.prototype.forEach || function(iter, bind) {
		for (var i = 0; i < this.length; i++)
			bind.__each(this[i], i, this);
	};

	var each_Object = function(iter, bind) {
		var entries = this._dontEnum || {};
		for (var i in this) {
			var val = this[i], entry = entries[i];
			if (!entry || entry.allow && entry.object[i] !== this[i])
				bind.__each(val, i, this);
		}
	};

	return {
		each: iterate(function(iter, bind) {
			try { (this.length != null ? each_Array : each_Object).call(this, iter, bind); }
			catch (e) { if (e !== $break) throw e; }
			return bind;
		}, "__each"),

		find: iterate(function(iter, bind, that) {
			return this.each(function(val, key) {
				if (bind.__find(val, key, that)) {
					this.value = val;
					throw $break;
				}
			}, {}).value;
		}, "__find", true),

		some: function(iter, bind) {
			return this.$super ? this.$super(iterator(iter), bind) :
				this.find(iter, bind) !== undefined;
		},

		every: iterate(function(iter, bind, that) {
			return this.$super ? this.$super(iter, bind) : this.find(function(val, i) {
				return this.__every(val, i, that);
			}, bind) === undefined;
		}, "__every", true),

		map: iterate(function(iter, bind, that) {
			return this.$super ? this.$super(iter, bind) : this.each(function(val, i) {
				this.push(bind.__map(val, i, that));
			}, []);
		}, "__map", true),

		filter: iterate(function(iter, bind, that) {
			return this.$super ? this.$super(iter, bind) : this.each(function(val, i) {
				if (bind.__filter(val, i, that)) this.push(val);
			}, []);
		}, "__filter", true),

		contains: function(obj) {
			return this.find(function(val) { return obj == val }) !== undefined;
		},

		max: iterate(function(iter, bind, that) {
			return this.each(function(val, i) {
				val = bind.__max(val, i, that);
				if (val >= (this.max || val)) this.max = val;
			}, {}).max;
		}, "__max", true),

		min: iterate(function(iter, bind, that) {
			return this.each(function(val, i) {
				val = bind.__min(val, i, that);
				if (val <= (this.min || val)) this.min = val;
			}, {}).min;
		}, "__min", true),

		pluck: function(prop) {
			return this.map(function(val) {
				return val[prop];
			});
		},

		sortBy: iterate(function(iter, bind, that) {
			return this.map(function(val, i) {
				return { value: val, compare: this.__sortBy(val, i, that) };
			}, bind).sort(function(left, right) {
				var a = left.compare, b = right.compare;
				return a < b ? -1 : a > b ? 1 : 0;
			}).pluck('value');
		}, "__sortBy", true),

		swap: function(i, j) {
			var tmp = this[i];
			this[i] = this[j];
			this[j] = tmp;
		},

		toArray: function() {
			return this.map();
		}
	}
})();

Object.inject({
	each: Enumerable.each,
	clone: function() {
		return this.each(function(val, i) {
			this[i] = val;
		}, {});
	}
}, true);

function $A(list, start, end) {
	if (!list) return [];
	else if (list.toArray && !start && end == null) return list.toArray();
	var res = [];
	if (!start) start = 0;
	if (end == null) end = list.length;
	for (var i = start; i < end; i++)
		res[i - start] = list[i];
	return res;
}

Array.methods = {};

Array.methods.inject(Enumerable);

Array.methods.inject({
	_type: "array",

	indexOf: Array.prototype.indexOf || function(obj, i) {
		i = i || 0;
		if (i < 0) i = Math.max(0, this.length + i);
		for (i; i < this.length; i++) if (this[i] == obj) return i;
		return -1;
	},

	lastIndexOf: Array.prototype.lastIndexOf || function(obj, i) {
		i = i != null ? i : this.length - 1;
		if (i < 0) i = Math.max(0, this.length + i);
		for (i; i >= 0; i--) if (this[i] == obj) return i;
		return -1;
	},

	find: function(iter) {
		if (iter && !/function|regexp/.test($typeof(iter))) return this[this.indexOf(iter)];
		else return Enumerable.find.call(this, iter);
	},

	remove: function(obj) {
		var i = this.indexOf(obj);
		if (i != -1) return this.splice(i, 1);
	},

	toArray: function() {
		var res = this.concat([]);
		return res.length == this.length ? res : Enumerable.toArray.call(this);
	},

	clone: function() {
		return this.toArray();
	},

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

	first: function() {
		return this[0];
	},

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

	compact: function() {
		return this.filter(function(value) {
			return value != null;
		});
	},

	append: function(obj) {
		return $each(obj, function(val) {
			this.push(val);
		}, this);
	},

	include: function(obj) {
		return $each(obj, function(val) {
			if (this.indexOf(val) == -1) this.push(val);
		}, this);
	},

	flatten: function() {
		return this.each(function(val) {
			if (val != null && val.flatten) this.append(val.flatten());
			else this.push(val);
		}, []);
	},

	shuffle: function() {
		var res = this.clone();
		var i = this.length;
		while (i--) res.swap(i, $random(0, i));
		return res;
	}
});

Array.inject(Array.methods, true);

if (!Array.prototype.push) {
	Array.inject({
		push: function() {
			for (var i = 0; i < arguments.length; i++)
				this[this.length] = arguments[i];
			return this.length;
		},

		pop: function() {
			var i = this.length - 1;
			var old = this[i];
			delete this[i];
			this.length = i;
			return old;
		},

		shift: function() {
			var old = this[0];
			for (var i = 0;i < this.length - 1; i++)
				this[i]=this[i + 1];
			delete this[this.length - 1];
			this.length--;
			return old;
		},

		unshift: function() {
			for (var i = this.length - 1;i >= 0; i--)
				this[i + arguments.length] = this[i];
			for (i = 0; i < arguments.length; i++)
				this[i] = arguments[i];
			return this.length;
		},

		splice: function(start, del, items) {
			var res = [];
			for (var i = 0; i < del; i++)
				res[i] = this[i + start];
			for (i = start; i < this.length - del; i ++)
				this[i] = this[i + del];
			this.length -= del;
			if(arguments.length > 2) {
				var len = arguments.length - 2;
				for (i = this.length - 1; i >= start; i--)
					this[i + len] = this[i];
				for (i = 0; i < len; i++)
					this[i + start] = arguments[i + 2];
			}
			return res;
		},

		slice: function(start, end) {
			if (start < 0) start += this.length;
			if (end < 0) end += this.length;
			else if (end == null) end = this.length;
			var res = [];
			for (var i = start; i < end; i++)
				res[i - start] = this[i];
			return res;
		}
	}, true);
}

String.inject({
	test: function(exp, param) {
		return new RegExp(exp, param || '').test(this);
	},

	toArray: function() {
		return this ? this.split(/\s+/) : [];
	},

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

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

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

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

	capitalize: function() {
		return (" " + this.toLowerCase()).replace(/\s[a-z]/g, function(match) {
			return match.toUpperCase();
		}).substring(1);
	},

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

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

if ("aa".replace(/\w/g, function() { return arguments[1] }) !== "01") {
	String.inject({
		replace: function(search, replace) {
			if (typeof replace != 'function')
				return this.$super(search, replace);
			var parts = [], pos = 0, a;
			while (a = search.exec(this)) {
			    a.push(a.index, a.input);
			    parts.push(this.substring(pos, a.index), replace.apply(null, a));
			    pos = a.index + a[0].length;
			    if (!search.global) break;
			}
			if (!parts.length) return this;
			parts.push(this.substring(pos));
			return parts.join('');
	    }
	});
}

Number.inject({
	_type: 'number',

	times: function(iter) {
		for (var i = 0; i < this; i++) iter();
		return this;
	},

	toInt: String.prototype.toInt,

	toFloat: String.prototype.toFloat
});

RegExp.inject({
	_type: "regexp"
});

if (!/ /g.global) {
	RegExp.inject({
		exec: function(str) {
			if (this.global == undefined) {
				this.global = /\/[^\/]*g[^\/]*$/.test(this);
				this.multiline = /\/[^\/]*m[^\/]*$/.test(this);
				this.ignoreCase = /\/[^\/]*i[^\/]*$/.test(this);
			    this.lastIndex = 0;
			}
			var last = this.lastIndex, res = this.$super(str.substring(last));
			if (!res) {
			    this.lastIndex = 0;
			    return res;
			}
			this.lastIndex = RegExp.lastIndex + last;
			res.index = this.lastIndex - res[0].length;
			if (!last) RegExp._input = str;
			res.input = RegExp._input;
			return res;
		}
	});	
}

function $H(obj) {
	return $typeof(obj) == 'hash' ? obj : new Hash(obj);
}

Hash = Object.extend({
	_type: "hash",

	$constructor: function(obj) {
		if (obj) this.merge(obj);
	},

	clone: function() {
		return new Hash(this);
	},

	merge: function(obj) {

		return obj.each(function(val, i) {
			this[i] = val;
		}, this);
	},

	keys: function() {
		return this.map(function(val, i) {
			return i;
		});
	},

	values: Enumerable.toArray
}, true);

Hash.inject(Enumerable, true);

Browser = (function() {
	var ua = navigator.userAgent, av = navigator.appVersion;
	var ret = {
		WIN: /Win/.test(ua),
		MAC: /Mac/.test(ua),
		UNIX: /X11/.test(ua),
		KHTML: document.childNodes && !document.all && !navigator.taintEnabled,
		OPERA: !!window.opera,
		GECKO: !!document.getBoxObjectFor
	};
	ret.IE = !ret.OPERA && /MSIE/.test(ua);
	if (ret.IE) {
		ret.IE5 = /MSIE 5.0/.test(av);
		if (!ret.IE5) ret[window.XMLHttpRequest ? 'IE7' : 'IE6'] = true;
		ret.MACIE = ret.MAC;
	}
	return ret;
})();

window.inject = document.inject = Object.prototype.inject;

Elements = Object.extend({
	$constructor: function(els, convert) {
		return (convert && els ? els : $A(els)).inject(this.__proto__);
	},

	$static: {
		inject: function(src) {
			return this.$super((typeof src == 'function' ? src() : src).each(function(val, i) {
				this[i] = typeof val != 'function' ? val : function() {
					var args = arguments, values = [], els = true;
					this.each(function(obj, i) {
						var ret = val.apply(obj, args);
						values.push(ret);
						if ($typeof(ret) != 'element') els = false;
					});
					return els ? new Elements(values, true) : values;
				}
			}, {}));
		}
	}
});

function $(el, filter) {
	if (el) {
		if (el._extended || [window, document].contains(el))
			return el;
		if (typeof(el) == 'string')
			el = ($(filter) || document).getElementById(el);
		if ($typeof(el) == 'element' && el.tagName) {
			if (!el.__proto__) Garbage.collect(el);
			if (!el.inject) el.inject = Object.prototype.inject;
			if (!el._type) el.inject.call(el.__proto__ && el.__proto__ != Object.prototype ? el.__proto__ : el, Element.prototype);
			el.inject(Element.tags[el.getTag()]);
			el._extended = true;
			return el;
		}
	}
};

function $$(sel, filter) {
	switch ($typeof(sel)) {
		case 'element': return new Elements($(sel));
		case 'string': sel = ($(filter) || document).getElementsBySelector(sel);
	}
	return $each(sel || [], function(el) {
		if (el = $(el)) this.push(el);
	}, new Elements());
};

function $E(sel, filter) {
	return $$(sel, filter)[0];
};

Element = Object.extend(function() {
	return {
		$constructor: function(el) {
			if (typeof(el) == 'string') el = document.createElement(el);
			return $(el);
		},

		$static: {
			inject: function(src) {
				Elements.inject(src);
				return this.$super(src);
			}
		}
	}
});

Element.inject(function() {
	function element(el) {
		return el ? $(el) || new Element(el) : null;
	}

	function walk(that, name, start) {
		var el = that[start ? start : name];
		while (el && $typeof(el) != 'element') el = el[name];
		return $(el);
	}
	var cache = {};

	return {
		_type: 'element',

		getElementsBySelector: function(selector) {
			var res = new Elements();
			selector.split(',').each(function(sel) {
				var els = null;
				sel.clean().split(' ').each(function(sel, i) {
					var param;
					if (cache[sel]) param = cache[sel].param;
					else {
						param = sel.match(/^(\w*|\*)(#([\w_-]+)|\.([\*\w_-]+))?(\[(\w+)(([!*^$]?=)["']?([^"'\]]*)["']?)?\])?$/);
						param[1] = param[1] || '*';
						cache[sel] = { param: param };
					}
					if (!param) return;
					if (i == 0) { 
						if (param[3]) {
							var el = this.getElementById(param[3]);
							if (!el || param[1] != '*' && Element.prototype.getTag.call(el) != param[1]) throw $break;
							els = [el];
						} else {
							els = $A(this.getElementsByTagName(param[1]));
						}
					} else { 
						els = els.each(function(val) {
							this.append(val.getElementsByTagName(param[1]))
						}, []);
						if (param[3]) els = els.filter(function(el) {
							return (el.id == param[3]);
						});
					}
					if (param[4]) els = els.filter(function(el) {
						return Element.prototype.hasClass.call(el, param[4].replace('*', '.*'));
					});
					if (param[6]) els = els.filter(function(name, value, operator) {
						var att = this.getProperty(param[6]), value = param[9], operator = param[8];
						if (att) return !operator ||
					 		operator == '=' && att == value ||
							operator == '*=' && att.test(value) ||
							operator == '^=' && att.test('^' + value) ||
							operator == '$=' && att.test(value + '$') ||
							operator == '!=' && att != value;
					});
				}, this);
				if (els) res.include(els);
			}, this);
			return res;
		},

		getElementById: function(id) {
			var el = document.getElementById(id);
			if (el)
				for (var par = el.parentNode; el && par != this; par = parent.parentNode)
					if (!par) el = null;
			return el;
		},

		getElements: function() {
			return $$('*', this);
		},

		injectBefore: function(el) {
			el = element(el);
			el.parentNode.insertBefore(this, el);
			return this;
		},

		injectAfter: function(el) {
			el = element(el);
			var next = el.getNext();
			if (!next) el.parentNode.appendChild(this);
			else el.parentNode.insertBefore(this, next);
			return this;
		},

		injectInside: function(el) {
			element(el).appendChild(this);
			return this;
		},

		append: function(el) {
			this.appendChild(element(el));
			return this;
		},

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

		clone: function(contents) {
			return $(this.cloneNode(contents !== false));
		},

		replaceWith: function(el) {
			el = element(el);
			this.parentNode.replaceChild(el, this);
			return el;
		},

		appendText: function(text) {
			if (Browser.IE) {
				switch(this.getTag()) {
					case 'style': this.styleSheet.cssText = text; return this;
					case 'script': this.setProperty('text', text); return this;
				}
			}
			this.appendChild(document.createTextNode(text));
			return this;
		},

		hasClass: function(name) {
			return this.className.test('(^|\\s*)' + name + '(\\s*|$)');
		},

		modifyClass: function(name, add) {
			if (!this.hasClass(name) ^ !add) 
				this.className = (add ? this.className + ' ' + name : 
					this.className.replace(name, '')).clean();
			return this;
		},

		addClass: function(name) {
			return this.modifyClass(name, true);
		},

		removeClass: function(name) {
			return this.modifyClass(name, false);
		},

		toggleClass: function(name) {
			return this.modifyClass(name, !this.hasClass(name));
		},

		getPrevious: function() {
			return walk(this, 'previousSibling');
		},

		getNext: function() {
			return walk(this, 'nextSibling');
		},

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

		getLast: function() {
			return walk(this, 'nextSibling', 'lastChild');
		},

		getParent: function() {
			return $(this.parentNode);
		},

		getChildren: function() {
			return $$(this.childNodes);
		},

		hasChild: function(el) {
			return $A(this.getElementsByTagName('*')).contains(el);
		},

		getTag: function() {
			return this.tagName.toLowerCase();
		},

		getProperty: function(name) {
			return (name == 'class') ? this.className : this.getAttribute(name);
		},

		setProperty: function(prop, value) {
			switch (prop) {
				case 'class': this.className = value; break;
				case 'style': if (this.setStyle) this.setStyle(value); break;
				default: this.setAttribute(prop, value);
			}
			return this;
		},

		setProperties: function(src) {
			return src.each(function(val, name) {
				this.setAttribute(name, val);
			}, this);
		},

		getHtml: function(html) {
			return this.innerHTML;
		},

		setHtml: function(html) {
			this.innerHTML = $A(arguments).join('');
			return this;
		}
	}
});

Element.tags = (function() {
	var formElement = {
		enable: function(enable) {
			var disable = !enable && enable !== undefined;
			if (disable) this.blur();
			this.disabled = disable;
		}
	};

	return {
		form: {
			getElements: function() {
				return $$('input, select, textarea', this);
			},

			blur: function() {
				this.getElements().each(function(el) {
					el.blur();
				});
			},
			enable: function(enable) {
				this.getElements().each(function(el) {
					el.enable(enable);
				});
			}
		},

		input: {
			getValue: function() {
				if (this.checked && /checkbox|radio/.test(this.type) ||
					/hidden|text|password/.test(this.type))
					return this.value;
			}			
		}.inject(formElement),

		select: {
			getValue: function() {
				if (this.selectedIndex != -1)
					return this.options[this.selectedIndex].value;
			}
		}.inject(formElement),

		textarea: {
			getValue: function() {
				return this.value;
			}
		}.inject(formElement)
	};
})();

document.getElementsBySelector = Element.prototype.getElementsBySelector;

Element.inject(function() {
	var methods = {
		getStyle: function(name, dontCompute) {
			name = name.toCamelCase();
			var style = this.style[name];
			if (!style) switch (name) {
				case 'opacity':
					return this.opacity || this.opacity == 0 ? this.opacity : this.getVisible() ? 1 : 0;
				case 'margin':
				case 'padding':
					var res = [];
					['top', 'right', 'bottom', 'left'].each(function(prop) {
						res.push(this.getStyle(property + '-' + prop, dontCompute) || '0');
					}, this);
					return res.every(function(val) {
						return val == res[0];
					}) ? res[0] : res;
			}
			if (!dontCompute) {
				if (!style) style = document.defaultView && document.defaultView.getComputedStyle(this, '').getPropertyValue(name.hyphenate())
					|| this.currentStyle && this.currentStyle[name];
				else if (style == 'auto' && /width|height/.test(name))
					return this['offset' + name.capitalize()] + 'px';
			}
			return style;
		},

		setStyle: function(name, value) {
			if (arguments.length > 2) value = $A(arguments, 1);
			switch (name) {
				case 'opacity':
					if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
					if (Browser.IE) this.style.filter = value > 0 && value < 1 ? 'alpha(opacity=' + value * 100 + ')' : '';
					this.style.opacity = this.opacity = value;
					this.setVisible(value);
					break;
				case 'clip':
					this.style.clip = $typeof(value) == 'array' ? 'rect(' + value.join('px ') + 'px)' : value;
					break;
				default:
					this.style[name.toCamelCase()] = value; 
			}
			return this;
		},

		getStyles: function() {
			return arguments.each(function(val) {
				this[val] = that.getStyle(val);
			}, {});
		},

		setStyles: function(styles) {
			switch ($typeof(styles)) {
				case 'object':
					styles.each(function(style, name) {
						this.setStyle(name, style);
					}, this);
					break;
				case 'string':
					this.cssText = styles;
			}
			return this;
		},

		getVisible: function() {
			var vis = this.getStyle('visibility');
			return vis == 'visible' || vis == 'inherit' || vis == 'inherited' || vis == '';
		},

		setVisible: function(visible) {
			return this.setStyle('visibility', visible ? 'inherit' : 'hidden');
		},

		getZIndex: function() {
			return this.getStyle('zIndex').toInt() || 0;
		},

		setZIndex: function(value) {
			this.style.zIndex = value;
		}
	};

	$A('opacity color background border margin padding clip display').each(function(name) {
		var part = name.capitalize();
		methods['get' + part] = function() {
			return this.getStyle(name);
		};
		methods['set' + part] = function(value) {
			return this.setStyle(name, arguments.length > 1 ? $A(arguments) : value);
		};
	});

	$A('left top width height').each(function(name) {
		var part = name.capitalize();
		methods['get' + part] = function() {
			return this['offset' + part];
		};
		methods['set' + part] = function(value) {
			this.style[name] = Math.round(value) + 'px';
		};
	});

	return methods;
});

var report = false;

Element.inject(function() {
	function cumulate(name, parent, iter, fix) {
		fix = fix && Browser.MACIE;
		var left = name + 'Left', top = name + 'Top';
		return function() {
			var el, next = this, x = 0, y = 0;
			do {
				el = next;
				x += el[left] || 0;
				y += el[top] || 0;
			} while((next = el[parent]) && (!iter || iter(el)))
			if (fix) ['margin', 'padding'].each(function(val) {
				x += this.getStyle(val + '-left').toInt() || 0;
				y += this.getStyle(val + '-top').toInt() || 0;
			}, $(el));
			return { x: x, y: y };
		}
	}

	return {
		getSize: function() {
			return { width: this.offsetWidth, height: this.offsetHeight };
		},

		getOffset: cumulate('offset', 'offsetParent', Browser.KHTML ? function(el) {
			return el.offsetParent != document.body ||
				$(el).getStyle('position') != 'absolute';
		} : null, true),

		getRelativeOffset: cumulate('offset', 'offsetParent', function(el) {
	        return Element.prototype.getTag.call(el) != 'body' &&
				!/relative|absolute/.test(Element.prototype.getStyle.call(el, 'position'))
		}),

		getScrollOffset: cumulate('scroll', 'parentNode'),

		getScrollPos: function() {
			return { x: this.scrollLeft, y: this.scrollTop };
		},

		getScrollSize: function() {
			return { width: this.scrollWidth, height: this.scrollHeight };
		},

		getBounds: function() {
			var off = this.getOffset();
			return {
				width: this.offsetWidth,
				height: this.offsetHeight,
				left: off.x,
				top: off.y,
				right: off.x + this.offsetWidth,
				bottom: off.y + this.offsetHeight
			};
		},

		setBounds: function(bounds) {
			this.setStyles(bounds.each(function(val, i) {
				if (val || val == 0) this[i] = val + 'px';
			}, { position: 'absolute' }));
		},

		scrollTo: function(x, y) {
			this.scrollLeft = x;
			this.scrollTop = y;
		}
	};
});

Element.eventMethods = {
	addEvent: function(type, func) {
		this.events = this.events || {};
		var fns = this.events[type] = this.events[type] || [];
		if (!fns.contains(func)) {
			fns.push(func);
			var that = this, fn = func, hasEvent = fn.parameters().length > 0;
			if (hasEvent) fn = function(event) { 
				return func.call(that, new Event(event));
			};
			if (this.addEventListener) {
				this.addEventListener(type == 'mousewheel' && Browser.GECKO ?
					'DOMMouseScroll' : type, fn, false);
			} else if (this.attachEvent) {
				if (!hasEvent) fn = func.bind(this);
				func.bound = fn;
				this.attachEvent('on' + type, fn);
			} else {
				this['on' + type] = function(event) {
					event = new Event(event);
					fns.each(function(fn) {
						fn.call(that, event);
						if (event.event.cancelBubble) throw $break;
					});
					return event.event.returnValue;
				};
			}
		}
		return this;
	},

	addEvents: function(src) {
		return (src || []).each(function(fn, type) {
			this.addEvent(type, fn);
		}, this);
	},

	removeEvent: function(type, func) {
		var fns = (this.events || {})[type];
		if (fns) {
			fns = $A(fns);
			if (fns.remove(func)) {
				if (this.removeEventListener) {
					this.removeEventListener(type == 'mousewheel' && window.gecko ?
						'DOMMouseScroll' : type, func, false);
				} else if (this.detachEvent) {
					this.detachEvent('on' + type, func.bound);
				} else if (!fns.length) {
					this['on' + type] = null;
				}
			}
		}
		return this;
	},

	removeEvents: function(type) {
		if (this.events) {
			if (type) {
				(this.events[type] || []).each(function(fn) {
					this.removeEvent(type, fn);
				}, this);
				delete this.events[type];
			} else {
				this.events.each(function(ev, type) {
					this.removeEvents(type);
				}, this);
				this.events = null;
			}
		}
		return this;
	},

	fireEvent: function(type) {
		var fns = (this.events || {})[type];
		if (fns) {
			var args = $A(arguments, 1);
			fns.each(function(fn) {
				fn.apply(this, args);
			}, this);
		}
	},

	dispose: function() {
		this.removeEvents();
	}
}

window.inject(Element.eventMethods);
document.inject(Element.eventMethods);
Element.inject(Element.eventMethods);

delete Event;
Event = Object.extend(function() {
	var keys = {
		 '8': 'backspace',
		'13': 'enter',
		'27': 'esc',
		'32': 'space',
		'37': 'left',
		'38': 'up',
		'39': 'right',
		'40': 'down',
		'46': 'delete'
	};

	return {
		$constructor: function(event) {
			this.event = event = event || window.event;
			this.type = event.type;
			this.target = event.target || event.srcElement;
			if (this.target.nodeType == 3)
				this.target = this.target.parentNode; 
			this.shift = event.shiftKey;
			this.control = event.ctrlKey;
			this.alt = event.altKey;
			this.meta = event.metaKey;
			if (/mousewheel|DOMMouseScroll/.test(this.type)) {
				this.wheel = event.wheelDelta ?
					event.wheelDelta / (window.opera ? -120 : 120) : 
					- (event.detail || 0) / 3;
			} else if (/key/.test(this.type)) {
				this.code = event.which || event.keyCode;
				this.key = keys[this.code] || String.fromCharCode(this.code).toLowerCase();
			} else if (/mouse/.test(this.type) || this.type == 'click') {
				this.page = {
					x: event.pageX || event.clientX + document.documentElement.scrollLeft,
					y: event.pageY || event.clientY + document.documentElement.scrollTop
				};
				this.client = {
					x: event.pageX ? event.pageX - window.pageXOffset : event.clientX,
					y: event.pageY ? event.pageY - window.pageYOffset : event.clientY
				};
				this.rightClick = event.which == 3 || event.button == 2;
				if (/^mouse(over|out)$/.test(this.type))
					this.relatedTarget = event.relatedTarget || this.type == 'mouseout' ? event.toElement : event.fromElement;
			}
		},

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

		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;
		}
	};
});

/*@cc_on
try { document.execCommand('BackgroundImageCache', false, true); }
catch (e) {}
@*/

window.inject({
	open: function(url, title, params) {
		var focus;
		if (params && typeof params != 'string') {
			if (params.confirm && !confirm(params.confirm))
				return null;
			(['toolbar', 'menubar', 'location', 'status', 'resizable', 
				'scrollbars']).each(function(d) {
				if (!params[d]) params[d] = 0;
			});
			if (params.width && params.height) {
				if (params.left == null) params.left = Math.round(
					Math.max(0, (screen.width - params.width) / 2));
				if (params.top == null) params.top = Math.round(
					Math.max(0, (screen.height - params.height) / 2 - 40));
			}
			focus = params.focus;
			params = params.each(function(p, n) {
				if (!/focus|confirm/.test(n))
					this.push(n + '=' + p);
			}, []).join(',');
		}
		var win = this.$super(url, title.replace(/\s+|\.+|-+/gi, ''), params);
		if (win && focus) win.focus();
		return win;
	},

	addEvent: function(type, fn) {
		if (type == 'domready') {
			if (this.loaded) fn();
			else if (!this.events || !this.events.domready) {
				var domReady = function() {
					if (this.loaded) return;
					this.loaded = true;
					if (this.timer)  this.timer = this.timer.clear();
					Element.prototype.fireEvent.call(this, 'domready');
					this.events.domready = null;
				}.bind(this);
				if (document.readyState && (Browser.KHTML || Browser.MACIE)) { 
					this.timer = (function() {
						window.status = document.readyState;
						if (['loaded','complete'].contains(document.readyState)) domReady();
					}).periodic(50);
				} else if (document.readyState && Browser.IE) { 
					document.write('<script id=ie_ready defer src=javascript:void(0)><\/script>');
					$('ie_ready').onreadystatechange = function() {
						if (this.readyState == 'complete') domReady();
					};
				} else { 
					this.addEvent('load', domReady);
					document.addEvent('DOMContentLoaded', domReady);
				}
			}
		}
		return this.$super(type, fn);
	}
});

Cookie = {
	set: function(name, value, expires, path) {
		document.cookie = name + '=' + escape(value) + (expires ? ';expires=' +
			expires.toGMTString() : '') + ';path=' + (path || '/');
	},
	get: function(name) {
		var res = document.cookie.match('(?:^|;)\\s*' + name + '=([^;]*)');
		if (res) return unescape(res[1]);
	},

	remove: function(name) {
		this.set(key, '', -1);
	}
};

Asset = {

	javascript: function(src, props) {
		return Asset.create('script', {
			type: 'text/javascript', src: src
		}, props, true);
	},

	css: function(src, props) {
		return Asset.create('link', {
			rel: 'stylesheet', media: 'screen', type: 'text/css', href: src
		}, props, true);
	},

	image: function(src, props) {
		props = props || {};
		var img = new Image();
		var onLoad = props.onLoad, done = false;
		img.onload = props.onLoad = function() {
			if (onLoad && !done) {
				done = true;
				return onLoad();
			}
		}
		img.src = props.src = src;
		return Asset.create('img', props);
	},

 	images: function(srcs, opts) {
		opts = opts || {};
		if ($typeof(srcs) != 'array') srcs = [srcs];
		var imgs = [], count = 0;
		return srcs.each(function(src) {
			this.push(Asset.image(src, {
				onLoad: function() {
					if (opts.onProgress) opts.onProgress(src);
					if (++count == srcs.length && opts.onComplete) opts.onComplete();
				}
			}));
		}, []);
	},

	create: function(type, defs, props, inject) {
		var el = new Element(type).setProperties($H(defs).merge(props));
		return inject ? el.injectInside($E('head')) : el;
	}
};

Garbage = (function() {
	var objects = [];

	window.addEvent('unload', function() {
		objects.each(function(obj) {
			if (obj.dispose) obj.dispose();
			if ($typeof(obj) == 'element') {
				for (var n in Element.prototype)
					obj[n] = null;
			} else { 
				for (var n in obj)
					delete obj[n];
			}
		});
	});

	return {
		collect: function() {
			objects.append(arguments);
		}
	}
})();

var Chain = {
	chain: function(fn) {
		(this.chains = this.chains || []).push(fn);
		return this;
	},

	callChain: function() {
		if (this.chains && this.chains.length)
			this.chains.shift().delay(1, this);
	},

	clearChain: function() {
		this.chains = [];
	}
};

var Callback = {
	addEvent: function(type, fn) {
		var ref = this.events = this.events || {};
		ref = ref[type] = ref[type] || [];
		if (!ref.contains(fn)) ref.push(fn);
		return this;
	},

	fireEvent: function(type) {
		return (this.events && this.events[type] || []).each(function(fn) {
			fn.apply(this, $A(arguments, 1));
		}, this);
	},

	removeEvent: function(type, fn) {
		if (this.events && this.events[type]) this.events[type].remove(fn);
		return this;
	},

	setOptions: function(opts) {
		return (this.options = $H(this.options)).merge(opts).each(function(val, i) {
			if (typeof val == 'function' && (i = i.match(/^on([A-Z]\w*)/)))
				this.addEvent(i[1].toLowerCase(), val);
		}, this);
	}
};

Fx = {};

Fx.Transitions = {
	linear: function(t, b, c, d) {
		return c * t / d + b;
	},

	sineInOut: function(t, b, c, d) {
		return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
	}
};

Fx.Base = Object.extend({
	options: {
		transition: Fx.Transitions.sineInOut,
		duration: 500,
		unit: 'px',
		wait: true,
		fps: 50
	},

	$constructor: function(opts) {
		this.element = this.element || null;
		this.setOptions(opts);
		if (this.options.$constructor)
			this.options.$constructor.call(this);
	},

	step: function() {
		var time = new Date().getTime();
		if (time < this.time + this.options.duration) {
			this.delta = time - this.time;
			this.update(this.get());
		} else {
			this.stop(true);
			this.update(this.to);
			this.fireEvent('onComplete', this.element, 10);
			this.callChain();
		}
	},

	set: function(to) {
		this.update(to);
		return this;
	},

	get: function() {
		return this.compute(this.from, this.to);
	},

	compute: function(from, to) {
		return this.options.transition(this.delta, from, (to - from), this.options.duration);
	},

	start: function(from, to) {
		if (!this.options.wait) this.stop();
		else if (this.timer) return this;
		this.from = from;
		this.to = to;
		this.time = new Date().getTime();
		this.timer = this.step.periodic(Math.round(1000 / this.options.fps), this);
		this.fireEvent('start', this.element);
		this.step();
		return this;
	},

	stop: function(end) {
		if (this.timer) {
			this.timer = this.timer.clear();
			if (!end) this.fireEvent('cancel', this.element);
		}
		return this;
	}
});

Fx.Base.inject(Chain);
Fx.Base.inject(Callback);

Fx.CSS = (function() {
	var single = {
		parse: function(value) {
			return value.toFloat();
		},

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

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

	var multi = {
		parse: function(value) {
			return value.push ? value : value.split(' ').map(function(val) {
				return val.toFloat();
			});
		},

		compute: function(from, to, fx) {
			return from.each(function(val, i) {
				this[i] = fx.compute(val, to[i]);
			}, []);
		},

		get: function(value, unit) {
			return value.join(unit + ' ') + unit;
		}
	};

	var color = {
		parse: function(value) {
			return value.push ? value : value.hexToRgb(true);
		},

		compute: function(from, to, fx) {
			return from.each(function(val, i) {
				this[i] = Math.round(fx.compute(val, to[i]));
			}, []);
		},

		get: function(value) {
			return 'rgb(' + value.join(',') + ')';
		}
	};

	return {
		select: function(property, to) {
			if (/color/i.test(property)) return color;
			if (/ /.test(to)) return multi;
			return single;
		},

		parse: function(el, property, fromTo) {
			if (!fromTo.push) fromTo = [fromTo];
			var from = fromTo[0], to = fromTo[1];
			if (!to && to != 0) {
				to = from;
				from = el.getStyle(property);
			}
			var css = this.select(property, to);
			return { from: css.parse(from), to: css.parse(to), css: css };
		}
	}
})();

Fx.Style = Fx.Base.extend({
	$constructor: function(el, prop, opts) {
		this.element = $(el);
		this.property = prop;
		this.$super(opts);
	},

	hide: function() {
		return this.set(0);
	},

	get: function() {
		return this.css.compute(this.from, this.to, this);
	},

	set: function(to) {
		this.css = Fx.CSS.select(this.property, to);
		return this.$super(this.css.parse(to));
	},

	start: function(from, to) {
		if (this.timer && this.options.wait) return this;
		var parsed = Fx.CSS.parse(this.element, this.property, [from, to]);
		this.css = parsed.css;
		return this.$super(parsed.from, parsed.to);
	},

	update: function(val) {
		this.element.setStyle(this.property, this.css.get(val, this.options.unit));
	}
});

Element.inject({
	effect: function(prop, opts) {
		return new Fx.Style(this, prop, opts);
	}
});

Fx.Styles = Fx.Base.extend({
	$constructor: function(el, options) {
		this.element = $(el);
		this.$super(options);
	},

	get: function() {
		var that = this;
		return this.from.each(function(val, i) {
			this[i] = that.css[i].compute(that.from[i], that.to[i], that);
		}, {});
	},

	set: function(to) {
		var parsed = {};
		this.css = {};
		to.each(function(val, i) {
			this.css[i] = Fx.CSS.select(i, val);
			parsed[i] = this.css[i].parse(val);
		}, this);
		return this.$super(parsed);
	},

	start: function(obj) {
		if (this.timer && this.options.wait) return this;
		this.now = {};
		this.css = {};
		var from = {}, to = {};
		obj.each(function(val, i) {
			var parsed = Fx.CSS.parse(this.element, i, val);
			from[i] = parsed.from;
			to[i] = parsed.to;
			this.css[i] = parsed.css;
		}, this);
		return this.$super(from, to);
	},

	update: function(val) {
		val.each(function(val, i) {
			this.element.setStyle(i, this.css[i].get(val, this.options.unit));
		}, this);
	}

});

Element.inject({
	effects: function(opts) {
		return new Fx.Styles(this, opts);
	}
});

