//MooTools, My Object Oriented Javascript Tools. Copyright (c) 2006 Valerio Proietti, <http://mad4milk.net>, MIT Style License.

var MooTools = {
        version: '1.11'
};

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

function $type(obj){
        if (!$defined(obj)) return false;
        if (obj.htmlElement) return 'element';
        var type = typeof obj;
        if (type == 'object' && obj.nodeName){
                switch(obj.nodeType){
                        case 1: return 'element';
                        case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
                }
        }
        if (type == 'object' || type == 'function'){
                switch(obj.constructor){
                        case Array: return 'array';
                        case RegExp: return 'regexp';
                        case Class: return 'class';
                }
                if (typeof obj.length == 'number'){
                        if (obj.item) return 'collection';
                        if (obj.callee) return 'arguments';
                }
        }
        return type;
};

function $merge(){
        var mix = {};
        for (var i = 0; i < arguments.length; i++){
                for (var property in arguments[i]){
                        var ap = arguments[i][property];
                        var mp = mix[property];
                        if (mp && $type(ap) == 'object' && $type(mp) == 'object') mix[property] = $merge(mp, ap);
                        else mix[property] = ap;
                }
        }
        return mix;
};

var $extend = function(){
        var args = arguments;
        if (!args[1]) args = [this, args[0]];
        for (var property in args[1]) args[0][property] = args[1][property];
        return args[0];
};

var $native = function(){
        for (var i = 0, l = arguments.length; i < l; i++){
                arguments[i].extend = function(props){
                        for (var prop in props){
                                if (!this.prototype[prop]) this.prototype[prop] = props[prop];
                                if (!this[prop]) this[prop] = $native.generic(prop);
                        }
                };
        }
};

$native.generic = function(prop){
        return function(bind){
                return this.prototype[prop].apply(bind, Array.prototype.slice.call(arguments, 1));
        };
};

$native(Function, Array, String, Number);

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

function $pick(obj, picked){
        return $defined(obj) ? obj : picked;
};

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

function $time(){
        return new Date().getTime();
};

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

var Abstract = function(obj){
        obj = obj || {};
        obj.extend = $extend;
        return obj;
};

var Window = new Abstract(window);
var Document = new Abstract(document);
document.head = document.getElementsByTagName('head')[0];

window.xpath = !!(document.evaluate);
if (window.ActiveXObject) window.ie = window[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true;
else if (document.childNodes && !document.all && !navigator.taintEnabled) window.webkit = window[window.xpath ? 'webkit420' : 'webkit419'] = true;
else if (document.getBoxObjectFor != null || window.mozInnerScreenX != null) window.gecko = true;

// DUKE
if (window.gecko && /Firefox\/3\.6/i.test(navigator.userAgent)) {
  window.xpath = false;
}
// DUKE

window.khtml = window.webkit;

Object.extend = $extend;

if (typeof HTMLElement == 'undefined'){
        var HTMLElement = function(){};
        if (window.webkit) document.createElement("iframe");
        HTMLElement.prototype = (window.webkit) ? window["[[DOMElement.prototype]]"] : {};
}
HTMLElement.prototype.htmlElement = function(){};

if (window.ie6) try {document.execCommand("BackgroundImageCache", false, true);} catch(e){};

var Class = function(properties){
        var klass = function(){
                return (arguments[0] !== null && this.initialize && $type(this.initialize) == 'function') ? this.initialize.apply(this, arguments) : this;
        };
        $extend(klass, this);
        klass.prototype = properties;
        klass.constructor = Class;
        return klass;
};

Class.empty = function(){};

Class.prototype = {

        extend: function(properties){
                var proto = new this(null);
                for (var property in properties){
                        var pp = proto[property];
                        proto[property] = Class.Merge(pp, properties[property]);
                }
                return new Class(proto);
        },

        implement: function(){
                for (var i = 0, l = arguments.length; i < l; i++) $extend(this.prototype, arguments[i]);
        }

};

Class.Merge = function(previous, current){
        if (previous && previous != current){
                var type = $type(current);
                if (type != $type(previous)) return current;
                switch(type){
                        case 'function':
                                var merged = function(){
                                        this.parent = arguments.callee.parent;
                                        return current.apply(this, arguments);
                                };
                                merged.parent = previous;
                                return merged;
                        case 'object': return $merge(previous, current);
                }
        }
        return current;
};

var Chain = new Class({

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

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

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

});

var Events = new Class({

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

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

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

});

var Options = new Class({

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

});

Array.extend({

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

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

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

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

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

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

        copy: function(start, length){
                start = start || 0;
                if (start < 0) start = this.length + start;
                length = length || (this.length - start);
                var newArray = [];
                for (var i = 0; i < length; i++) newArray[i] = this[start++];
                return newArray;
        },

        remove: function(item){
                var i = 0;
                var len = this.length;
                while (i < len){
                        if (this[i] === item){
                                this.splice(i, 1);
                                len--;
                        } else {
                                i++;
                        }
                }
                return this;
        },

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

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

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

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

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

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

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

});

Array.prototype.each = Array.prototype.forEach;
Array.each = Array.forEach;

function $A(array){
        return Array.copy(array);
};

function $each(iterable, fn, bind){
        if (iterable && typeof iterable.length == 'number' && $type(iterable) != 'object'){
                Array.forEach(iterable, fn, bind);
        } else {
                 for (var name in iterable) fn.call(bind || iterable, iterable[name], name);
        }
};

Array.prototype.test = Array.prototype.contains;

String.extend({

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

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

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

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

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

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

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

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

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

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

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

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

});

Array.extend({

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

        hexToRgb: function(array){
                if (this.length != 3) return false;
                var rgb = [];
                for (var i = 0; i < 3; i++){
                        rgb.push(parseInt((this[i].length == 1) ? this[i] + this[i] : this[i], 16));
                }
                return array ? rgb : 'rgb(' + rgb.join(',') + ')';
        }

});

Function.extend({

        create: function(options){
                var fn = this;
                options = $merge({
                        'bind': fn,
                        'event': false,
                        'arguments': null,
                        'delay': false,
                        'periodical': false,
                        'attempt': false
                }, options);
                if ($chk(options.arguments) && $type(options.arguments) != 'array') options.arguments = [options.arguments];
                return function(event){
                        var args;
                        if (options.event){
                                event = event || window.event;
                                args = [(options.event === true) ? event : new options.event(event)];
                                if (options.arguments) args.extend(options.arguments);
                        }
                        else args = options.arguments || arguments;
                        var returns = function(){
                                return fn.apply($pick(options.bind, fn), args);
                        };
                        if (options.delay) return setTimeout(returns, options.delay);
                        if (options.periodical) return setInterval(returns, options.periodical);
                        if (options.attempt) try {return returns();} catch(err){return false;};
                        return returns();
                };
        },

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

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

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

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

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

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

});

Number.extend({

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

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

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

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

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

});

var Element = new Class({

        initialize: function(el, props){
                if ($type(el) == 'string'){
                        if (window.ie && props && (props.name || props.type)){
                                var name = (props.name) ? ' name="' + props.name + '"' : '';
                                var type = (props.type) ? ' type="' + props.type + '"' : '';
                                delete props.name;
                                delete props.type;
                                el = '<' + el + name + type + '>';
                        }
                        el = document.createElement(el);
                }
                el = $(el);
                return (!props || !el) ? el : el.set(props);
        }

});

var Elements = new Class({

        initialize: function(elements){
                return (elements) ? $extend(elements, this) : this;
        }

});

Elements.extend = function(props){
        for (var prop in props){
                this.prototype[prop] = props[prop];
                this[prop] = $native.generic(prop);
        }
};

function $(el){
        if (!el) return null;
        if (el.htmlElement) return Garbage.collect(el);
        if ([window, document].contains(el)) return el;
        var type = $type(el);
        if (type == 'string'){
                el = document.getElementById(el);
                type = (el) ? 'element' : false;
        }
        if (type != 'element') return null;
        if (el.htmlElement) return Garbage.collect(el);
        if (['object', 'embed'].contains(el.tagName.toLowerCase())) return el;
        $extend(el, Element.prototype);
        el.htmlElement = function(){};
        return Garbage.collect(el);
};

document.getElementsBySelector = document.getElementsByTagName;

function $$(){
        var elements = [];
        for (var i = 0, j = arguments.length; i < j; i++){
                var selector = arguments[i];
                switch($type(selector)){
                        case 'element': elements.push(selector);
                        case 'boolean': break;
                        case false: break;
                        case 'string': selector = document.getElementsBySelector(selector, true);
                        default: elements.extend(selector);
                }
        }
        return $$.unique(elements);
};

$$.unique = function(array){
        var elements = [];
        for (var i = 0, l = array.length; i < l; i++){
                if (array[i].$included) continue;
                var element = $(array[i]);
                if (element && !element.$included){
                        element.$included = true;
                        elements.push(element);
                }
        }
        for (var n = 0, d = elements.length; n < d; n++) elements[n].$included = null;
        return new Elements(elements);
};

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

Element.extend = function(properties){
        for (var property in properties){
                HTMLElement.prototype[property] = properties[property];
                Element.prototype[property] = properties[property];
                Element[property] = $native.generic(property);
                var elementsProperty = (Array.prototype[property]) ? property + 'Elements' : property;
                Elements.prototype[elementsProperty] = Elements.Multi(property);
        }
};

Element.extend({

        set: function(props){
                for (var prop in props){
                        var val = props[prop];
                        switch(prop){
                                case 'styles': this.setStyles(val); break;
                                case 'events': if (this.addEvents) this.addEvents(val); break;
                                case 'properties': this.setProperties(val); break;
                                default: this.setProperty(prop, val);
                        }
                }
                return this;
        },

        inject: function(el, where){
                el = $(el);
                switch(where){
                        case 'before': el.parentNode.insertBefore(this, el); break;
                        case 'after':
                                var next = el.getNext();
                                if (!next) el.parentNode.appendChild(this);
                                else el.parentNode.insertBefore(this, next);
                                break;
                        case 'top':
                                var first = el.firstChild;
                                if (first){
                                        el.insertBefore(this, first);
                                        break;
                                }
                        default: el.appendChild(this);
                }
                return this;
        },

        injectBefore: function(el){
                return this.inject(el, 'before');
        },

        injectAfter: function(el){
                return this.inject(el, 'after');
        },

        injectInside: function(el){
                return this.inject(el, 'bottom');
        },

        injectTop: function(el){
                return this.inject(el, 'top');
        },

        adopt: function(){
                var elements = [];
                $each(arguments, function(argument){
                        elements = elements.concat(argument);
                });
                $$(elements).inject(this);
                return this;
        },

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

        clone: function(contents){
                var el = $(this.cloneNode(contents !== false));
                if (!el.$events) return el;
                el.$events = {};
                for (var type in this.$events) el.$events[type] = {
                        'keys': $A(this.$events[type].keys),
                        'values': $A(this.$events[type].values)
                };
                return el.removeEvents();
        },

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

        appendText: function(text){
                this.appendChild(document.createTextNode(text));
                return this;
        },

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

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

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

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

        setStyle: function(property, value){
                switch(property){
                        case 'opacity': return this.setOpacity(parseFloat(value));
                        case 'float': property = (window.ie) ? 'styleFloat' : 'cssFloat';
                }
                property = property.camelCase();
                switch($type(value)){
                        case 'number': if (!['zIndex', 'zoom'].contains(property)) value += 'px'; break;
                        case 'array': value = 'rgb(' + value.join(',') + ')';
                }
                this.style[property] = value;
                return this;
        },

        setStyles: function(source){
                switch($type(source)){
                        case 'object': Element.setMany(this, 'setStyle', source); break;
                        case 'string': this.style.cssText = source;
                }
                return this;
        },

        setOpacity: function(opacity){
                if (opacity == 0){
                        if (this.style.visibility != "hidden") this.style.visibility = "hidden";
                } else {
                        if (this.style.visibility != "visible") this.style.visibility = "visible";
                }
                if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
                if (window.ie) this.style.filter = (opacity == 1) ? '' : "alpha(opacity=" + opacity * 100 + ")";
                this.style.opacity = this.$tmp.opacity = opacity;
                return this;
        },

        getStyle: function(property){
                property = property.camelCase();
                var result = this.style[property];
                if (!$chk(result)){
                        if (property == 'opacity') return this.$tmp.opacity;
                        result = [];
                        for (var style in Element.Styles){
                                if (property == style){
                                        Element.Styles[style].each(function(s){
                                                var style = this.getStyle(s);
                                                result.push(parseInt(style) ? style : '0px');
                                        }, this);
                                        if (property == 'border'){
                                                var every = result.every(function(bit){
                                                        return (bit == result[0]);
                                                });
                                                return (every) ? result[0] : false;
                                        }
                                        return result.join(' ');
                                }
                        }
                        if (property.contains('border')){
                                if (Element.Styles.border.contains(property)){
                                        return ['Width', 'Style', 'Color'].map(function(p){
                                                return this.getStyle(property + p);
                                        }, this).join(' ');
                                } else if (Element.borderShort.contains(property)){
                                        return ['Top', 'Right', 'Bottom', 'Left'].map(function(p){
                                                return this.getStyle('border' + p + property.replace('border', ''));
                                        }, this).join(' ');
                                }
                        }
                        if (document.defaultView) result = document.defaultView.getComputedStyle(this, null).getPropertyValue(property.hyphenate());
                        else if (this.currentStyle) result = this.currentStyle[property];
                }
                if (window.ie) result = Element.fixStyle(property, result, this);
                if (result && property.test(/color/i) && result.contains('rgb')){
                        return result.split('rgb').splice(1,4).map(function(color){
                                return color.rgbToHex();
                        }).join(' ');
                }
                return result;
        },

        getStyles: function(){
                return Element.getMany(this, 'getStyle', arguments);
        },

        walk: function(brother, start){
                brother += 'Sibling';
                var el = (start) ? this[start] : this[brother];
                while (el && $type(el) != 'element') el = el[brother];
                return $(el);
        },

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

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

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

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

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

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

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

        getProperty: function(property){
                var index = Element.Properties[property];
                if (index) return this[index];
                var flag = Element.PropertiesIFlag[property] || 0;
                if (!window.ie || flag) return this.getAttribute(property, flag);
                var node = this.attributes[property];
                return (node) ? node.nodeValue : null;
        },

        removeProperty: function(property){
                var index = Element.Properties[property];
                if (index) this[index] = '';
                else this.removeAttribute(property);
                return this;
        },

        getProperties: function(){
                return Element.getMany(this, 'getProperty', arguments);
        },

        setProperty: function(property, value){
                var index = Element.Properties[property];
                if (index) this[index] = value;
                else this.setAttribute(property, value);
                return this;
        },

        setProperties: function(source){
                return Element.setMany(this, 'setProperty', source);
        },

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

        setText: function(text){
                var tag = this.getTag();
                if (['style', 'script'].contains(tag)){
                        if (window.ie){
                                if (tag == 'style') this.styleSheet.cssText = text;
                                else if (tag ==  'script') this.setProperty('text', text);
                                return this;
                        } else {
                                this.removeChild(this.firstChild);
                                return this.appendText(text);
                        }
                }
                this[$defined(this.innerText) ? 'innerText' : 'textContent'] = text;
                return this;
        },

        getText: function(){
                var tag = this.getTag();
                if (['style', 'script'].contains(tag)){
                        if (window.ie){
                                if (tag == 'style') return this.styleSheet.cssText;
                                else if (tag ==  'script') return this.getProperty('text');
                        } else {
                                return this.innerHTML;
                        }
                }
                return ($pick(this.innerText, this.textContent));
        },

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

        empty: function(){
                Garbage.trash(this.getElementsByTagName('*'));
                return this.setHTML('');
        }

});

Element.fixStyle = function(property, result, element){
        if ($chk(parseInt(result))) return result;
        if (['height', 'width'].contains(property)){
                var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'];
                var size = 0;
                values.each(function(value){
                        size += element.getStyle('border-' + value + '-width').toInt() + element.getStyle('padding-' + value).toInt();
                });
                return element['offset' + property.capitalize()] - size + 'px';
        } else if (property.test(/border(.+)Width|margin|padding/)){
                return '0px';
        }
        return result;
};

Element.Styles = {'border': [], 'padding': [], 'margin': []};
['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
        for (var style in Element.Styles) Element.Styles[style].push(style + direction);
});

Element.borderShort = ['borderWidth', 'borderStyle', 'borderColor'];

Element.getMany = function(el, method, keys){
        var result = {};
        $each(keys, function(key){
                result[key] = el[method](key);
        });
        return result;
};

