/* version : 0.3.1 */

/**
 * Core object
 *
 */
if (typeof window.nhn == 'undefined') {
	window.nhn = new Object;
}

/**
 * 지정된 id 를 가지는 객체를 반환한다.
 * argument를 복수개로 지정하면 배열로 객체를 반환하며,
 * 아이디에 해당하는 객체가 존재하지 않으면 null 을 반환한다.
 * 또한 "<tagName>" 과 같은 형식의 문자열을 입력하면 tagName을 가지는 객체를 생성한다.
 * @id core.$
 * @param {String} 객체의 아이디(복수개 가능)
 * @return element
 */
function $(sID/*, id1, id2*/) {
	var ret = new Array;
	var el  = null;
	var reg = /<([a-z]+|h[1-5])>/i;

	for(var i=0; i < arguments.length; i++) {
		el = arguments[i];
		if (typeof el == "string") {
			if (reg.test(el)) {
				el = document.createElement(RegExp.$1);
			} else {
				el = document.getElementById(el);
			}
		}
		if (el) ret[ret.length] = el;
	}
	return ret.length?((arguments.length>1)?ret:ret[0]):null;
}

/**
 * 클래스 타입을 정의한다. 생성자는 $init 으로 정의한다.
 * @id core.$Class
 * @param {object} 클래스 정의 object
 * @return {class} 클래스 타입
 */
 function $Class(oDef) {
	var typeClass = function() {
		if (typeof this.$super != "undefined") this.$super.$this = this;
		if (typeof this.$init != "undefined") this.$init.apply(this,arguments);
	};

	typeClass.prototype = oDef;
	typeClass.extend = $Class.extend;

	return typeClass;
 }

/**
 * 클래스를 상속한다.
 * 상속된 클래스에서 this.$super.method 로 상위 메소드에 접근할 수도 있으나,
 * this.$super.$super.method 와 같이 한 단계 이상의 부모 클래스에는 접근할 수 없다.
 * @id core.$Class.extend
 * @import core.$Class
 * @param {class} 수퍼 클래스 객체
 * @return {class} 확장된 클래스 타입
 */
 $Class.extend = function(superClass) {
	this.prototype.$super = new Object;

	var superFunc = function(m, func) {
		return function() {
			var f = this.$this[m];
			this.$this[m] = func;
			this.$this[m].apply(this.$this, arguments);
			this.$this[m] = f;
		};
	};

	for(var x in superClass.prototype) {
		if (typeof this.prototype[x] == 'undefined') this.prototype[x] = superClass.prototype[x];
		if (typeof this.prototype[x] != 'function') continue;
		this.prototype.$super[x] = superFunc(x, superClass.prototype[x]);
	}

	return this;
};

/**
 * 지정된 CSS 셀렉터에 해당하는 객체의 배열을 반환한다.
 * 해당하는 객체가 존재하지 않으면 빈 배열을 반환한다.
 * @id core.$$
 * @param {string} CSS 셀렉터
 * @return {array of element} 객체의 배열
 */
