/* lutil-base.js: */

function lclass() {
	var name, base, inherit = [], members = null, staticMembers = null, c, i, a;
	for (i = 0; i < arguments.length; i++) {
		a = arguments[i];
		if (typeof a == "string") {
			name = a;
		} else if (typeof a == "function") {
			base = a;
		} else if (a instanceof Array) {
			inherit = inherit.concat(a);
		} else if (typeof a == "object") {
			if (members == null) {
				members = a;
			} else if (staticMembers == null) {
				staticMembers = a;
			}
		}
	}

	if (!base) {
		base = {__base: null, base: null, prototype: {}};
	}
	
	c = members.cons || function() { };

	copyFields(c.prototype, base.prototype);
	for (i = 0; i < inherit.length; i++) {
		copyFields(c.prototype, inherit[i].prototype);
	}
	copyFields(c.prototype, members);
	copyFields(c, staticMembers);
	
	c.base = base;
	
	c.prototype._instanceOf = lclass._instanceOf;
	c.prototype.__class = c; // Damn you KHTML
	
	if (name) {
		window[name] = c;
	}

	if (c.cons) {
		c.cons.apply(c);
	}

	return c;
}

lclass._instanceOf = function(c) {
	var ci;
	for (ci = this.__class; ci.base; ci = ci.base) {
		if (c == ci) {
			return true;
		}
	}
	return false;
};

function copyFields(d, s) {
	var i;
	for (i in s) {
		d[i] = s[i];
	}
}

function mergeFields(d, s) {
	var i;
	for (i in s) {
//		if (!d[i]) {
		if (typeof d[i] == "undefined") {
			d[i] = s[i];
		}
	}
}

function $P(p, d) {
	var i;

	if (typeof p != "object") {
		return d;
	}

	for (i in p) {
		if (typeof d[i] != "undefined") {
			d[i] = p[i];
		}
	}

	return d;
}

function $PM(obj, p, d) {
	copyFields(obj, $P(p, d));
}

mergeFields(Array.prototype, {
	removeAt: function(index) {
		this.splice(index, 1);
	},

	insertAt: function(index, elem) {
		this.splice(index, 0, elem);
	},

	contains: function(elem) {
		var i;
		for (i = 0; i < this.length; i++) {
			if (this[i] == elem) {
				return true;
			}
		}
		return false;
	},

	indexOf: function(elem) {
		var i;
		for (i = 0; i < this.length; i++) {
			if (this[i] == elem) {
				return i;
			}
		}
		return false;
	},

	remove: function(elem) {
		var i;
		for (i = 0; i < this.length; i++) {
			if ((this[i].equals && this[i].equals(elem)) || this[i] == elem) {
				this.removeAt(i);
				i--;
			}
		}
	},

	filter: function(f) {
		var i, r = [];
		for (i = 0; i < this.length; i++) {
			if (f(this[i])) {
				r.push(this[i]);
			}
		}
		return r;
	},

	map: function(f) {
		var i, r = [];
		for (i = 0; i < this.length; i++) {
			r[i] = f(this[i]);
		}
		return r;
	},

	clone: function() {
		var i, r = [];
		for (i = 0; i < this.length; i++) {
			r[i] = this[i];
		}
		return r;
	},

	append: function(els) {
		var i;
		for (i = 0; i < els.length; i++) {
			this.push(els[i]);
		}
	}
});

mergeFields(Array, {
	fromValue: function(v, n) {
		if (typeof n == "undefined") {
			n = 1;
		}
		var i, r = new Array(n);
		for (i = 0; i < n; i++) {
			r[i] = v;
		}
		return r;
	},

	fromCollection: function(c) {
		var i, r = new Array(c.length);
		for (i = 0; i < c.length; i++) {
			r[i] = c[i];
		}
		return r;
	}
});

mergeFields(String.prototype, {
	lastIndexOf: function(c) {
		var i;
		for (i = this.length - 1; i >= 0; i--) {
			if (this.charAt(i) == c) {
				return i;
			}
		}
		return -1;
	},

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

	repeat: function(n) {
		var r = "", i;
		for (i = 0; i < n; i++) {
			r += this;
		}
		return r;
	},

	startsWith: function(s) {
		return this.substring(0, s.length) == s;
	},

	beginsWith: function(s) {
		return this.startsWith(s);
	},

	endsWith: function(s) {
		return (this.length >= s.length) && (this.substring(this.length - s.length) == s);
	},

	hexDec: function() {
		var r = 0, f = 1, c = "0123456789ABCDEF", i, d;
		for (i = this.length - 1; i >= 0; i--) {
			d = c.indexOf(this.charAt(i).toUpperCase());
			if (d == -1) {
				return null;
			}
			r += d * f;
			f *= 16;
		}
		return r;
	},

	classToCamelCase: function() {
		return this.replace(/-([a-z])/g, function(dummy, c) { return c.toUpperCase(); });
	},

	camelCaseToClass: function() {
		return this.replace(/([a-z])([A-Z])/g, function(dummy, c, c2) { return c + "-" + c2.toLowerCase(); });
	},

	format: function() {
		var parms = [this];
		var i;

		for (i = 0; i < arguments.length; i++) {
			parms[i + 1] = arguments[i];
		}
		
		return String.format.apply(window, parms);
	}
});

mergeFields(String, {
	format: function() {
		fstr = arguments[0];
		parms = arguments;
	//	parms = Array.fromCollection(arguments);
	//	var i;
	//	var parms = [];
	//	for (i = 1; i < arguments.length; i++) {
	//		parms[i - 1] = "" + arguments[i];
	//	}
		
		return fstr.replace(/\{(\d+?)\}/g, function(str, index) {
			index = parseInt(index);
			
			if (index < 0) {
				return "(String.format: invalid index)";
			} else if (index >= parms.length - 1) {
				return "(String.format: too few arguments)";
			} else {
				return parms[parseInt(index) + 1];
			}
		});
	}
});

mergeFields(Number.prototype, {
	toHex: function(pad) {
		var r = "", c = "0123456789ABCDEF", n = this;
		while (n > 0) {
			r = c.charAt(n & 0xF) + r;
			n >>= 4;
		}
		if (r == "") {
			r = "0";
		}
		if (pad && r.length < pad) {
			r = "0".repeat(pad - r.length) + r;
		}
		return r;
	}
});