Element.setMany = function(el, method, pairs){
        for (var key in pairs) el[method](key, pairs[key]);
        return el;
};

Element.Properties = new Abstract({
        'class': 'className', 'for': 'htmlFor', 'colspan': 'colSpan', 'rowspan': 'rowSpan',
        'accesskey': 'accessKey', 'tabindex': 'tabIndex', 'maxlength': 'maxLength',
        'readonly': 'readOnly', 'frameborder': 'frameBorder', 'value': 'value',
        'disabled': 'disabled', 'checked': 'checked', 'multiple': 'multiple', 'selected': 'selected'
});
Element.PropertiesIFlag = {
        'href': 2, 'src': 2
};

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

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

window.extend(Element.Methods.Listeners);
document.extend(Element.Methods.Listeners);
Element.extend(Element.Methods.Listeners);

var Garbage = {

        elements: [],

        collect: function(el){
                if (!el.$tmp){
                        Garbage.elements.push(el);
                        el.$tmp = {'opacity': 1};
                }
                return el;
        },

        trash: function(elements){
                for (var i = 0, j = elements.length, el; i < j; i++){
                        if (!(el = elements[i]) || !el.$tmp) continue;
                        if (el.$events) el.fireEvent('trash').removeEvents();
                        for (var p in el.$tmp) el.$tmp[p] = null;
                        for (var d in Element.prototype) el[d] = null;
                        Garbage.elements[Garbage.elements.indexOf(el)] = null;
                        el.htmlElement = el.$tmp = el = null;
                }
                Garbage.elements.remove(null);
        },

        empty: function(){
                Garbage.collect(window);
                Garbage.collect(document);
                Garbage.trash(Garbage.elements);
        }

};

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

var Event = new Class({

        initialize: function(event){
                if (event && event.$extended) return event;
                this.$extended = true;
                event = event || window.event;
                this.event = event;
                this.type = event.type;
                this.target = event.target || event.srcElement;
                if (this.target.nodeType == 3) this.target = this.target.parentNode;
                this.shift = event.shiftKey;
                this.control = event.ctrlKey;
                this.alt = event.altKey;
                this.meta = event.metaKey;
                if (['DOMMouseScroll', 'mousewheel'].contains(this.type)){
                        this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
                } else if (this.type.contains('key')){
                        this.code = event.which || event.keyCode;
                        for (var name in Event.keys){
                                if (Event.keys[name] == this.code){
                                        this.key = name;
                                        break;
                                }
                        }
                        if (this.type == 'keydown'){
                                var fKey = this.code - 111;
                                if (fKey > 0 && fKey < 13) this.key = 'f' + fKey;
                        }
                        this.key = this.key || String.fromCharCode(this.code).toLowerCase();
                } else if (this.type.test(/(click|mouse|menu)/)){
                        this.page = {
                                'x': event.pageX || event.clientX + (document.body.scrollLeft || document.documentElement.scrollLeft),
                                'y': event.pageY || event.clientY + (document.body.scrollTop || document.documentElement.scrollTop)
                        };
                        this.client = {
                                'x': event.pageX ? event.pageX - window.pageXOffset : event.clientX,
                                'y': event.pageY ? event.pageY - window.pageYOffset : event.clientY
                        };
                        this.rightClick = (event.which == 3) || (event.button == 2);
                        switch(this.type){
                                case 'mouseover': this.relatedTarget = event.relatedTarget || event.fromElement; break;
                                case 'mouseout': this.relatedTarget = event.relatedTarget || event.toElement;
                        }
                        this.fixRelatedTarget();
                }
                return this;
        },

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

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

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

});

Event.fix = {

        relatedTarget: function(){
                if (this.relatedTarget && this.relatedTarget.nodeType == 3) this.relatedTarget = this.relatedTarget.parentNode;
        },

        relatedTargetGecko: function(){
                try {Event.fix.relatedTarget.call(this);} catch(e){this.relatedTarget = this.target;}
        }

};

Event.prototype.fixRelatedTarget = (window.gecko) ? Event.fix.relatedTargetGecko : Event.fix.relatedTarget;

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

Element.Methods.Events = {

        addEvent: function(type, fn){
                this.$events = this.$events || {};
                this.$events[type] = this.$events[type] || {'keys': [], 'values': []};
                if (this.$events[type].keys.contains(fn)) return this;
                this.$events[type].keys.push(fn);
                var realType = type;
                var custom = Element.Events[type];
                if (custom){
                        if (custom.add) custom.add.call(this, fn);
                        if (custom.map) fn = custom.map;
                        if (custom.type) realType = custom.type;
                }
                if (!this.addEventListener) fn = fn.create({'bind': this, 'event': true});
                this.$events[type].values.push(fn);
                return (Element.NativeEvents.contains(realType)) ? this.addListener(realType, fn) : this;
        },

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

        addEvents: function(source){
                return Element.setMany(this, 'addEvent', source);
        },

        removeEvents: function(type){
                if (!this.$events) return this;
                if (!type){
                        for (var evType in this.$events) this.removeEvents(evType);
                        this.$events = null;
                } else if (this.$events[type]){
                        this.$events[type].keys.each(function(fn){
                                this.removeEvent(type, fn);
                        }, this);
                        this.$events[type] = null;
                }
                return this;
        },

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

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

};

window.extend(Element.Methods.Events);
document.extend(Element.Methods.Events);
Element.extend(Element.Methods.Events);

Element.Events = new Abstract({

        'mouseenter': {
                type: 'mouseover',
                map: function(event){
                        event = new Event(event);
                        if (event.relatedTarget != this && !this.hasChild(event.relatedTarget)) this.fireEvent('mouseenter', event);
                }
        },

        'mouseleave': {
                type: 'mouseout',
                map: function(event){
                        event = new Event(event);
                        if (event.relatedTarget != this && !this.hasChild(event.relatedTarget)) this.fireEvent('mouseleave', event);
                }
        },

        'mousewheel': {
                type: (window.gecko) ? 'DOMMouseScroll' : 'mousewheel'
        }

});

Element.NativeEvents = [
        'click', 'dblclick', 'mouseup', 'mousedown',
        'mousewheel', 'DOMMouseScroll',
        'mouseover', 'mouseout', 'mousemove',
        'keydown', 'keypress', 'keyup',
        'load', 'unload', 'beforeunload', 'resize', 'move',
        'focus', 'blur', 'change', 'submit', 'reset', 'select',
        'error', 'abort', 'contextmenu', 'scroll'
];

Function.extend({

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

});

Elements.extend({

        filterByTag: function(tag){
                return new Elements(this.filter(function(el){
                        return (Element.getTag(el) == tag);
                }));
        },

        filterByClass: function(className, nocash){
                var elements = this.filter(function(el){
                        return (el.className && el.className.contains(className, ' '));
                });
                return (nocash) ? elements : new Elements(elements);
        },

        filterById: function(id, nocash){
                var elements = this.filter(function(el){
                        return (el.id == id);
                });
                return (nocash) ? elements : new Elements(elements);
        },

        filterByAttribute: function(name, operator, value, nocash){
                var elements = this.filter(function(el){
                        var current = Element.getProperty(el, name);
                        if (!current) return false;
                        if (!operator) return true;
                        switch(operator){
                                case '=': return (current == value);
                                case '*=': return (current.contains(value));
                                case '^=': return (current.substr(0, value.length) == value);
                                case '$=': return (current.substr(current.length - value.length) == value);
                                case '!=': return (current != value);
                                case '~=': return current.contains(value, ' ');
                        }
                        return false;
                });
                return (nocash) ? elements : new Elements(elements);
        }

});

function $E(selector, filter){
        return ($(filter) || document).getElement(selector);
};

function $ES(selector, filter){
        return ($(filter) || document).getElementsBySelector(selector);
};