function $$(sSelector, oRoot) {
	if (typeof $$._compile == "undefined") {
		var statics = {
			_cache: [],
			_regex: {
				bparse : /([^>|\+|\s]+)\s*([>|\+]?)\s*/g,
				fparse : /\s*([>|\+]?)\s*([^>|\+|\s]+)/g,

				tag : /^([\w-]+)/,
				id : /#([\w-]+)/g,
				cls : /\.([\w-]+)/g,

				pseudo : /:([\w-]+(\(.*\))?)/g,
				pseudoparse : /([\w-]+)(\((.*)\))?/,
				
				attr : /\[([^\]]+)]/g,
				attrparse : /^(\[)([\w-]+)([!|\^|\$|\*]?=)(.*)(\])$/
			},
			_push : function(arr, el) {
				arr[arr.length] = el;
				return arr;
			},
			_next : function(o) {
				while (o && (o = o.nextSibling) && o.nodeType != 1);
				return o;
			},
			_prev : function(o) {
				while (o && (o = o.previousSibling) && o.nodeType != 1);
				return o;
			},
			_index : function(o) {
				var idx = 1;
				for (var child = o.parentNode.firstChild; child && child != o; child = $$._next(child), idx++);
				return idx;
			},
			_pseudo : {
				first_child : function(o) {
					return !($$._prev(o));
				},
				last_child : function(o) {
					return !($$._next(o));
				},
				empty : function(o) {
					return !(o.firstChild);
				},
				contains : function(o, arg) {
					return o.innerHTML.indexOf(arg) > -1;
				}
			},
			_filter : function(selector, backward) {
				var filter = {};
				var declare = {}, cond = [ 'true' ];
				var varname, func = '';
				var id, tag, classes, pseudoes, attres;

				if (id = selector.match($$._regex.id)) {
					if (id[1]) return false;
					filter.id = id[0].substr(1);
				}

				tag = selector.match($$._regex.tag);
				filter.tag = (tag && tag[1]) || '*';

				if (classes = selector.match($$._regex.cls)) {
					for (var cls, i = 0; cls = classes[i]; i++)
						cond.push('/\\b'+cls.substr(1)+'\\b/.test(o.className)');
				}

				if (pseudoes = selector.match($$._regex.pseudo)) {
					for (var pseudo, i = 0; pseudo = pseudoes[i]; i++) {
						pseudo = pseudo.substr(1).match($$._regex.pseudoparse);
						cond.push('$$._pseudo.' + pseudo[1].replace('-', '_') + '(o, "' + pseudo[3] + '")');
					}
				}

				if (attres = selector.match($$._regex.attr)) {
					for (var attr, i = 0; attr = attres[i]; i++) {
						
						attr = attr.match($$._regex.attrparse); // 2, 3, 4
						varname = 'v_' + attr[2];

						if (!declare[attr[2]]) {
							func += 'var ' + varname + ' = o.getAttribute("' + attr[2] + '") || "";\n';
							declare[attr[2]] = true;
						}
						
						switch (attr[3]) {
						case '=':
							cond.push(varname + ' == "' + attr[4] + '"');
							break;
						case '!=':
							cond.push(varname + ' != "' + attr[4] + '"');
							break;
						case '^=':
							cond.push(varname + '.indexOf("' + attr[4] + '") == 0');
							break;
						case '$=':
							cond.push(varname + '.substr(' + varname + '.length - "' + attr[4] + '".length) == "' + attr[4] + '"');
							break;
						case '*=':
							cond.push(varname + '.indexOf("' + attr[4] + '") > -1');
							break;
						default:
							cond.push(varname + ' != null');
						}

					}
				}

				if (backward) {
					if (filter.tag && filter.tag != '*')
						cond.push('(casei ? o.tagName.toLowerCase() == "' + filter.tag.toLowerCase() + '" : o.tagName == "' + filter.tag + '")');
					
					if (filter.id) cond.push('o.id == "' + filter.id + '"');
				}
				
				filter.func = new Function('o', 'casei', func + '\nreturn (' + cond.join(" && ") + ')');
				
				return filter;
			},
			_trace : function(root, o, selectors, idx, casei) {		
				if (idx == -1) return true;
				
				var selector = selectors[idx];
				
				switch (selector.type) {
				case '':
					for (o = o.parentNode; o != root; o = o.parentNode)
						if (selector.filter.func(o, casei))
							if ($$._trace(root, o, selectors, idx - 1, casei))
								return true;
					
					break;
				case '>':
					o = o.parentNode;
					if (o && o != root && selector.filter.func(o, casei))
						if ($$._trace(root, o, selectors, idx - 1, casei))
							return true;
							
					break;
				case '+':
					o = this._prev(o);

					if (o && selector.filter.func(o, casei))
						if ($$._trace(root, o, selectors, idx - 1, casei))
							return true;
					
					break;
				}
				
				return false;
				
			},
			_compile : function(query, bquery, fquery) {
				if (!$$._cache[query]) {
					var cache = { backward : [], forward : [] };

					bquery.replace($$._regex.bparse, function(all, selector, type) {
						$$._push(cache.backward, {
							'type'   : type,
							'filter' : $$._filter(selector, true)
						});
					});

					var i = 0, method;
					var code = [];

					fquery.replace($$._regex.fparse, function(all, type, selector) {
						cache.forward.push({
							'type'   : type,
							'filter' : $$._filter(selector)
						});
						
						switch (type) {
						case '>':
							method = "_getChildren";
							break;
						case '':
							method = "_getOffspring";
							break;
						case '+':
							method = "_getBrother";
							break;
						}
						
						$$._push(code,'result = $$.' + method + '(result, forward[' + (i++) + '].filter, casei);');
						
					});

					$$._push(code,'return result;');
					cache.filter = new Function('result', 'forward', 'casei', code.join('\n'));

					$$._cache[query] = cache;
				}

				return $$._cache[query];
			},
			_getChildren : function(objs, filter, casei) {
				var ret = [];
				var child;
				
				for (var i = 0, obj; obj = objs[i]; i++) {
					
					for (child = obj.firstChild; child; child = $$._next(child))
						if (filter.func(child, casei)) $$._push(ret, child);
					
				}
				
				return ret;
				
			},
			_getOffspring : function(objs, filter, casei) {
				var ret = [];
				var childs;
				
				for (var i = 0, obj; obj = objs[i]; i++) {
					
					childs = obj.getElementsByTagName(filter.tag);
					for (var j = 0, child; child = childs[j]; j++)
						if (filter.func(child, casei)) $$._push(ret, child);
					
				}
				
				return ret;
				
			},
			
			_getBrother : function(objs, filter, casei) {
				var ret = [];
				var child;
				
				for (var i = 0, obj; obj = objs[i]; i++) {
					if (child = $$._next(obj))
						if (filter.func(child, casei)) $$._push(ret, child);
					
				}
				return ret;
			}
		};

		for(var x in statics) {
			$$[x] = statics[x];
		}
	}

	oRoot = $(oRoot || document);

	var parts  = sSelector.match(/(.*#[\w]+[^>|\+|\s]*)([>|\+|\s].*)/) || [];
	var cache  = $$._compile(sSelector, parts[1] || sSelector, parts[2] || '');
	var idx    = cache.backward.length - 1;
	var filter = cache.backward[idx].filter;
	var sands  = [], result = [];
	var casei  = (oRoot == document || (oRoot.ownerDocument || oRoot.document) == document);

	if (filter.id) {
		$$._push(sands, document.getElementById(filter.id));
	} else if (filter.tag == "*") {
		sands = oRoot.all;
	} else {
		sands = oRoot.getElementsByTagName(casei ? filter.tag.toLowerCase() : filter.tag);
	}

	for (var i = 0, sand; sand = sands[i]; i++) {
		if (filter.func(sand, casei))
			if ($$._trace(oRoot, sand, cache.backward, idx - 1, casei))
				$$._push(result, sand);
	}

	return cache.filter(result, cache.forward, casei);
}

/**
 * 주어진 원소를 가진 배열 객체를 만든다.
 * @id core.$A
 * @import core.$A.toArray
 * @param {array} 배열 혹은 배열에 준하는 컬렉션 타입
 * @return {$A}
 */
function $A(array) {
	if (typeof array == 'undefined') array = new Array;
	if (array instanceof $A) return array;
	if (window === this) return new $A(array);

	if (array instanceof Array && !(array.callee && window.opera)) {
		this._array = array;
	} else {
		this._array = new Array;
		for(var i=0; i < array.length; i++) {
			this._array[this._array.length] = array[i];
		}
	}
}

/**
 * 배열의 크기를 반환한다
 * @id core.$A.length
 * @return Number 배열의 크기
 * @
 */
$A.prototype.length = function(nLen, oElem) {
	if (typeof len == "number") {
		var l = this._array.length;
		this._array.length = len;
		
		if (typeof elem != "undefined") {
			for(var i=l; i < len; i++) {
				this._array[i] = elem;
			}
		}
	} else {
		return this._array.length;
	}
};

/**
 * 주어진 원소가 존재하는지 검사한다. 존재하면 true를, 그렇지 않으면 false를 반환한다
 * @id core.$A.has
 * @param {void} 검색할 값
 * @return Boolean
 * @import core.$A.indexOf
 */
$A.prototype.has = function(any) {
	return (this.indexOf(any) > -1);
};

/**
 * 주어진 원소가 배열에 몇 번째 요소로서 존재하는지 반환한다.
 * 배열의 인덱스는 0부터 시작한다. 해당 원소가 존재하지 않으면 -1 을 반환한다.
 * @id core.$A.indexOf
 * @param {void} 검색할 값
 * @return {Number} 검색결과 인덱스 번호
 */
$A.prototype.indexOf = function(any) {
	if (typeof this._array.indexOf != 'undefined') return this._array.indexOf(any);

	for(var i=0; i < this._array.length; i++) {
		if (this._array[i] == any) return i;
	}
	return -1;
};

/**
 * JavaScript 배열 객체를 반환한다
 * @id core.$A.$value
 * @return {Array} JavaScript 배열 객체
 */
$A.prototype.$value = function() {
	return this._array;
};

/**
 * 배열 객체에 엘리먼트를 추가한다.
 * @id core.$A.push
 * @param {void} 추가할 엘리먼트(복수개 가능)
 * @return {Number} 엘리먼트를 추가한 후의 배열 객체 크기
 */
$A.prototype.push = function(element1/*, ...*/) {
	return this._array.push.apply(this._array, $A(arguments).$value());
};

/**
 * 배열의 마지막 엘리먼트를 제거하고 제거된 엘리먼트를 반환한다.
 * @id core.$A.pop
 * @return {void} 제거된 엘리먼트
 */
$A.prototype.pop = function() {
	return this._array.pop();
};

/**
 * 배열의 첫 엘리먼트를 제거하고 제거된 엘리먼트를 반환한다.
 * @id core.$A.shift
 * @return {void} 제거된 엘리먼트
 */
$A.prototype.shift = function() {
	return this._array.shift();
};

/**
 * 주어진 한 개 이상의 엘리먼트를 배열 앞부분에 삽입하고, 해당 배열의 바뀐 크기를 반환한다.
 * @id core.$A.unshift
 * @param {void} 추가할 엘리먼트(복수개 가능)
 * @return {Nmber} 엘리먼트를 추가한 후의 배열 객체 크기
 */
$A.prototype.unshift = function(element1/*, ...*/) {
	return this._array.unshift.apply(this._array, $A(arguments).$value());
};

/**
 * 주어진 콜백함수를 배열의 각 요소에 실행한다.
 * @id core.$A.forEach
 * @import core.$A[Break, Continue]
 */
$A.prototype.forEach = function(callback, thisObject) {
	if (typeof this._array.forEach == 'function') return this._array.forEach.apply(this._array, arguments);

	for(var i=0; i < this._array.length; i++) {
		try {
			callback.call(thisObject, this._array[i], i, this._array);
		} catch(e) {
			if (e instanceof $A.Break) break;
			if (e instanceof $A.Continue) continue;
		}
	}

	return this;
};

/**
 * 주어진 함수를 모든 엘리먼트에 적용하고 이 결과가 적용된 새로운 $A 배열을 반환한다.
 * @id core.$A.map
 */
$A.prototype.map = function(callback, thisObject) {
	if (typeof this._array.map == 'function') return this._array.map.apply(this._array, arguments);

	for(var i=0; i < this._array.length; i++) {
		try {
			this._array[i] = callback.call(thisObject, this._array[i], i, this._array);
		} catch(e) {
			if (e instanceof $A.Break) break;
			if (e instanceof $A.Continue) continue;
		}
	}

	return this;
};

/**
 * 주어진 콜백 함수를 만족시키는 요소만으로 만들어진 새로운 $A 배열을 반환한다.
 * 콜백 함수는 Boolean 값을 반환해야 한다.
 * @id core.$A.filter
 * @import core.$A.forEach
 */
$A.prototype.filter = function(callback, thisObject) {
	var ar = new Array;

	this.forEach(function(v,i,a) {
		if (callback.call(thisObject, v, i, a) === true) {
			ar[ar.length] = v;
		}
	});

	return $A(ar);
};

/**
 * 모든 배열의 원소가 주어진 콜백 함수를 만족시키는지를 검사한다.
 * 콜백함수는 Boolean 값을 반환해야 한다.
 * @id core.$A.every
 * @import core.$A.forEach
 */
$A.prototype.every = function(callback, thisObject) {
	if (typeof this._array.every != 'undefined') return this._array.every(callback, thisObject);

	var result = true;
	this.forEach(function(v, i, a) {
		if (callback.call(thisObject, v, i, a) === false) {
			result = false;
			$A.Break();
		}
	});
	return result;
};

/**
 * 주어진 콜백 함수를 만족시키는 배열의 원소가 존재하는지를 검사한다.
 * 모든 배열의 요소중에서 하나라도 콜백함수를 만족시키면 이 메소드는 true를 반환한다.
 * 콜백함수는 Boolean 값을 반환해야 한다.
 * @id core.$A.every
 * @import core.$A.forEach
 */
$A.prototype.some = function(callback, thisObject) {
	if (typeof this._array.some != 'undefined') return this._array.some(callback, thisObject);

	var result = false;
	this.forEach(function(v, i, a) {
		if (callback.call(thisObject, v, i, a) === true) {
			result = true;
			$A.Break();
		}
	});
	return result;
};

/**
 * 주어진 값을 제외한 새로운 $A배열을 반환한다
 * @id core.$A.refuse
 * @import core.$A.filter
 */
$A.prototype.refuse = function(value) {
	return this.filter(function(v,i,a) { return (v !== value); });
};

/**
 * 주어진 시작 인덱스와 끝 인덱스까지의 배열 요소로 이루어진 새로운 $A 배열을 반환한다.
 * @id core.$A.slice
 */
$A.prototype.slice = function(start, end) {
	var a = this._array.slice.call(this._array, start, end);
	return $A(a);
};

/**
 * 특정 인덱스로부터 주어진 갯수만큼의 배열을 잘라서 반환한다.
 * @id core.$A.splice
 */
$A.prototype.splice = function(index, howMany/*, element*/) {
	var a = this._array.splice.apply(this._array, arguments);
	return $A(a);
};

/**
 * each 혹은 filter 메소드에서 반복구문을 중단한다.
 * @id core.$A.Break
 */
$A.Break = function() {
	if (window === $A) throw new $A.Break;
};

/**
 * each 혹은 filter 메소드에서 현재 인덱스의 반복구문을 건너뛴다.
 * @id core.$A.Continue
 */
$A.Continue = function() {
	if (window === $A) throw new $A.Continue;
};

/**
 * $H 해시 객체를 리턴한다
 * @id core.$H
 */
function $H(hashObject) {
	if (typeof hashObject == "undefined") hashObject = new Object;
	if (hashObject instanceof $H) hashObject = hashObject.toObject();
	if (this === window) return new $H(hashObject);

	this._table = hashObject;
}

/**
 * Object 를 반환한다.
 */
$H.prototype.$value = function() {
	return this._table;
};

/**
 * 해시 객체의 크기를 반환한다.
 * @id core.$H.length
 * @return {Number} 해시의 크기
 */
$H.prototype.length = function() {
	var i = 0;
	for(var k in this._table) {
		if (typeof Object.prototype[k] != 'undeifned' && Object.prototype[k] === this._table[k]) continue;
		i++;
	}

	return i;
};

/**
 * $H 해시 객체의 각 요소에 주어진 콜백함수를 실행한다.
 * @id core.$H.forEach
 * @import core.$H[Break, Continue]
 */
$H.prototype.forEach = function(callback, thisObject) {
	for(var k in this._table) {
		if (typeof Object.prototype[k] != 'undeifned' && Object.prototype[k] === this._table[k]) continue;
		try {
			callback.call(thisObject, this._table[k], k, this._table);
		} catch(e) {
			if (e instanceof $H.Break) break;
			if (e instanceof $H.Continue) continue;
		}
	}
	return this;
};

/**
 * $H 해시 객체에서 필터 콜백함수를 만족하는 객체의 $H 객체를 리턴한다.
 * 콜백함수는 Boolean 값을 반환해야 한다.
 * @id core.$H.map
 * @import core.$H.forEach
 * @return {$H} 해시객체
 */
$H.prototype.filter = function(callback, thisObject) {
	var h = $H();
	this.forEach(function(v,k,o) {
		if(callback.call(thisObject, v, k, o) === true) {
			h.add(k,v);
		}
	});
	return h;
};

/**
 * $H 해시 객체의 각 요소에 주어진 콜백함수를 실행하고, 함수의 반환값을 해당 요소에 대입한다.
 * @id core.$H.map
 * @import core.$H.forEach
 * @return {$H} 해시객체
 */
$H.prototype.map = function(callback, thisObject) {
	var t = this._table;
	this.forEach(function(v,k,o) {
		t[k] = callback.call(thisObject, v, k, o);
	});
	return this;
};

/**
 * 해시 테이블에 값을 추가한다.
 * @id core.$H.add
 * @return {void} 추가된 값
 */
$H.prototype.add = function(key, value) {
	this._table[key] = value;
	return this._table[key];
};

/**
 * 해시 테이블에 존재하는 값을 제거한다.
 * @id core.$H.remove
 * @return {void} 제거된 값
 */
$H.prototype.remove = function(key) {
	if (typeof this._table[key] == "undefined") return null;
	var val = this._table[key];
	delete this._table[key];
	
	return val;
};

/**
 * 해시 테이블에서 주어진 값을 검색하고, 결과가 존재하면 키를 반환한다.
 * 결과가 존재하지 않으면 false 를 반환한다.
 * @id core.$H.search
 */
$H.prototype.search = function(value) {
	var result = false;
	this.forEach(function(v,k,o) {
		if (v === value) {
			result = k;
			$H.Break();
		}
	});
	return result;
};

/**
 * 해시 테이블에 주어진 키가 있는지 확인한다.
 * @id core.$H.hasKey
 * @return {Boolean} 키의 존재 여부
 */
$H.prototype.hasKey = function(key) {
	var result = false;
	
	return (typeof this._table[key] != "undefined");
};

/**
 * 해시 테이블에 주어진 값이 있는지 확인한다.
 * @id core.$H.hasValue
 * @import core.$H.search
 * @return {Boolean} 값의 존재 여부
 */
$H.prototype.hasValue = function(value) {
	return (this.search(value) !== false);
};

/**
 * 값을 기준으로 해시 객체를 정렬하고 현재 객체를 리턴한다.
 * @id core.$H.sort
 * @import core.$H.values, core.$H.search
 * @return {$H} 해시 객체
 */
$H.prototype.sort = function() {
	var o = new Object;
	var a = this.values();
	var k = false;

	a.sort();

	for(var i=0; i < a.length; i++) {
		k = this.search(a[i]);

		o[k] = a[i];
		delete this._table[k];
	}
	
	this._table = o;
	
	return this;
};

/**
 * 키값을 기준으로 해시 객체를 정렬하고 현재 객체를 리턴한다.
 * @id core.$H.ksort
 * @import core.$H.keys
 * @return {$H} 해시 객체
 */
$H.prototype.ksort = function() {
	var o = new Object;
	var a = this.keys();

	a.sort();

	for(var i=0; i < a.length; i++) {
		o[a[i]] = this._table[a[i]];
	}

	this._table = o;

	return this;
};

/**
 * 해시 키의 배열을 반환한다.
 * @id core.$H.keys
 * @return {Array} 해시 키의 배열
 */
$H.prototype.keys = function() {
	var keys = new Array;
	for(var k in this._table) {
		keys.push(k);
	}

	return keys;
};

/**
 * 해시 값의 배열을 반환한다.
 * @id core.$H.values
 * @return {Array} 해시 값의 배열
 */
$H.prototype.values = function() {
	var values = new Array;
	for(var k in this._table) {
		values.push(this._table[k]);
	}

	return values;
};

/**
 * forEach, filter 등의 반복문 도중 실행을 중단할 때 사용한다.
 * @id core.$H.Break
 */
$H.Break = function() {
	if (this === $H) throw new $H.Break;
};

/**
 * forEach, filter 등의 반복문 도중 현재 단계를 건너뛀 때 사용한다.
 * @id core.$H.Continue
 */
$H.Continue = function() {
	if (this === $H) throw new $H.Continue;
};

/**
 * $Element 객체를 반환한다.
 * @id core.$Element
 */
function $Element(el) {
	if (this === window) return new $Element(el);
	if (el instanceof $Element) return el;

	this._element = $(el);
	this.tag = this._element?this._element.tagName.toLowerCase():'';

	this._queue = new Array;
}

/**
 * DOMElement 객체를 반환한다.
 * @id core.$Element.$value
 */
$Element.prototype.$value = function() {
	return this._element;
};

/**
 * 객체의 display style 속성을 조사해서 none 이면 false 를 반환한다.
 * @id core.$Element.visible
 * @import core.$Element.css
 */
$Element.prototype.visible = function() {
	return (this.css("display") != "none");
};

/**
 * 객체가 화면에 보이도록 display style 속성을 변경한다.
 * @id core.$Element.show
 */
$Element.prototype.show = function() {
	var s = this._element.style;
	var b = "block";
	var c = {p:b,div:b,form:b,h1:b,h2:b,h3:b,h4:b,ol:b,ul:b,td:"table-cell",th:"table-cell",li:"list-item",table:"table",thead:"table-header-group",tbody:"table-row-group",tfoot:"table-footer-group",tr:"table-row",col:"table-column",colgroup:"table-column-group",caption:"table-caption"};

	try {
		if(typeof c[this.tag] == "string") {
			s.display = c[this.tag];
		} else {
			s.display = "inline";
		}
	} catch(e) {
		s.display = "block";
	}

	return this;
};

/**
 * 객체가 화면에 보이지 않도록 display style 속성을 변경한다.
 * @id core.$Element.hide
 */
$Element.prototype.hide = function() {
	this._element.style.display = "none";

	return this;
};

/**
 * 객체를 보이거나 감추도록 display 속성을 toggle 한다.
 * @id core.$Element.toggle
 * @import core.$Element[visible, show, hide]
 */
$Element.prototype.toggle = function() {
	this[this.visible()?"hide":"show"]();

	return this;
};

/**
 * 투명도 값을 가져오거나 설정한다. 첫번째 argument가 설정되어있으면 해당값으로 투명도를 설정한다.
 * 투명도 값은 0 ~ 1 사이의 실수값으로 정한다.
 * @id core.$Element.opacity
 * @return {Number} 불투명도 실수값
 */
$Element.prototype.opacity = function(value) {
	var v,e = this._element,b=this.visible();
	if (typeof value == "number") {
		value = Math.max(Math.min(value,1),0);
		
		if (typeof e.filters != "undefined") {
			value = Math.ceil(value*100);
			if (typeof e.filters.alpha != "undefined") {
				e.filters.alpha.opacity = value;
			} else {
				e.style.filter = (e.style.filter + " alpha(opacity=" + value + ")");
			}
		} else {
			e.style.opacity = value;
		}
		
		return value;
	}

	if (typeof e.filters != "undefined") {
		v = (typeof e.filters.alpha == "undefined")?(b?100:0):e.filters.alpha.opacity;
		v = v / 100;
	} else {
		v = parseFloat(e.style.opacity);
		if (isNaN(v)) v = b?1:0;
	}
	
	return v;
};

/**
 * 객체를 Fade-in 효과와 함께 나타나도록 한다.
 * id core.$Element.appear
 * @import core.$Element[visible,opacity,show]
 * @param {Number} duration 나타나는 시간(초단위)
 * @param {Function} 완전히 나타나고 난 후의 콜백 함수
 */
$Element.prototype.appear = function(duration, callback) {
	var self = this;
	var op   = this.opacity();
	
	if (op == 1) return this;
	try { clearTimeout(this._fade_timer); } catch(e){};

	callback = callback || new Function;

	var step = (1-op) / ((duration||0.3)*100);
	var func = function(){
		op += step;
		self.opacity(op);

		if (op >= 1) {
			callback(self);
		} else {
			self._fade_timer = setTimeout(func, 10);
		}
	};

	this.show();
	func();

	return this;
};

/**
 * 객체를 Fade-out 효과와 함께 사라지도록 한다. 완전히 투명해지고 나면 display 속성이 none 으로 변한다.
 * @id core.$Element.disappear
 * @import core.$Element[visible,opacity,hide]
 * @param {Number} duration 사라지는 시간(초단위)
 * @param {Function} 완전히 사라지고 난 후의 콜백 함수
 */
$Element.prototype.disappear = function(duration, callback) {
	var self = this;
	var op   = this.opacity();

	if (op == 0) return this;
	try { clearTimeout(this._fade_timer); } catch(e){};

	callback = callback || new Function;
	
	var step = op / ((duration||0.3)*100);
	var func = function(){
		op -= step;
		self.opacity(op);

		if (op <= 0) {
			self.hide();
			callback(self);
		} else {
			self._fade_timer = setTimeout(func, 10);
		}
	};

	func();

	return this;
};

/**
 * 객체의 CSS 속성을 얻을 수 있다. 단, 첫번째 argument 에 얻을 속성키를 입력해야 한다.
 * 만일, 첫번째 argument 가 Object 혹은 $Hash 타입이면 반대로 CSS를 주어진 값으로 적용한다.
 * @id core.$Element.css
 * @param {String,Object} sName CSS 속성이름 혹은 설정값 객체
 * @param {String} sValue 설정값
 */
$Element.prototype.css = function(sName, sValue) {
	var e = this._element;

	if (typeof sName == "string") {
		var view;

		if (typeof sValue == "string" || typeof sValue == "number") {
			var obj = new Object;
			obj[sName] = sValue;
			sName = obj;
		} else {
			if (e.currentStyle) {
				if (sName == "cssFloat") sName = "styleFloat";
				return e.currentStyle[sName];
			} else if (window.getComputedStyle) {
				if (sName == "cssFloat") sName = "float";
				return document.defaultView.getComputedStyle(e,null).getPropertyValue(sName.replace(/([A-Z])/g,"-$1").toLowerCase());
			}
			
			return e.style[sName];
		}
	}
	
	
	if (typeof $H != "undefined" && sName instanceof $H) {
		sName = sName.$value();
	}

	if (typeof sName == "object") {
		var v, type;

		for(var k in sName) {
			v    = sName[k];
			type = (typeof v);
			if (type != "string" && type != "number") continue;
			if (k == "cssFloat" && navigator.userAgent.indexOf("MSIE") > -1) k = "styleFloat";
			try {
				e.style[k] = v;
			} catch(err) {
				if (k == "cursor" && v == "pointer") {
					e.style.cursor = "hand";
				} else if (("#top#left#right#bottom#").indexOf(k+"#") > 0 && (type == "number" || !isNaN(parseInt(v)))) {
					e.style[k] = parseInt(v)+"px";
				}
			}
		}
	}
	
	return this;
};

/**
 * 객체의 속성을 구하거나 설정한다.
 * @param {String,Object} sName 속성이름 혹은 설정값 객체
 * @param {String} sValue 설정값
 */
$Element.prototype.attr = function(sName, sValue) {
	var e = this._element;

	if (typeof sName == "string") {
		if (typeof sValue != "undefined") {
			var obj = new Object;
			obj[sName] = sValue;
			sName = obj;
		} else {
			if (sName == "class" || sName == "className") return e.className;
			return e.getAttrbute(sName);
		}
	}
	
	if (typeof $H != "undefined" && sName instanceof $H) {
		sName = sName.$value();
	}

	if (typeof sName == "object") {
		for(var k in sName) {
			e.setAttribute(k, sName[k]);
		}
	}
	
	return this;
};

/**
 * 객체의 문서상의 offset 위치값을 반환한다. top, left 값을 전달하면 해당 값으로 위치값을 정의한다.
 * @id core.$Element.offset
 * @import core.$Element.css
 * @param {Number} top 문서 좌상단으로부터의 top 좌표(px)
 * @param {Number} left 문서 좌상단으로부터의 left 좌표(px)
 * @return {TypePos} 문서 좌상단으로부터의 좌표(px)
 */
$Element.prototype.offset = function(top, left) {
	var e = this._element;
	var t = 0, l = 0;

	if (typeof top == "number" && typeof left == "number") {
		// TODO : positioning
		return {top:top, left:left};
	}

	while(typeof e != "undefined" && e != null) {
		t += e.offsetTop;
		l += e.offsetLeft;
		e = e.offsetParent;
	}

	return {top:t, left:l};
};

/**
 * 객체의 픽셀단위 실제 너비를 구하거나 설정한다.
 * @id core.$Element.width
 * @return {Number} 객체의 실제 너비
 */
$Element.prototype.width = function(width) {
	if (typeof width == "number") {
		var e = this._element;
		
		e.style.width = width+"px";
		if (e.offsetWidth != width) {
			e.style.width = (width*2 - e.offsetWidth) + "px";
		}
	}

	return this._element.offsetWidth;
};

/**
 * 객체의 픽셀단위 실제 높이를 구하거나 설정한다.
 * @id core.$Element.height
 * @return {Number} 객체의 실제 녺이
 */
$Element.prototype.height = function(height) {
	if (typeof height == "number") {
		var e = this._element;

		e.style.height = height+"px";
		if (e.offsetWidth != height) {
			e.style.height = (height*2 - e.offsetWidth) + "px";
		}
	}

	return this._element.offsetHeight;
};

/**
 * 지정한 클래스 이름이 설정되어 있는지 확인한다.
 * @id core.$Element.hasClass
 * @param {String} sClass 확인할 클래스 이름
 * @return {Boolean} 클래스 이름 설정 여부
 */
$Element.prototype.hasClass = function(sClass) {
	return (new RegExp("\\b"+sClass+"\\b")).test(this._element.className);
};

/**
 * 지정한 클래스 이름을 추가한다.
 * @id core.$Element.addClass
 * @param {String} sClass 추가할 클래스 이름
 * @return {$Element} 현재의 객체
 */
$Element.prototype.addClass = function(sClass) {
	var e = this._element;
	e.className = e.className.replace(new RegExp("\\b"+sClass+"(\\b)|$"), " "+sClass+"$1");
	return this;
};

/**
 * 지정한 클래스 이름을 제거한다.
 * @id core.$Element.removeClass
 * @param {String} sClass 제거할 클래스 이름
 * @return {$Element} 현재의 객체
 */
$Element.prototype.removeClass = function(sClass) {
	var e = this._element;
	e.className = e.className.replace(new RegExp("\\b"+sClass+"\\b"), " ");
	return this;
};

/**
 * 객체의 text값을 반환한다. sText 값이 설정되면 설정된 값으로 엘리먼트의 text를 변경한다.
 * @id core.$Element.text
 * @param {String} sText 설정할 텍스트
 */
$Element.prototype.text = function(sText) {
	var prop = (typeof this._element.innerText != "undefined")?"innerText":"textContent";

	if (typeof sText == "string") {
		this._element[prop] = sText;
	}

	return this._element[prop];
};

/**
 * 객체 내부의 html을 반환한다. sHTML 값이 설정되면 전달받은 값으로 내부 html을 설정한다.
 * @id core.$Element.html
 * @param {String} sHTML 설정할 HTML 문자열
 * @return {String} 내부 HTML
 */
$Element.prototype.html = function(sHTML) {
	if (typeof sHTML == "string") {
		this._element.innerHTML = sHTML;
	}

	return this._element.innerHTML;
};

/**
 * 객체의 outerHTML 을 반환한다.
 * @id core.$Element.outerHTML
 * @return {String} 외부 HTML
 */
$Element.prototype.outerHTML = function() {
	var e = this._element;
	if (typeof e.outerHTML != "undefined") return e.outerHTML;

	var div = $("<div>");

	this._element.parentNode.insertBefore(div, this._element);
	div.appendChild(this._element);

	var s = div.innerHTML;
	div.parentNode.insertBefore(this._element, div);
	div.parentNode.removeChild(div);

	return s;
};

/**
 * 현재 객체의 마지막 자식노드로 새 객체를 추가한다.
 * @id core.$Element.append
 */
$Element.prototype.append = function(oElement) {
	var o = $Element(oElement).$value();

	this._element.appendChild(o);

	return $Element(o);
};

/**
 * 현재 객체의 첫번째 자식노드로 새 객체를 추가한다.
 * @id core.$Element.prepend
 */
$Element.prototype.prepend = function(oElement) {
	var e = this._element;
	var o = $Element(oElement).$value();

	if (e.childNodes.length > 0) {
		e.insertBefore(o, e.childNodes[0]);
	} else {
		e.appendChild(o);
	}

	return $Element(o);
};

/**
 * 현재의 객체를 다른 노드로 대체한다.
 * @id core.$Element.replace
 */
$Element.prototype.replace = function(oElement) {
	var e = this._element;
	var o = $Element(oElement).$value();

	e.parentNode.insertBefore(o, e);
	e.parentNode.removeChild(e);

	return $Element(o);
};

/**
 * 현재 객체를 지정한 객체의 마지막 자식노드로 추가한다.
 * @id core.$Element.appendTo
 */
$Element.prototype.appendTo = function(oElement) {
	var o = $Element(oElement).$value();

	o.appendChild(this._element);

	return this;
};

/**
 * 현재 객체를 지정한 객체의 첫번째 자식노드로 추가한다.
 * @id core.$Element.prependTo
 */
$Element.prototype.prependTo = function(oElement) {
	var o = $Element(oElement).$value();

	if (o.childNodes.length > 0) {
		o.insertBefore(this._element, o.childNodes[0]);
	} else {
		o.appendChild(this._element);
	}

	return this;
};

/**
 * 현재 객체의 앞에 지정한 객체를 삽입한다.
 * @id core.$Element.before
 */
$Element.prototype.before = function(oElement) {
	var o = $Element(oElement).$value();

	this._element.parentNode.insertBefore(o, this._element);

	return $Element(o);
};

/**
 * 현재 객체의 뒤에 지정한 객체를 삽입한다.
 * @id core.$Element.after
 * @import core.$Element.before
 */
$Element.prototype.after = function(oElement) {
	var o = this.before(oElement);
	o.before(this);

	return o;
};

/**
 * 함수 객체를 리턴한다.
 * @id core.$Fn
 * @param {Function} 함수 객체
 * @import core.$Fn.toFunction
 */
function $Fn(func, thisObject) {
	if (this === window) return new $Fn(func, thisObject);

	this._events = [];
	this._tmpElm = null;

	if (typeof func == "function") {
		this._func = func;
		this._this = thisObject;
	} else if (typeof func == "string" && typeof thisObject == "string") {
		this._func = new Function(func, thisObject);
	}
};

/**
 * Function 객체를 반환한다.
 * @return {Function} 함수 객체
 */
$Fn.prototype.$value = function() {
	return this._func;
};

/**
 * 함수를 thisObject 의 메소드로 묶은 Function 을 반환한다.
 * @id core.$Fn.bind
 * @import core.$A
 */
$Fn.prototype.bind = function() {
	var a = $A(arguments).$value();
	var f = this._func;
	var t = this._this;

	var b = function() {
		var args = $A(arguments).$value();

		// fix opera concat bug
		if (a.length) args = a.concat(args);

		return f.apply(t, args);
	};

	return b;
};

/**
 *
 * @id core.$Fn.bindForEvent
 * @import core.$A
 * @import core.$Event
 */
$Fn.prototype.bindForEvent = function() {
	var a = arguments;
	var f = this._func;
	var t = this._this;
	var m = this._tmpElm || null;

	var b = function(e) {
		var args = $A(a);
		if (typeof e == "undefined") e = window.event;

		if (typeof e.currentTarget == "undefined") {
			try { e.currentTarget = m; } catch(e) {} // hooriza modified
		}

		args.unshift($Event(e));

		return f.apply(t, args.$value());
	};

	return b;
};

/**
 * 함수를 특정 객체의 이벤트에 추가한다
 * @id core.$Fn.attach
 * @import core.$Fn[detach, gc]
 */
$Fn.prototype.attach = function(oElement, sEvent) {
	var f;
	
	if ((oElement instanceof Array) || ($A && (oElement instanceof $A) && (oElement=oElement.$value()))) {
		for(var i=0; i < oElement.length; i++) {
			this.attach(oElement[i], sEvent);
		}
		return this;
	}

	if ($Element && oElement instanceof $Element) {
		oElement = oElement.$value();
	}

	oElement = $(oElement);
	sEvent   = sEvent.toLowerCase();
	
	this._tmpElm = oElement;
	f = this.bindForEvent();
	this._tmpElm = null;

	if (typeof oElement.attachEvent != "undefined") {
		oElement.attachEvent("on"+sEvent, f);
	} else {
		if (sEvent == "mousewheel") sEvent = "DOMMouseScroll";
	

		if (sEvent == "DOMMouseScroll" && navigator.userAgent.indexOf("WebKit") > 0) {
			var events = "__jindo_wheel_events";

			if (typeof oElement[events] == "undefined") oElement[events] = new Array;
			if (typeof oElement.onmousewheel == "object") {
				oElement.onmousewheel = function(evt) {
					if (!this[events]) return;
					for(var i=0; i < this[events].length; i++) {
						this[events][i](evt);
					}
				}
			}

			oElement[events][oElement[events].length] = f;
		} else {
			oElement.addEventListener(sEvent, f, false);
		}
	}

	this._events[this._events.length] = {element:oElement, event:sEvent, func:f};
	$Fn.gc.pool.push({element:oElement, event:sEvent, func:f});

	return this;
};

/**
 * 함수를 특정 객체의 이벤트에서 제거한다
 * @id core.$Fn.detach
 * @import core.$Fn[attach, gc]
 */
$Fn.prototype.detach = function(oElement, sEvent) {
	if ((oElement instanceof Array) || ($A && (oElement instanceof $A) && (oElement=oElement.$value()))) {
		for(var i=0; i < oElement.length; i++) {
			this.detach(oElement[i], sEvent);
		}
		return this;
	}

	if ($Element && oElement instanceof $Element) {
		oElement = oElement.$value();
	}

	oElement = $(oElement);
	sEvent   = sEvent.toLowerCase();
	
	var e = this._events;
	var l = this._events.length;
	var f = null;

	for(var i=0; i < l; i++) {
		if (e[i].element !== oElement || e[i].event !== sEvent) continue;
		f = e[i].func;
		for(var j=i; j < l-1; j++) {
			this._events[j] = this._events[j+1];
		}
		break;
	}

	if (this._events.length) this._events.length--;

	if (typeof oElement.detachEvent != "undefined") {
		oElement.detachEvent("on"+sEvent, f);
	} else {
		if (sEvent.toLowerCase() == "mousewheel") sEvent = "DOMMouseScroll";

		if (sEvent == "DOMMouseScroll" && navigator.userAgent.indexOf("WebKit") > 0) {
			var events = "__jindo_wheel_events", found = false;
			if (!oElement[events]) return;
			for(var i=0; i < oElement[events].length; i++) {
				if (oElement[events][i] == f) {
					found = true;
				} else if (found) {
					oElement[events][i-1] = oElement[events][i];
				}
			}
			if (oElement[events].length) oElement[events].length--;
		} else {
			oElement.removeEventListener(sEvent, f, false);
		}
	}

	return this;
};

/**
 * Window가 종료될 때, DOM Element 에 할당된 이벤트 핸들러를 제거한다.
 * @id core.$Fn.gc
 * @import core.$Fn.gcinit
 */
$Fn.gc = function() {
	var p = $Fn.gc.pool;
	var l = $Fn.gc.pool.length;
	for(var i=0; i < l; i++) {
		try{ $Fn(p[i].func).detach(p[i].element, p[i].event) }catch(e){};
	}
};

/**
 * @id core.$Fn.gcinit
 */
$Fn.gc.pool = new Array;
$Fn($Fn.gc).attach(window, "unload");

/**
 * JavaScript Core 이벤트 객체로부터 $Event 객체를 생성한다.
 * evt 를 $Event 객체의 인스턴스라고 하면, evt.element 로 이벤트가 실행된 객체를 알 수 있다.
 * @id core.$Event
 */
function $Event(e) {
	if (this === window) return new $Event(e);

	if (typeof e == 'undefined') e = window.event;
	this._event = e;

	this.currentElement = e.currentTarget;

	this.element = e.target || e.srcElement;
	this.type    = e.type && e.type.toLowerCase(); // hooriza modified
	if (this.type == "dommousescroll") {
		this.type = "mousewheel";
	}
}

/**
 * 마우스 이벤트 정보 객체를 반환한다.
 * @id core.$Event.mouse
 */
$Event.prototype.mouse = function() {
	var e    = this._event;
	var delta = 0;
	var left  = (e.which&&e.button==0)||!!(e.button&1);
	var mid   = (e.which&&e.button==1)||!!(e.button&4);
	var right = (e.which&&e.button==2)||!!(e.button&2);

	if (e.wheelDelta) {
		delta = e.wheelDelta / 120;
	} else if (e.detail) {
		delta = -e.detail / 3;
	}

	return {
		delta  : delta,
		left   : left,
		middle : mid,
		right  : right
	};
};

/**
 * 키보드 이벤트 정보 객체를 반환한다.
 * @id core.$Event.key
 */
$Event.prototype.key = function() {
	var e     = this._event;
	var k     = e.keyCode;

	return {
		keyCode : k,
		alt     : e.altKey,
		ctrl    : e.ctrlKey,
		meta    : e.metaKey,
		shift   : e.shiftKey,
		up      : (k == 38),
		down    : (k == 40),
		left    : (k == 37),
		right   : (k == 39),
		enter   : (k == 13)
	};
};

/**
 * 커서 위치 정보 객체를 반환한다.
 * @id core.$Event.position
 */
$Event.prototype.pos = function() {
	var e   = this._event;
	var b   = document.body;
	var de  = document.documentElement;
	var pos = [b.scrollLeft || de.scrollLeft,b.scrollTop || de.scrollTop];

	return {
		clientX : e.clientX,
		clientY : e.clientY,
		pageX   : e.pageX || e.clientX+pos[0]-b.clientLeft,
		pageY   : e.pageY || e.clientY+pos[1]-b.clientTop,
		layerX  : e.offsetX || e.layerX - 1,
		layerY  : e.offsetY || e.layerY - 1
	};
};

/**
 * 현재의 이벤트를 중지한다.
 * @id core.$Event.stop
 */
$Event.prototype.stop = function() {
	if (typeof this._event.preventDefault != "undefined") this._event.preventDefault();
	if (typeof this._event.stopPropagation != "undefined") this._event.stopPropagation();

	this._event.returnValue = false;
	this._event.cancelBubble = true;
};

/**
 * Agent 객체를 반환한다. Agent 객체는 브라우저와 OS에 대한 정보를 알 수 있도록 한다.
 * @id core.$Agent
 */
function $Agent() {
	if (window !== this) return;

	var a = new $Agent;
	window.$Agent = function() {
		return a;
	};

	return a;
}

/**
 * 웹브라우저에 대한 정보 객체를 반환한다.
 * @id core.$Agent.navigator
 * @return {TypeNavigatorInfo} 웹브라우저 정보 객체
 */
$Agent.prototype.navigator = function() {
	var info = new Object;
	var ver  = -1;
	var u    = navigator.userAgent;
	var v    = navigator.vendor;
	var f    = function(s,h){ return ((h||"").indexOf(s) > -1); };

	info.opera     = (typeof window.opera != "undefined") || f("Opera",u);
	info.ie        = !info.opera && f("MSIE",u);
	info.mozilla   = f("Gecko",u);
	info.firefox   = f("Firefox",u);
	info.camino    = f("Camino",v);
	info.netscape  = f("Netscape",u);
	info.safari    = f("Apple",v);
	info.omniweb   = f("OmniWeb",u);
	info.icab      = f("iCab",v);
	info.konqueror = f("KDE",v);

	try {
		if (info.ie) {
			ver = u.match(/(?:MSIE) ([0-9.]+)/)[1];
		} else if (info.firefox||info.opera||info.omniweb) {
			ver = u.match(/(?:Firefox|Opera|OmniWeb)\/([0-9.]+)/)[1];
		} else if (info.mozilla) {
			ver = u.match(/rv:([0-9.]+)/)[1];
		} else if (info.safari) {
			ver = parseFloat(u.match(/Safari\/(0-9.]+)/)[1]);
			if (ver == 100) {
				ver = 1.1;
			} else {
				ver = [1.0,1.2,-1,1.3,2.0,3.0][Math.floor(ver/100)];
			}
		} else if (info.icab) {
			ver = u.match(/iCab[ \/]([0-9.]+)/)[1];
		}

		info.version = parseFloat(ver);
		if (isNaN(info.version)) info.version = -1;
	} catch(e) {
		info.version = -1;
	}

	$Agent.prototype.navigator = function() {
		return info;
	};

	return info;
};