mergeFields(RegExp.prototype, {
	scan: function(string) {
		var match, r = [];
		while (match = this.exec(string)) {
			r.push(match);
		}
		return r;
	}
});

mergeFields(Math, {
	sgn: function(a) {
		if (a > 0) {
			return 1;
		} else if (a < 0) {
			return -1;
		} else {
			return 0;
		}
	}
});

lclass("LDelegate", {
	cons: function(v, v2) {
		if (v._instanceOf && v._instanceOf(LDelegate)) {
			this.obj = v.obj;
			this.func = v.func;
		} else if (v.length && v.length == 2) {
			this.obj = v[0] || window;
			this.func = v[1];
		} else if (typeof v == "function") {
			this.obj = window;
			this.func = v;
		} else if (typeof v == "object" && typeof v2 == "function") {
			this.obj = v;
			this.func = v2;
		}
	},

	apply: function(a) {
		return this.func.apply(this.obj, a);
	},

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

	equals: function(v) {
		return (this.obj == v.obj && this.func == v.func);
	}
});

lclass("LBrowser", {}, {
	cons: function() {
		LBrowser.isIE = (navigator.userAgent.indexOf("MSIE") != -1);
	}
});

lclass("LRect", {
	cons: function(x1, y1, x2, y2) {
		this.x1 = x1;
		this.y1 = y1;
		this.x2 = x2;
		this.y2 = y2;
	},

	contains: function(p) {
		return (p.x >= this.x1 && p.x <= this.x2 && p.y >= this.y1 && p.y <= this.y2);
	}
});

lclass("Serializer", {}, {
	serialize: function(o) {
		var i, r;
		if (typeof o == "string") {
			return "\"" + this.escape(o) + "\"";
		} else if (typeof o == "number") {
			return o;
		} else if (typeof o == "boolean") {
			return o ? "true" : "false";
		} else if (o === null) {
			return "null";
		} else if (o instanceof Array) {
			r = "[";
			for (i = 0; i < o.length; i++) {
				if (typeof(o[i]) == "function") {
					continue;
				}
				if (r != "[") {
					r += ",";
				}
				r += Serializer.serialize(o[i]);
			}
			return r + "]";
		} else if (typeof o == "object") {
			r = "{";
			for (i in o) {
				if (typeof(o[i]) == "function") {
					continue;
				}
				if (r != "{") {
					r += ",";
				}
				r += i + ":" + Serializer.serialize(o[i]);
			}
			return r + "}";
		}
	},

	unserialize: function(d) {
		this.data = d;
		this.p = 0;
		return this.doUnserialize();
	},

	doUnserialize: function() {
		var i, k, r;
		if (this.data.charAt(this.p) >= "0" && this.data.charAt(this.p) <= "9") {
			r = "";
			for (; this.data.charAt(this.p) >= "0" && this.data.charAt(this.p) <= "9"; this.p++) {
				r += this.data.charAt(this.p);
			}
			return parseInt(r);
		} else if (this.data.charAt(this.p) == "\"") {
			r = "";
			var esc = false;
			for (this.p++; /*this.data.charAt(this.p - 1) == "\\" || this.data.charAt(this.p) != "\""*/true ; this.p++) {
				if (esc) {
					switch (this.data.charAt(this.p)) {
						case "n":
							r += "\n";
							break;
						case "r":
							r += "\r";
							break;
						case "\\":
							r += "\\";
							break;
					}
					esc = false;
					continue;
				}
				if (this.data.charAt(this.p) == "\\") {
					esc = true;
					continue;
				} else if (this.data.charAt(this.p) == "\"") {
					break;
				}
				r += this.data.charAt(this.p);
			}
			this.p++;
			return this.unescape(r);
		} else if (this.data.substring(this.p, this.p + 4) == "true") {
			this.p += 4;
			return true;
		} else if (this.data.substring(this.p, this.p + 5) == "false") {
			this.p += 5;
			return false;
		} else if (this.data.substring(this.p, this.p + 4) == "null") {
			this.p += 4;
			return null;
		} else if (this.data.charAt(this.p) == "{") {
			r = {};
			while (this.data.charAt(this.p) != "}") {
				k = "";
				for (this.p++; this.data.charAt(this.p) != ":"; this.p++) {
					k += this.data.charAt(this.p);
				}
				this.p++;
				r[k] = this.doUnserialize();
			}
			this.p++;
			return r;
		} else if (this.data.charAt(this.p) == "[") {
			r = [];
			while (this.data.charAt(this.p) != "]") {
				this.p++;
				r.push(this.doUnserialize());
			}
			this.p++;
			return r;
		}
	},

	escape: function(s) {
		return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
	},

	unescape: function(s) {
		return s.replace(/\\\\/g, "\\").replace(/\\"/g, "\"").replace(/\\n/g, "\n").replace(/\\r/g, "\r");
	}
});

/* lutil-events.js: */

lclass("LEvent", {
	cons: function(e) {
		this.e = e;
	},

	thisElement: function() {
		return this._thisElement;
	},

	target: function() {
		return this.e.target;
	},

	type: function() {
		return this.e.type;
	},

	keyCode: function() {
		return this.e.keyCode || this.e.which;
	},

	wheelDelta: function() {
		// Adapted from http://adomas.org/javascript-mouse-wheel/
		if (this.e.wheelDelta) {
			if (window.opera) {
				return this.e.wheelDelta / 120;
			} else {
				return this.e.wheelDelta / -120;
			}
		} else if (this.e.detail) {
			return this.e.detail / 3;
		}
	},

	preventDefault: function() {
		if (this.e.preventDefault) {
			this.e.preventDefault();
		}
		this.e.returnValue = false;
	},

	getAttribute: function(a) {
		return this.e[a];
	},

	stop: function() {
		if (this.e.stopPropagation) {
			this.e.stopPropagation();
		}
		this.e.cancelBubble = true;
	},

	preventDefault: function() {
		if (this.e.preventDefault) {
			this.e.preventDefault();
		}
		this.e.returnValue = false;
	},

	mousePosition: function() {
		if (this.e.pageX) {
			return {x: this.e.pageX, y: this.e.pageY};
		} else if (this.e.clientX) {
			return {x: document.body.scrollLeft + this.e.clientX, y: document.body.scrollTop + this.e.clientY};
		} else {
			return {x: 0, y: 0};
		}
	},

	clientMousePosition: function() {
		if (this.e.clientX) {
			return {x: this.e.clientX, y: this.e.clientY};
		} else {
			return {x: 0, y: 0};
		}
	}
}, {
	KEY_ENTER: 13,
	KEY_LEFT: 37,
	KEY_UP: 38,
	KEY_RIGHT: 39,
	KEY_DOWN: 40,
	KEY_SPACE: 32
});

