// dollarsign.js v0.3pre

// A jQuery clone with less features, more bugs and less speed
// by Jeffery To <jeffery.to@wwwins.com>

// Guvf urycf jjjvaf / vfbone / Nrtvf znxr fb zhpu $ gung rira gur pbzcrgrag rzcyblrrf trg fbzr

(function () {
var win = window,
	doc = win.document,
	_$ = win.$,
	undef, ds;

var PROP = 'dollarsign' + (new Date()).getTime(),
	ELID = 1,
	store = {};

function makeArray(o) {
	var l, a, i;

	if (o == null) {
		return [];
	}
	if (o instanceof Array) {
		return o.concat();
	}
	if (typeof o == 'string' || o instanceof String) {
		return o.split('');
	}

	l = parseInt(o.length, 10);

	if (isNaN(l) || !isFinite(l) || l <= 0) {
		return [];
	}

	try {
		a = Array.prototype.concat.call(o);
	} catch (e) {
		a = null;
	}

	// sometimes a is an array but all of its members are undefined
	// strings in ie
	// nodelists in safari 2
	// so we need to check
	// sigh
	if (a) {
		// find a non-member of o and compare
		// can't use the in operator because it doesn't work with
		// nodelists in safari 2
		for (i = l - 1; i >= 0; i--) {
			if (o[i] != null) {
				if (a[i] !== o[i]) {
					a = null;
				}
				break;
			}
		}
	}

	if (!a) {
		a = [];
		for (i = l - 1; i >= 0; i--) {
			a[i] = o[i];
		}
	}

	return a;
};

function extend() {
	var args = makeArray(arguments), deep = false, o, p, q, l, i, j;
	if (args[0] === true) {
		deep = args.shift();
	}
	l = args.length;
	if (l == 1) {
		o = ds;
	} else {
		o = args.shift();
		l--;
	}
	for (i = 0; i < l; i++) {
		p = args[i];
		for (j in p) {
			q = p[j];
			if (typeof q != 'undefined') {
				o[j] = (deep && typeof q == 'object' && q) ?
						arguments.callee(true, (o[j] || ((q instanceof Array) ? [] : {})), q) :
						q;
			}
		}
	}
	return o;
};

function tester(s) {
	var fns = [], id = '', tag, next, a, b, c, l, i;

	// hierarchy
	a = s.match(/^([>+~]*)(.*)$/);
	next = a[1].charAt(0);
	s = a[2];

	// tag
	a = s.match(/^(\*|\w*)(.*)$/);
	tag = a[1].toLowerCase();
	s = a[2];

	// everything else
	if (a = s.match(/[#.:](?:\w|\\\.|\\:|\\\[|\\\])+/g)) {
		l = a.length;
		for (i = 0; i < l; i++) {
			b = a[i];
			c = b.substring(1);
			switch (b.charAt(0)) {
			case '#':
				// id
				id = c;
				if (id.indexOf('\\') > -1) {
					id = id.replace(/\\([.:\[\]])/g, '$1');
				}
				break;
			case '.':
				// class
				(function () {
					var re = new RegExp('\\b' + c + '\\b');
					fns.push(function (el) { return (re.test(el.className)); });
				})()
				break;
			case ':':
				// pseudo-class
				break;
			}
		}
	}

	if (tag !== '' && tag !== '*') {
		fns.unshift(function (el) { return (el.nodeName.toLowerCase() === tag); });
	}
	if (id !== '') {
		fns.unshift(function (el) { return (el.id === id); });
	}

	return function (el, pos) {
		var l = fns.length, r = true, i;
		for (i = 0; i < l; i++) {
			if (!fns[i](el, pos)) {
				r = false;
				break;
			}
		}
		return {
			pass: r,
			next: (next == '' || next == '>') ? 'parentNode' : 'previousSibling',
			stopNext: (next == '>' || next == '+')
		};
	};
}

var DOM = {
	isElement: function (o) { return !!(o && o.nodeType == 1); },
	isDocument: function (o) { return !!(o && o.nodeType == 9); },
	contains: function (a, d) {
		if (a && d) {
			if (a.contains) {
				return a.contains(d);
			} else if (a.compareDocumentPosition) {
				// based on http://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html
				return !!(a.compareDocumentPosition(d) & 0x10);
			} else {
				for (d = d.parentNode; d; d = d.parentNode) {
					if (d == a) {
						return true;
					}
				}
			}
		}
		return false;
	},
	nextElement: function(el, next, all) {
		var p = el[next], r;
		if (!all) {
			while (p && p.nodeType != 1 && p.nodeType != 9) {
				p = p[next];
			}
			r = p;
		} else {
			r = [];
			while (p) {
				if (p.nodeType == 1) {
					r.push(p);
				}
				p = p[next];
			}
		}
		return r;
	},
	// from http://www.quirksmode.org/dom/getElementsByTagNames.html
	sort: function (els) {
		var sorted = true;
		if (els.length > 0) {
			if (els[0].sourceIndex != null) {
				els.sort(function (a, b) { return a.sourceIndex - b.sourceIndex; });
			} else if (els[0].compareDocumentPosition) {
				els.sort(function (a, b) { return 3 - (a.compareDocumentPosition(b) & 0x06); });
			} else {
				sorted = false;
			}
		}
		return sorted;
	}
};

// XXX todo: use querySelectorAll
function sel(s, doc, root, els) {
	var D = DOM, result = [], canId, sorted, a, l, i;

	// split and normalize s
	// e.g. "  body , div   span, ul>li, label+ input  " becomes
	// ["body", "div span", "ul >li", "label +input"]
	s = ds.map(s.split(','), function (v) {
		var w = ds.trim(v.replace(/([>+~])\s*/g, ' $1')).replace(/\s+/, ' ');
		return (w !== '') ? w : null;
	});

	// check to make sure doc exists and is a document node
	if (!D.isDocument(doc)) {
		doc = ((root && root.ownerDocument) || win.document);
	}

	if (!root) {
		root = doc;
	}
	if (!D.isElement(root) && !D.isDocument(root)) {
		return result;
	}

	if (els) {
		// filter given list for elements
		els = ds.grep(makeArray(els), function (el) { return D.isElement(el); });
	} else {
		// check if we can use doc.getElementById
		canId = ((root == doc || D.contains(doc, root)) && doc.getElementById);
	}

	// for each selector
	ds.each(s, function (i, v) {
		var f = v.split(' ').reverse(), list, id, tag, el;

		// find starting list of elements, if necessary
		if (els) {
			list = els.concat();
		} else {
			if (canId) {
				id = f[0].match(/#((?:\w|\\\.|\\:|\\\[|\\\])+)/);
				if (id) {
					id = id[1];
					if (id.indexOf('\\') > -1) {
						id = id.replace(/\\([.:\[\]])/g, '$1');
					}
					el = doc.getElementById(id);
					if (!el) {
						return result;
					}
					// getElementById in ie is broken
					// e.g. for id "foo", it returns any element with name="foo" or id="FOO"
					// so we need to test for it
					if (el.id == id && D.contains(root, el)) {
						list = [el];
					}
				}
			}
			if (!list) {
				tag = f[0].match(/^\w+/);
				// XXX todo: check ds.support.objectAll
				list = makeArray(root.getElementsByTagName((tag) ? tag[0] : '*'));
			}
		}

		// convert selector into test functions
		f = ds.map(f, function (w) { return tester(w); });

		// test each element
		list = ds.map(list, function (el, i) {
			var fns = f.concat(), fn = fns.shift(), pass = true, next, stopNext, p, r;

			// run first test on actual element
			r = fn(el, i);
			if (!r.pass) {
				return null;
			}

			// run remaining tests on parents / siblings
			fn = fns[0];
			next = r.next;
			stopNext = r.stopNext;
			p = D.nextElement(el, next);
			while (p && p != root && fns.length > 0) {
				r = fn(p, i);
				if (r.pass) {
					fns.shift();
					fn = fns[0];
					next = r.next;
					stopNext = r.stopNext;
				} else if (stopNext) {
					break;
				}
				p = D.nextElement(p, next);
			}

			// make sure the node we end on is also okay
			// (for selectors that start with > or +
			if (fns.length == 0 && !stopNext) {
				while (p && p != root) {
					p = D.nextElement(p, next);
				}
			}
			if (fns.length > 0 || p != root) {
				pass = false;
			}

			return (pass) ? el : null;
		});

		result = result.concat(list);
	});

	// sort elements by dom order
	sorted = D.sort(result);

	// remove duplicates
	// only for multiple selectors
	if (s.length > 1) {
		if (sorted) {
			a = result;
			l = a.length;
			result = [];
			for (i = 0; i < l; i++) {
				if (a[i] != a[i + 1]) {
					result.push(a[i]);
				}
			}
		} else {
			result = ds.unique(result);
		}
	}

	return result;
}

function DS(a, s) {
	var l, i;
	if (a) {
		l = a.length;
		for (i = 0; i < l; i++) {
			this[i] = a[i];
		}
	}
	this.length = (l || 0);
	this.selector = (s || '');
}

/*
 * core
 */

ds = function (o, node) {
	var me = arguments.callee, ds, tag, div, wrapper, serialize;

	if (o instanceof DS) {
		// dollar sign object
		ds = extend(new DS(o, o.selector), {
			context: o.context,
			prevObject: o.prevObject
		});
		return ds;
	}

	if (me.isFunction(o)) {
		// document ready listener
		return me(doc).ready(o);
	}
	if (me.isArray(o)) {
		// array of elements
		return new DS(o);
	}

	if (o == null) {
		o = '';
	}
	if (o === '') {
		o = doc;
	}

	if (typeof o != 'string') {
		// element or null (document)
		ds = new DS([o]);
		ds.context = o;
		return ds;
	}

	if (o.indexOf('<') > -1) {
		// html
		o = o.replace(/^[^<]+/, '').replace(/[^>]+$/, '');
		tag = o.match(/^<(\w+)/)[1].toLowerCase();
		wrapper = [];
		switch (tag) {
		case 'li':
			wrapper.push('ul');
			break;
		case 'option':
			wrapper.push('select');
			break;
		case 'thead':
		case 'tbody':
		case 'tfoot':
		case 'tr':
			wrapper.push('table');
			break;
		case 'td':
		case 'th':
			wrapper.push('table', 'tr');
			break;
		case 'fieldset':
			wrapper.push('form');
			break;
		case 'legend':
			wrapper.push('form', 'fieldset');
			break;
		case 'link':
		case 'style':
		case 'script':
			// for style see http://social.msdn.microsoft.com/forums/en-US/iewebdevelopment/thread/33fd33f7-e857-4f6f-978e-fd486eba7174/
			if (!me.support.htmlSerialize) {
				o = '<br />' + o;
				serialize = true;
			}
			break;
		}
		node = me(node)[0];
		div = me(((DOM.isDocument(node)) ? node : node.ownerDocument).createElement('div'))
			.html(
				me.map(wrapper, function (t) { return ['<', t, '>']; }).join('') +
				o +
				me.map(wrapper.reverse(), function (t) { return ['</', t, '>']; }).join('')
			);
		me.each(wrapper, function () { div = div.children().eq(0); });
		ds = new DS(div.contents().get().slice((serialize) ? 1 : 0));
		// can't do div.html('') as that clears child node text in ie
		div = div[0];
		ds.each(function () { div.removeChild(this); });
		return ds;
	}

	// selector
	return me(node || doc).find(o);
};

ds.fn = DS.prototype = {
	// make DS look like an array
	push: [].push,
	sort: [].sort,
	splice: [].splice,

	// object accessors
	get: function (i) { return (arguments.length == 0) ? makeArray(this) : this[i]; },
	each: function (fn) {
		ds.each(makeArray(this), fn);
		return this;
	},
	size: function () { return this.length; },
	index: function (el) { return ds.inArray(makeArray(this)); },

	// plugins
	extend: function () {
		var args = makeArray(arguments);
		if (args.length > 0) {
			args.unshift(ds.fn);
			extend.apply(ds, args);
		}
		return ds;
	},

	// data

	data: function (n, v) {
		var P = PROP, s = store, l = arguments.length;
		if (l == 0) {
			return this;
		}
		if (l == 1) {
			if (this.length == 0) {
				return undef;
			}
			return (s[(this[0][P] || '')] || {})[n];
		}
		this.each(function () {
			var id = (this[P] || (this[P] = ELID++));
			(s[id] || (s[id] = {}))[n] = v;
		});
		return this;
	},

	removeData: function (n) {
		var P = PROP, s = store;
		this.each(function () { delete (s[(this[P] || '')] || {})[n]; });
		return this;
	},

	queue: function (n, o) {
		switch (arguments.length) {
		case 0:
			n = 'fx';
		case 1:
			if (typeof n == 'string') {
				return (this.data(n) || []);
			}
			o = n;
			n = 'fx';
		default:
			if (!o) {
				return this;
			}
			if (!ds.isArray(o)) {
				this.each(function () {
					var el = $(this), q = el.data(n);
					if (!q) {
						el.data(n, q = []);
					}
					q.push(o);
				});
			} else {
				this.each(function () { $(this).data(n, o.concat()); });
			}
			this.each(function () {
				var q = $(this).data(n);
				if (q.length == 1) {
					q[0].call(this);
				}
			});
		}
		return this;
	},

	dequeue: function (n) {
		n = (typeof n == 'string') ? n : 'fx';
		this.each(function () {
			var q = $(this).queue(n);
			q.shift();
			if (q.length > 0) {
				q[0].call(this);
			}
		});
		return this;
	}
};

// interoperability
ds.noConflict = function () {
	win.$ = _$;
	return ds;
}



/*
 * utilities
 */

extend({
	// array and object operations

	makeArray: makeArray,
	extend: extend,

	each: function (o, cb) {
		var l, i;
		if (this.isArray(o)) {
			l = o.length;
			for (i = 0; i < l; i++) {
				if (cb.call(o[i], i, o[i]) === false) {
					break;
				}
			}
		} else {
			for (i in o) {
				if (cb.call(o[i], i, o[i]) === false) {
					break;
				}
			}
		}
		return o;
	},

	grep: function (a, cb, invert) {
		var b = [];
		this.each(a, function (i, v) {
			var r = cb.call(win, v, i);
			if ((!invert && r) || (invert && !r)) {
				b.push(v);
			}
		});
		return b;
	},

	map: function (a, cb) {
		var b = [], me = this;
		this.each(a, function (i, v) {
			var r = cb.call(win, v, i);
			if (me.isArray(r)) {
				b = b.concat(r);
			} else if (r != null) {
				b.push(r);
			}
		});
		return b;
	},

	inArray: function (v, a) {
		var r = -1;
		this.each(a, function (i, w) {
			if (w === v) {
				r = i;
				return false;
			}
			return true;
		});
		return r;
	},

	merge: function (a, b) {
		this.each(b, function (i, v) { a.push(v); });
		return a;
	},

	unique: function (a) {
		var b = [], me = this;
		this.each(a, function (i, v) {
			if (me.inArray(v, b) == -1) {
				b.push(v);
			}
		});
		return b;
	},

	// test operations
	isArray: function (o) { return (o instanceof Array); },
	isFunction: function (o) { return (typeof o == 'function'); },

	// string operations
	trim: function (s) { return String(s).replace(/^\s+/, '').replace(/\s+$/, ''); }
});



// browser and feature detection

(function () {
	var div = doc.createElement('div'), o;

	div.innerHTML = ' <a href="#" style="color: black;">a</a>';
	o = div.getElementsByTagName('a')[0];
	ds.support = {
		cssFloat: (typeof div.style.cssFloat == 'string'),
		hrefNormalized: (o.getAttribute('href') == '#'),
		leadingWhitespace: (div.firstChild != o),
		noCloneEvent: true,
		opacity: (typeof div.style.opacity == 'string'),
		style: (typeof o.getAttribute('style') == 'string')
	};

	if (o.attachEvent && o.fireEvent) {
		o.attachEvent('onclick', function () {
			ds.support.noCloneEvent = false;
			o.detachEvent('onclick', arguments.callee);
		});
		o.cloneNode(true).fireEvent('onclick');
	}

	div.innerHTML = '<link />';
	ds.support.htmlSerialize = (div.getElementsByTagName('link').length == 1);

	div.innerHTML = '<object><param name="p" /></object>';
	o = div.getElementsByTagName('object')[0];
	ds.support.objectAll = (o.getElementsByTagName('*').length == 1);

	o = doc.createElement('script');
	try {
		o.appendChild(doc.createTextNode(''));
		ds.support.scriptEval = true;
	} catch (e) {
		ds.support.scriptEval = false;
	}

	div.innerHTML = '<table></table>';
	ds.support.tbody = (div.getElementsByTagName('tbody').length == 0);

	div = o = null;
})();

function boxModelTest() {
	ds.boxModel = ds.support.boxModel = (doc.compatMode == 'CSS1Compat');
}

// distilled from http://www.quirksmode.org/js/detect.html
ds.browser = {
	safari: false,
	opera: false,
	msie: false,
	mozilla: false,
	version: ''
};
ds.each([
	{ flag: 'safari', str: 'AppleWebKit' },
	{ flag: 'opera', prop: win.opera, ver: 'Opera' },
	{ flag: 'msie', str: 'MSIE' },
	{ flag: 'mozilla', str: 'Gecko', ver: 'rv' }
], function () {
	var ua = navigator.userAgent, str = this.str, i = ua.indexOf(this.ver || str);
	if (!this.prop && ua.indexOf(str) == -1) {
		return true;
	}
	ds.browser[this.flag] = true;
	if (i > -1) {
		ds.browser.version = ua.substring(i).match(/[\d.]+/)[0];
	}
	return false;
});

/*
 * attributes
 */

var attrMap = (ds.browser.msie) ?
	// list from http://webbugtrack.blogspot.com/2007/08/bug-242-setattribute-doesnt-always-work.html
	{
		acceptcharset: 'acceptCharset',
		accesskey: 'accessKey',
		allowtransparency: 'allowTransparency',
		bgcolor: 'bgColor',
		cellpadding: 'cellPadding',
		cellspacing: 'cellSpacing',
		'class': 'className',
		colspan: 'colSpan',
		defaultchecked: 'defaultChecked',
		defaultselected: 'defaultSelected',
		defaultvalue: 'defaultValue',
		'for': 'htmlFor',
		frameborder: 'frameBorder',
		hspace: 'hSpace',
		longdesc: 'longDesc',
		maxlength: 'maxLength',
		marginwidth: 'marginWidth',
		marginheight: 'marginHeight',
		noresize: 'noResize',
		noshade: 'noShade',
		readonly: 'readOnly',
		rowspan: 'rowSpan',
		tabindex: 'tabIndex',
		valign: 'vAlign',
		vspace: 'vSpace'
	} :
	{
		className: 'class',
		htmlFor: 'for'
	};

extend(ds.fn, {
	// attr
	attr: function (o, v) {
		var self = this, l = arguments.length, el;
		if (l == 0) {
			return this;
		}
		if (typeof o != 'string') {
			ds.each(o, function (i, v) { self.attr(i, v); });
			return this;
		}
		if (l == 1 && this.length == 0) {
			return undef;
		}
		el = this[0];
		o = (attrMap[o]) ? attrMap[o] : o;
		if (o != 'style' || ds.support.style) {
			if (l == 1) {
				v = el.getAttribute(o);
				return (v != null) ? v : el[o];
			} else {
				this.each((ds.isFunction(v)) ?
					function (i) { this.setAttribute(o, v.call(this, i)); } :
					function () { this.setAttribute(o, v); });
			}
		} else {
			if (l == 1) {
				return el.style.cssText;
			} else {
				this.each((ds.isFunction(v)) ?
					function (i) { this.style.cssText = v.call(this, i); } :
					function () { this.style.cssText = v; });
			}
		}
		return this;
	},
	removeAttr: function (n) {
		n = (attrMap[n]) ? attrMap[n] : n;
		this.each(function () { this.removeAttribute(n); });
		return this;
	},

	// class
	addClass: function (c) {
		c = ds.unique(ds.trim(String(c)).replace(/\s+/, ' ').split(' '));
		this.each(function () {
			var el = ds(this), cn = this.className;
			ds.each(c, function (i, v) {
				if (!el.hasClass(v)) {
					cn += ' ' + v;
				}
			});
			this.className = cn;
		});
		return this;
	},
	hasClass: function (c) {
		var re = new RegExp('\\b' + c + '\\b'), r = false;
		this.each(function () {
			if (re.test(this.className)) {
				r = true;
				return false;
			}
			return true;
		});
		return r;
	},
	removeClass: function (c) {
		var res = ds.map(ds.unique(ds.trim(String(c)).replace(/\s+/, ' ').split(' ')), function (v) { return new RegExp('(?:^|\\s+)' + v + '(?:$|\\s+)'); });
		this.each(function () {
			var cn = this.className;
			ds.each(res, function () { cn = cn.replace(this, ' '); });
			this.className = ds.trim(cn);
		});
		return this;
	},
	toggleClass: function (c, s) {
		var l = arguments.length;
		this.each(function () {
			var el = ds(this), add = (l > 1) ? s : !el.hasClass(c);
			el[(add) ? 'addClass' : 'removeClass'](c);
		});
		return this;
	},

	// html
	html: function (s) {
		if (arguments.length == 0) {
			return (this.length > 0) ? this[0].innerHTML : null;
		}
		this.each(function () { this.innerHTML = s; });
		return this;
	},

	// text
	text: function (s) {
		if (arguments.length > 0) {
			return this.html(String(s).replace(/&/g, '&').replace(/</g, '&lt;').replace(/>/g, '>'));
		}
		s = '';
		this.each(function () { s += ds(this).html().replace(/<[^>]*>/g, '').replace(/&/g, '&'); });
		return s;
	},

	// val
	val: function (o) {
		var el, a;
		if (arguments.length == 0) {
			if (this.length == 0) {
				return undef;
			}
			el = this[0];
			return (el.nodeName.toLowerCase() == 'select' && el.multiple) ?
				ds.map(makeArray(el.options), function (o) { return (o.selected) ? o.value : null; }) :
				el.value;
		}
		a = (ds.isArray(o)) ? o : null;
		this.each(function () {
			switch(this.nodeName.toLowerCase()) {
			case 'select':
				ds.each(makeArray(this.options), (a) ?
					function () { this.selected = (ds.inArray(this.value, a) > -1); } :
					function () { this.selected = (this.value == o); });
				break;
			case 'input':
				if ((this.type == 'radio' || this.type == 'checkbox') && a) {
					this.checked = (ds.inArray(this.value, a) > -1);
					break;
				}
			default:
				this.value = o;
				break;
			}
		});
		return this;
	}
});



/*
 * traversing
 */

// this is also used for intermediate DS objects so that they have enough context to perform computations
function prep(n, o, s) {
	return extend((n instanceof DS) ? n : ds(n), {
		selector: (s != null) ? ds.trim(o.selector + s) : '',
		context: o.context,
		prevObject: o
	});
}

function rel(f, rel, all, s) {
	var o = prep(ds.unique(ds.map(this.get(), function (el) { return DOM.nextElement(el, rel, all); })), this, '.' + f + '()');
	return (arguments.length > 3) ? prep(o.filter(String(s)), this, '.' + f + '(' + s + ')') : o;
}

var useChildren;

// test if we can use the .children collection
// broken in safari 2, see https://bugs.webkit.org/show_bug.cgi?id=3330
(function () {
	var div = document.createElement('div');
	div.innerHTML = '<span><span></span></span>';
	useChildren = (!!div.children && (div.children.length == 1));
	div = null;
})();

extend(ds.fn, {
	// filtering

	is: function (s) { return (this.filter(s).length > 0); },
	hasClass: function (c) { return this.is('.' + c); },

	not: function (o) {
		var self = this, l = this.get();
		if (o instanceof DS) {
			o = o.get();
		}
		l = ds.grep(l, (typeof o == 'string') ?
			function (el) { return !prep(el, self).is(o); } :
			(ds.isArray(o)) ?
				function (el) { return (ds.inArray(el, o) == -1); } :
				function (el) { return (el != o); });
		return prep(l, this, (typeof o == 'string') ? '.not(' + o + ')' : null);
	},

	slice: function () {
		return prep(Array.prototype.slice.apply(this.get(), arguments), this, '.slice(' + makeArray(arguments).join(',') + ')');
	},

	eq: function (i) {
		return prep((i >= 0 && i < this.length) ? this[i] : [], this, '.eq(' + i + ')');
	},

	filter: function (o) {
		var c = this.context,
			doc = (c) ?
				(DOM.isDocument(c)) ? c : c.ownerDocument :
				win.doc;
		return (ds.isFunction(o)) ?
			this.map(function () { return (o.apply(this, arguments)) ? this : null; }) :
			prep(sel(o, doc, doc, this.get()), this, '.filter(' + o + ')');
	},

	map: function (fn) {
		// note args reversed
		return prep(ds.map(this.get(), function (el, i) { return fn.call(el, i, el); }), this, '.map(' + fn + ')'); // XXX
	},

	// finding

	next:    function () { return rel.apply(this, ['next',   'nextSibling',     false].concat(makeArray(arguments))); },
	nextAll: function () { return rel.apply(this, ['next',   'nextSibling',     true ].concat(makeArray(arguments))); },
	prev:    function () { return rel.apply(this, ['prev',   'previousSibling', false].concat(makeArray(arguments))); },
	prevAll: function () { return rel.apply(this, ['prev',   'previousSibling', true ].concat(makeArray(arguments))); },
	parent:  function () { return rel.apply(this, ['parent', 'parentNode',      false].concat(makeArray(arguments))); },
	parents: function () { return rel.apply(this, ['parent', 'parentNode',      true ].concat(makeArray(arguments))); },

	siblings: function (s) {
		var self = this, o = prep(ds.unique(ds.map(this.get(), function (el) {
			var n = prep(el, self);
			return n.prevAll().get().concat(n.nextAll().get());
		})), this, '.siblings()');
		return (arguments.length > 0) ? prep(o.filter(String(s)), this, '.siblings(' + s + ')') : o;
	},

	children: function (s) {
		var D = DOM, o = prep(ds.unique(ds.map(this.get(), function (el) {
			var c = el.children;
			if (c && useChildren) {
				return makeArray(c);
			}
			if (!(c = el.childNodes)) {
				return null;
			}
			return ds.grep(makeArray(c), function (n) { return D.isElement(n); });
		})), this, '.children()');
		return (arguments.length > 0) ? prep(o.filter(String(s)), this, '.children(' + s + ')') : o;
	},

	closest: function (s) {
		var self = this;
		// jQuery doesn't do unique() across all result elements
		return prep(ds.map(this.get(), function (el) {
			var o = prep(el, self), a = o.parents(s);
			return (o.is(s)) ?
				el :
				(((a = o.parents(s)).length > 0) ? a[0] : null);
		}), this);
	},

	add: function (o) {
		var c = this.content;
		return prep(ds.unique(this.get().concat(ds(o, c).get())), this, (typeof o == 'string') ? ', ' + o : null);
	},

	find: function (s) {
		var c = this.context;
		return prep(ds.unique(ds.map(this.get(), function (el) { return sel(s, c, el); })), this, ' ' + s);
	},

	offsetParent: function () { return prep((this.length > 0) ? this[0].offsetParent : [], this); },

	contents: function () {
		return prep(ds.unique(ds.map(this.get(), function (el) {
			return (el.nodeName.toLowerCase() == 'iframe') ?
				el.contentDocument :
				((el.childNodes) ? makeArray(el.childNodes) : null);
		})), this, '.contents()');
	},

	// chaining
	andSelf: function () { return prep(ds.unique(ds.merge(this.get(), (this.prevObject) ? this.prevObject.get() : [])), this); },
	end: function () { return (this.prevObject || ds([])); }
});



/*
 * Manipulation
 */

function insertNodes(nodes, parent, next) {
	var f = (parent.ownerDocument || doc).createDocumentFragment();
	ds.each(nodes, function () { f.appendChild(this); });
	parent.insertBefore(f, next);
}

function innerNodes(outer) {
	var D = DOM;
	return ds.map(outer, function (el) {
		var cur = el, next = cur.firstChild;
		while (next) {
			if (!D.isElement(next)) {
				next = D.nextElement(next, 'nextSibling');
			}
			if (next) {
				cur = next;
				next = next.firstChild;
			}
		}
		return cur;
	});
}

extend(ds.fn, {
	// copying
	clone: function (e) {
		var P = PROP,
			h = handleEvent,
			els = prep(ds.map(this.get(), function (el) { return el.cloneNode(true); }), this),
			copy = els.find('*').andSelf().get(),
			orig;

		ds.each(copy, function () { this[P] = null; });

		// XXX todo: if noCloneEvent == false then ?

		if (e) {
			orig = this.find('*').andSelf().get();
			ds.each(orig, function (i) {
				var events = ds(this).data('events'), c;
				if (!events) {
					return;
				}
				c = copy[i];
				ds.each(events, function (type, ed) {
					if (ed && ed.order && ed.order.length > 0) {
						c['on' + type] = h;
					}
				});
				ds(c).data('events', extend({}, events));
			});
		}

		return els;
	},

	// removing
	empty: function () {
		this.each(function () {
			var el = this;
			ds(el).find('*').unbind();
			ds.each(makeArray(el.childNodes).reverse(), function () { el.removeChild(this); });
		});
		return this;
	},
	remove: function (s) {
		((arguments.length > 0) ? this.filter(String(s)) : this).each(function () {
			var p = this.parentNode;
			ds(this).find('*').andSelf().unbind();
			if (p) {
				p.removeChild(this);
			}
		});
		return this;
	},

	// inserting inside
	append: function (o) {
		var ins = insertNodes, l = this.length;
		if (l == 0) {
			return this;
		}
		o = ds(o);
		if (l == 1) {
			ins(o.get(), this[0], null);
		} else {
			this.each(function () { ins(o.clone(true).get(), this, null); });
			o.remove();
		}
		return this;
	},
	prepend: function (o) {
		var ins = insertNodes, l = this.length;
		if (l == 0) {
			return this;
		}
		o = ds(o);
		if (l == 1) {
			ins(o.get(), this[0], this[0].firstChild);
		} else {
			this.each(function () { ins(o.clone(true).get(), this, this.firstChild); });
			o.remove();
		}
		return this;
	},

	// inserting outside
	after: function (o) {
		var ins = insertNodes, l = this.length;
		if (l == 0) {
			return this;
		}
		o = ds(o);
		if (l == 1) {
			ins(o.get(), this[0].parentNode, this[0].nextSibling);
		} else {
			this.each(function () { ins(o.clone(true).get(), this.parentNode, this.nextSibling); });
			o.remove();
		}
		return this;
	},
	before: function (o) {
		var ins = insertNodes, l = this.length;
		if (l == 0) {
			return this;
		}
		o = ds(o);
		if (l == 1) {
			ins(o.get(), this[0].parentNode, this[0]);
		} else {
			this.each(function () { ins(o.clone(true).get(), this.parentNode, this); });
			o.remove();
		}
		return this;
	},

	// inserting around
	wrap: function (o) {
		var inn = innerNodes, ins = insertNodes;
		o = ds(o);
		this.each(function () {
			var w = o.clone(true).get(),
				p = this.parentNode,
				s = this.nextSibling;
			ds(inn(w)).append(this);
			ins(w, p, s);
		});
		return this;
	},
	wrapAll: function (o) {
		var inn = innerNodes, ins = insertNodes, w, p, s;
		if (this.length == 0) {
			return this;
		}
		w = ds(o).get(),
		p = this[0].parentNode;
		s = this[0].nextSibling;
		ds(inn(w)).append(this);
		ins(w, p, s);
		return this;
	},
	wrapInner: function (o) {
		var inn = innerNodes, ins = insertNodes;
		o = ds(o);
		this.each(function () {
			var w = o.clone(this).get();
			ds(inn(w)).append(makeArray(this.childNodes));
			ins(w, this, null);
		});
		return this;
	},

	// replacing
	replaceWith: function (o) { return this.after(o).remove(); },
	replaceAll: function (s) {
		var self = this, orig = ds(s).get(), els = [];
		if (orig.length > 0) {
			ds(orig.shift()).replaceWith(this);
			els = els.concat(this.get());
			ds.each(orig, function () {
				var o = self.clone(true);
				ds(this).replaceWith(o);
				els = els.concat(o.get());
			});
		}
		return prep(els, this, '.replaceAll(' + s + ')');
	}
});

ds.each([
	{ name: 'appendTo', fn: 'append' },
	{ name: 'prependTo', fn: 'prepend' },
	{ name: 'insertAfter', fn: 'after' },
	{ name: 'insertBefore', fn: 'before' }
], function () {
	var fn = this.fn;
	ds.fn[this.name] = function (s) {
		ds(s)[fn](this);
		return this;
	};
});



/*
 * css
 */

function toCamelCase(s) {
	return String(s).replace(/-./g, function (s) { return s.charAt(1).toUpperCase(); });
}
function toCSSCase(s) {
	return String(s).replace(/[A-Z]/g, function (s) { return '-' + s.toLowerCase(); });
}

function toPixelValue(el, prop, v) {
	var a = /^(-?)(\d*\.?\d*)(em|ex|in|cm|mm|pt|pc|%)$/.exec(v), sign, val, unit, s, rs, fs;
	if (!a) {
		return v;
	}
	sign = a[1];
	val = parseFloat(a[2]);
	unit = a[3];
	if (prop == 'fontSize') {
		// font size of el is relative to font size of el's parent
		el = el.parentNode;
	}
	if (unit == '%') {
		// can't compute percentages since we're setting 'left'
		val *= 0.01;
		unit = 'em';
	}
	// adapted from http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
	s = el.style.left;
	rs = el.runtimeStyle.left;
	el.runtimeStyle.left = el.currentStyle.left;
	el.style.left = '1' + unit;
	fs = parseFloat(el.style.pixelLeft);
	el.style.left = s;
	el.runtimeStyle.left = rs;
	return sign + (val * fs) + 'px';
}

var numProps = {
		zIndex: true,
		lineHeight: true,
		opacity: true
	},
	ieProgid = /\bprogid:DXImageTransform\.Microsoft\.Alpha\([^)]*\)/gi,
	ieAlpha = /\balpha\([^)]*\)/gi,
	ieOpacity = /opacity=([\d.]+)/i;

function getBoxDetails(el, width) {
	var o = ds(el),
		l = (width) ? 'left' : 'top',
		r = (width) ? 'right' : 'bottom',
		a = [
			o.css('margin-' + l),
			(o.css('border-' + l + '-style') != 'none') ? o.css('border-' + l + '-width') : 0,
			o.css('padding-' + l),
			(width) ? el.offsetWidth : el.offsetHeight,
			o.css('padding-' + r),
			(o.css('border-' + r + '-style') != 'none') ? o.css('border-' + r + '-width') : 0,
			o.css('margin-' + r)
		];
	a = ds.map(a, function (v) { return parseFloat(v); });
	a[3] = a[3] - a[1] - a[2] - a[4] - a[5];
	return a;
}

function dim(width, v) {
	var s = (width) ? 'Width' : 'Height', el, w;
	if (arguments.length == 1) {
		if (this.length == 0) {
			return null;
		}
		el = this[0];
		if (el == win) {
			return (el['inner' + s] ||
				(doc.documentElement && doc.documentElement['client' + s]) ||
				(doc.body && doc.body['client' + s]) ||
				0);
		} else if (DOM.isDocument(el)) {
			return ((el.documentElement && el.documentElement['scroll' + s]) ||
				(el.body && el.body['scroll' + s]) ||
				0);
		}
		return getBoxDetails(el, width)[3];
	}
	v = ds.trim(String(v));
	if (/[\d.]$/.test(v)) {
		v += 'px';
	}
	s = s.toLowerCase();
	if (ds.support.boxModel) {
		this.each(function () { ds(this).css(s, v); });
	} else {
		w = parseFloat(v);
		this.each(function () {
			var el = ds(this), a;
			el.css(s, v);
			a = getBoxDetails(this, width);
			el.css(s, (w - a[3] + w) + 'px');
		});
	}
	return this;
}

function innerDim(width) {
	var a = getBoxDetails(this, width);
	return a[2] + a[3] + a[4];
}

function outerDim(width, margin) {
	var a = getBoxDetails(this, width);
	return a[1] + a[2] + a[3] + a[4] + a[5] + ((margin) ? a[0] + a[6] : 0);
}

function scrollPos(left, v) {
	var s = (left) ? 'Left' : 'Top';
	if (arguments.length == 1) {
		return (this.length > 0) ? this[0]['scroll' + s] : null;
	}
	this.each(function () {
		this['scroll' + s] = v;
	});
	return this;
}

extend(ds.fn, {
	// css
	css: function (o, v) {
		var self = this, l = arguments.length, el, dv, progid, alpha, opacity, f;
		if (l == 0) {
			return this;
		}
		if (typeof o != 'string') {
			ds.each(o, function (i, v) { self.css(i, v); });
			return this;
		}
		if (l == 1 && this.length == 0) {
			return undef;
		}
		if (o != 'opacity' || ds.support.opacity) {
			if (l == 1) {
				// adapted from http://www.quirksmode.org/dom/getstyles.html
				el = this[0];
				dv = (el.ownerDocument || doc).defaultView;
				if (dv && dv.getComputedStyle) {
					return dv.getComputedStyle(el, null).getPropertyValue(toCSSCase(o));
				}
				if (el.currentStyle) {
					o = (o == 'float') ?
						(ds.support.cssFloat) ? 'cssFloat' : 'styleFloat' :
						toCamelCase(o);
					return toPixelValue(el, o, el.currentStyle[o]);
				}
				return undef;
			} else {
				o = (o == 'float') ?
					(ds.support.cssFloat) ? 'cssFloat' : 'styleFloat' :
					toCamelCase(o);
				v = String(v);
				if (!numProps[o] && /^-?\d*\.?\d*$/.test(v) && /\d/.test(v)) {
					v += 'px';
				}
				this.each(function () { this.style[o] = v; });
			}
		} else {
			progid = ieProgid;
			alpha = ieAlpha;
			opacity = ieOpacity;
			if (l == 1) {
				el = this[0];
				v = 100;
				// search for the lowest opacity value
				f = el.style.filter;
				ds.each([progid, alpha], function () {
					var a = f.match(this);
					ds.each(a, function (i, s) {
						var b = s.match(opacity), op = parseFloat((b || [])[1]);
						if (op < v) {
							v = op;
						}
					});
					f = f.replace(this, '');
				});
				return v / 100;
			} else {
				v = ' alpha(opacity=' + (parseFloat(v) * 100) + ')';
				this.each(function () {
					var s = this.style;
					s.filter = ds.trim(s.filter.replace(progid, '').replace(alpha, '') + v).replace(/\s+/g, ' ');
					s.zoom = '1'; // force hasLayout to apply filter
				});
			}
		}
		return this;
	},

	// height and width
	width:  function () { return dim.apply(this, [true ].concat(makeArray(arguments))); },
	height: function () { return dim.apply(this, [false].concat(makeArray(arguments))); },
	innerWidth:  function () { return (this.length > 0) ? innerDim.call(this[0], true ) : null; },
	innerHeight: function () { return (this.length > 0) ? innerDim.call(this[0], false) : null; },
	outerWidth:  function () { return (this.length > 0) ? outerDim.apply(this[0], [true ].concat(makeArray(arguments))) : null; },
	outerHeight: function () { return (this.length > 0) ? outerDim.apply(this[0], [false].concat(makeArray(arguments))) : null; },

	// positioning
	scrollLeft: function () { return scrollPos.apply(this, [true ].concat(makeArray(arguments))); },
	scrollTop:  function () { return scrollPos.apply(this, [false].concat(makeArray(arguments))); }
});



/*
 * events
 * adapted from Dean Edwards
 * http://dean.edwards.name/weblog/2005/10/add-event/
 * http://dean.edwards.name/weblog/2005/10/add-event2/
 */


// a counter used to create unique IDs
var FNID = 1;

ds.Event = function (o, el) {
	var me = arguments.callee, e = this;

	if (e == win) {
		return new me(o);
	}
	if (o instanceof me) {
		return extend(e, o);
	}
	if (typeof o == 'string') {
		e.type = o;
		e.target = el;
		return e;
	}

	ds.each([
		'type',
		'currentTarget',
		'keyCode',
		'charCode',
		'which',
		'altKey',
		'ctrlKey',
		'metaKey',
		'shiftKey',
		'button',
		'clientX',
		'clientY',
		'screenX',
		'screenY',
		'result'
	], function (i, v) { e[v] = o[v]; });

	extend(e, {
		originalEvent: o,
		target: (o.target) ?
			(DOM.isElement(o.target)) ? o.target : o.target.parentNode :
			o.srcElement,
		relatedTarget: (o.relatedTarget || ((o.type == 'mouseover') ? o.fromElement : o.toElement)),
		timeStamp: (o.timeStamp || (new Date()).getTime()),
		// pageX / pageY from http://www.quirksmode.org/js/events_properties.html
		pageX: (o.pageX || o.pageY) ?
			o.pageX :
			(o.clientX || o.clientY) ? o.clientX + doc.body.scrollLeft + doc.documentElement.scrollLeft : 0,
		pageY: (o.pageX || o.pageY) ?
			o.pageY :
			(o.clientX || o.clientY) ? o.clientY + doc.body.scrollTop + doc.documentElement.scrollTop : 0,
		cancelled: (o.returnValue === false),
		stopped: (o.cancelBubble === true),
		halted: (o.halted === true)
	});

	return e;
};

ds.Event.prototype = {
	cancelled: false,
	stopped: false,
	halted: false,

	preventDefault: function () {
		var oe = this.originalEvent;
		this.cancelled = true;
		if (!oe) {
			return;
		}
		oe.returnValue = false;
		if (oe.preventDefault) {
			oe.preventDefault();
		}
	},
	isDefaultPrevented: function () { return this.cancelled; },

	stopPropagation: function () {
		var oe = this.originalEvent;
		this.stopped = true;
		if (!oe) {
			return;
		}
		oe.cancelBubble = true;
		if (oe.stopPropagation) {
			oe.stopPropagation();
		}
	},
	isPropagationStopped: function () { return this.stopped; },

	stopImmediatePropagation: function () {
		this.stopPropagation();
		this.halted = true;
		if (!oe) {
			return;
		}
		oe.halted = true;
	},
	isImmediatePropagationStopped: function () { return this.halted; }
};

function handleEvent(e) {
	var el = this, args = makeArray(arguments),
		type, isbeforeunload, events, ed, fns, order, ret;

	// grab the event object (IE uses a global event object)
	e = (e || win.event);
	if (!e.currentTarget) {
		e.currentTarget = el;
	}
	if (e.timeStamp == null) {
		e.timeStamp = (new Date()).getTime();
	}
	e = new ds.Event(e);
	args[0] = e;

	type = e.type;
	isbeforeunload = (this == win && type == 'beforeunload');
	if (!isbeforeunload) {
		ret = true;
	}

	// get a reference to the hash table of event handlers
	if (!(events = ds(this).data('events')) || !(ed = events[type])) {
		return ret;
	}
	fns = ed.fns;
	order = ed.order;

	// execute each event handler
	ds.each(order, function (i, id) {
		var f = fns[id], r;
		e.data = f.data;
		r = f.fn.apply(el, args);

		if (!isbeforeunload) {
			if (e.isDefaultPrevented()) {
				ret = false;
			}
			if (r === false) {
				e.preventDefault();
				e.stopPropagation();
				ret = false;
			}

		} else {
			if (typeof r == 'string') {
				ret = e.returnValue = r;
			}
		}

		if (typeof r != 'undefined') {
			e.result = r;
			e.originalEvent.result = r;
		}

		return !e.isImmediatePropagationStopped();
	});

	return ret;
}

function triggerEvent(els, doDefault, o, d) {
	var t, ont;
	if (!(o instanceof ds.Event) && typeof o != 'string') {
		o = extend(new ds.Event(), o);
	}
	d = (d || []);
	t = (typeof o == 'string') ? o : o.type;
	ont = 'on' + t;

	els.each(function () {
		var el = this, e = new ds.Event(o, el), a = [e].concat(d), r;

		if (t == 'click' && doDefault && el.click) {
			// this bubbles on its own
			el.click();
			return;
		}

		while (el) {
			r = true;
			if (el[ont]) {
				r = el[ont].apply(el, a);
			}
			if (r === false) {
				break;
			}
			if (doDefault && !e.isDefaultPrevented() && el[t]) {
				el[t]();
			}
			if (e.isPropagationStopped()) {
				break;
			}
			el = el.parentNode;
		}
	});

	return els;
}

function mouseenter(e) {
	var contains = DOM.contains, from = e.relatedTarget, to = e.target, o, d;
	if ((from == this || contains(this, from)) || !(to == this || contains(this, to))) {
		return;
	}
	o = extend(new ds.Event(e), { type: 'mouseenter', originalEvent: null });
	o.stopPropagation();
	d = makeArray(arguments);
	d.shift();
	ds(this).trigger(o, d);
}

function mouseleave(e) {
	var contains = DOM.contains, from = e.target, to = e.relatedTarget, o, d;
	if (!(from == this || contains(this, from)) || (to == this || contains(this, to))) {
		return;
	}
	o = extend(new ds.Event(e), { type: 'mouseleave', originalEvent: null });
	o.stopPropagation();
	d = makeArray(arguments);
	d.shift();
	ds(this).trigger(o, d);
}

var nativeMouseenter;

// based on http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
(function () {
	var div = document.createElement('div');
	nativeMouseenter = ('onmouseenter' in div);
	if (!nativeMouseenter) {
		div.setAttribute('onmouseenter', 'return;');
		nativeMouseenter = ds.isFunction(div.onmouseenter);
	}
	div = null;
})();

// document ready based on
// http://dean.edwards.name/weblog/2006/06/again/
// http://javascript.nwbox.com/ContentLoaded/
// we'll stick with browser detects for now, since to feature detect these feels dangerous
function setupReady() {
	var fn = ready;
	if (ds._readying) {
		return;
	}
	// mozilla, newer webkit, opera
	if (doc.addEventListener) {
		doc.addEventListener('DOMContentLoaded', fn, false);
	}
	// older webkit
	if (ds.browser.safari && parseFloat(ds.browser.version) < 525.13) {
		ds._readyTimer = setInterval(function () {
			if (/loaded|complete/.test(document.readyState)) {
				fn(); // call the onload handler
			}
		}, 0);
	}
	// ie
	if (ds.browser.msie) {
		ds._readyRSC = function (e) {
			if (doc.readyState == 'complete') {
				fn();
			}
		};
		doc.attachEvent('onreadystatechange', ds._readyRSC);
		if (win == win.top) {
			ds._readyTimer = setInterval(function () {
				var done = false;
				try {
					doc.documentElement.doScroll('left');
					done = true;
				} catch (e) {
					done = false;
				}
				if (done) {
					fn();
				}
			}, 0);
		}
	}
	// fallback
	ds(window).load(fn);
	ds._readying = true;
}

function ready() {
	var fn = arguments.callee;
	if (ds._readied) {
		return;
	}
	// mozilla, newer webkit, opera
	if (doc.addEventListener) {
		doc.removeEventListener('DOMContentLoaded', fn, false);
	}
	// older webkit
	if (ds.browser.safari && parseFloat(ds.browser.version) < 525.13) {
		clearInterval(ds._readyTimer);
		ds._readyTimer = null;
	}
	// ie
	if (ds.browser.msie) {
		doc.detachEvent('onreadystatechange', ds._readyRSC);
		ds._readyRSC = null;
		if (win == win.top) {
			clearInterval(ds._readyTimer);
			ds._readyTimer = null;
		}
	}
	// fallback
	ds(window).unbind('load', fn);
	ds._readied = true;

	// note: we pass an event object instead of the dollar sign object
	// this is different that with jQuery
	ds(doc).trigger('ready');
}

ds.fn.extend({
	// event handling
	bind: function (t, o, fn) {
		var P = PROP, h = handleEvent, u = {}, hasDoc = false, id;
		ds.each(ds.trim(String(t)).replace(/\s+/, ' ').split(' '), function (i, v) { u[v] = true; });
		if (arguments.length < 3) {
			fn = o;
			o = undef;
		}
		// assign each event handler a unique ID
		id = (fn[P] || (fn[P] = FNID++));
		this.each(function () {
			var elem = this, el = ds(this), events = el.data('events');
			// create a hash table of event types for the element
			if (!events) {
				el.data('events', (events = {}));
			}
			ds.each(u, function (type) {
				// create a hash table of event handlers for each element/event pair
				var ed = (events[type] || (events[type] = { fns: {}, order: [] })),
					fns = ed.fns,
					order = ed.order,
					g = elem['on' + type],
					i;
				if (g != h) {
					// store the existing event handler (if there is one)
					if (g) {
						i = (g[P] || (g[P] = FNID++));
						if (!fns[i]) {
							fns[i] = { fn: g };
							order.push(i);
						}
					}
					// assign a global event handler to do all the work
					elem['on' + type] = h;
				}
				// store the event handler in the hash table
				if (!fns[id]) {
					fns[id] = { fn: fn, data: o };
					order.push(id);
				}
			});
			if (this == doc) {
				hasDoc = true;
			}
		});
		if (!nativeMouseenter) {
			if (u.mouseenter) {
				this.bind('mouseover', mouseenter);
			}
			if (u.mouseleave) {
				this.bind('mouseout', mouseleave);
			}
		}
		if (hasDoc && u.ready) {
			setupReady();
		}
		return this;
	},

	one: function () {
		var args = makeArray(arguments), fn = args.pop();
		args.push(function (e) {
			ds(this).unbind(e.type, arguments.callee);
			return fn.apply(this, arguments);
		});
		return this.bind.apply(this, args);
	},

	unbind: function (t, fn) {
		var h = handleEvent, enter = mouseenter, leave = mouseleave, u = {}, els, id;
		ds.each(ds.trim(String(t)).replace(/\s+/, ' ').split(' '), function (i, v) { u[v] = true; });
		switch (arguments.length) {
		case 0:
			this.each(function () {
				var el = ds(this), events = el.data('events'), type;
				if (!events) {
					return;
				}
				for (type in events) {
					if (this['on' + type] == h) {
						this['on' + type] = null;
					}
				}
				el.removeData('events');
			});
			break;
		case 1:
			els = [];
			this.each(function () {
				var elem = this, el = ds(this), events = el.data('events');
				if (!events) {
					return;
				}
				ds.each(u, function (type) {
					if (elem['on' + type] == h) {
						elem['on' + type] = null;
					}
					delete events[type];
				});
				els.push(this);
			});
			if (!nativeMouseenter) {
				if (u.mouseover && !u.mouseenter) {
					ds.each(els, function () {
						var el = ds(this), events = el.data('events');
						if (events.mouseenter && events.mouseenter.order.length > 0) {
							el.bind('mouseover', enter);
						}
					});
				}
				if (u.mouseout && !u.mouseleave) {
					ds.each(els, function () {
						var el = ds(this), events = el.data('events');
						if (events.mouseleave && events.mouseleave.order.length > 0) {
							el.bind('mouseout', leave);
						}
					});
				}
			}
			break;
		default:
			if (!(id = fn[PROP])) {
				break;
			}
			this.each(function () {
				var el = ds(this), events = el.data('events');
				if (!events) {
					return;
				}
				ds.each(u, function (type) {
					var ed = events[type], fns, order, i;
					if (!ed) {
						return;
					}
					fns = ed.fns;
					order = ed.order;
					if (!fns[id] || (i = ds.inArray(id, order)) == -1) {
						return;
					}
					// delete the event handler from the hash table
					delete fns[id];
					order.splice(i, 1);
				});
			});
			break;
		}
		return this;
	},

	trigger: function (e, d) { return triggerEvent(this, true, e, d); },
	triggerHandler: function (e, d) { return triggerEvent(this, false, e, d); },

	// interaction helpers
	hover: function (over, out) {
		return this.mouseenter(over).mouseleave(out);
	},
	toggle: function () {
		var fns = makeArray(arguments), l = fns.length, i = 0;
		if (l == 0) {
			return this;
		}
		return this.click(function () {
			fns[i].apply(this, arguments);
			i = (i + 1) % l;
		});
	}
});

// event helpers
ds.each([
	'blur',
	'change',
	'click',
	'dblclick',
	'error',
	'focus',
	'keydown',
	'keypress',
	'keyup',
	'select',
	'submit'
], function (i, type) {
	ds.fn[type] = function () {
		return (arguments.length == 0) ? this.trigger(type) : this.bind(type, arguments[0]);
	};
});

ds.each([
	'load',
	'mousedown',
	'mouseenter',
	'mouseleave',
	'mousemove',
	'mouseout',
	'mouseover',
	'mouseup',
	'ready',
	'resize',
	'scroll',
	'unload'
], function (i, type) {
	ds.fn[type] = function (fn) { return this.bind(type, fn); };
});

// XXX todo: clear cache on page unload



ds(boxModelTest);
win.$ = ds;
})();