/**
 * OS에 대한 정보객체를 반환한다.
 * @id core.$Agent.os
 * @return {TypeOSInfo} OS 정보 객체
 */
$Agent.prototype.os = function() {
	var info = new Object;
	var u    = navigator.userAgent;
	var p    = navigator.platform;
	var f    = function(s,h){ return (h.indexOf(s) > -1); };

	info.win     = f("Win",p);
	info.mac     = f("Mac",p);
	info.linux   = f("Linux",p);
	info.win2000 = info.win && (f("NT 5.0",p) || f("2000",p));
	info.winxp   = info.win && (f("NT 5.1",p) || f("Win32",p));
	info.xpsp2   = info.winxp && (f("SV1",u) || f("MSIE 7",u));
	info.vista   = f("NT 6.0",p);

	$Agent.prototype.os = function() {
		return info;
	};

	return info;
};

/**
 * Flash에 대한 정보객체를 반환한다.
 * @id core.$Agent.flash
 * @return {TypeFlashInfo} Flash 정보 객체
 */
$Agent.prototype.flash = function() {
	var info = new Object;
	var p    = navigator.plugins;
	var m    = navigator.mimeTypes;
	var f    = null;

	info.installed = false;
	info.version   = -1;

	if (typeof p != "undefined" && p.length) {
		f = p["Shockwave Flash"];
		if (f) {
			info.installed = true;
			if (f.description) {
				info.version = parseFloat(f.description(/[0-9.]+/)[0]);
			}
		}

		if (p["Shockwave Flash 2.0"]) {
			info.installed = true;
			info.version   = 2;
		}
	} else if (typeof m != "undefined" && m.length) {
		f = m["application/x-shockwave-flash"];
		info.installed = (f && f.enabledPlugin);
	} else {
		for(var i=9; i > 1; i--) {
			try {
				f = new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+i);

				info.installed = true;
				info.version   = i;
				break;
			} catch(e) {}
		}
	}

	$Agent.prototype.info = function() {
		return info;
	};

	return info;
};