lclass("LEventHandling", {
	attachEvent: function(e, d1, d2) {
		var o = this.n || this, newtype = false;
		if (!this.eventHandlers) {
			this.eventHandlers = {};
		}
		if (!this.eventHandlers[e]) {
			this.eventHandlers[e] = [];
			newtype = true;
		}
		this.eventHandlers[e].push(new LDelegate(d1, d2));
		if (newtype && !o._instanceOf) {
			if (o.addEventListener) {
				o.addEventListener(e, LEventHandling.__eventHandler, false);
			/*} else if (o.attachEvent) {
				o.attachEvent("on" + e, LEventHandling.__eventHandler);
				Debug.writeln("Used IE way");
			*/} else {
				if (!o["on" + e]) {
					o["on" + e] = LEventHandling.__eventHandler;
				}
			}
		}
	},

	detachEvent: function(e, d1, d2) {
		var h;
		var o = this.n || this;
		if (this.eventHandlers && (h = this.eventHandlers[e])) {
			h.remove(new LDelegate(d1, d2));
			if (h.length == 0) {
				if (!o._instanceOf) {
					if (o.removeEventListener) {
						o.removeEventListener(e, LEventHandling.__eventHandler, false);
					/*} else if (o.detachEvent) {
						o.detachEvent("on" + e, LEventHandling.__eventHandler);
					*/} else {
						o["on" + e] = null;
					}
				}
				this.eventHandlers[e] = null;
			}
		}
	},

	fireEvent: function(e, data) {
		var o = this.n || this;
		var data = data || {};
		try {
			data.type = e;
		} catch (e) {
			// ignore it, type readonly
		}
		if (o.eventHandlers && o.eventHandlers[e]) {
			return LEventHandling.__eventHandler.apply(this, [data]);
		} else {
			return true;
		}
/*		if (o["on" + e]) {
			return o["on" + e](data);
		} else {
			return true;
		}*/
	}
}, {
	__eventHandler: function(e) {
		var e = e || window.event;
		e = new LEvent(e);
		e._thisElement = this.__lutilObj || this;
		var h;
		if (!e._thisElement.eventHandlers) {
			return true;
		}
		h = e._thisElement.eventHandlers[e.type()];
		var i, rv = true, r;
		if (h) {
			for (i = 0; i < h.length; i++) {
				r = h[i].call(e);
				if (typeof r != "undefined") {
					rv = r;
				}
			}
		}

		if (typeof rv != "undefined") {
			e.returnValue = rv;
			if (!rv) {
				e.preventDefault();
			}
			return rv;
		}
	}
});

lclass("LWindow", [LEventHandling], {
	cons: function() {
		window.__lutilObj = this;

		window.onscroll = function(e) {
			window.__lutilObj.fireEvent("scroll", e);
		};
		window.onresize = function(e) {
			window.__lutilObj.fireEvent("resize", e);
		};
		window.onload = function(e) {
			window.__lutilObj.fireEvent("load", e);
		};
		window.onunload = function(e) {
			window.__lutilObj.fireEvent("unload", e);
		};
	}
});

lwindow = new LWindow();

lclass("LTimer", [LEventHandling], {
	cons: function(t, d1, d2) {
		this._time = t;
		if (typeof d1 != "undefined") {
			this.attachEvent("expire", d1, d2);
		}
		this._repeat = false;
		this._running = false;
	},

	setTime: function(t) {
		if (!this.running()) {
			this._time = t;
		}
	},

	time: function() {
		return this._time;
	},
	
	setRepeat: function(v) {
		if (!this.running()) {
			this._repeat = v;
		}
		return this;
	},

	repeat: function() {
		return this._repeat;
	},

	running: function() {
		return this._running;
	},

	start: function() {
		if (this.running()) {
			return;
		}
		this._running = true;
		var obj = this;
		this._id = (this.repeat() ? setInterval : setTimeout)(function() { obj._running = false; obj.fireEvent("expire"); }, this._time);
	},

	reset: function() {
		this.stop();
		this.start();
	},

	stop: function() {
		this._running = false;
		this.repeat() ? clearInterval(this._id) : clearTimeout(this._id);
	}
});

/* lutil-dom.js: */

// Requires events

lclass("LNode", {
	cons: function(n) {
		this.n = n;
		this.n.__lutilObj = this;

		if (LBrowser.isIE) {
			LNode.__ieNodeList.push(n);
		}
	},

	node: function() {
		return this.n;
	},

	parentNode: function() {
		return this.n.parentNode ? (this.n.parentNode.__lutilObj || $(this.n.parentNode)) : null;
	},

	firstChild: function() {
		return this.n.firstChild ? (this.n.firstChild.__lutilObj || $(this.n.firstChild)) : null;
	},

	lastChild: function() {
		return this.n.lastChild ? (this.n.lastChild.__lutilObj || $(this.n.lastChild)) : null;
	},

	nextSibling: function() {
		return this.n.nextSibling ? (this.n.nextSibling.__lutilObj || $(this.n.nextSibling)) : null;
	},

	prevSibling: function() {
		return this.n.prevSibling ? (this.n.prevSibling.__lutilObj || $(this.n.prevSibling)) : null;
	},

	destroy: function() {
		this.removeSelf();
		// DEEEEEPRECATED
	},

	removeSelf: function() {
		this.n.parentNode.removeChild(this.n);
	}
}, {
	__ieCleanup: function() {
		var i, e, o, n;
		for (i = 0; i < LNode.__ieNodeList.length; i++) {
			o = LNode.__ieNodeList[i];
			for (e in o.eventHandlers) {
				o.n["on" + e] = null;
			}
			o.eventHandlers = null;
			o.n.__lutilObj = null;
		}
		LNode.__ieNodeList = null;
	},

	ELEMENT: 1,
	TEXT: 3
});