$$.shared = {

        'regexp': /^(\w*|\*)(?:#([\w-]+)|\.([\w-]+))?(?:\[(\w+)(?:([!*^$]?=)["']?([^"'\]]*)["']?)?])?$/,

        'xpath': {

                getParam: function(items, context, param, i){
                        var temp = [context.namespaceURI ? 'xhtml:' : '', param[1]];
                        if (param[2]) temp.push('[@id="', param[2], '"]');
                        if (param[3]) temp.push('[contains(concat(" ", @class, " "), " ', param[3], ' ")]');
                        if (param[4]){
                                if (param[5] && param[6]){
                                        switch(param[5]){
                                                case '*=': temp.push('[contains(@', param[4], ', "', param[6], '")]'); break;
                                                case '^=': temp.push('[starts-with(@', param[4], ', "', param[6], '")]'); break;
                                                case '$=': temp.push('[substring(@', param[4], ', string-length(@', param[4], ') - ', param[6].length, ' + 1) = "', param[6], '"]'); break;
                                                case '=': temp.push('[@', param[4], '="', param[6], '"]'); break;
                                                case '!=': temp.push('[@', param[4], '!="', param[6], '"]');
                                        }
                                } else {
                                        temp.push('[@', param[4], ']');
                                }
                        }
                        items.push(temp.join(''));
                        return items;
                },

                getItems: function(items, context, nocash){
                        var elements = [];
                        var xpath = document.evaluate('.//' + items.join('//'), context, $$.shared.resolver, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
                        for (var i = 0, j = xpath.snapshotLength; i < j; i++) elements.push(xpath.snapshotItem(i));
                        return (nocash) ? elements : new Elements(elements.map($));
                }

        },

        'normal': {

                getParam: function(items, context, param, i){
                        if (i == 0){
                                if (param[2]){
                                        var el = context.getElementById(param[2]);
                                        if (!el || ((param[1] != '*') && (Element.getTag(el) != param[1]))) return false;
                                        items = [el];
                                } else {
                                        items = $A(context.getElementsByTagName(param[1]));
                                }
                        } else {
                                items = $$.shared.getElementsByTagName(items, param[1]);
                                if (param[2]) items = Elements.filterById(items, param[2], true);
                        }
                        if (param[3]) items = Elements.filterByClass(items, param[3], true);
                        if (param[4]) items = Elements.filterByAttribute(items, param[4], param[5], param[6], true);
                        return items;
                },

                getItems: function(items, context, nocash){
                        return (nocash) ? items : $$.unique(items);
                }

        },

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

        getElementsByTagName: function(context, tagName){
                var found = [];
                for (var i = 0, j = context.length; i < j; i++) found.extend(context[i].getElementsByTagName(tagName));
                return found;
        }

};

$$.shared.method = (window.xpath) ? 'xpath' : 'normal';

var Selectors = {

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

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

};

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

Selectors.Pseudo = new Abstract();

Selectors.XPath = {

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

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

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

};

Selectors.Filter = {

        getParam: function(items, separator, context, tag, id, classNames, attributes, pseudos){
                var i;
                if (separator){
                        var found = [], j = items.length;
                        switch (separator){
                                case ' ':
                                        for (i = 0; i < j; i++) found.extend(items[i].getElementsByTagName(tag));
                                        break;
                                case '>':
                                        for (i = 0; i < j; i++){
                                                var children = items[i].childNodes;
                                                for (var k = 0, l = children.length; k < l; k++){
                                                        if (Selectors.Filter.hasTag(children[k], tag)) found.push(children[k]);
                                                }
                                        }
                                        break;
                                default:
                                        var all = !!(separator == '~');
                                        for (i = 0; i < j; i++){
                                                var next = items[i];
                                                while ((next = next.nextSibling)){
                                                        if (Selectors.Filter.hasTag(next, tag)){
                                                                found.push(next);
                                                                if (!all) break;
                                                        }
                                                }
                                        }
                        }
                        items = (id) ? Elements.filterById(found, id, true) : found;
                } else {
                        if (id){
                                var el = context.getElementById(id);
                                if (!el || ((tag != '*') && (tag != el.tagName.toLowerCase()))) return false;
                                items = [el];
                        } else {
                                items = $A(context.getElementsByTagName(tag));
                        }
                }
                for (i = classNames.length; i--; i) items = Elements.filterByClass(items, classNames[i], true);
                for (i = attributes.length; i--; i){
                        var bits = attributes[i];
                        items = Elements.filterByAttribute(items, bits[0], bits[1], bits[2], true);
                }
                for (i = pseudos.length; i--; i){
                        var pseudo = pseudos[i];
                        if (pseudo.parser && pseudo.parser.filter){
                                var temp = {}, xparser = pseudo.parser, argument = pseudo.argument;
                                items = items.filter(function(el, i, array){
                                        return xparser.filter(el, argument, i, array, temp);
                                });
                                temp = null;
                        } else {
                                items = Elements.filterByAttribute(items, pseudo.name, ($chk(pseudo.argument)) ? '=' : false, pseudo.argument, true);
                        }
                }
                return items;
        },

        getItems: function(items, context, nocash){
                return (nocash) ? items : new Elements(items);
        },

        hasTag: function(el, tag){
                return (el.nodeName && el.nodeType == 1 && (tag == '*' || el.tagName.toLowerCase() == tag));
        }

};

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

Element.Methods.Dom = {

        getElements: function(selector, nocash){
                /*var items = [];
                selector = selector.trim().split(' ');
                for (var i = 0, j = selector.length; i < j; i++){
                        var sel = selector[i];
                        var param = sel.match($$.shared.regexp);
                        if (!param) break;
                        param[1] = param[1] || '*';
                        var temp = $$.shared[$$.shared.method].getParam(items, this, param, i);
                        if (!temp) break;
                        items = temp;
                }
                return $$.shared[$$.shared.method].getItems(items, this, nocash);*/
    var items = [];
    var separators = [];
    selector = selector.trim().replace(Selectors.sRegExp, function(match){
            if (match.charAt(2)) match = match.trim();
            separators.push(match.charAt(0));
            return '%' + match.charAt(1);
    }).split('%');
    for (var i = 0, j = selector.length; i < j; i++){
            var params = Selectors.$parse(selector[i]);
            if (!params) break;
            var temp = Selectors.Method.getParam(items, separators[i - 1] || false, this, params.tag, params.id, params.classes, params.attributes, params.pseudos);
            if (!temp) break;
            items = temp;
    }
    return Selectors.Method.getItems(items, this, nocash);              
        },

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

        getElementsBySelector: function(selector, nocash){
                var elements = [];
                selector = selector.split(',');
                for (var i = 0, j = selector.length; i < j; i++) elements = elements.concat(this.getElements(selector[i], true));
                return (nocash) ? elements : $$.unique(elements);
        }

};

Element.extend({

        getElementById: function(id){
                var el = document.getElementById(id);
                if (!el) return false;
                for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
                        if (!parent) return false;
                }
                return el;
        }/*compatibility*/,

        getElementsByClassName: function(className){ 
                return this.getElements('.' + className); 
        }

});

document.extend(Element.Methods.Dom);
Element.extend(Element.Methods.Dom);

Element.extend({

        getValue: function(){
                switch(this.getTag()){
                        case 'select':
                                var values = [];
                                $each(this.options, function(option){
                                        if (option.selected) values.push((option.value != '')?option.value:option.text);
                                });
                                return (this.multiple) ? values : values[0];
                        case 'input': if (!(this.checked && ['checkbox', 'radio'].contains(this.type)) && !['hidden', 'text', 'password'].contains(this.type)) break;
                        case 'textarea': return this.value;
                }
                return false;
        },

        getFormElements: function(){
                return $$(this.getElementsByTagName('input'), this.getElementsByTagName('select'), this.getElementsByTagName('textarea'));
        },

        toQueryString: function(){
                var queryString = [];
                this.getFormElements().each(function(el){
                        var name = el.name;
                        var value = el.getValue();
                        if (value === false || !name || el.disabled) return;
                        var qs = function(val){
                                queryString.push(name + '=' + encodeURIComponent(val));
                        };
                        if ($type(value) == 'array') value.each(qs);
                        else qs(value);
                });
                return queryString.join('&');
        }

});

Element.extend({

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

        getSize: function(){
                return {
                        'scroll': {'x': this.scrollLeft, 'y': this.scrollTop},
                        'size': {'x': this.offsetWidth, 'y': this.offsetHeight},
                        'scrollSize': {'x': this.scrollWidth, 'y': this.scrollHeight}
                };
        },

        getPosition: function(overflown){
                overflown = overflown || [];
                var el = this, left = 0, top = 0;
                do {
                        left += el.offsetLeft || 0;
                        top += el.offsetTop || 0;
                        el = el.offsetParent;
                } while (el);
                overflown.each(function(element){
                        left -= element.scrollLeft || 0;
                        top -= element.scrollTop || 0;
                });
                return {'x': left, 'y': top};
        },

        getTop: function(overflown){
                return this.getPosition(overflown).y;
        },

        getLeft: function(overflown){
                return this.getPosition(overflown).x;
        },

        getCoordinates: function(overflown){
                var position = this.getPosition(overflown);
                var obj = {
                        'width': this.offsetWidth,
                        'height': this.offsetHeight,
                        'left': position.x,
                        'top': position.y
                };
                obj.right = obj.left + obj.width;
                obj.bottom = obj.top + obj.height;
                return obj;
        }

});

Element.Events.domready = {

        add: function(fn){
                if (window.loaded){
                        fn.call(this);
                        return;
                }
                var domReady = function(){
                        if (window.loaded) return;
                        window.loaded = true;
                        window.timer = $clear(window.timer);
                        this.fireEvent('domready');
                }.bind(this);
                if (document.readyState && window.webkit){
                        window.timer = function(){
                          //console.log(document.readyState);
                                if (['loaded','complete'].contains(document.readyState)) domReady();
                        }.periodical(50);
                } else if (document.readyState && window.ie){
                        if (!$('ie_ready')){
                                var src = (window.location.protocol == 'https:') ? '://0' : 'javascript:void(0)';
                                document.write('<script id="ie_ready" defer src="' + src + '"><\/script>');
                                $('ie_ready').onreadystatechange = function(){
                                        if (this.readyState == 'complete') domReady();
                                };
                        }
                } else {
                        window.addListener("load", domReady);
                        document.addListener("DOMContentLoaded", domReady);
                }
        }

};

window.onDomReady = function(fn){ 
        return this.addEvent('domready', fn); 
};

window.extend({

        getWidth: function(){
                if (this.webkit419) return this.innerWidth;
                if (this.opera) return document.body.clientWidth;
                return document.documentElement.clientWidth;
        },

        getHeight: function(){
                if (this.webkit419) return this.innerHeight;
                if (this.opera) return document.body.clientHeight;
                return document.documentElement.clientHeight;
        },

        getScrollWidth: function(){
                if (this.ie) return Math.max(document.documentElement.offsetWidth, document.documentElement.scrollWidth);
                if (this.webkit) return document.body.scrollWidth;
                return document.documentElement.scrollWidth;
        },

        getScrollHeight: function(){
                if (this.ie) return Math.max(document.documentElement.offsetHeight, document.documentElement.scrollHeight);
                if (this.webkit) return document.body.scrollHeight;
                return document.documentElement.scrollHeight;
        },

        getScrollLeft: function(){
                return this.pageXOffset || document.documentElement.scrollLeft;
        },

        getScrollTop: function(){
                return this.pageYOffset || document.documentElement.scrollTop;
        },

        getSize: function(){
                return {
                        'size': {'x': this.getWidth(), 'y': this.getHeight()},
                        'scrollSize': {'x': this.getScrollWidth(), 'y': this.getScrollHeight()},
                        'scroll': {'x': this.getScrollLeft(), 'y': this.getScrollTop()}
                };
        },
        getPosition: function(){return {'x': 0, 'y': 0};}

});

var Fx = {};

Fx.Base = new Class({

        options: {
                onStart: Class.empty,
                onComplete: Class.empty,
                onCancel: Class.empty,
                transition: function(p){
                        return -(Math.cos(Math.PI * p) - 1) / 2;
                },
                duration: 500,
                unit: 'px',
                wait: true,
                fps: 50
        },

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

        step: function(){
                var time = $time();
                if (time < this.time + this.options.duration){
                        this.delta = this.options.transition((time - this.time) / this.options.duration);
                        this.setNow();
                        this.increase();
                } else {
                        this.stop(true);
                        this.set(this.to);
                        this.fireEvent('onComplete', this.element, 10);
                        this.callChain();
                }
        },

        set: function(to){
                this.now = to;
                this.increase();
                return this;
        },

        setNow: function(){
                this.now = this.compute(this.from, this.to);
        },

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

        start: function(from, to){
                if (!this.options.wait) this.stop();
                else if (this.timer) return this;
                this.from = from;
                this.to = to;
                this.change = this.to - this.from;
                this.time = $time();
                this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
                this.fireEvent('onStart', this.element);
                return this;
        },

        stop: function(end){
                if (!this.timer) return this;
                this.timer = $clear(this.timer);
                if (!end) this.fireEvent('onCancel', this.element);
                return this;
        }/*compatibility*/,

        custom: function(from, to){
                return this.start(from, to);
        },

        clearTimer: function(end){
                return this.stop(end);
        }

});

Fx.Base.implement(new Chain, new Events, new Options);

Fx.CSS = {

        select: function(property, to){
                if (property.test(/color/i)) return this.Color;
                var type = $type(to);
                if ((type == 'array') || (type == 'string' && to.contains(' '))) return this.Multi;
                return this.Single;
        },

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

};

Fx.CSS.Single = {

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

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

        getValue: function(value, unit, property){
                if (unit == 'px' && property != 'opacity') value = Math.round(value);
                return value + unit;
        }

};

Fx.CSS.Multi = {

        parse: function(value){
                return value.push ? value : value.split(' ').map(function(v){
                        return parseFloat(v);
                });
        },

        getNow: function(from, to, fx){
                var now = [];
                for (var i = 0; i < from.length; i++) now[i] = fx.compute(from[i], to[i]);
                return now;
        },

        getValue: function(value, unit, property){
                if (unit == 'px' && property != 'opacity') value = value.map(Math.round);
                return value.join(unit + ' ') + unit;
        }

};

Fx.CSS.Color = {

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

        getNow: function(from, to, fx){
                var now = [];
                for (var i = 0; i < from.length; i++) now[i] = Math.round(fx.compute(from[i], to[i]));
                return now;
        },

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

};

Fx.Style = Fx.Base.extend({

        initialize: function(el, property, options){
                this.element = $(el);
                this.property = property;
                this.parent(options);
        },

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

        setNow: function(){
                this.now = this.css.getNow(this.from, this.to, this);
        },

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

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

        increase: function(){
                this.element.setStyle(this.property, this.css.getValue(this.now, this.options.unit, this.property));
        }

});

Element.extend({

        effect: function(property, options){
                return new Fx.Style(this, property, options);
        }

});

Fx.Styles = Fx.Base.extend({

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

        setNow: function(){
                for (var p in this.from) this.now[p] = this.css[p].getNow(this.from[p], this.to[p], this);
        },

        set: function(to){
                var parsed = {};
                this.css = {};
                for (var p in to){
                        this.css[p] = Fx.CSS.select(p, to[p]);
                        parsed[p] = this.css[p].parse(to[p]);
                }
                return this.parent(parsed);
        },

        start: function(obj){
                if (this.timer && this.options.wait) return this;
                this.now = {};
                this.css = {};
                var from = {}, to = {};
                for (var p in obj){
                        var parsed = Fx.CSS.parse(this.element, p, obj[p]);
                        from[p] = parsed.from;
                        to[p] = parsed.to;
                        this.css[p] = parsed.css;
                }
                return this.parent(from, to);
        },

        increase: function(){
                for (var p in this.now) this.element.setStyle(p, this.css[p].getValue(this.now[p], this.options.unit, p));
        }

});

Element.extend({

        effects: function(options){
                return new Fx.Styles(this, options);
        }

});

Fx.Elements = Fx.Base.extend({

        initialize: function(elements, options){
                this.elements = $$(elements);
                this.parent(options);
        },

        setNow: function(){
                for (var i in this.from){
                        var iFrom = this.from[i], iTo = this.to[i], iCss = this.css[i], iNow = this.now[i] = {};
                        for (var p in iFrom) iNow[p] = iCss[p].getNow(iFrom[p], iTo[p], this);
                }
        },

        set: function(to){
                var parsed = {};
                this.css = {};
                for (var i in to){
                        var iTo = to[i], iCss = this.css[i] = {}, iParsed = parsed[i] = {};
                        for (var p in iTo){
                                iCss[p] = Fx.CSS.select(p, iTo[p]);
                                iParsed[p] = iCss[p].parse(iTo[p]);
                        }
                }
                return this.parent(parsed);
        },

        start: function(obj){
                if (this.timer && this.options.wait) return this;
                this.now = {};
                this.css = {};
                var from = {}, to = {};
                for (var i in obj){
                        var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {}, iCss = this.css[i] = {};
                        for (var p in iProps){
                                var parsed = Fx.CSS.parse(this.elements[i], p, iProps[p]);
                                iFrom[p] = parsed.from;
                                iTo[p] = parsed.to;
                                iCss[p] = parsed.css;
                        }
                }
                return this.parent(from, to);
        },

        increase: function(){
                for (var i in this.now){
                        var iNow = this.now[i], iCss = this.css[i];
                        for (var p in iNow) this.elements[i].setStyle(p, iCss[p].getValue(iNow[p], this.options.unit, p));
                }
        }

});

Fx.Scroll = Fx.Base.extend({

        options: {
                overflown: [],
                offset: {'x': 0, 'y': 0},
                wheelStops: true
        },

        initialize: function(element, options){
                this.now = [];
                this.element = $(element);
                this.bound = {'stop': this.stop.bind(this, false)};
                this.parent(options);
                if (this.options.wheelStops){
                        this.addEvent('onStart', function(){
                                document.addEvent('mousewheel', this.bound.stop);
                        }.bind(this));
                        this.addEvent('onComplete', function(){
                                document.removeEvent('mousewheel', this.bound.stop);
                        }.bind(this));
                }
        },

        setNow: function(){
                for (var i = 0; i < 2; i++) this.now[i] = this.compute(this.from[i], this.to[i]);
        },

        scrollTo: function(x, y){
                if (this.timer && this.options.wait) return this;
                var el = this.element.getSize();
                var values = {'x': x, 'y': y};
                for (var z in el.size){
                        var max = el.scrollSize[z] - el.size[z];
                        if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z].limit(0, max) : max;
                        else values[z] = el.scroll[z];
                        values[z] += this.options.offset[z];
                }
                return this.start([el.scroll.x, el.scroll.y], [values.x, values.y]);
        },

        toTop: function(){
                return this.scrollTo(false, 0);
        },

        toBottom: function(){
                return this.scrollTo(false, 'full');
        },

        toLeft: function(){
                return this.scrollTo(0, false);
        },

        toRight: function(){
                return this.scrollTo('full', false);
        },

        toElement: function(el){
                var parent = this.element.getPosition(this.options.overflown);
                var target = $(el).getPosition(this.options.overflown);
                return this.scrollTo(target.x - parent.x, target.y - parent.y);
        },

        increase: function(){
                this.element.scrollTo(this.now[0], this.now[1]);
        }

});

Fx.Slide = Fx.Base.extend({

        options: {
                mode: 'vertical'
        },

        initialize: function(el, options){
                this.element = $(el);
                this.wrapper = new Element('div', {'styles': $extend(this.element.getStyles('margin'), {'overflow': 'hidden'})}).injectAfter(this.element).adopt(this.element);
                this.element.setStyle('margin', 0);
                this.setOptions(options);
                this.now = [];
                this.parent(this.options);
                this.open = true;
                this.addEvent('onComplete', function(){
                        this.open = (this.now[0] === 0);
                });
                if (window.webkit419) this.addEvent('onComplete', function(){
                        if (this.open) this.element.remove().inject(this.wrapper);
                });
        },

        setNow: function(){
                for (var i = 0; i < 2; i++) this.now[i] = this.compute(this.from[i], this.to[i]);
        },

        vertical: function(){
                this.margin = 'margin-top';
                this.layout = 'height';
                this.offset = this.element.offsetHeight;
        },

        horizontal: function(){
                this.margin = 'margin-left';
                this.layout = 'width';
                this.offset = this.element.offsetWidth;
        },

        slideIn: function(mode){
                this[mode || this.options.mode]();
                return this.start([this.element.getStyle(this.margin).toInt(), this.wrapper.getStyle(this.layout).toInt()], [0, this.offset]);
        },

        slideOut: function(mode){
                this[mode || this.options.mode]();
                return this.start([this.element.getStyle(this.margin).toInt(), this.wrapper.getStyle(this.layout).toInt()], [-this.offset, 0]);
        },

        hide: function(mode){
                this[mode || this.options.mode]();
                this.open = false;
                return this.set([-this.offset, 0]);
        },

        show: function(mode){
                this[mode || this.options.mode]();
                this.open = true;
                return this.set([0, this.offset]);
        },

        toggle: function(mode){
                if (this.wrapper.offsetHeight == 0 || this.wrapper.offsetWidth == 0) return this.slideIn(mode);
                return this.slideOut(mode);
        },

        increase: function(){
                this.element.setStyle(this.margin, this.now[0] + this.options.unit);
                this.wrapper.setStyle(this.layout, this.now[1] + this.options.unit);
        }

});

Fx.Transition = function(transition, params){
        params = params || [];
        if ($type(params) != 'array') params = [params];
        return $extend(transition, {
                easeIn: function(pos){
                        return transition(pos, params);
                },
                easeOut: function(pos){
                        return 1 - transition(1 - pos, params);
                },
                easeInOut: function(pos){
                        return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
                }
        });
};

Fx.Transitions = new Abstract({

        linear: function(p){
                return p;
        }

});

Fx.Transitions.extend = function(transitions){
        for (var transition in transitions){
                Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
                Fx.Transitions.compat(transition);
        }
};

Fx.Transitions.compat = function(transition){
        ['In', 'Out', 'InOut'].each(function(easeType){
                Fx.Transitions[transition.toLowerCase() + easeType] = Fx.Transitions[transition]['ease' + easeType];
        });
};

Fx.Transitions.extend({

        Pow: function(p, x){
                return Math.pow(p, x[0] || 6);
        },

        Expo: function(p){
                return Math.pow(2, 8 * (p - 1));
        },

        Circ: function(p){
                return 1 - Math.sin(Math.acos(p));
        },

        Sine: function(p){
                return 1 - Math.sin((1 - p) * Math.PI / 2);
        },

        Back: function(p, x){
                x = x[0] || 1.618;
                return Math.pow(p, 2) * ((x + 1) * p - x);
        },

        Bounce: function(p){
                var value;
                for (var a = 0, b = 1; 1; a += b, b /= 2){
                        if (p >= (7 - 4 * a) / 11){
                                value = - Math.pow((11 - 6 * a - 11 * p) / 4, 2) + b * b;
                                break;
                        }
                }
                return value;
        },

        Elastic: function(p, x){
                return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
        }

});

['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
        Fx.Transitions[transition] = new Fx.Transition(function(p){
                return Math.pow(p, [i + 2]);
        });
        Fx.Transitions.compat(transition);
});

var Drag = {};

Drag.Base = new Class({

        options: {
                handle: false,
                unit: 'px',
                onStart: Class.empty,
                onBeforeStart: Class.empty,
                onComplete: Class.empty,
                onSnap: Class.empty,
                onDrag: Class.empty,
                limit: false,
                modifiers: {x: 'left', y: 'top'},
                grid: false,
                snap: 6
        },

        initialize: function(el, options){
                this.setOptions(options);
                this.element = $(el);
                this.handle = $(this.options.handle) || this.element;
                this.mouse = {'now': {}, 'pos': {}};
                this.value = {'start': {}, 'now': {}};
                this.bound = {
                        'start': this.start.bindWithEvent(this),
                        'check': this.check.bindWithEvent(this),
                        'drag': this.drag.bindWithEvent(this),
                        'stop': this.stop.bind(this)
                };
                this.attach();
                if (this.options.initialize) this.options.initialize.call(this);
        },

        attach: function(){
                this.handle.addEvent('mousedown', this.bound.start);
                return this;
        },

        detach: function(){
                this.handle.removeEvent('mousedown', this.bound.start);
                return this;
        },

        start: function(event){
                this.fireEvent('onBeforeStart', this.element);
                this.mouse.start = event.page;
                var limit = this.options.limit;
                this.limit = {'x': [], 'y': []};
                for (var z in this.options.modifiers){
                        if (!this.options.modifiers[z]) continue;
                        this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
                        this.mouse.pos[z] = event.page[z] - this.value.now[z];
                        if (limit && limit[z]){
                                for (var i = 0; i < 2; i++){
                                        if ($chk(limit[z][i])) this.limit[z][i] = ($type(limit[z][i]) == 'function') ? limit[z][i]() : limit[z][i];
                                }
                        }
                }
                if ($type(this.options.grid) == 'number') this.options.grid = {'x': this.options.grid, 'y': this.options.grid};
                document.addListener('mousemove', this.bound.check);
                document.addListener('mouseup', this.bound.stop);
                this.fireEvent('onStart', this.element);
                event.stop();
        },

        check: function(event){
                var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
                if (distance > this.options.snap){
                        document.removeListener('mousemove', this.bound.check);
                        document.addListener('mousemove', this.bound.drag);
                        this.drag(event);
                        this.fireEvent('onSnap', this.element);
                }
                event.stop();
        },

        drag: function(event){
                this.out = false;
                this.mouse.now = event.page;
                for (var z in this.options.modifiers){
                        if (!this.options.modifiers[z]) continue;
                        this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
                        if (this.limit[z]){
                                if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
                                        this.value.now[z] = this.limit[z][1];
                                        this.out = true;
                                } else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
                                        this.value.now[z] = this.limit[z][0];
                                        this.out = true;
                                }
                        }
                        if (this.options.grid[z]) this.value.now[z] -= (this.value.now[z] % this.options.grid[z]);
                        this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
                }
                this.fireEvent('onDrag', this.element);
                event.stop();
        },

        stop: function(){
                document.removeListener('mousemove', this.bound.check);
                document.removeListener('mousemove', this.bound.drag);
                document.removeListener('mouseup', this.bound.stop);
                this.fireEvent('onComplete', this.element);
        }

});