/**
 * SilverLight에 대한 정보객체를 반환한다.
 * @id core.$Agent.silverlight
 */
$Agent.prototype.silverlight = function() {
	var info = new Object;
	var p    = navigator.plugins;
	var s    = null;

	info.installed = false;
	info.version   = -1;

	if (typeof p != "undefined" && p.length) {
		s = p["Silverlight Plug-In"];
		if (s) {
			info.installed = true;
		}
	} else {
		try {
			s = new ActiveXObject("AgControl.AgControl");
			info.installed = true;
		} catch(e) {}
	}

	$Agent.prototype.silverlight = function() {
		return info;
	};

	return info;
};

/**
 * Ajax 객체를 반환한다.
 * @id core.$Ajax
 * @import core.$Ajax.option, core.$Fn.bind
 */
function $Ajax(url, option) {
	if (this === window) return new $Ajax(url, option);

	function _getXHR() {
		if (window.XMLHttpRequest) {
			return new XMLHttpRequest();
		} else if (ActiveXObject) {
			try { return new ActiveXObject('MSXML2.XMLHTTP'); }
			catch(e) { return new ActiveXObject('Microsoft.XMLHTTP'); }
			return null;
		}
	}

	var loc    = document.location.toString();
	var domain = loc.match(/^https?:\/\/([a-z0-9_\-\.]+)/i)[1];

	this._url = url;
	this._options  = new Object;
	this._headers  = new Object;
	this._crossdom = /^https?:\/\//i.test(url) && !(new RegExp("^https?://"+domain+"(:[0-9]+)?(?:/|$)","i").test(url));

	this.option({ type:"post",timeout:0,proxy:"",onload:function(){} }); // default value
	this.option(option);

	if (this._crossdom) {
		this._request = new $Ajax.CrossRequest(this.option("proxy"));
	} else {
		this._request = _getXHR();
	}
}