lclass("LEntityRef", LNode, {
	cons: function(ent) {
		this.n = document.createEntityReference(ent);
	}
});

if (LBrowser.isIE) {
	LNode.__ieNodeList = [];
	lwindow.attachEvent("unload", LNode.__ieCleanup);
}

lclass("LDocument", LNode, [LEventHandling], {
	cons: function() {
		this.n = document;
		this.n.__lutilObj = this;
	},

	scrollPosition: function() {
		if (typeof self.pageXOffset != "undefined") {
			return {x: self.pageXOffset, y: self.pageYOffset};
		} else if (document.body && typeof document.body.scrollTop != "undefined") {
			return {x: document.body.scrollLeft, y: document.body.scrollTop};
		} else if (document.documentElement && typeof document.documentElement.scrollTop != "undefined") {
			return {x: document.documentElement.scrollLeft, y: document.documentElement.scrollTop};
		} else {
			return {x: 0, y: 0};
		}
	},

	viewportSize: function() {
		if (typeof window.innerWidth != "undefined") {
			return {w: window.innerWidth, h: window.innerHeight};
		} else if (typeof document.documentElement != "undefined"
			&& typeof document.documentElement.clientWidth != "undefined"
			&& document.documentElement.clientWidth != 0)
		{
			return {w: document.documentElement.clientWidth,
				h: document.documentElement.clientHeight};
		} else {
			var body = document.getElementsByTagName("body")[0];
			return {w: body.clientWidth, h: body.clientHeight};
		}
	}
});

ldocument = new LDocument();

lclass("LElementNode", LNode, [LEventHandling], {
	cons: function(n) {
		if (typeof n == "string") {
			this.n = document.createElement(n);
		} else {
			this.n = n;
		}
		
		if (LBrowser.isIE) {
			LNode.__ieNodeList.push(this);
		}

		this.n.__lutilObj = this;
		
		if (arguments.length > 1) {
			var i;
			for (i = 1; i < arguments.length; i++) {
				this.append(arguments[i]);
			}
		}
	},

	text: function() {
		var r = "", i;
		for (i = 0; i < this.n.childNodes.length; i++) {
			if (this.n.childNodes[i].nodeType == 3) {
				r += this.n.childNodes[i].nodeValue;
			}
		}
		return r;
	},

	setText: function(v) {
		if (this.n.childNodes.length == 1 && this.n.firstChild.nodeType == LNode.TEXT) {
			this.n.firstChild.nodeValue = v;
		} else {
			this.empty();
			this.n.appendChild(document.createTextNode(v));
		}
		return this;
	},

	setHTML: function(v) {
		this.n.innerHTML = v;
	},

	appendHTML: function(v) {
		this.n.innerHTML += v;
	},

	style: function(p) {
		if (typeof p == "object") {
			this.apply(p);
			return this;
		} else {
			return this.n.style;
		}
	},
	
	// Thanks Munter and White etc
	getStyle: function(prop) {
		var cc = prop.classToCamelCase();
		var c = prop.camelCaseToClass();
		var v;

		if (v = this.n.style[cc]) {
			return v;
		}
		
		if (document.defaultView && document.defaultView.getComputedStyle) {
//			Debug.writeln("IN");
			return document.defaultView.getComputedStyle(this.n, "").getPropertyValue(c);
		} else if (this.n.currentStyle) {
			return this.n.currentStyle[cc];
		}

		return "";
	},
	
	apply: function(p) {
		var i, n;
		for (i in p) {
			if (typeof i == "string") {
				n = (i.indexOf("-") != -1) ? i.classToCamelCase() : i;
				if (typeof p[i] == "string") {
					this.n.style[n] = p[i];
				} else if (p[i]._instanceOf && p[i]._instanceOf(LCSSValue)) {
					if (p[i].apply) {
						p[i].apply(this);
					} else {
						this.n.style[n] = p[i].toCss();
					}
				}
			}
		}
		return this;
	},

	append: function() {
		var i, a, ai;
		for (i = 0; i < arguments.length; i++) {
			a = arguments[i];
			if (a._instanceOf && a._instanceOf(LNode)) {
				this.n.appendChild(a.n);
			} else if (a._instanceOf && a._instanceOf(LCustomElementNode)) {
				this.n.appendChild(a.n.n);
			} else if (typeof a == "string" || typeof a == "number") {
				this.n.appendChild(document.createTextNode(a));
			} else if (typeof a == "object") {
				for (i in a) {
					ai = i;
					if (i == "_class") {
						this.n.className = a[i];
						continue;
					}
					this.n.setAttribute(ai, a[i]);
				}
			}
		}
		return this;
	},

	insertBefore: function(p1, p2) {
		if (typeof p2 == "undefined") {
			$(p1).parentNode().insertBefore(this, p1);
		} else {
			this.n.insertBefore($(p1).n, $(p2).n);
		}
	},

	replaceWith: function(n) {
		this.n.parentNode.replaceChild($(n).n, this.n);
	},

	setAttribute: function(a, v) {
		this.n.setAttribute(a, v);
	},

	getAttribute: function(a) {
		return this.n.getAttribute(a);
	},
	
	setSelected: function(v) {
		var i;
		for (i = 0; i < this.n.options.length; i++) {
			if (this.n.options[i].value == v) {
				this.n.selectedIndex = i;
				break;
			}
		}
	},

	selectedIndex: function() {
		return this.n.selectedIndex;
	},

	setSelectedIndex: function(v) {
		this.n.selectedIndex = v;
	},

	value: function() {
		return this.n.value;
	},

	setValue: function(v) {
		this.n.value = v;
	},

	checked: function() {
		return this.n.checked;
	},

	setChecked: function(v) {
		this.n.checked = v;
	},

	setSelectionText: function(t) {
		if (typeof this.n.selectionStart != "undefined") {
			var s = this.n.selectionStart;
			this.n.value = this.n.value.substr(0, s) + t + this.n.value.substr(this.n.selectionEnd);
			this.n.selectionStart = this.n.selectionEnd = s + 1;
		}
	},

	offsetLeft: function() {
		return this._getOffset("offsetLeft");
	},

	offsetTop: function() {
		return this._getOffset("offsetTop");
	},

	position: function() {
		return {x: this.offsetLeft(), y: this.offsetTop()};
	},

	offsetPosition: function() {
		return {x: this.n.offsetLeft, y: this.n.offsetTop};
	},

	offsetHeight: function() {
		return this.n.offsetHeight;
	},

	offsetWidth: function() {
		return this.n.offsetWidth;
	},

	dimensions: function() {
		return {w: this.offsetWidth(), h: this.offsetHeight()};
	},

	_getOffset: function(p) {
		var n, r = 0;
		for (n = this.n; n; n = n.offsetParent) {
			r += n[p];
		}
		return r;
	},

	clientHeight: function() {
		return this.n.clientHeight;
	},

	clientWidth: function() {
		return this.n.clientWidth;
	},

	geometry: function() {
		return new LRect(this.offsetLeft(), this.offsetTop(), this.offsetLeft() + this.offsetWidth() - 1, this.offsetTop() + this.offsetHeight() - 1);
	},

	setGeometry: function(rect) {
		this.n.style.left = rect.x + "px";
		this.n.style.top = rect.y + "px";
		this.n.style.width = rect.w + "px";
		this.n.style.height = rect.h + "px";
	},

	contentDimensions: function() {
		var r, s;
		s = {width: this.n.style.width, height: this.n.style.height, display: this.n.style.display};
		this.n.style.width = "auto";
		this.n.style.height = "auto";
		this.n.style.display = "";
		r = {w: this.n.clientWidth, h: this.n.clientHeight};
		this.n.style.width = s.width;
		this.n.style.height = s.height;
		this.n.style.display = s.display;
		return r;
	},

	contentHeight: function() {
		var r, s;
		s = {height: this.n.style.height || "auto", display: this.n.style.display || "", visibility: this.n.style.visibility || "visible"};
		this.n.style.height = "auto";
		this.n.style.visibility = "hidden";
		this.n.style.display = "";
		r = this.n.clientHeight;
		this.n.style.height = s.height;
		this.n.style.display = s.display;
		this.n.style.visibility = s.visibility;
		return r;
	},

	className: function() {
		return this.n.className;
	},

	setClass: function(c) {
		this.n.className = c;
		return this;
	},

	hasClass: function(c) {
		return this.n.className.match(new RegExp("^" + c + "\\b|\\b" + c + "\\b|\\b" + c + "$"));
	},

	addClass: function(c) {
		if (!this.hasClass(c)) {
			this.n.className += " " + c;
		}
		return this;
	},
	
	removeClass: function(c) {
		this.n.className = this.n.className.replace(new RegExp("^" + c + "\\b|\\b" + c + "\\b|\\b" + c + "$"), " ").replace(/ {2,}/, " ");
		return this;
	},

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

	empty: function(v) {
		while (this.n.firstChild) {
			this.n.removeChild(this.n.firstChild);
		}
		return this;
	},

	getElementsByTagName: function(t) {
		return Array.fromCollection(this.n.getElementsByTagName(t));
	}
});