Drag.Base.implement(new Events, new Options);

Element.extend({

        makeResizable: function(options){
                return new Drag.Base(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
        }

});

Drag.Move = Drag.Base.extend({

        options: {
                droppables: [],
                container: false,
                overflown: []
        },

        initialize: function(el, options){
                this.setOptions(options);
                this.element = $(el);
                this.droppables = $$(this.options.droppables);
                this.container = $(this.options.container);
                this.position = {'element': this.element.getStyle('position'), 'container': false};
                if (this.container) this.position.container = this.container.getStyle('position');
                if (!['relative', 'absolute', 'fixed'].contains(this.position.element)) this.position.element = 'absolute';
                var top = this.element.getStyle('top').toInt();
                var left = this.element.getStyle('left').toInt();
                if (this.position.element == 'absolute' && !['relative', 'absolute', 'fixed'].contains(this.position.container)){
                        top = $chk(top) ? top : this.element.getTop(this.options.overflown);
                        left = $chk(left) ? left : this.element.getLeft(this.options.overflown);
                } else {
                        top = $chk(top) ? top : 0;
                        left = $chk(left) ? left : 0;
                }
                this.element.setStyles({'top': top, 'left': left, 'position': this.position.element});
                this.parent(this.element);
        },

        start: function(event){
                this.overed = null;
                if (this.container){
                        var cont = this.container.getCoordinates();
                        var el = this.element.getCoordinates();
                        if (this.position.element == 'absolute' && !['relative', 'absolute', 'fixed'].contains(this.position.container)){
                                this.options.limit = {
                                        'x': [cont.left, cont.right - el.width],
                                        'y': [cont.top, cont.bottom - el.height]
                                };
                        } else {
                                this.options.limit = {
                                        'y': [0, cont.height - el.height],
                                        'x': [0, cont.width - el.width]
                                };
                        }
                }
                this.parent(event);
        },

        drag: function(event){
                this.parent(event);
                var overed = this.out ? false : this.droppables.filter(this.checkAgainst, this).getLast();
                if (this.overed != overed){
                        if (this.overed) this.overed.fireEvent('leave', [this.element, this]);
                        this.overed = overed ? overed.fireEvent('over', [this.element, this]) : null;
                }
                return this;
        },

        checkAgainst: function(el){
                el = el.getCoordinates(this.options.overflown);
                var now = this.mouse.now;
                return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
        },

        stop: function(){
                if (this.overed && !this.out) this.overed.fireEvent('drop', [this.element, this]);
                else this.element.fireEvent('emptydrop', this);
                this.parent();
                return this;
        }

});

Element.extend({

        makeDraggable: function(options){
                return new Drag.Move(this, options);
        }

});

var XHR = new Class({

        options: {
                method: 'post',
                async: true,
                onRequest: Class.empty,
                onSuccess: Class.empty,
                onFailure: Class.empty,
                urlEncoded: true,
                encoding: 'utf-8',
                autoCancel: false,
                headers: {}
        },

        setTransport: function(){
                this.transport = (window.XMLHttpRequest) ? new XMLHttpRequest() : (window.ie ? new ActiveXObject('Microsoft.XMLHTTP') : false);
                return this;
        },

        initialize: function(options){
                this.setTransport().setOptions(options);
                this.options.isSuccess = this.options.isSuccess || this.isSuccess;
                this.headers = {};
                if (this.options.urlEncoded && this.options.method == 'post'){
                        var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
                        this.setHeader('Content-type', 'application/x-www-form-urlencoded' + encoding);
                }
                if (this.options.initialize) this.options.initialize.call(this);
        },

        onStateChange: function(){
                if (this.transport.readyState != 4 || !this.running) return;
                this.running = false;
                var status = 0;
                try {status = this.transport.status;} catch(e){};
                if (this.options.isSuccess.call(this, status)) this.onSuccess();
                else this.onFailure();
                this.transport.onreadystatechange = Class.empty;
        },

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

        onSuccess: function(){
                this.response = {
                        'text': this.transport.responseText,
                        'xml': this.transport.responseXML
                };
                this.fireEvent('onSuccess', [this.response.text, this.response.xml]);
                this.callChain();
        },

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

        setHeader: function(name, value){
                this.headers[name] = value;
                return this;
        },

        send: function(url, data){
                if (this.options.autoCancel) this.cancel();
                else if (this.running) return this;
                this.running = true;
                if (data && this.options.method == 'get'){
                        url = url + (url.contains('?') ? '&' : '?') + data;
                        data = null;
                }
                this.transport.open(this.options.method.toUpperCase(), url, this.options.async);
                this.transport.onreadystatechange = this.onStateChange.bind(this);
                if ((this.options.method == 'post') && this.transport.overrideMimeType) this.setHeader('Connection', 'close');
                $extend(this.headers, this.options.headers);
                for (var type in this.headers) try {this.transport.setRequestHeader(type, this.headers[type]);} catch(e){};
                this.fireEvent('onRequest');
                this.transport.send($pick(data, null));
                return this;
        },

        cancel: function(){
                if (!this.running) return this;
                this.running = false;
                this.transport.abort();
                this.transport.onreadystatechange = Class.empty;
                this.setTransport();
                this.fireEvent('onCancel');
                return this;
        }

});

XHR.implement(new Chain, new Events, new Options);

var Ajax = XHR.extend({

        options: {
                data: null,
                update: null,
                onComplete: Class.empty,
                evalScripts: false,
                evalResponse: false
        },

        initialize: function(url, options){
                this.addEvent('onSuccess', this.onComplete);
                this.setOptions(options);
                this.options.data = this.options.data || this.options.postBody;
                if (!['post', 'get'].contains(this.options.method)){
                        this._method = '_method=' + this.options.method;
                        this.options.method = 'post';
                }
                this.parent();
                this.setHeader('X-Requested-With', 'XMLHttpRequest');
                this.setHeader('Accept', 'text/javascript, text/html, application/xml, text/xml, */*');
                this.url = url;
        },

        onComplete: function(){
                if (this.options.update) $(this.options.update).empty().setHTML(this.response.text);
                if (this.options.evalScripts || this.options.evalResponse) this.evalScripts();
                this.fireEvent('onComplete', [this.response.text, this.response.xml], 20);
        },

        request: function(data){
                data = data || this.options.data;
                switch($type(data)){
                        case 'element': data = $(data).toQueryString(); break;
                        case 'object': data = Object.toQueryString(data);
                }
                if (this._method) data = (data) ? [this._method, data].join('&') : this._method;
                return this.send(this.url, data);
        },

        evalScripts: function(){
                var script, scripts;
                if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) scripts = this.response.text;
                else {
                        scripts = [];
                        var regexp = /<script[^>]*>([\s\S]*?)<\/script>/gi;
                        while ((script = regexp.exec(this.response.text))) scripts.push(script[1]);
                        scripts = scripts.join('\n');
                }
                if (scripts) (window.execScript) ? window.execScript(scripts) : window.setTimeout(scripts, 0);
        },

        getHeader: function(name){
                try {return this.transport.getResponseHeader(name);} catch(e){};
                return null;
        }

});

Object.toQueryString = function(source){
        var queryString = [];
        for (var property in source) queryString.push(encodeURIComponent(property) + '=' + encodeURIComponent(source[property]));
        return queryString.join('&');
};

Element.extend({

        send: function(options){
                return new Ajax(this.getProperty('action'), $merge({data: this.toQueryString()}, options, {method: 'post'})).request();
        }

});

var Cookie = new Abstract({

        options: {
                domain: false,
                path: false,
                duration: false,
                secure: false
        },

        set: function(key, value, options){
                options = $merge(this.options, options);
                value = encodeURIComponent(value);
                if (options.domain) value += '; domain=' + options.domain;
                if (options.path) value += '; path=' + options.path;
                if (options.duration){
                        var date = new Date();
                        date.setTime(date.getTime() + options.duration * 24 * 60 * 60 * 1000);
                        value += '; expires=' + date.toGMTString();
                }
                if (options.secure) value += '; secure';
                document.cookie = key + '=' + value;
                return $extend(options, {'key': key, 'value': value});
        },

        get: function(key){
                var value = document.cookie.match('(?:^|;)\\s*' + key.escapeRegExp() + '=([^;]*)');
                return value ? decodeURIComponent(value[1]) : false;
        },

        remove: function(cookie, options){
                if ($type(cookie) == 'object') this.set(cookie.key, '', $merge(cookie, {duration: -1}));
                else this.set(cookie, '', $merge(options, {duration: -1}));
        }

});