/**
 * 주어진 데이터로 Ajax를 호출한다.
 * @id core.$Ajax._onload
 * @param {Function} Ajax 호출이 완료된 후 실행할 함수
 */
$Ajax.prototype._onload = function() {
	if (this._request.readyState == 4) {
		this._options.onload($Ajax.Response(this._request));
	}
};

/**
 * Ajax를 호출한다.
 * @id core.$Ajax.request
 * @param {Object} oData 요청시 보낼 데이터
 * @param {Function} onComplete 요청이 완료되었을 때 실행할 함수
 */
$Ajax.prototype.request = function(oData) {
	var req = this._request;
	var opt = this._options;
	var data, v,a = [], data = "";
	if (typeof oData == "undefined" || !oData) {
		data = null;
	} else {
		for(var k in oData) {
			v = oData[k];
			if (typeof v == "function") v = v();
			a[a.length] = k+"="+encodeURIComponent(v);
		}
		data = a.join("&");
	}
	
	req.open(opt.type.toUpperCase(), this._url, true);
	req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
	req.setRequestHeader("charset", "utf-8");
	for(var x in this._headers) {
		if (typeof this._headers[x] == "function") continue;
		req.setRequestHeader(x, String(this._headers[x]));
	}
	
	if (typeof req.onload != "undefined") {
		req.onload = $Fn(this._onload, this).bind();
	} else {
		req.onreadystatechange = $Fn(this._onload, this).bind();
	}
	
	req.send(data);
};