lclass("LCustomElementNode", { });

// lambda
var a = function(a) {
	return function() {
		var ar = arguments;
		this.nl.forEach(function(n) {
			n[a].apply(n, ar);
		});
		return this.nl;
	};
};

lclass("LElementNodeList", {
	cons: function(nl) {
		this.nl = nl;
	},

	each: function(f, o) {
		this.nl.forEach(f, o);
	},

	setText: a("setText"),
	addClass: a("addClass"),
	removeClass: a("removeClass"),
	toggleClass: a("toggleClass"),
	setClass: a("setClass"),
	append: a("append"),
	attachEvent: a("attachEvent"),
	apply: a("apply")
});

delete a;

lclass("LTextNode", LNode, {
	cons: function(n) {
		this.n = n;
	},

	text: function() {
		return this.n.nodeValue;
	},

	setText: function(v) {
		this.n.nodeValue = v;
	}
});

lclass("LCSSValue", {});

lclass("LCSSColor", LCSSValue, {
	cons: function(s, g, b) {
		var m;
		if (typeof g == "undefined") {
			s = s.trim().toLowerCase();
			if (LCSSColor.namedColors[s]) {
				this.r = LCSSColor.namedColors[s].r;
				this.g = LCSSColor.namedColors[s].g;
				this.b = LCSSColor.namedColors[s].b;
			} else if (m = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i.exec(s)) {
				this.r = parseInt(m[1]);
				this.g = parseInt(m[2]);
				this.b = parseInt(m[3]);
			} else if (m = /^#([0-9A-F]{3})$/i.exec(s)) {
				this.r = m[1].charAt(0).hexDec() * 17;
				this.g = m[1].charAt(1).hexDec() * 17;
				this.b = m[1].charAt(2).hexDec() * 17;
			} else if (m = /^#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i.exec(s)) {
				this.r = m[1].hexDec();
				this.g = m[2].hexDec();
				this.b = m[3].hexDec();
			} else if (typeof s == "number") {
				this.r = parseInt(s);
				this.g = parseInt(s);
				this.b = parseInt(s);
			} else {
				this.r = 0;
				this.g = 0;
				this.b = 0;
			}
		} else {
			this.r = s;
			this.g = g;
			this.b = b;
		}
	},

	tween: function(b, t) {
		return new LCSSColor(this.r + (b.r - this.r) * t, this.g + (b.g - this.g) * t, this.b + (b.b - this.b) * t);
	},

	toCss: function() {
		return "rgb(" + Math.floor(this.r) + "," + Math.floor(this.g) + "," + Math.floor(this.b) + ")";
	}
}, {
	cons: function() {
		
	},

	fromHSV: function(h, s, v) {
		// Stolen from Wikipedia
		var f, p, q, t, hi;
		
		hi = Math.floor(h * 60) % 6;
		f = h * 60 - hi;
		p = v * (1 - s);
		q = v * (1 - f * s);
		t = v * (1 - (1 - f) * s);

		switch (hi) {
			case 0:
				return new LCSSColor(v, t, p);
			case 1:
				return new LCSSColor(q, v, p);
			case 2:
				return new LCSSColor(p, v, t);
			case 3:
				return new LCSSColor(p, q, v);
			case 4:
				return new LCSSColor(t, p, v);
			case 5:
				return new LCSSColor(v, p, q);
		}
		return new LCSSColor(0, 0, 0);
	}
});