var Json = {

        toString: function(obj){
                switch($type(obj)){
                        case 'string':
                                return '"' + obj.replace(/(["\\])/g, '\\$1') + '"';
                        case 'array':
                                return '[' + obj.map(Json.toString).join(',') + ']';
                        case 'object':
                                var string = [];
                                for (var property in obj) string.push(Json.toString(property) + ':' + Json.toString(obj[property]));
                                return '{' + string.join(',') + '}';
                        case 'number':
                                if (isFinite(obj)) break;
                        case false:
                                return 'null';
                }
                return String(obj);
        },

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

};

Json.Remote = XHR.extend({

        initialize: function(url, options){
                this.url = url;
                this.addEvent('onSuccess', this.onComplete);
                this.parent(options);
                this.setHeader('X-Request', 'JSON');
        },

        send: function(obj){
                return this.parent(this.url, 'json=' + Json.toString(obj));
        },

        onComplete: function(){
                this.fireEvent('onComplete', [Json.evaluate(this.response.text, this.options.secure)]);
        }

});

var Asset = new Abstract({

        javascript: function(source, properties){
                properties = $merge({
                        'onload': Class.empty
                }, properties);
                var script = new Element('script', {'src': source}).addEvents({
                        'load': properties.onload,
                        'readystatechange': function(){
                                if (this.readyState == 'complete') this.fireEvent('load');
                        }
                });
                delete properties.onload;
                return script.setProperties(properties).inject(document.head);
        },

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

        image: function(source, properties){
                properties = $merge({
                        'onload': Class.empty,
                        'onabort': Class.empty,
                        'onerror': Class.empty
                }, properties);
                var image = new Image();
                image.src = source;
                var element = new Element('img', {'src': source});
                ['load', 'abort', 'error'].each(function(type){
                        var event = properties['on' + type];
                        delete properties['on' + type];
                        element.addEvent(type, function(){
                                this.removeEvent(type, arguments.callee);
                                event.call(this);
                        });
                });
                if (image.width && image.height) element.fireEvent('load', element, 1);
                return element.setProperties(properties);
        },

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

});

var Hash = new Class({

        length: 0,

        initialize: function(object){
                this.obj = object || {};
                this.setLength();
        },

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

        hasKey: function(key){
                return (key in this.obj);
        },

        set: function(key, value){
                if (!this.hasKey(key)) this.length++;
                this.obj[key] = value;
                return this;
        },

        setLength: function(){
                this.length = 0;
                for (var p in this.obj) this.length++;
                return this;
        },

        remove: function(key){
                if (this.hasKey(key)){
                        delete this.obj[key];
                        this.length--;
                }
                return this;
        },

        each: function(fn, bind){
                $each(this.obj, fn, bind);
        },

        extend: function(obj){
                $extend(this.obj, obj);
                return this.setLength();
        },

        merge: function(){
                this.obj = $merge.apply(null, [this.obj].extend(arguments));
                return this.setLength();
        },

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

        keys: function(){
                var keys = [];
                for (var property in this.obj) keys.push(property);
                return keys;
        },

        values: function(){
                var values = [];
                for (var property in this.obj) values.push(this.obj[property]);
                return values;
        }

});

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

Hash.Cookie = Hash.extend({

        initialize: function(name, options){
                this.name = name;
                this.options = $extend({'autoSave': true}, options || {});
                this.load();
        },

        save: function(){
                if (this.length == 0){
                        Cookie.remove(this.name, this.options);
                        return true;
                }
                var str = Json.toString(this.obj);
                if (str.length > 4096) return false;
                Cookie.set(this.name, str, this.options);
                return true;
        },

        load: function(){
                this.obj = Json.evaluate(Cookie.get(this.name), true) || {};
                this.setLength();
        }

});

Hash.Cookie.Methods = {};
['extend', 'set', 'merge', 'empty', 'remove'].each(function(method){
        Hash.Cookie.Methods[method] = function(){
                Hash.prototype[method].apply(this, arguments);
                if (this.options.autoSave) this.save();
                return this;
        };
});
Hash.Cookie.implement(Hash.Cookie.Methods);

var Color = new Class({

        initialize: function(color, type){
                type = type || (color.push ? 'rgb' : 'hex');
                var rgb, hsb;
                switch(type){
                        case 'rgb':
                                rgb = color;
                                hsb = rgb.rgbToHsb();
                                break;
                        case 'hsb':
                                rgb = color.hsbToRgb();
                                hsb = color;
                                break;
                        default:
                                rgb = color.hexToRgb(true);
                                hsb = rgb.rgbToHsb();
                }
                rgb.hsb = hsb;
                rgb.hex = rgb.rgbToHex();
                return $extend(rgb, Color.prototype);
        },

        mix: function(){
                var colors = $A(arguments);
                var alpha = ($type(colors[colors.length - 1]) == 'number') ? colors.pop() : 50;
                var rgb = this.copy();
                colors.each(function(color){
                        color = new Color(color);
                        for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
                });
                return new Color(rgb, 'rgb');
        },

        invert: function(){
                return new Color(this.map(function(value){
                        return 255 - value;
                }));
        },

        setHue: function(value){
                return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
        },

        setSaturation: function(percent){
                return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
        },

        setBrightness: function(percent){
                return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
        }

});

function $RGB(r, g, b){
        return new Color([r, g, b], 'rgb');
};

function $HSB(h, s, b){
        return new Color([h, s, b], 'hsb');
};

Array.extend({

        rgbToHsb: function(){
                var red = this[0], green = this[1], blue = this[2];
                var hue, saturation, brightness;
                var max = Math.max(red, green, blue), min = Math.min(red, green, blue);
                var delta = max - min;
                brightness = max / 255;
                saturation = (max != 0) ? delta / max : 0;
                if (saturation == 0){
                        hue = 0;
                } else {
                        var rr = (max - red) / delta;
                        var gr = (max - green) / delta;
                        var br = (max - blue) / delta;
                        if (red == max) hue = br - gr;
                        else if (green == max) hue = 2 + rr - br;
                        else hue = 4 + gr - rr;
                        hue /= 6;
                        if (hue < 0) hue++;
                }
                return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
        },

        hsbToRgb: function(){
                var br = Math.round(this[2] / 100 * 255);
                if (this[1] == 0){
                        return [br, br, br];
                } else {
                        var hue = this[0] % 360;
                        var f = hue % 60;
                        var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
                        var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
                        var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
                        switch(Math.floor(hue / 60)){
                                case 0: return [br, t, p];
                                case 1: return [q, br, p];
                                case 2: return [p, br, t];
                                case 3: return [p, q, br];
                                case 4: return [t, p, br];
                                case 5: return [br, p, q];
                        }
                }
                return false;
        }

});

var Scroller = new Class({

        options: {
                area: 20,
                velocity: 1,
                onChange: function(x, y){
                        this.element.scrollTo(x, y);
                }
        },

        initialize: function(element, options){
                this.setOptions(options);
                this.element = $(element);
                this.mousemover = ([window, document].contains(element)) ? $(document.body) : this.element;
        },

        start: function(){
                this.coord = this.getCoords.bindWithEvent(this);
                this.mousemover.addListener('mousemove', this.coord);
        },

        stop: function(){
                this.mousemover.removeListener('mousemove', this.coord);
                this.timer = $clear(this.timer);
        },

        getCoords: function(event){
                this.page = (this.element == window) ? event.client : event.page;
                if (!this.timer) this.timer = this.scroll.periodical(50, this);
        },

        scroll: function(){
                var el = this.element.getSize();
                var pos = this.element.getPosition();

                var change = {'x': 0, 'y': 0};
                for (var z in this.page){
                        if (this.page[z] < (this.options.area + pos[z]) && el.scroll[z] != 0)
                                change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
                        else if (this.page[z] + this.options.area > (el.size[z] + pos[z]) && el.scroll[z] + el.size[z] != el.scrollSize[z])
                                change[z] = (this.page[z] - el.size[z] + this.options.area - pos[z]) * this.options.velocity;
                }
                if (change.y || change.x) this.fireEvent('onChange', [el.scroll.x + change.x, el.scroll.y + change.y]);
        }

});

Scroller.implement(new Events, new Options);

var Slider = new Class({

        options: {
                onChange: Class.empty,
                onComplete: Class.empty,
                onTick: function(pos){
                        this.knob.setStyle(this.p, pos);
                },
                mode: 'horizontal',
                steps: 100,
                offset: 0
        },

        initialize: function(el, knob, options){
                this.element = $(el);
                this.knob = $(knob);
                this.setOptions(options);
                this.previousChange = -1;
                this.previousEnd = -1;
                this.step = -1;
                this.element.addEvent('mousedown', this.clickedElement.bindWithEvent(this));
                var mod, offset;
                switch(this.options.mode){
                        case 'horizontal':
                                this.z = 'x';
                                this.p = 'left';
                                mod = {'x': 'left', 'y': false};
                                offset = 'offsetWidth';
                                break;
                        case 'vertical':
                                this.z = 'y';
                                this.p = 'top';
                                mod = {'x': false, 'y': 'top'};
                                offset = 'offsetHeight';
                }
                this.max = this.element[offset] - this.knob[offset] + (this.options.offset * 2);
                this.half = this.knob[offset]/2;
                this.getPos = this.element['get' + this.p.capitalize()].bind(this.element);
                this.knob.setStyle('position', 'relative').setStyle(this.p, - this.options.offset);
                var lim = {};
                lim[this.z] = [- this.options.offset, this.max - this.options.offset];
                this.drag = new Drag.Base(this.knob, {
                        limit: lim,
                        modifiers: mod,
                        snap: 0,
                        onStart: function(){
                                this.draggedKnob();
                        }.bind(this),
                        onDrag: function(){
                                this.draggedKnob();
                        }.bind(this),
                        onComplete: function(){
                                this.draggedKnob();
                                this.end();
                        }.bind(this)
                });
                if (this.options.initialize) this.options.initialize.call(this);
        },

        set: function(step){
                this.step = step.limit(0, this.options.steps);
                this.checkStep();
                this.end();
                this.fireEvent('onTick', this.toPosition(this.step));
                return this;
        },

        clickedElement: function(event){
                var position = event.page[this.z] - this.getPos() - this.half;
                position = position.limit(-this.options.offset, this.max -this.options.offset);
                this.step = this.toStep(position);
                this.checkStep();
                this.end();
                this.fireEvent('onTick', position);
        },

        draggedKnob: function(){
                this.step = this.toStep(this.drag.value.now[this.z]);
                this.checkStep();
        },

        checkStep: function(){
                if (this.previousChange != this.step){
                        this.previousChange = this.step;
                        this.fireEvent('onChange', this.step);
                }
        },

        end: function(){
                if (this.previousEnd !== this.step){
                        this.previousEnd = this.step;
                        this.fireEvent('onComplete', this.step + '');
                }
        },

        toStep: function(position){
                return Math.round((position + this.options.offset) / this.max * this.options.steps);
        },

        toPosition: function(step){
                return this.max * step / this.options.steps;
        }

});

Slider.implement(new Events);
Slider.implement(new Options);

var SmoothScroll = Fx.Scroll.extend({

        initialize: function(options){
                this.parent(window, options);
                this.links = (this.options.links) ? $$(this.options.links) : $$(document.links);
                var location = window.location.href.match(/^[^#]*/)[0] + '#';
                this.links.each(function(link){
                        if (link.href.indexOf(location) != 0) return;
                        var anchor = link.href.substr(location.length);
                        if (anchor && $(anchor)) this.useLink(link, anchor);
                }, this);
                if (!window.webkit419) this.addEvent('onComplete', function(){
                        window.location.hash = this.anchor;
                });
        },

        useLink: function(link, anchor){
                link.addEvent('click', function(event){
                        this.anchor = anchor;
                        this.toElement(anchor);
                        event.stop();
                }.bindWithEvent(this));
        }

});

var Sortables = new Class({

        options: {
                handles: false,
                onStart: Class.empty,
                onComplete: Class.empty,
                ghost: true,
                snap: 3,
                onDragStart: function(element, ghost){
                        ghost.setStyle('opacity', 0.7);
                        element.setStyle('opacity', 0.7);
                },
                onDragComplete: function(element, ghost){
                        element.setStyle('opacity', 1);
                        ghost.remove();
                        this.trash.remove();
                }
        },

        initialize: function(list, options){
                this.setOptions(options);
                this.list = $(list);
                this.elements = this.list.getChildren();
                this.handles = (this.options.handles) ? $$(this.options.handles) : this.elements;
                this.bound = {
                        'start': [],
                        'moveGhost': this.moveGhost.bindWithEvent(this)
                };
                for (var i = 0, l = this.handles.length; i < l; i++){
                        this.bound.start[i] = this.start.bindWithEvent(this, this.elements[i]);
                }
                this.attach();
                if (this.options.initialize) this.options.initialize.call(this);
                this.bound.move = this.move.bindWithEvent(this);
                this.bound.end = this.end.bind(this);
        },

        attach: function(){
                this.handles.each(function(handle, i){
                        handle.addEvent('mousedown', this.bound.start[i]);
                }, this);
        },

        detach: function(){
                this.handles.each(function(handle, i){
                        handle.removeEvent('mousedown', this.bound.start[i]);
                }, this);
        },

        start: function(event, el){
                this.active = el;
                this.coordinates = this.list.getCoordinates();
                if (this.options.ghost){
                        var position = el.getPosition();
                        this.offset = event.page.y - position.y;
                        this.trash = new Element('div').inject(document.body);
                        this.ghost = el.clone().inject(this.trash).setStyles({
                                'position': 'absolute',
                                'left': position.x,
                                'top': event.page.y - this.offset
                        });
                        document.addListener('mousemove', this.bound.moveGhost);
                        this.fireEvent('onDragStart', [el, this.ghost]);
                }
                document.addListener('mousemove', this.bound.move);
                document.addListener('mouseup', this.bound.end);
                this.fireEvent('onStart', el);
                event.stop();
        },

        moveGhost: function(event){
                var value = event.page.y - this.offset;
                value = value.limit(this.coordinates.top, this.coordinates.bottom - this.ghost.offsetHeight);
                this.ghost.setStyle('top', value);
                event.stop();
        },

        move: function(event){
                var now = event.page.y;
                this.previous = this.previous || now;
                var up = ((this.previous - now) > 0);
                var prev = this.active.getPrevious();
                var next = this.active.getNext();
                if (prev && up && now < prev.getCoordinates().bottom) this.active.injectBefore(prev);
                if (next && !up && now > next.getCoordinates().top) this.active.injectAfter(next);
                this.previous = now;
        },

        serialize: function(converter){
                return this.list.getChildren().map(converter || function(el){
                        return this.elements.indexOf(el);
                }, this);
        },

        end: function(){
                this.previous = null;
                document.removeListener('mousemove', this.bound.move);
                document.removeListener('mouseup', this.bound.end);
                if (this.options.ghost){
                        document.removeListener('mousemove', this.bound.moveGhost);
                        this.fireEvent('onDragComplete', [this.active, this.ghost]);
                }
                this.fireEvent('onComplete', this.active);
        }

});

Sortables.implement(new Events, new Options);

var Tips = new Class({

        options: {
                onShow: function(tip){
                        tip.setStyle('visibility', 'visible');
                },
                onHide: function(tip){
                        tip.setStyle('visibility', 'hidden');
                },
                maxTitleChars: 30,
                showDelay: 100,
                hideDelay: 100,
                className: 'tool',
                offsets: {'x': 16, 'y': 16},
                fixed: false
        },

        initialize: function(elements, options){
                this.setOptions(options);
                this.toolTip = new Element('div', {
                        'class': this.options.className + '-tip',
                        'styles': {
                                'position': 'absolute',
                                'top': '0',
                                'left': '0',
                                'visibility': 'hidden'
                        }
                }).inject(document.body);
                this.wrapper = new Element('div').inject(this.toolTip);
                $$(elements).each(this.build, this);
                if (this.options.initialize) this.options.initialize.call(this);
        },

        build: function(el){
                el.$tmp.myTitle = (el.href && el.getTag() == 'a') ? el.href.replace('http://', '') : (el.rel || false);
                if (el.title){
                        var dual = el.title.split('::');
                        if (dual.length > 1){
                                el.$tmp.myTitle = dual[0].trim();
                                el.$tmp.myText = dual[1].trim();
                        } else {
                                el.$tmp.myText = el.title;
                        }
                        el.removeAttribute('title');
                } else {
                        el.$tmp.myText = false;
                }
                if (el.$tmp.myTitle && el.$tmp.myTitle.length > this.options.maxTitleChars) el.$tmp.myTitle = el.$tmp.myTitle.substr(0, this.options.maxTitleChars - 1) + "&hellip;";
                el.addEvent('mouseenter', function(event){
                        this.start(el);
                        if (!this.options.fixed) this.locate(event);
                        else this.position(el);
                }.bind(this));
                if (!this.options.fixed) el.addEvent('mousemove', this.locate.bindWithEvent(this));
                var end = this.end.bind(this);
                el.addEvent('mouseleave', end);
                el.addEvent('trash', end);
        },

        start: function(el){
                this.wrapper.empty();
                if (el.$tmp.myTitle){
                        this.title = new Element('span').inject(new Element('div', {'class': this.options.className + '-title'}).inject(this.wrapper)).setHTML(el.$tmp.myTitle);
                }
                if (el.$tmp.myText){
                        this.text = new Element('span').inject(new Element('div', {'class': this.options.className + '-text'}).inject(this.wrapper)).setHTML(el.$tmp.myText);
                }
                $clear(this.timer);
                this.timer = this.show.delay(this.options.showDelay, this);
        },

        end: function(event){
                $clear(this.timer);
                this.timer = this.hide.delay(this.options.hideDelay, this);
        },

        position: function(element){
                var pos = element.getPosition();
                this.toolTip.setStyles({
                        'left': pos.x + this.options.offsets.x,
                        'top': pos.y + this.options.offsets.y
                });
        },

        locate: function(event){
                var win = {'x': window.getWidth(), 'y': window.getHeight()};
                var scroll = {'x': window.getScrollLeft(), 'y': window.getScrollTop()};
                var tip = {'x': this.toolTip.offsetWidth, 'y': this.toolTip.offsetHeight};
                var prop = {'x': 'left', 'y': 'top'};
                for (var z in prop){
                        var pos = event.page[z] + this.options.offsets[z];
                        if ((pos + tip[z] - scroll[z]) > win[z]) pos = event.page[z] - this.options.offsets[z] - tip[z];
                        this.toolTip.setStyle(prop[z], pos);
                };
        },

        show: function(){
                if (this.options.timeout) this.timer = this.hide.delay(this.options.timeout, this);
                this.fireEvent('onShow', [this.toolTip]);
        },

        hide: function(){
                this.fireEvent('onHide', [this.toolTip]);
        }

});

Tips.implement(new Events, new Options);

var Group = new Class({

        initialize: function(){
                this.instances = $A(arguments);
                this.events = {};
                this.checker = {};
        },

        addEvent: function(type, fn){
                this.checker[type] = this.checker[type] || {};
                this.events[type] = this.events[type] || [];
                if (this.events[type].contains(fn)) return false;
                else this.events[type].push(fn);
                this.instances.each(function(instance, i){
                        instance.addEvent(type, this.check.bind(this, [type, instance, i]));
                }, this);
                return this;
        },

        check: function(type, instance, i){
                this.checker[type][i] = true;
                var every = this.instances.every(function(current, j){
                        return this.checker[type][j] || false;
                }, this);
                if (!every) return;
                this.checker[type] = {};
                this.events[type].each(function(event){
                        event.call(this, this.instances, instance);
                }, this);
        }

});

var Accordion = Fx.Elements.extend({

        options: {
                onActive: Class.empty,
                onBackground: Class.empty,
                display: 0,
                show: false,
                height: true,
                width: false,
                opacity: true,
                fixedHeight: false,
                fixedWidth: false,
                wait: false,
                alwaysHide: false
        },

        initialize: function(){
                var options, togglers, elements, container;
                $each(arguments, function(argument, i){
                        switch($type(argument)){
                                case 'object': options = argument; break;
                                case 'element': container = $(argument); break;
                                default:
                                        var temp = $$(argument);
                                        if (!togglers) togglers = temp;
                                        else elements = temp;
                        }
                });
                this.togglers = togglers || [];
                this.elements = elements || [];
                this.container = $(container);
                this.setOptions(options);
                this.previous = -1;
                if (this.options.alwaysHide) this.options.wait = true;
                if ($chk(this.options.show)){
                        this.options.display = false;
                        this.previous = this.options.show;
                }
                if (this.options.start){
                        this.options.display = false;
                        this.options.show = false;
                }
                this.effects = {};
                if (this.options.opacity) this.effects.opacity = 'fullOpacity';
                if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
                if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
                for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
                this.elements.each(function(el, i){
                        if (this.options.show === i){
                                this.fireEvent('onActive', [this.togglers[i], el]);
                        } else {
                                for (var fx in this.effects) el.setStyle(fx, 0);
                        }
                }, this);
                this.parent(this.elements);
                if ($chk(this.options.display)) this.display(this.options.display);
        },

        addSection: function(toggler, element, pos){
                toggler = $(toggler);
                element = $(element);
                var test = this.togglers.contains(toggler);
                var len = this.togglers.length;
                this.togglers.include(toggler);
                this.elements.include(element);
                if (len && (!test || pos)){
                        pos = $pick(pos, len - 1);
                        toggler.injectBefore(this.togglers[pos]);
                        element.injectAfter(toggler);
                } else if (this.container && !test){
                        toggler.inject(this.container);
                        element.inject(this.container);
                }
                var idx = this.togglers.indexOf(toggler);
                toggler.addEvent('click', this.display.bind(this, idx));
                if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
                if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
                element.fullOpacity = 1;
                if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
                if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
                element.setStyle('overflow', 'hidden');
                if (!test){
                        for (var fx in this.effects) element.setStyle(fx, 0);
                }
                return this;
        },

        display: function(index){
                index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
                if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
                this.previous = index;
                var obj = {};
                this.elements.each(function(el, i){
                        obj[i] = {};
                        var hide = (i != index) || (this.options.alwaysHide && (el.offsetHeight > 0));
                        this.fireEvent(hide ? 'onBackground' : 'onActive', [this.togglers[i], el]);
                        for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
                }, this);
                return this.start(obj);
        },

        showThisHideOpen: function(index){return this.display(index);}

});

Fx.Accordion = Accordion;/*	
	Script: form.validator.js
	A css-class based form validation system.
	
	Dependencies:
	Mootools: <Moo.js>, <Utility.js>, <Common.js>, <Element.js>, <Function.js>, <Event.js>, <String.js>, <Fx.Base.js>, 
			<Window.Base.js>, <Fx.Style.js>, <Fx.Styles.js>, <Dom.js>
			
	Authors:
		Aaron Newton, <aaron [dot] newton [at] cnet [dot] com>
		Based on validation.js by Andrew Tetlaw (http://tetlaw.id.au/view/blog/really-easy-field-validation-with-prototype)

	Class: InputValidator
	This class contains functionality to test a field for various criteria and also to generate 
	an error message when that test fails.
	
	Arguments:
	className - a className that this field will be related to (see example below);
	options - an object with name/value pairs.
	
	Options:
	errorMsg - a message to display; see section below for details.
	test - a function that returns true or false
	
	errorMsg:
	The errorMsg option can be any of the following:
		string - the message to display if the field fails validation
		boolean false - do not display a message at all
		function - a function to evaluate that returns either a string or false.
			This function will be passed two parameters: the field being evaluated and
			any properties defined for the validator as a className (see examples below)
	
	test:
	The test option is a function that will be passed the field being evaluated and
	any properties defined for the validator as a className (see example below); this
	function must return true or false.

	Examples:
(start code)
//html code
<input type="text" name="firstName" class="required" id="firstName">
//simple validator
var isEmpty = new InputValidator('required', {
	errorMsg: 'This field is required.',
	test: function(field){
		return ((element.getValue() == null) || (element.getValue().length == 0));
	}
});
isEmpty.test($("firstName")); //true if empty
isEmpty.getError($("firstName")) //returns "This field is required."

//two complex validators
<input type="text" name="username" class="minLength maxLength" validatorProps="{minLength:10, maxLength:100}" id="username">

var minLength = new InputValidator ('minLength', {
	errorMsg: function(element, props){
		//props is {minLength:10, maxLength:100}
		if($type(props.minLength))
			return 'Please enter at least ' + props.minLength + ' characters (you entered ' + element.value.length + ' characters).';
		else return '';
	}, 
	test: function(element, props) {
		//if the value is >= than the minLength value, element passes test
		return (element.value.length >= $pick(props.minLength, 0));
		else return false;
	}
});

minLength.test($('username'));

var maxLength = new InputValidator ('maxLength', {
	errorMsg: function(element, props){
		//props is {minLength:10, maxLength:100}
		if($type(props.maxLength))
			return 'Please enter no more than ' + props.maxLength + ' characters (you entered ' + element.value.length + ' characters).';
		else return '';
	}, 
	test: function(element, props) {
		//if the value is <= than the maxLength value, element passes test
		return (element.value.length <= $pick(props.maxLength, 10000));
		else return false;
	}
});(end)
	*/

var InputValidator = new Class({
	initialize: function(className, options){
		this.options = Object.extend({
			errorMsg: 'Validation failed.',
			test: function(field){return true}
		}, options || {});
		this.className = className;
	},
/*	Property: test
		Tests a field against the validator's rule(s).
		
		Arguments:
		field - the form input to test
		
		Returns:
		true - the field passes the test
		false - it does not pass the test
	*/
	test: function(field){
		if($(field)) return this.options.test($(field), this.getProps(field));
		else return false;
	},
/*	Property: getError
		Retrieves the error message for the validator.
		
		Arguments:
		field - the form input to test
		
		Returns:
		The error message or the boolean false if no message is meant to be returned.
	*/
	getError: function(field){
		var err = this.options.errorMsg;
		if($type(err) == "function") err = err($(field), this.getProps(field));
		return err;
	},
	getProps: function(field){
		/*if($(field) && $(field).getProperty('validatorProps')){
			try {
				return Json.evaluate($(field).getProperty('validatorProps'));
			}catch(e){ return {}}
		} else {
			return {}
		}*/
    var rx = new RegExp('validatorProps\{(.*)\}', 'i');
    if ($(field) && (props = $(field).className.match(rx)) && props && (props.length > 0)) {
      try {
        return Json.evaluate('{' + props[1] + '}');
      } catch(e) { return {} }
    } else {
      return {}
    }
	}
});

/*	Class: FormValidator
		Evalutes an entire form against all the validators that are set up, displaying messages
		and returning a true/false response for the evaluation of the entire form.
		
		An instance of the FormValidator class will test each field and then behave according to
		the options passed in.
		
		Arguments:
		form - the form to evaluate
		options - an object with name/value pairs
		
		Options:
		fieldSelectors - the selector for fields to include in the validation;
				defaults to: "input, select, textarea"
		useTitles - use the titles of inputs for the error message; overrides
				the messages defined in the InputValidators (see <InputValidator>); defaults to false
		evaluateOnSubmit - validate the form when the user submits it; defaults to true
		evaluateFieldsOnBlur - validate the fields when the blur event fires; defaults to true
		onFormValidate - function to execute when the form validation completes; this function
			is passed two arguments: a boolean (true if the form passed validation) and the form element
		onElementValidate - function to execute when an input element is tested; this function
			is passed two arguments: a boolean (true if the form passed validation) and the input element
		
		Example:
(start code)var myFormValidator = new FormValidator($('myForm'), {
	onFormValidate: myFormHandler,
	useTitles: true
});(end)

		Note: FormValidator must be configured with <Validator> objects; see below for details as well as a list of built-in validators. Each <Validator> will be applied to any input that matches its className within the elements of the form that match the fieldSelectors option.
	*/
var FormValidator = new Class({
	initialize: function(form, options){
		this.options = Object.extend({
			fieldSelectors:"input,select,textarea",
			useTitles:false,
			evaluateOnSubmit:true,
			evaluateFieldsOnBlur: true,
			useFX: true,
			useShowFX: true,
			useHideFX: true,
			focusFirstError: true,
			onFormValidate: function(isValid, form){},
			onElementValidate: function(isValid, field){}
		}, options || {});
		try {
			this.form = $(form);
			if(this.options.evaluateOnSubmit) this.form.addEvent('submit', this.onSubmit.bind(this));
			if(this.options.evaluateFieldsOnBlur) this.watchFields();
		}catch(e){//console.log('error: %s', e);
		}
	},
	watchFields: function(){
		try{
			this.form.getElementsBySelector(this.options.fieldSelectors).each(function(el){
				el.addEvent('blur', this.validateField.pass(el, this));
			}, this);
		}catch(e){//console.log('error: %s', e);
		}
	},
	onSubmit: function(event){
		if(!this.validate()) new Event(event).stop();
	},
/*	Property: reset
		Removes all the error messages from the form.
	*/
	reset: function() {
		this.form.getElementsBySelector(this.options.fieldSelectors).each(this.resetField, this);
	}, 
/*	Property: validate
		Validates all the inputs in the form; note that this function is called on submit unless
		you specify otherwise in the options.
	*/
	validate : function() {
	  this.focusedFirst = false;
		var result = this.form.getElementsBySelector(this.options.fieldSelectors, true).map(function(field) { return this.validateField(field); }, this);
		result = result.every(function(val){
			return val;
		});
		this.options.onFormValidate(result, this.form);
		return result;
	},
/*	Property: validateField
		Validates the value of a field against all the validators.
		
		Arguments:
		field - the input element to evaluate
	*/
	validateField: function(field){
		field = $(field);
		var result = true;
		if(field){
		  if (field.disabled) return true;
			var validators = field.className.split(" ").some(function(cn){
				return FormValidator.getValidator(cn);
			});
			result = field.className.split(" ").map(function(className){
				var test = this.test(className,field);
				return test;
			}, this);
			result = result.every(function(val){
				return val;
			});
			if(validators){
				if(result) field.addClass('validation-passed').removeClass('validation-failed');
				else field.addClass('validation-failed').removeClass('validation-passed');
			}
		}
		return result;
	},
	getPropName: function(className){
		return '__advice'+className;
	},
/*	Property: test
		Tests a field against a specific validator.
		
		Arguments:
		className - the className associated with the validator
		field - the input element
	*/
	test: function(className, field){
		field = $(field);
		var isValid = true;
		if(field) {
			var validator = FormValidator.getValidator(className);
			if(validator && this.isVisible(field)) {
				isValid = validator.test(field);
				//if the element is visible and it failes to validate
				if(!isValid && validator.getError(field)){
					var advice = this.makeAdvice(className, field, validator.getError(field));
					this.showAdvice(className, field);
				} else this.hideAdvice(className, field);
				if (this.options.focusFirstError && !isValid && !this.focusedFirst) {
				  this.focusedFirst = true;
				  try {
				    field.focus();
				  } catch(e) {}
				}
				this.options.onElementValidate(isValid, field);
			}
		}
		return isValid;
	},
	showAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && !field[this.getPropName(className)] && (advice.getStyle('display') == "none" || advice.getStyle('visiblity') == "hidden" || advice.getStyle('opacity')==0)){
			field[this.getPropName(className)] = true;
			advice.setStyles({
				'display':'block',
				'visibility':'hidden'
			});
			if (this.options.useFX && this.options.useShowFX) {
  			var h = advice.getSize().scrollSize.y;
  			var pt = advice.getStyle('padding-top').toInt();
  			var pb = advice.getStyle('padding-bottom').toInt();
  			var mt = advice.getStyle('margin-top').toInt();
  			var mb = advice.getStyle('margin-bottom').toInt();
  			h = h-pt-pb;
  			advice.setStyles({
  				'opacity':0,
  				'height':'0px',
  				'padding-top':'0px',
  				'padding-bottom':'0px',
  				'margin-top':'0px',
  				'margin-bottom':'0px'
  			}).effects().start({
  				'height':h,
  				'opacity':1,
  				'padding-top':pt,
  				'padding-bottom':pb,
  				'margin-top':mt,
  				'margin-bottom':mb
  			});
			} else {
        advice.setStyles({
          'opacity':1,
          'visibility':'visible'
        });			  
			}
		}
	},
	hideAdvice: function(className, field){
		var advice = this.getAdvice(className, field);
		if(advice && field[this.getPropName(className)]) {
			field[this.getPropName(className)] = false;
      if (this.options.useFX && this.options.useHideFX) {
  			var h = advice.getSize().scrollSize.y;
  			var pt = advice.getStyle('padding-top').toInt();
  			var pb = advice.getStyle('padding-bottom').toInt();
  			var mt = advice.getStyle('margin-top').toInt();
  			var mb = advice.getStyle('margin-bottom').toInt();
  			h = h-pt-pb;
  			advice.effects().start({
  				'height':0,
  				'opacity':0,
  				'padding-top':0,
  				'padding-bottom':0,
  				'margin-top':0,
  				'margin-bottom':0
  			}).chain(function(){
  				advice.setStyles({
  					'display':'none',
  					'height':h+'px',
  					'padding-top':pt+'px',
  					'padding-bottom':pb+'px',
  					'margin-top':mt+'px',
  					'margin-bottom':mb+'px'
  				});
  			});
      } else {
        advice.setStyles({
          'opacity':0,
          'display':'none'
        });        
      }
		}
	},
	isVisible : function(field) {
		while(field.tagName != 'BODY') {
			if($(field).getStyle('display') == "none") return false;
			field = field.parentNode;
		}
		return true;
	},
	getAdvice: function(className, field) {
		return $('advice-' + className + '-' + this.getFieldId(field))
	},
	makeAdvice: function(className, field, error){
		var errorMsg = this.options.useTitles ? $pick(field.getProperty('title'), error):error;
		var advice = this.getAdvice(className, field);
		if(!advice){
			advice = new Element('div').addClass('validation-advice').setProperty(
				'id','advice-'+className+'-'+this.getFieldId(field)).setStyle('display','none');
			switch (field.type.toLowerCase()) {
				case 'radio':
					var p = $(field.parentNode);
					if(p) {
						p.adopt(advice);
						break;
					}
				default: advice.injectAfter($(field));
		  };
		}
		advice.setHTML(errorMsg);
		return advice;
	},
	getFieldId : function(field) {
		return field.id ? field.id : field.id = "input_"+field.name;
	},
/*	Property: resetField
		Removes all the error messages for a specific field.
		
		Arguments:
		field - the field to reset.
	*/
	resetField: function(field) {
		field = $(field);
		if(field) {
			var cn = field.className.split(" ");
			cn.each(function(className) {
				var prop = this.getPropName(className);
				if(field[prop]) this.hideAdvice(className, field);
				field.removeClass('validation-failed');
				field.removeClass('validation-passed');
			}, this);
		}
	}
});

/*	Section: FormValidator global functions
		These functions are available to the <FormValidator> object itself, not instances of it.
		Use these functions to add validators to the FormValidator object, which will be available
		to all instances of the FormValidator class.
	*/
FormValidator.adders = {
/*  Property: validators
    An array of <Validator> objects.
  */
  validators:[],
/*  Property: add
    Adds a new form validator to the FormValidator object.
    
    Arguments:
    className - the className associated with the validator
    options - the <Validator> options (errorMsg and test)
    Example:
(start code)
FormValidator.add('isEmpty', {
  errorMsg: 'This field is required',
  test: function(element){
    if(element.value.length ==0) return false;
    else return true;
  }
});
  */
  add : function(className, options) {
    this.validators[className] = new InputValidator(className, options);
    return this;
  },
/*  Property: addAllThese
    An array of InputValidator configurations (see <FormValidator.add> above).
    
    Example:
(start code)
FormValidator.addAllThese([
  ['className1', {errorMsg: ..., test: ...}],
  ['className2', {errorMsg: ..., test: ...}],
  ['className3', {errorMsg: ..., test: ...}],
]);
  */
  addAllThese : function(validators) {
    $A(validators).each(function(validator) {
      this.add(validator[0], validator[1]);
    }, this);
  },
  getValidator: function(className){
    return FormValidator.validators[className] = $pick(FormValidator.validators[className], false);
  }
};	

Object.extend(FormValidator, FormValidator.adders);
FormValidator.implement(FormValidator.adders);

/*	Section: Included InputValidators
		Here is are the validators that are included in this libary. Add the className to
		any input and then create a new <FormValidator> and these will automatically be
		applied. See <FormValidator.add> on how to add your own.

		Property: IsEmpty
		Evalutes if the input is empty; this is a utility validator, see <FormValidator.required>.
		
		Error Msg - returns false (no message)
			*/
FormValidator.add('IsEmpty', {
	errorMsg: false,
	test: function(element) { 
		if(element.type == "select-one"||element.type == "select")
			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != "");
		else {
		  if (element.type == "select-multiple") {
			  return ((element.getValue() == null) || (element.getValue().length == 0));
		  } else {
  			var space = new RegExp('[a-z,0-9,а-я\*_]', 'i');
  			return ((element.getValue() == null) || (element.getValue().length == 0) || (!element.getValue().match(space)));
		  }
		}
	}
});


FormValidator.addAllThese([
/*	Property: required
		Displays an error if the field is empty.
		
		Error Msg - "This field is required"			
	*/
	['required', {
		errorMsg: function(element){return 'This field is required.'}, 
		test: function(element) { 
			return !FormValidator.getValidator('IsEmpty').test(element); 
		}
	}],
/*	Property: minLength
		Displays a message if the input value is less than the supplied length.
		
		Error Msg - Please enter at least [defined minLength] characters (you entered [input length] characters)
		
		Note:
		You must add this className AND properties for it to your input.
	
		Example:
		><input type="text" name="username" class="minLength props{minLength:10}" id="username">
	*/
	['minLength', {
		errorMsg: function(element, props){
			if($type(props.minLength))
				return 'Please enter at least ' + props.minLength + ' characters (you entered ' + element.getValue().length + ' characters).';
			else return '';
		}, 
		test: function(element, props) {
			if($type(props.minLength)) return (element.getValue().length >= $pick(props.minLength, 0));
			else return true;
		}
	}],
/*	Property: maxLength
		Displays a message if the input value is less than the supplied length.
		
		Error Msg - Please enter no more than [defined maxLength] characters (you entered [input length] characters)
		
		Note:
		You must add this className AND properties for it to your input.
		
		Example:
		><input type="text" name="username" class="maxLength props{maxLength:100}" id="username">
	*/
	['maxLength', {
		errorMsg: function(element, props){
			//props is {maxLength:10}
			if($type(props.maxLength))
				return 'Please enter no more than ' + props.maxLength + ' characters (you entered ' + element.getValue().length + ' characters).';
			else return '';
		}, 
		test: function(element, props) {
			//if the value is <= than the maxLength value, element passes test
			return (element.getValue().length <= $pick(props.maxLength, 10000));
		}
	}],
/*	Property: validate-number
		Validates that the entry is a number.
		
		Error Msg - 'Please enter a valid number in this field.'
	*/	
	['validate-number', {
		errorMsg: 'Please enter a valid number in this field.',
		test: function(element) {
				return FormValidator.getValidator('IsEmpty').test(element) || ((!isNaN(element.getValue()) && !/^\s+$/.test(element.getValue())));
		}
	}],
/*	Property: validate-digits
		Validates that the entry contains only numbers

		Error Msg - 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.'
	*/
	['validate-digits', {
		errorMsg: 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.', 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || 
				(!/[^a-zA-Z]/.test(element.getValue()) && /[\d]/.test(element.getValue()));
		}
	}],
/*	Property: validate-alpha
		Validates that the entry contains only letters 

		Error Msg - 'Please use letters only (a-z) in this field.'
	*/
	['validate-alpha', {
		errorMsg: 'Please use letters only (a-z) in this field.', 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^[a-zA-Z]+$/.test(element.getValue())
		}
	}],
/*	Property: validate-alphanum
		Validates that the entry is letters and numbers only

		Error Msg - 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.'
	*/
	['validate-alphanum', {
		errorMsg: 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.', 
		test: function(element) {
			return FormValidator.getValidator('IsEmpty').test(element) || !/\W/.test(element.getValue())
		}
	}],
/*	Property: validate-date
		Validates that the entry parses to a date.

		Error Msg - 'Please use this date format: mm/dd/yyyy. For example 03/17/2006 for the 17th of March, 2006.'
	*/
	['validate-date', {
		errorMsg: 'Please use this date format: mm/dd/yyyy. For example 03/17/2006 for the 17th of March, 2006.',
		test: function(element) {
			if(FormValidator.getValidator('IsEmpty').test(element)) return true;
	    var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
	    if(!regex.test(element.getValue())) return false;
	    var d = new Date(element.getValue().replace(regex, '$1/$2/$3'));
	    return (parseInt(RegExp.$1, 10) == (1+d.getMonth())) && 
        (parseInt(RegExp.$2, 10) == d.getDate()) && 
        (parseInt(RegExp.$3, 10) == d.getFullYear() );
		}
	}],
/*	Property: validate-email
		Validates that the entry is a valid email address.

		Error Msg - 'Please enter a valid email address. For example fred@domain.com .'
	*/
	['validate-email', {
		errorMsg: 'Please enter a valid email address. For example fred@domain.com .', 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(element.getValue());
		}
	}],
/*	Property: validate-url
		Validates that the entry is a valid url

		Error Msg - 'Please enter a valid URL.'
	*/
	['validate-url', {
		errorMsg: 'Please enter a valid URL.', 
		test: function (element) {
			return FormValidator.getValidator('IsEmpty').test(element) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i.test(element.getValue());
		}
	}],
/*	Property: validate-date-au
		Validates that the entry matches dd/mm/yyyy.

		Error Msg - 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.'
	*/
	

	['validate-date-au', {
		errorMsg: 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.',
		test: function(element) {
			if(FormValidator.getValidator('IsEmpty').test(element)) return true;
	    var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
	    if(!regex.test(element.getValue())) return false;
	    var d = new Date(element.getValue().replace(regex, '$2/$1/$3'));
	    return (parseInt(RegExp.$2, 10) == (1+d.getMonth())) && 
        (parseInt(RegExp.$1, 10) == d.getDate()) && 
        (parseInt(RegExp.$3, 10) == d.getFullYear() );
		}
	}],
/*	Property: validate-currency-dollar
		Validates that the entry matches any of the following:
			- [$]1[##][,###]+[.##]
			- [$]1###+[.##]
			- [$]0.##
			- [$].##
		
		Error Msg - 'Please enter a valid $ amount. For example $100.00 .'
	*/
	['validate-currency-dollar', {
		errorMsg: 'Please enter a valid $ amount. For example $100.00 .', 
		test: function(element) {
			// [$]1[##][,###]+[.##]
			// [$]1###+[.##]
			// [$]0.##
			// [$].##
			return FormValidator.getValidator('IsEmpty').test(element) ||  /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(element.getValue());
		}
	}],
/*	Property: validate-one-required
		Validates that all the entries within the same node are not empty.

		Error Msg - 'Please enter something for at least one of the above options.'
		
		Note:
		This validator will get the parent element for the input and then check all its children.
		To use this validator, enclose all the inputs you want to group in another element (doesn't
		matter which); you only need apply this class to *one* of the elements.
		
		Example:
(start code)
<div>
	<input ....>
	<input ....>
	<input .... className="validate-one-required">
</div>(end)
	*/
	['validate-one-required', {
		errorMsg: 'Please enter something for at least one of the above options.', 
		test: function (element) {
			var p = element.parentNode;
			var options = p.getElements('input');
			return $A(options).some(function(el) {
				return el.getValue();
			});
		}
	}]
]);