/**
 * @id core.$Ajax.abort
 */
$Ajax.prototype.abort = function() {
	this._request.abort();
};

/**
 * 옵션을 가져오거나 설정한다.
 * 첫번째 전달값의 타입이 Object 이면 값을 설정하고 문자열이면 해당하는 옵션값을 반환한다.
 * @id core.$Ajax.option
 * @param {String} name 가지고 오거나 설정할 옵션이름
 * @param {void}   value 값을 설정할 옵션이름
 * @return {void}  설정된 옵션값 혹은 $Ajax 객체
 */
$Ajax.prototype.option = function(name, value) {
	if (typeof name == "undefined") return "";
	if (typeof name == "string") {
		if (typeof value == "undefined") return this._options[name];
		this._options[name] = value;
		return this;
	}

	try { for(var x in name) this._options[x] = name[x] } catch(e) {};
	
	return this;
};

/**
 * 헤더를 가져오거나 설정한다.
 * 첫번째 전달값의 타입이 Object 이면 값을 설정하고 문자열이면 해당하는 헤더값을 반환한다.
 * @id core.$Ajax.header
 * @param {String} name 가지고 오거나 설정할 헤더 이름
 * @param {void} value 값을 설정할 헤더 값
 * @return {void} 설정된 헤더값 혹은 $Ajax 객체
 */