LCSSColor.namedColors = {}; // KLUDGE ALERT

LCSSColor.namedColors = {
	maroon: new LCSSColor("#800000"),
	red: new LCSSColor("#FF0000"),
	orange: new LCSSColor("#FFA500"),
	yellow: new LCSSColor("#FFFF00"),
	olive: new LCSSColor("#808000"),
	purple: new LCSSColor("#800080"),
	fuchsia: new LCSSColor("#FF00FF"),
	white: new LCSSColor("#FFFFFF"),
	lime: new LCSSColor("#00FF00"),
	green: new LCSSColor("#008000"),
	navy: new LCSSColor("#000080"),
	blue: new LCSSColor("#0000FF"),
	aqua: new LCSSColor("#00FFFF"),
	teal: new LCSSColor("#008080"),
	black: new LCSSColor("#000000"),
	silver: new LCSSColor("#C0C0C0"),
	gray: new LCSSColor("#808080")
};

lclass("LCSSOpacity", LCSSValue, {
	cons: function(o) {
		this.o = parseFloat(o);
	},

	tween: function(b, t) {
		return new LCSSOpacity(this.o + (b.o - this.o) * t);
	},

	apply: function(el) {
		el.style().mozOpacity = this.o;
		el.style().opacity = this.o;
		el.style().filter = "alpha(opacity=" + Math.floor(this.o * 100) + ")";
	}
});

lclass("LCSSFlashOpacity", LCSSValue, {
	cons: function(o) {
		this.o = parseFloat(o);
	},

	tween: function(b, t) {
		return new LCSSFlashOpacity(this.o + (b.o - this.o) * t);
	},

	apply: function(el) {
		el.n.SetVariable("/_root._alpha", "" + Math.max(0, Math.min(Math.floor(this.o * 100), 100)));
	}
});

lclass("LCSSLength", LCSSValue, {
	cons: function(s, u) {
		if (typeof u == "undefined") {
			if (m = /^(-?[\d\.]+)(em|ex|px|in|cm|mm|pt|pc|%)$/.exec(s.trim())) {
				this.v = parseFloat(m[1]);
				this.u = m[2];
			} else {
				this.v = parseFloat(s);
				this.u = "px";
			}
		} else {
			this.v = parseFloat(s);
			this.u = u;
		}
	},

	tween: function(b, t) {
		return new LCSSLength(this.v + (b.v - this.v) * t, this.u);
	},

	toCss: function() {
		return this.v + this.u;
	}
});

lclass("LCSSOther", LCSSValue, {
	cons: function(v) {
		this.v = v;
	},

	tween: function(b, t) {
	/*	return new LCSSLength(this.v + (b.v - this.v) * t, this.u);*/
		return new LCSSOther(this.v);
	},

	toCss: function() {
		return this.v;
	}
});

lclass("LCSSSelector", {
	cons: function(s) {
		var m = /(\*)|([a-z][a-z0-9_-]*)|( *> *)|( *\+ *)|( +)|:([a-z-]+)(?:\(([^\)]+)\))?|\.([a-z][a-z0-9_-]*)|#([a-z][a-z0-9_-]*)|\[([a-z][a-z0-9_-]*)(?:=?([^\]]*))?\]/gi.scan(s);
		var i, s, t, f = [], p;
		this.parts = [];
		for (i = 0; i < m.length; i++) {
			s = m[i];
			for (t = 1; t <= 10; t++) {
				if (s[t]/*typeof s[t] != "undefined"*/) {
					p = {t: t};
					switch (t) {
						case LCSSSelector.ATTRIB:
						case LCSSSelector.PSEUDO:
							p.p1 = s[t];
							if (typeof s[t + 1] != "undefined") {
								p.p2 = s[t + 1];
							}
							break;
						case LCSSSelector.ALL:
						case LCSSSelector.CHILD:
						case LCSSSelector.DESCENDANT:
						case LCSSSelector.SIBLING:
							break;
						default:
							p.p1 = s[t];
					}
					f.push(p);
					break;
				}
			}
			if (p.t == LCSSSelector.CHILD || p.t == LCSSSelector.DESCENDANT || p.t == LCSSSelector.SIBLING) {
			//	f.sort(this._fragmentSortFunc);
				this.parts.append(f);
				f = [];
			}
		}
		if (f.length) {
		//	f.sort(this._fragmentSortFunc);
			this.parts.append(f);
		}
//		Debug.writeln(Debug.print_r(this.parts));
	},

	select: function(context) {
		var p, r = [], nr, i, a, f, n;
		
		for (i = 0; i < this.parts.length; i++) {
			p = this.parts[i];
			switch (p.t) {
				case LCSSSelector.CHILD:
					nr = [];
					for (a = 0; a < r.length; a++) {
						for (n = r[a].firstChild; n; n = n.nextSibling) {
							if (n.nodeType == 1) {
								nr.push(n);
							}
						}
					}
					r = nr;
					break;
				case LCSSSelector.DESCENDANT:
					if (r.length == 0) {
						return [];
					}
					nr = [];
					for (a = 0; a < r.length; a++) {
						nr = nr.concat(Array.fromCollection(r[a].getElementsByTagName("*")));
					}
					r = nr;
					break;
				case LCSSSelector.SIBLING:
					if (r.length == 0) {
						return [];
					}
					nr = [];
					for (a = 0; a < r.length; a++) {
						for (n = r[a].nextSibling; n && n.nodeType != 1; n = n.nextSibling)
							;
						if (n) {
							nr.push(n);
						}
					}
					r = nr;
					break;
				case LCSSSelector.ELEMENT:
					if (r.length == 0) {
						if (typeof context != "undefined") {
							r = $(context).getElementsByTagName(p.p1);
						} else {
							r = Array.fromCollection(document.getElementsByTagName(p.p1));
						}
					} else {
						r = r.filter(function(e) {
							return e.nodeName.toUpperCase() == p.p1.toUpperCase();
						});
					}
					break;
				case LCSSSelector.KLASS:
					if (r.length == 0) {
						if (typeof context != "undefined") {
							r = $(context).getElementsByTagName("*");
						} else {
							r = Array.fromCollection(document.getElementsByTagName("*"));
						}
					}
					r = r.filter(function(e) {
						return e.className.match(new RegExp("^" + p.p1 + "\\b|\\b" + p.p1 + "\\b|\\b" + p.p1 + "$", "i"));
					});
					break;
				case LCSSSelector.ID:
					if (r.length == 0) {
						if (typeof context != "undefined") {
							r.push($(context).getElementById(p.p1).n);
						} else {
							r.push(document.getElementById(p.p1));
						}
					}
					r = r.filter(function(e) {
						return e.getAttribute("id").toUpperCase() == p.p1.toUpperCase();
					});
					break;
				case LCSSSelector.ATTRIB:
					r = r.filter(function(e) {
						return e.getAttribute(p.p1) && (!p.p2 || (e.getAttribute(p.p1).toUpperCase() == p.p2.toUpperCase()));
					});
					break;
				case LCSSSelector.PSEUDO:
					switch (p.p1.toLowerCase()) {
						case "first-child":
							r = r.filter(function(e) {
								var n;
								for (n = e.parentNode.firstChild; n && n.nodeType != 1; n = n.nextSibling)
									;
								return (n == e);
							});
							break;
					}
					break;
			}
			if (r.length == 0) {
				return [];
			}
		}
		return $(r);
	},

	_fragmentSortFunc: function(a, b) {
		var order = [
			LCSSSelector.ID,
			LCSSSelector.ALL,
			LCSSSelector.ELEMENT,
			LCSSSelector.KLASS,
			LCSSSelector.PSEUDO,
			LCSSSelector.CHILD,
			LCSSSelector.DESCENDANT,
			LCSSSelector.SIBLING
		];
		return order.indexOf[b.t] - order.indexOf[a.t];
	}
}, {
	ALL: 1,
	ELEMENT: 2,
	CHILD: 3,
	SIBLING: 4,
	DESCENDANT: 5,
	PSEUDO: 6,
	// 7 == pseudo.p2
	KLASS: 8,
	ID: 9,
	ATTRIB: 10
	// 11 == attrib.p2
});