/* do not edit below this line */   
/* Section: Change Log 

$Source: /cvs/main/flatfile/html/rb/js/global/cnet.global.framework/common/js.widgets/form.validator.js,v $
$Log: form.validator.js,v $
Revision 1.3  2007/01/26 05:48:03  newtona
docs update

Revision 1.2  2007/01/22 22:00:15  newtona
numerous bug fixes to modalizer, stickywin, and popupdetails
updated for mootools 1.0
fixed date validation in form.validator

Revision 1.1  2007/01/19 01:22:05  newtona
*** empty log message ***


*/

var GeoHelper = new Class({
  
  //prefix: '',

  callback: Class.empty,
  
  load_phone_code: false,
  
  load: function(model, el, target, any, prefix) {
    //if (prefix) this.prefix = prefix;
    if (!prefix) prefix = '';
    el = $(el);
    target = $(target);
    any = (any != null) ? any : false;
    var id = el.value;
    if (id == '' || id == '*') {
      // Disable all combos
      if (model == 'region') {
        var region = $(prefix + 'region_id');
        region.options.length = 0;
        region.setAttribute('disabled', 'disabled');
      }
      var city = $(prefix + 'city_id');
      city.options.length = 0;
      city.setAttribute('disabled', 'disabled');
      return;
    }
    
    if (target != prefix + 'city') {
      var city = $(prefix + 'city_id');
      city.options.length = 0;
      city.setAttribute('disabled', 'disabled');
    }

    if (id == '*') return;
    
    new Ajax('/geo/browse/' + model + '/', {
      useWaiter: true,
      update: target.getParent(),
      method: 'post',
      postBody: Object.toQueryString({model: model, id: id, any: (any ? 1 : 0), prefix: prefix, load_phone_code: (this.load_phone_code ? 1 : 0)})
    }).
      addEvent('onComplete', this.callback.bind(this, [model, el, target, any])).
      request();
  }
  
});