$Ajax.prototype.header = function(name, value) {
	if (typeof name == "undefined") return "";
	if (typeof name == "string") {
		if (typeof value == "undefined") return this._headers[name];
		this._headers[name] = value;
		return this;
	}
	
	try { for(var x in name) this._headers[x] = name[x] } catch(e) {};
	
	return this;
};

/**
 * Ajax 응답 객체
 * @id core.$Ajax.Response
 * @param {Object} req 요청 객체
 */
$Ajax.Response  = function(req) {
	if (this === $Ajax) return new $Ajax.Response(req);	
	this._response = req;
};

/**
 * XML 객체를 반환한다.
 * @id core.$Ajax.Response.xml
 */
$Ajax.Response.prototype.xml = function() {
	return this._response.responseXML; 
};

$Ajax.Response.prototype.text = function() {
	return this._response.responseText;
};

/**
 * 응답헤더를 가져온다. 인자를 전달하지 않으면 모든 헤더를 반환한다.
 * @id core.$Ajax.Response.header
 * @param {String} 가져올 응답헤더의 이름
 */
$Ajax.Response.prototype.header = function(name) {
	if (typeof name == "string") return this._response.getResponseHeader(name);
	return this._response.getAllResponseHeaders();
};

/**
 * Cross-domain Ajax Request Object
 * @id core.$Ajax.CrossRequest
 */
$Ajax.CrossRequest = function(proxy) {
	this._headers = new Object;
	this._respHeaders = new Object;
	this._proxy = proxy;
	this.onload = function(){};
};

/**
 * @id core.$Ajax.CrossRequest.open
 */
$Ajax.CrossRequest.prototype.open = function(type, url) {
	var re  = /https?:\/\/([a-z0-9_\-\.]+)/i;
	var dom = document.location.toString().match(re);
	
	this._type   = type;
	this._url    = url;
	this._remote = String(url).match(/(https?:\/\/[a-z0-9_\-\.]+)/i)[1];
	this._iframe = null;
	this._domain = (dom[1] != document.domain)?document.domain:"";

	if (typeof $Ajax.CrossRequest._callbacks == "undefined") {
		$Ajax.CrossRequest._callbacks = new Object;
	}
};

$Ajax.CrossRequest.prototype._callback = function(id, data, header) {
	var t = this;

	this.readyState   = 4;
	this.responseText = data;
	
	this._respHeaderString = header;
	header.replace(/^([\w\-]+)\s*:\s*(.+)$/m, function($0,$1,$2) {
		t._respHeaders[$1] = $2;
	});

	this.onload(this);

	setTimeout(function(){ t.abort() }, 10);
};

/**
 * @id core.$Ajax.CrossRequest.abort
 */
$Ajax.CrossRequest.prototype.abort = function() {
	try {
		this._iframe.parentNode.removeChild(this._iframe);
	} catch(e) {}
};

/**
 * @id core.$Ajax.CrossRequest.send
 */