function $S(s, context) {
	var l = new LCSSSelector(s).select(context);
	if (l.length == 0) {
		return null;
	} else {
		return l[0];
	}
}

function $L(s, context) {
	return new LElementNodeList(new LCSSSelector(s).select(context));
}

function $C(s, context) {
	return new LCSSSelector(s).select(context);
}

function $(a) {
	var r, i;
	if (arguments.length == 1) {
//		if (typeof a == "undefined" || typeof a == "null" || a == null) {
		if (!a) {
			return null;
		} else if (a instanceof Array) {
			r = [];
			for (i = 0; i < a.length; i++) {
				r[i] = $(a[i]);
			}
			return r;
		} else if (typeof a == "string") {
			r = document.getElementById(a);
			if (!r) {
				return null;
			}
			return r.__lutilObj || new LElementNode(r);
		} else if (typeof a == "object" && a._instanceOf && a._instanceOf(LElementNode)) {
			return a;
		} else if (typeof a == "object" && a._instanceOf && a._instanceOf(LCSSSelector)) {
			return a.select();
		} else if (a.nodeType) {
			if (a.nodeType == 1) {
				return a.__lutilObj || new LElementNode(a);
			} else if (a.nodeType == 3) {
				return a.__lutilObj || new LTextNode(a);
			}
		}
	} else {
		r = [];
		for (i = 0; i < arguments.length; i++) {
			r[i] = $(arguments[i]);
		}
		return r;
	}
}

/* lutil-animation.js: */

// Requires events