var geo = new GeoHelper();
/* MooWaiter 1.0 by Andy Chentsov
 * Created on: 6.02.2008
 *
 * This simple piece of code automates the creating of Ajax loading symbols.
 * The loading symbol covers an HTML element with correct position and size - example:
 * $('myElement').startWaiting() and $('myElement').stopWaiting()
 *
 * Ported for Mootools from Protoload by Andreas Kalsch http://aka-fotos.de/MooWaiter/ All credit goes to him
 */
MooWaiter = {
  // the script to wait this amount of msecs until it shows the loading element
  timeUntilShow: 250,
  
  // opacity of loading element
  opacity: 0.8,
  
  injectInplace: true,

  // Start waiting status - show loading element
  startWaiting: function(className, timeUntilShow, opacity, injectInplace) {
    element = this;
    className = className || 'waiting';
    timeUntilShow = timeUntilShow || MooWaiter.timeUntilShow;
    opacity = opacity || MooWaiter.opacity;
    injectInplace = (injectInplace != null) ? injectInplace : MooWaiter.injectInplace;
    element._waiting = true;
    if (!element._loading) {      var e = element._loading = new Element('div', {
      	'styles': {
      		'position': 'absolute',
      		'opacity': opacity
      	}
      }).injectInside(injectInplace ? ( ($(element).getTag() == 'td') ? document.body : ($(element).getParent() || document.body) ) : document.body);
    }
    element._loading.className = className;
    (function() {
      if (this._waiting) {
        $(this._loading).setStyles({
          'left': this.getLeft(),
          'top': this.getTop(),
          'width': this.getSize()['size']['x'],
          'height': this.getSize()['size']['y']
        });
      }
    }).bind(element).delay(timeUntilShow);
    return this;
  },
  
  // Stop waiting status - hide loading element
  stopWaiting: function() {
    element = this;
    if (element._waiting) {
      element._waiting = false;
      $(element._loading).remove();
      element._loading = null;
    }
    return this;
  }
};

if (MooTools) {
  Element.extend(MooWaiter);

  /* Extends functionality from <Waiter> into <Ajax>.
                 
     Additional Options:
     useWaiter - (boolean) if true will automatically apply a <Waiter> to the update target; defaults to false. Note: if you do not specify a value for update option this is ignored.
     waiterOptions - (object) options value passed on to <Waiter> class.
     waiterTarget - (element) if specified, the Waiter will overlay this element, otherwise it uses the update target specified in the ajax options.
  */
  if (typeof Ajax != "undefined") {
     var Ajax = Ajax.extend({
         options: {
           useWaiter: false,
           waiterOptions: {
             className: 'waiting',
             timeUntilShow: MooWaiter.timeUntilShow,
             opacity: MooWaiter.opacity,
             injectInplace: MooWaiter.injectInplace
           },
           waiterTarget: false
         },
         initialize: function(url, options) {
           this.parent(url, options);
           if (this.options.useWaiter && ($(this.options.waiterTarget) || $(this.options.update))) {
             var stop = function() {
               ($(this.options.waiterTarget) || $(this.options.update)).stopWaiting();
             }.bind(this);
             this.addEvent('onComplete', stop);
             this.addEvent('onFailure', stop);
           }
         },
         request: function(data) {
           if (this.options.useWaiter) {
             try {
               ($(this.options.waiterTarget) || $(this.options.update)).startWaiting(this.options.waiterOptions.className, this.options.waiterOptions.timeUntilShow, this.options.waiterOptions.opacity, this.options.waiterOptions.injectInplace);
             } catch(e) {}
           }
           this.parent(data);
           return this;
         }
     });
  }
}
window.extend({

        getWidth: function(){
                if (this.webkit419) return this.innerWidth;
                if (this.opera) return document.body.clientWidth;
                return document.documentElement.clientWidth || document.body.clientWidth;
        },

        getHeight: function(){
                if (this.webkit419) return this.innerHeight;
                if (this.opera) return document.body.clientHeight;
                return document.documentElement.clientHeight || document.body.clientHeight;
        },

        getScrollWidth: function(){
                if (this.ie) return Math.max(document.documentElement.offsetWidth || document.body.offsetWidth, document.documentElement.scrollWidth || document.body.scrollWidth);
                if (this.webkit) return document.body.scrollWidth;
                return document.documentElement.scrollWidth;
        },

        getScrollHeight: function(){
                if (this.ie) return Math.max(document.documentElement.offsetHeight || document.body.offsetHeight, document.documentElement.scrollHeight || document.body.scrollHeight);
                if (this.webkit) return document.body.scrollHeight;
                return document.documentElement.scrollHeight;
        },

        getScrollLeft: function(){
                return this.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
        },

        getScrollTop: function(){
                return this.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
        },

        getSize: function(){
                return {
                        'size': {'x': this.getWidth(), 'y': this.getHeight()},
                        'scrollSize': {'x': this.getScrollWidth(), 'y': this.getScrollHeight()},
                        'scroll': {'x': this.getScrollLeft(), 'y': this.getScrollTop()}
                };
        }

});

Element.extend({

        getValue: function() {
          /*if ((this.getTag() == 'input') && ['checkbox', 'radio'].contains(this.type)) {
            return this.checked ? 1 : 0;
          } else {
            return this.parent();
          }*/
                switch(this.getTag()){
                        case 'select':
                                var values = [];
                                $each(this.options, function(option){
                                        if (option.selected) values.push((option.value != '')?option.value:option.text);
                                });
                                return (this.multiple) ? values : values[0];
                        case 'input': 
                                if (['checkbox', 'radio'].contains(this.type)) {
                                        return this.checked ? 1 : 0;
                                } else {
                                        if (!['hidden', 'text', 'password'].contains(this.type)) break;
                                }
                        case 'textarea': return this.value;
                }
                return false;
        }

});

Element.counter = 1;

Element.extend({

  upwards: function(iterator, parent) {
    var element = this;
    while (element) {
      if (iterator(element)) return element;
      element = element.getParent ? element.getParent() : null;
      if (parent && (parent == element)) return null;
    }
    return element;
  },

  getParentByTagName: function(tagName) {
    return this.upwards(function(e) {
      if (e.getTag() == tagName) 
        return e;
    });
  },

  identify: function() {
    var id = this.getAttribute('id');
    if (id) return id;
    do { id = 'anonymous_element_' + Element.counter++ } while ($(id));
    this.setAttribute('id', id);
    return id;
  },

  hasAttribute: function(attribute) {
    return this.getAttributeNode(attribute);
  },
  
  fireStdEvent: function(event) {
    if (window.ie) {
    	var eventObj = document.createEventObject();
    	eventObj.fromElement = eventObj.toElement = this;
      return this.fireEvent_native('on' + event, eventObj);
    } else {
      var evt = document.createEvent("Events");
      evt.initEvent(event, true, true);
      return this.dispatchEvent(evt);
    }
  },
  
  focusFirstElement: function() {
    if (this.getTag() != 'form') return;
    if (this.elements.length <= 0) return;
    // Find first non-hidden input
    for (var i = 0; i < this.elements.length; i++) {
      if (this.elements[i].type == 'hidden') continue;
      this.elements[i].focus();
      break;
    }
  },

  highlight: function(color, options) {
    if (!this.fx) this.fx = this.effect('background-color', $merge({ wait: false, duration: 500 }));
    if (options) this.fx.$merge(options);
    var from = this.getStyle('background-color').substring(1) || 'transparent';               
    var to   = color;
    /*this.fx.start(from, to).chain(function() {
      this.setStyle('background-color', from);
    }.bind(this));*/
    this.fx.start(from, to).chain(function() {
      //this.setStyle('background-color', 'transparent');
      this.setStyle('background-color', '');
    }.bind(this));
  }
  
});

Element.Storage = {

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

};

Element.Methods.Storage = {

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

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

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

};

window.extend(Element.Methods.Storage);
document.extend(Element.Methods.Storage);
Element.extend(Element.Methods.Storage);

if (!Object.prototype.hasOwnProperty) {
	Object.prototype.hasOwnProperty = function(property) {
		try {
			var prototype = this.constructor.prototype;
			while (prototype) {
				if (prototype[property] == this[property]) {
				  return false;
  			}
	   		prototype = prototype.prototype;
  		}
		} catch(e) {}
		return true;
	}
}

Element.extend({

  toQueryString: function(filter){
    var elements = this.getFormElements();
    if (filter && $type(filter) == 'function') {
      elements = elements.filter(filter);
    }
    
    /*var queryString = [];
    elements.each(function(el){
      var name = el.name;
      var value = el.getValue();
      if (value === false || !name || el.disabled) return;
      var qs = function(val){
        queryString.push(name + '=' + encodeURIComponent(val));
      };
      if ($type(value) == 'array') value.each(qs);
      else qs(value);
    });
    return queryString.join('&');*/
    var values = {};
    var queryString = [];
    elements.each(function(el){
      var name = el.name;
      var value = el.getValue();
      if (value === false || !name || el.disabled) return;
      var qs = function(val){
        //queryString.push(name + '=' + encodeURIComponent(val));
        values[name] = encodeURIComponent(val);
      };
      if ($type(value) == 'array') value.each(qs);
      else qs(value);
    });
    $H(values).each(function(value, name) {
      queryString.push(name + '=' + value);
    });
    return queryString.join('&');
  }

});

String.extend({
  pad: function(len, chr) {
    chr = chr || ' ';
    var times = function(chr, count) {
      var result = '';
      for (var i = 0; i < count; i++) result += chr;
      return result;
    }
    return times(chr, len - this.length) + this;
  },
  
  extractFileName: function() {
    return this.split("/").getLast();
  },
  
  nl2br: function() {
    return this.replace(/\n/g, '<br/>').replace(/\r/g, '');
  },
  
  prepare: function(params) {
    return this.replace(/\:[a-z0-9_\-]+/ig, function(value) {
      return params[value] || '';
    });
  },

  sprintf: function(params) {
    var _params = $A(params).copy();
    return this.replace(/%[a-z0-9]{1}/ig, function(value) {
      try {
        return (_params.length > 0) ? _params.shift() : '';
      } catch(e) {
        return '';
      }
    });
  }
});