$Ajax.CrossRequest.prototype.send = function(data) {
	this.responseXML  = "";
	this.responseText = "";

	var re     = /https?:\/\/([a-z0-9_\-\.]+)/i;
	var id     = String(Math.floor(Math.random()*10000));
	var url    = this._remote+"/ajax_remote_callback.html?type="+this._type;
	var header = new Array;

	$Ajax.CrossRequest._callbacks[id] = $Fn(this._callback, this).bind();

	for(var x in this._headers) {
		header[header.length] = "'"+x+"':'"+this._headers[x]+"'";
	}

	header = "{"+header.join(",")+"}";

	url += "&id="+id;
	url += "&header="+encodeURIComponent(header);
	url += "&proxy="+encodeURIComponent(this._proxy);
	url += "&domain="+this._domain;
	url += "&url="+encodeURIComponent(this._url.replace(re, ""));
	url += "#"+encodeURIComponent(data);

	var fr = this._iframe = $("<iframe>");
	fr.style.position = "absolute";
	fr.style.visibility = "hidden";
	fr.style.width = "1px";
	fr.style.height = "1px";

	var body = document.body || document.documentElement;
	if (body.firstChild) body.insertBefore(fr, body.firstChild);
	else body.appendChild(fr);

	fr.src = url;
};

/**
 * @id core.$Ajax.CrossRequest.setRequestHeader
 */
$Ajax.CrossRequest.prototype.setRequestHeader = function(sName, sValue) {
	this._headers[sName] = sValue;
};

/**
 * @id core.$Ajax.CrossRequest.getResponseHeader
 */
$Ajax.CrossRequest.prototype.getResponseHeader = function(sName) {
	return this._respHeaders[sName];
};

/**
 * @id core.$Ajax.CrossRequest.getAllResponseHeaders
 */
$Ajax.CrossRequest.prototype.getAllResponseHeaders = function() {
	return this._respHeaderString;
};

/**
 * JSON 객체를 반환한다.첫번째 argument는 객체 혹은 JSON 문자열이 된다.
 * @id core.$Json
 */
function $Json(sObject) {
	if (window === this) return new $Json(sObject);
	
	if (typeof sObject == "string") {
		try {
			sObject = eval("("+sObject+")");
		} catch(e) {
			sObject = new Object;
		}
	}

	this._object = sObject;
}

/**
 * XML 문자열로부터 JSON 객체를 반환한다.
 * @id core.$Json.fromXML
 */
$Json.fromXML = function(sXML) {
	var o  = new Object;
	var re = /\s*<(\/?[\w:\-]+)((?:\s+[\w:\-]+\s*=\s*"[^"]*")*)\s*>\s*|\s*<!\[CDATA\[([\w\W]*?)\]\]>\s*|\s*([^<]*)\s*/ig;
	var re2= /^[0-9]+(?:\.[0-9]+)?$/;
	var ec = {"&amp;":"&","&nbsp;":" ","&quot;":"\"","&lt;":"<","&gt;":">"};
	var fg = {tags:["/"],stack:[o]};
	var es = function(s){ return s.replace(/&[a-z]+;/g, function(m){ return (typeof ec[m] == "string")?ec[m]:m; }) };
	var at = function(s,c){ s.replace(/([\w\:\-]+)\s*=\s*"([^"]*)"/g, function($0,$1,$2){ c[$1] = es($2) }) };
	var cb = function($0,$1,$2,$3,$4) {
		var cur, cdata = "";
		var idx = fg.stack.length - 1;
		
		if (typeof $1 == "string" && $1) {
			if ($1.substr(0,1) != "/") {
				cur = fg.stack[idx];
				
				if (typeof cur[$1] == "undefined") {
					cur[$1] = new Object; 
					cur = fg.stack[idx+1] = cur[$1];
				} else if (cur[$1] instanceof Array) {
					var len = cur[$1].length;
					cur[$1][len] = new Object;
					cur = fg.stack[idx+1] = cur[$1][len];  
				} else {
					cur[$1] = [cur[$1], new Object];
					cur = fg.stack[idx+1] = cur[$1][1];
				}
				
				if (typeof $2 == "string" && $2) at($2,cur);
				
				fg.tags[idx+1] = $1;
			} else {
				fg.tags.length--;
				fg.stack.length--;
			}
		} else if (typeof $3 == "string" && $3) {
			cdata = $3;
		} else if (typeof $4 == "string" && $4) {
			cdata = es($4);
		}
		
		if (cdata.length > 0) {
			var par = fg.stack[idx-1];
			var tag = fg.tags[idx];

			if (re2.test(cdata)) cdata = parseFloat(cdata);
			else if (cdata == "true" || cdata == "false") cdata = new Boolean(cdata);

			if (par[tag] instanceof Array) {
				par[tag][par[tag].length-1] = cdata;
			} else {
				par[tag] = cdata;
			}
		}
	};
	
	sXML = sXML.replace(/<(\?|\!-)[^>]*>/g, "");
	sXML.replace(re, cb);
	
	return $Json(o);
};

/**
 * JSON 객체의 값을 path 형태로 받아온다.
 * @id core.$Json.get
 * @param {String} sPath path 문자열
 * @return {Array} 객체의 배열
 */
$Json.prototype.get = function(sPath) {
	var o = this._object;
	var p = sPath.split("/");
	var re = /^([\w:\-]+)\[([0-9]+)\]$/;
	var stack = [[o]], cur = stack[0];
	var len = p.length, c_len, idx, buf, j, e;
	
	for(var i=0; i < len; i++) {
		if (p[i] == "." || p[i] == "") continue;
		if (p[i] == "..") {
			stack.length--;
		} else {
			buf = new Array;
			idx = -1;
			c_len = cur.length;
			
			if (re.test(p[i])) idx = parseInt(RegExp.$2);			
			if (c_len == 0) return new Array;
			
			for(j=0; j<c_len; j++) {
				e = cur[j][p[i]];
				if (typeof e != "undefined") {
					if (e instanceof Array)  buf = buf.concat(e);
					buf[buf.length] = e; 
				}
			}
			stack[stack.length] = buf;
		}
		
		cur = stack[stack.length-1];
	}
	
	return cur;
};

/**
 * JSON 객체를 JSON 문자열로 변환한다.
 * @id core.$Json.toString
 * @return {String} JSON 문자열
 */
$Json.prototype.toString = function() {
	var func = {
		$ : function($) {
			if (typeof $ == "undefined") return '""';
			if (typeof $ == "boolean") return $?"true":"false";
			if (typeof $ == "string") return this.s($);
			if (typeof $ == "number") return $;
			if ($ instanceof Array) return this.a($);
			if ($ instanceof Object) return this.o($);
		},
		s : function(s) {
			var e = {'"':'\\"',"\\":"\\\\","\n":"\\n","\r":"\\r","\t":"\\t"};
			var c = function(m){ return (typeof e[m] != "undefined")?e[m]:m };
			return '"'+s.replace(/[\\"'\n\r\t]/g, c)+'"';
		},
		a : function(a) {
			var s = "[",c = "",n=a.length;
			for(var i=0; i < n; i++) {
				if (typeof a[i] == "function") continue;
				s += c+this.$(a[i]);
				if (!c) c = ",";
			}
			return s+"]";
		},
		o : function(o) {
			var s = "{",c = "";
			for(var x in o) {
				if (typeof o[x] == "function") continue;
				s += c+this.s(x)+":"+this.$(o[x]);
				if (!c) c = ",";
			}
			return s+"}";
		}
	};

	return func.$(this._object);
};

/**
 * JSON 객체를 XML 문자열로 변환한다.
 * @id core.$Json.toXML
 * @return {String} XML 문자열
 */
$Json.prototype.toXML = function() {
	var f = function($,tag) {
		var t = function(s) { return "<"+tag+">"+s+"</"+tag+">" };
		
		switch (typeof $) {
			case "undefined":
			case "null":
				return t("");
			case "number":
				return t($);
			case "string":
				if ($.indexOf("<") < 0) return t($.replace(/&/g,"&amp;"));
				else return t("<![CDATA["+$+"]]>");
			case "boolean":
				return t(String($));
			case "object":
				var ret = "";
				if ($ instanceof Array) {
					var len = $.length;
					for(var i=0; i < len; i++) { ret += f($[i],tag); };
				} else {
					for(var x in $) { ret += f($[x],x); };
					if (tag) ret = t(ret);
				}
				return ret;
		}
	};
	
	return f(this._object, "");
};

/**
 * JSON  데이터 객체를 반환한다.
 * @id core.$Json.toObject
 * @import core.$Json.$value
 * @return {Object} 데이터 객체
 */
$Json.prototype.toObject = function() {
	return this._object;
};

/**
 * $Json.toObject의 alias function
 * @id core.$Json.$value
 */
$Json.prototype.$value = $Json.prototype.toObject;