lclass("LAnimation", [LEventHandling], {
	cons: function(frames) {
		var i, a, el, tl, frame, p, pi, f;
		
		this.timelines = [];
		this._fps = 20;
		this._tweenFunc = LAnimation.linearTween;
		this._storems = null;
		this._loop = false;
		this.timer = new LTimer(50, this, this._tick);
		this.timer.setRepeat(true);

	/*	if (frames instanceof Array) {
			var i;
			for (i = 0; i < frames.length; i++) {
				this.keyframe.apply(this, frames[i]);
			}
		}*/

		for (i = 0; i < frames.length; i++) {
			//Debug.writeln(frames[i]);
			if (!frames[i]) {
				// Mysterious IE bug? :\
				continue;
			}
			el = $(frames[i][0]);
			if (el) {
				tl = this.timeline(el);
				for (a = 1; a < frames[i].length; a++) {
					frame = frames[i][a][0];
					p = frames[i][a][1].split(/;/);
					for (pi = 0; pi < p.length; pi++) {
						f = this.keyframe(tl, frame, p[pi], false);
					}
//					Debug.writeln(Debug.print_r(tl));
				}
			}
		}
	//	Debug.writeln("Wtf");
	},
	
	updateTimelines: function() {
		var i, p, a;
		for (i = 0; i < this.timelines.length; i++) {
			for (p in this.timelines[i][1]) {
				this.timelines[i][1][p].sort(this._sortFunc);
				if (this.timelines[i][1][p][0].auto) {
					this.timelines[i][1][p].shift();
					this.keyframe(this.timelines[i][1], 0, d = (p + ":" + this.timelines[i][0].getStyle(p)), true);
				} else if (this.timelines[i][1][p][0].frame != 0) {
//					Debug.writeln("Default style GET");
					this.keyframe(this.timelines[i][1], 0, d = (p + ":" + this.timelines[i][0].getStyle(p)), true);
//					Debug.writeln(Debug.print_r(this.timelines[i][1][p]));
				}
			}
		}
	},
	
	fps: function() {
		return this._fps;
	},

	setFps: function(v) {
		this._fps = v;
	},

	timeline: function(el) {
		for (i = 0; i < this.timelines.length; i++) {
			if (this.timelines[i][0] == el) {
				return this.timelines[i][1];
			}
		}

		this.timelines.push([el, {}]);
		return this.timelines[this.timelines.length - 1][1];
	},

	keyframe: function(tl, frame, rule, auto) {
		var matches, prop, val, tween, rv;
		
		if (!(matches = /^\s*([a-zA-Z-]+?)([:>])\s*([^;]*);?\s*$/.exec(rule))) {
			return;
		}
		
		prop = matches[1].classToCamelCase();
		if (typeof tl[prop] == "undefined") {
			tl[prop] = [];
		}

		tween = (matches[2] == ">" ? true : false);
		
		// FIXME
        
		switch (prop) {
			case "color":
			case "backgroundColor":
				val = new LCSSColor(matches[3]);
				break;
			case "opacity":
				val = new LCSSOpacity(matches[3]);
				break;
		    case "LFlashOpacity":
		        val = new LCSSFlashOpacity(matches[3]);
		        break;
			case "width": case "height": case "left": case "top": case "right": case "bottom":
			case "marginTop": case "marginRight": case "marginBottom": case "marginLeft": 
				val = new LCSSLength(matches[3]);
				break;
			default:
				val = new LCSSOther(matches[3]);
		}
		
		tl[prop][auto ? "unshift" : "push"](rv = new LAnimationKeyframe(val, frame, tween, auto));

		return rv;
	},

	start: function() {
		if (this.paused()) {
			this.resume();
			return;
		}

		this.updateTimelines();

		this.startms = new Date().getTime();

		this.timer.setTime(1000 / this._fps);
		this.timer.start();
		this._tick();
	},

	setTween: function(v) {
		this._tweenFunc = v;
		return this;
	},

	loop: function() {
		return this._loop;
	},

	setLoop: function(v) {
		this._loop = v;
		return this;
	},

	stop: function() {
		this.timer.stop();
	},

	pause: function() {
		this.timer.stop();
		this._storems = new Date().getTime() - this.startms;
	},

	paused: function() {
		return this._storems != null;
	},

	resume: function() {
		if (this.paused()) {
			this.gotoMs(this._storems);
			this._storems = null;
			this.timer.start();
		}
	},

	toggle: function() {
		this.paused() ? this.resume() : this.pause();
	},

	gotoMs: function(ms) {
		this.startms = new Date().getTime() - ms;
		this._tick();
	},

	rewind: function() {
		this.gotoMs(0);
	},

	_tick: function() {
		var el, p, i, tl, l, n, t, v, go = false, ms = new Date().getTime() - this.startms;
		
		for (ei = 0; ei < this.timelines.length; ei++) {
			for (p in this.timelines[ei][1]) {
				tl = this.timelines[ei][1][p];
				el = this.timelines[ei][0];
				l = null;
				n = null;
				for (i = 0; i < tl.length; i++) {
					if (tl[i].frame > ms) {
						n = tl[i];
						if (i > 0) {
							l = tl[i - 1];
						}
						break;
					}
				}

				if (n == null) {
					l = tl[tl.length - 1];
				} else {
					go = true;
				}

				if (l == null) {
					l = tl[0];
				}

				if (n == null || !n.tween) {
					if (l.val.apply) {
						l.val.apply(el);
					} else {
//						if (p == "visibility") {
//							Debug.writeln("p" + l.val.toCss());
//						}
						el.style()[p] = l.val.toCss();
					}
				} else if (l != null) {
					if (ms == l.frame) {
						t = 0.0;
					} else {
						t = (ms - l.frame) / (n.frame - l.frame);
					}
				//	this.el.style()[p] = this._tweenProperty(p, l.val, n.val, t, tl.u);
					v = l.val.tween(n.val, this._tweenFunc(t));
					if (v.apply) {
						v.apply(el);
					} else {
						el.style()[p] = v.toCss();
					}
				}
			}
		}

		if (!go) {
			this.fireEvent("finish", null);
			if (this.loop()) {
				this.rewind();
			} else {
				this.stop();
			/*	if (this.destroy) {
					this.el.destroy();
				}*/
			}
		}
	},

	_sortFunc: function(a, b) {
		return a.frame - b.frame;
	},

	_getUnit: function(v) {
		return /([a-zA-Z%]*)$/.exec(v)[1];
	}
}, {
	linearTween: function(t) {
		return t;
	},

	smoothEnd: function(t) {
		return Math.sin(t * Math.PI * 0.5);
	},

	smoothStart: function(t) {
		return 1 - Math.cos(t * Math.PI * 0.5);
	}
});

lclass("LAnimationKeyframe", {
	cons: function (val, frame, tween, auto) {
		this.val = val;
		this.frame = frame;
		this.tween = tween;
		this.auto = auto;
	}
});

lclass("LEffect", {
	cons: function(el, init, stop) {
		this.init = init;
		this._stop = stop;

		if (el._effect) {
			el._effect.stop();
			return;
		}
		el._effect = this;
		with (this.anim = new LAnimation(el)) {
			setFps(20);
			
			attachEvent("finish", function() { el._effect = null; });
		}

		init(this.anim);

		this.anim.start();

		return a;
	},
	
	stop: function() {
		this.anim.stop();
		if (this._stop) {
			this._stop();
		}
	}
});

var LEffects = {
	blindDown: function(el, duration) {
		var el = $(el);
		
		new LEffect(el, function(a) {
			a.setTween(LAnimation.smoothEnd);
			a.keyframe("height: " + el.offsetHeight() + "px", 0, false);
			a.keyframe("height: " + el.contentHeight() + "px", duration || 1000, true);
		}, null);
	},

	blindUp: function(el, duration) {
		var el = $(el);
		
		new LEffect(el, function(a) {
			a.setTween(LAnimation.smoothEnd);
			a.keyframe("height: " + el.offsetHeight() + "px", 0, false);
			a.keyframe("height: 0px", duration || 1000, true);
		});
	},

	fadeIn: function(el, duration) {
		new LEffect($(el), function(a) {
			a.keyframe("opacity: 0.0", 0, false);
			a.keyframe("opacity: 1.0", duration || 1000, true);
		});
	},

	fadeOut: function(el, duration) {
		new LEffect($(el), function(a) {
			a.keyframe("opacity: 1.0", 0, false);
			a.keyframe("opacity: 0.0", duration || 1000, true);
		});
	},

	doEffect: function(el) {
		var a;
		if (el._effectInProgress) {
			
			return;
		}
		el._effectInProgess = true;
		with (a = new LAnimation(el)) {
			setFps(20);
			
			attachEvent("finish", function() { el._effectInProgress = false; });
		}

		return a;
	}
};