Number.extend({

  pad: function(len, chr) {
    chr = chr || ' ';
    var times = function(chr, count) {
      var result = '';
      for (var i = 0; i < count; i++) result += chr;
      return result;
    }
    var s = this.toString();
    return times(chr, len - s.length) + s;
  },
  
  /*
  Property: numberFormat
    Format a number with grouped thousands.
 
  Arguments:
    decimals, optional - integer, number of decimal percision; default, 2
    dec_point, optional - string, decimal point notation; default, '.'
    thousands_sep, optional - string, grouped thousands notation; default, ','
 
  Returns:
    a formatted version of number.
 
  Example:
    >(36432.556).numberFormat()  // returns 36,432.56
    >(36432.556).numberFormat(2, '.', ',')  // returns 36,432.56
  */
  numberFormat: function(decimals, dec_point, thousands_sep) {
    decimals = Math.abs(decimals) + 1 ? decimals : 2;
    dec_point = dec_point || '.';
    thousands_sep = thousands_sep || ',';
 
    var matches = /(-)?(\d+)(\.\d+)?/.exec((isNaN(this) ? 0 : this) + ''); // returns matches[1] as sign, matches[2] as numbers and matches[3] as decimals
    var remainder = matches[2].length > 3 ? matches[2].length % 3 : 0;
    return (matches[1] ? matches[1] : '') + (remainder ? matches[2].substr(0, remainder) + thousands_sep : '') + matches[2].substr(remainder).replace(/(\d{3})(?=\d)/g, "$1" + thousands_sep) + 
      (decimals ? dec_point + (+matches[3] || 0).toFixed(decimals).substr(2) : '');
  }

});

var Hint = new Class({

	initialize: function(el) {
		this.element = $(el);
		this.overed = false;
		this.hideTimer = null;
		
		this.element.addEvent('mouseover', this.resetTimer.bind(this));
		this.element.addEvent('mouseout', this.hide.bind(this));
	},
	
	show: function(target) {
		this.resetTimer();
		if (this.overed) return;
		this.overed = true;
		var bounds = $(target).getCoordinates();
    this.element.setStyles({'opacity': 0, 'visibility': 'visible'});
		this.element.setStyles({'left': bounds.left - this.element.offsetWidth - 8, 'top': bounds.top - 23});
		this.element.effect('opacity', {duration: 200}).start(1);
	},

	hide: function() {
		if (!this.overed) return;
		this.resetTimer();
		this.hideTimer = this.hideNow.delay(500, this);
	},
	
	hideNow: function() {
		this.overed = false;
		//this.element.setStyle('visibility', 'hidden');
		this.element.effect('opacity', {duration: 200}).start(0);
	},
	
	resetTimer: function() {
		clearTimeout(this.hideTimer);
	}

});
var SimpleBox = new Class({

  options: {
    opacity: 0.7,
    title: '',
    size: {
      width: 380,
      height: 180
    },
    closeWithOverlay: false,
    className: ''
  },

  initialize: function(message, options) {
    this.message = message;
    this.setOptions(options);

    this.handlers = {
      key: this.key.bindWithEvent(this),
      resize: this.resize.bind(this),
      close: this.close.bind(this)
    };

    this.build();
    this.open();
  },

  resize: function() {
    var overlay = $('simplebox-overlay');
    var simplebox = $('simplebox');
    if (overlay && simplebox) {
      var sizes = window.getSize();
      if (window.ie6) {
        overlay.setStyles({
          'left': sizes.scroll.x,
          'top': sizes.scroll.y
        });
      }
      overlay.setStyles({
        'width': sizes.size.x,
        'height': sizes.size.y
      });
      var bounds = simplebox.getSize().size;
      simplebox.setStyles({
        'left': window.getScrollLeft() + Math.round((sizes.size.x - bounds.x) / 2),
        'top': window.getScrollTop() + Math.round((sizes.size.y - bounds.y) / 2) - 20
      });
    }
  },

  toggleListeners: function(state) {
    var task = state ? 'addEvent' : 'removeEvent';
    if (window.gecko) {
      window[task]('keydown', this.handlers.key);
    } else {
      document.body[task]('keydown', this.handlers.key);
    }
    window[task]('resize', this.handlers.resize);
    window[task]('scroll', this.handlers.resize);
  },

  showOverlay: function() {
    var overlay = $('simplebox-overlay');
    if (!overlay) {
      overlay = new Element('div', {
        'id': 'simplebox-overlay',
        'class': 'simplebox-overlay',
        'styles': {
          'position': window.ie6 ? 'absolute' : 'fixed',
          'left': 0,
          'top': 0,
          'width': 1,
          'height': 1,
          'padding': 0,
          'margin': 0,
          'opacity': 0,
          'visibility': 'visible'
        }
      }).injectInside(document.body);
      /*if (window.ie6) {    
        this.overlay.style.setExpression('left', 'document.body.scrollLeft || document.documentElement.scrollLeft');
        this.overlay.style.setExpression('top', 'document.body.scrollTop || document.documentElement.scrollTop');
      }*/
    } else {
      overlay.setStyles({
        'display': '',
        'opacity': 0
      });
    }
    this.resize();
    this.toggleListeners(true);
    if (this.options.closeWithOverlay) {
      overlay.addEvent('click', this.handlers.close);
    }
    overlay.effect('opacity', {duration: 250}).start(this.options.opacity);
  },

  hideOverlay: function() {
    var overlay = $('simplebox-overlay');
    if (overlay) {
      overlay.effect('opacity', {duration: 250}).start(0).chain(function() {
        overlay.setStyle('display', 'none');
        this.toggleListeners(false);
        overlay.removeEvent('click', this.handlers.close);
        overlay.remove();
      }.bind(this));
    }
  },
  
  build: function() {
    var box = $('simplebox');
    if (!box) {
      box = new Element('div', {
        'id': 'simplebox',
        'class': 'simplebox',
        'styles': {
          'position': 'absolute',
          'left': 0,
          'top': 0,
          'width': this.options.size.width,
          'height': this.options.size.height,
          'opacity': 0,
          'visibility': 'visible'
        }
      }).addClass(this.options.className).injectInside(document.body);
      
      title = new Element('h1', {
        'id': 'simplebox-title'
      }).injectInside(box);

      msg = new Element('div', {
        'id': 'simplebox-message',
        'class': 'simplebox-message',
        'styles': {
          'overflow': 'hidden'
        }
      }).injectInside(box);

      buttons = new Element('div', {
        'id': 'simplebox-buttons',
        'class': 'simplebox-buttons',
        'align': 'center'
      }).injectInside(box);

      okbutton = new Element('input', {
        'id': 'simplebox-ok-button',
        'type': 'button',
        'value': '           OK           '
      }).injectInside(buttons).addEvent('click', this.handlers.close);
      
      this.resize();
    } else {
      title = $('simplebox-title');
      msg = $('simplebox-message');
      buttons = $('simplebox-buttons');
    }
    title.setHTML(this.options.title);
    msg.setHTML(this.message);
    var height = this.options.size.height - title.getSize().size.y - buttons.getSize().size.y;
    height -= box.getStyle('padding-top').toInt() + box.getStyle('padding-bottom').toInt();
    height -= title.getStyle('padding-top').toInt() + title.getStyle('padding-bottom').toInt() +
      title.getStyle('margin-top').toInt() + title.getStyle('margin-bottom').toInt();
    height -= msg.getStyle('padding-top').toInt() + msg.getStyle('padding-bottom').toInt() +
      msg.getStyle('margin-top').toInt() + msg.getStyle('margin-bottom').toInt();
    height -= buttons.getStyle('padding-top').toInt() + msg.getStyle('padding-bottom').toInt();
    msg.setStyle('height', height);
    box.effect('opacity', {duration: 250}).start(1);
  },
  
  open: function() {
    try {
      window.focus();
    } catch(e) {}
    this.showOverlay();
  },
  
  close: function() {
    this.hideOverlay();
    var box = $('simplebox');
    if (box) {
      box.effect('opacity', {duration: 250}).start(0).chain(function() {
        box.remove();
      });
    }
  },
  
  key: function(event) {
    if ((event.key == 'enter') || (event.key == 'esc')) {
      event.stop();
      this.close();
    }
  }

});

SimpleBox.implement(new Options);

var PopupForm = new Class({

  options: {
    validate: true,
    update: null,
    validatorOptions: {},
    ajax: true,
    zIndex: 1000,
    resetOnComplete: true
  },

  initialize: function(popup, form, options) {
    this.popup = $(popup);
    this.form = $(form);
    this.setOptions(options);
    this.parameters = [];
    
    if (this.options.validate) {
      this.validator = new FormValidator(this.form, $merge(this.options.validatorOptions, {
        useTitles: true,
        evaluateFieldsOnBlur: false,
        evaluateOnSubmit: false,
        useHideFX: false
      })); 
    }

    this.bind = {
      close: this.close.bind(this),
      reset: this.reset.bind(this),
      postComplete: this.postComplete.bind(this),
      resize: this.resized.bind(this)
    }
  },
  
  resized: function() {
    if (this.overlay) {
      var sizes = window.getSize();
      this.overlay.setStyles({
        'left': sizes.scroll.x,
        'top': sizes.scroll.y,
        'width': sizes.size.x,
        'height': sizes.size.y
      });
    }
    if (this.anchor) {
      var pos = this.anchor.getPosition();
      var point = {
        'x': pos.x - this.popup.offsetWidth - 6,
        'y': pos.y - 34
      };
      this.popup.setStyles({
        'left': point.x,
        'top': point.y
      });
    }
  },

  toggleListeners: function(state) {
    var task = state ? 'addEvent' : 'removeEvent';
    window[task]('resize', this.bind.resize);
    window[task]('scroll', this.bind.resize);
  },

  showOverlay: function() {
    if (!this.overlay) {
      this.overlay = new Element('div', {
        'styles': {
          'position': 'absolute',
          'left': 0,
          'top': 0,
          'width': 1,
          'height': 1,
          'padding': 0,
          'margin': 0,
          'background': 'white',
          'opacity': 0,
          'visibility': 'visible',
          'z-index': this.options.zIndex - 1
        }
      }).injectInside(document.body);
    } else {
      this.overlay.setStyle('display', '');
    }
    this.resized();
    this.toggleListeners(true);
    this.overlay.addEvent('click', this.bind.close);
  },

  hideOverlay: function() {
    if (this.overlay) {
      this.overlay.setStyle('display', 'none');
      this.toggleListeners(false);
      this.overlay.removeEvent('click', this.bind.close);
    }
  },

  open: function(el) {
    this.parameters = Array.prototype.slice.call(arguments, 1);
    this.fireEvent('onOpen', Array.prototype.slice.call(arguments, 1));

    this.anchor = $(el);
    if (!this.anchor) return;

    var pos = this.anchor.getPosition();
    var point = {
      'x': pos.x - this.popup.offsetWidth - 6,
      'y': pos.y - 34
    };

    this.showOverlay();
    var form = this.form;
    this.popup.setStyles({
      'z-index': this.options.zIndex,
      'opacity': 0,
      'visibility': 'visible',
      'left': point.x,
      'top': point.y
    }).
      effect('opacity', {duration: 400}).
      addEvent('onComplete', function() {
        (function() {
          if (form) {
            try {
              form.focusFirstElement();
            } catch(e) {}
          }
        }).delay(200);
      }).
      start(1);
  },
  
  post: function() {
    this.fireEvent('onBeforePost');
    if (this.options.validate) {
      if (this.validator && !this.validator.validate()) {
        return;
      }
    }
    // pocess form.action if contains %s
    url = this.form.action;
    if ((url.indexOf('%s') != -1)) {
      url = url.sprintf(this.parameters);
    }
    this.form.action = url;
    if (this.options.ajax) {
      new Ajax(url, {
        method: 'post',
        data: this.form.toQueryString(), 
        evalScripts: true,
        useWaiter: true,
        update: this.options.update,
        onComplete: this.bind.postComplete,
        onFailure: function(transport) {
          new SimpleBox(transport.responseText, {title: 'Ошибка', size: {width: 300, height: 150}, className: 'errorbox' });
        }.bind(this)
      }).request();
    } else {
      this.form.submit();
    }
  },
  
  cancel: function() {
    this.hideOverlay();
    this.popup.effect('opacity', {duration: 200}).start(0);
    this.reset();
    this.fireEvent('onClose');
  },
  
  close: function() {
    this.cancel();
  },

  postComplete: function(transport) {
    if (this.options.resetOnComplete) {
      this.reset();
    }
    this.fireEvent('onPost', transport);
    this.fireEvent('onComplete', transport);
    this.close();
  },
  
  reset: function() {
    if (this.validator) {
      this.validator.reset();
    }
    this.form.reset();
    this.fireEvent('onReset');
  }
  
});

PopupForm.implement(new Options, new Events);

var Animation = Fx.Base.extend({
	
	increase: function() {
		this.fireEvent('onUpdate', this.now);
	}

});

var HelpBox = {
	
	current: -1,
	
	pages: [],

	add: function(page_id) {
		var page = $(page_id + '-page');
		if (!page) return;
		HelpBox.pages.push({
			id: page_id,
			page: page,
			content: $(page_id + '-content'),
			shortcut: $(page_id + '-shortcut')
		});
		if (HelpBox.current == -1) {
			HelpBox.current = HelpBox.pages.length - 1;
		}
	},
	
	find: function(page_id) {
		var result = -1;
		HelpBox.pages.some(function(page, idx) {
			if (page.id == page_id) {
				result = idx;
				return true;
			}
			return false;
		});
		return result;
	},
	
	toggle: function(page_id) {
		var page_index = HelpBox.find(page_id);
		if (page_index == -1) return;
		
		var last_active = HelpBox.current;
		HelpBox.current = page_index;

		if (!HelpBox.pages[HelpBox.current].content.getAttribute('fullheight')) {
			var h = HelpBox.pages[HelpBox.current].content.getElement('.page-body').offsetHeight + 5;
			HelpBox.pages[HelpBox.current].content.setAttribute('fullheight', h);
		} else {
			var h = parseInt(HelpBox.pages[HelpBox.current].content.getAttribute('fullheight'));
		}

		var shortcut_height = HelpBox.pages[last_active].shortcut.offsetHeight;
		
		//var curr_shortcut_height = HelpBox.pages[HelpBox.current].shortcut.clientHeight - 8;
		var curr_shortcut_height = HelpBox.pages[HelpBox.current].shortcut.offsetHeight - 8;
		if (curr_shortcut_height < 14) curr_shortcut_height = 14;

		var min_height = 0;

		HelpBox.pages[last_active].page.effects({duration: 800}).start({
			'background-color': ['#FFFFFF', '#ECECEC']
		});

		HelpBox.pages[HelpBox.current].page.effects({duration: 200}).start({
			'background-color': ['#ECECEC', '#FFFFFF']
		});

		var toggle = false;

		new Animation({duration: 300}).
		addEvent('onUpdate', function(value) {
			HelpBox.pages[last_active].content.setStyle('height', Math.abs(value));
			HelpBox.pages[HelpBox.current].content.setStyle('height', Math.abs(h - value));

      y = HelpBox.pages[HelpBox.current].shortcut.getStyle('height').toInt();
      if (y > 1) {
        HelpBox.pages[HelpBox.current].shortcut.setStyle('height', y - 1);
      }

      if (( value <= (shortcut_height + 10) ) && !toggle) {
        toggle = true;
        //HelpBox.pages[last_active].shortcut.setStyle('opacity', 0).setStyle('height', curr_shortcut_height);
        HelpBox.pages[last_active].shortcut.setStyle('opacity', 0).setStyle('height', curr_shortcut_height).removeClass('hidden').effect('opacity').start(1);
        HelpBox.pages[HelpBox.current].shortcut.addClass('hidden').setStyle('height', 0);
      }

			/*if ((value > shortcut_height) && !toggle) {
				toggle = true;
				HelpBox.pages[last_active].shortcut.setStyle('opacity', 0).removeClass('hidden').effect('opacity').start(1);
				HelpBox.pages[HelpBox.current].shortcut.addClass('hidden');
			}*/
		}).
		addEvent('onComplete', function() {
			HelpBox.pages[last_active].content.addClass('hidden');
			HelpBox.pages[HelpBox.current].content.removeClass('hidden');
			
			HelpBox.pages[HelpBox.current].content.setStyle('height', 'auto');
		}).
		start(h, min_height);
	},
	
	ask: function(id) {
	  var h = $(id + '-text').offsetHeight;
	  $(id + '-text').setStyle('display', 'none');
	  $(id + '-form').setStyle('display', 'block').setStyle('height', h);
	},
	
	post: function(form, id) {
	  form = $(form);
	  if (!form) return;
	  if (form.elements['question'].value == '') {
	    alert('Укажите текст вопроса.');
	    form.elements['question'].focus();
	    return;
	  }
	  form.send({
	    useWaiter: true,
	    onComplete: function(id) {
	      form.reset();
	      new SimpleBox('Ваш вопрос отправлен.', {title: 'Вопрос', size: {width: 300, height: 150}, 'class': 'info' });
        $(id + '-text').setStyle('display', 'block');
        $(id + '-form').setStyle('display', 'none');
	    }.pass(id),
      onFailure: function(transport) {
        new SimpleBox(transport.responseText, {title: 'Ошибка', size: {width: 300, height: 150} });
      }
	  });
	}

};
