//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2009 Aaron Newton <http://clientcide.com/>, Valerio Proietti <http://mad4milk.net> & the MooTools team <http://mootools.net/developers>, MIT Style License.

MooTools.More = {
    'version': '1.2.2.2'
};

/*
Script: MooTools.Lang.js
    Provides methods for localization.

    License:
        MIT-style license.

    Authors:
        Aaron Newton
*/

(function(){

    var data = {
        language: 'en-US',
        languages: {
            'en-US': {}
        },
        cascades: ['en-US']
    };
    
    var cascaded;

    MooTools.lang = new Events();

    $extend(MooTools.lang, {

        setLanguage: function(lang){
            if (!data.languages[lang]) return this;
            data.language = lang;
            this.load();
            this.fireEvent('langChange', lang);
            return this;
        },

        load: function() {
            var langs = this.cascade(this.getCurrentLanguage());
            cascaded = {};
            $each(langs, function(set, setName){
                cascaded[setName] = this.lambda(set);
            }, this);
        },

        getCurrentLanguage: function(){
            return data.language;
        },

        addLanguage: function(lang){
            data.languages[lang] = data.languages[lang] || {};
            return this;
        },

        cascade: function(lang){
            var cascades = (data.languages[lang] || {}).cascades || [];
            cascades.combine(data.cascades);
            cascades.erase(lang).push(lang);
            var langs = cascades.map(function(lng){
                return data.languages[lng];
            }, this);
            return $merge.apply(this, langs);
        },

        lambda: function(set) {
            (set || {}).get = function(key, args){
                return $lambda(set[key]).apply(this, $splat(args));
            };
            return set;
        },

        get: function(set, key, args){
            if (cascaded && cascaded[set]) return (key ? cascaded[set].get(key, args) : cascaded[set]);
        },

        set: function(lang, set, members){
            this.addLanguage(lang);
            langData = data.languages[lang];
            if (!langData[set]) langData[set] = {};
            $extend(langData[set], members);
            if (lang == this.getCurrentLanguage()){
                this.load();
                this.fireEvent('langChange', lang);
            }
            return this;
        },

        list: function(){
            return Hash.getKeys(data.languages);
        }

    });

})();

/*
Script: Element.Measure.js
    Extends the Element native object to include methods useful in measuring dimensions.

    Element.measure / .expose methods by Daniel Steigerwald
    License: MIT-style license.
    Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz

    License:
        MIT-style license.

    Authors:
        Aaron Newton

*/

Element.implement({

    measure: function(fn){
        var vis = function(el) {
            return !!(!el || el.offsetHeight || el.offsetWidth);
        };
        if (vis(this)) return fn.apply(this);
        var parent = this.getParent(),
            toMeasure = [], 
            restorers = [];
        while (!vis(parent) && parent != document.body) {
            toMeasure.push(parent.expose());
            parent = parent.getParent();
        }
        var restore = this.expose();
        var result = fn.apply(this);
        restore();
        toMeasure.each(function(restore){
            restore();
        });
        return result;
    },

    expose: function(){
        if (this.getStyle('display') != 'none') return $empty;
        var before = this.getStyles('display', 'position', 'visibility');
        return this.setStyles({
            display: 'block',
            position: 'absolute',
            visibility: 'hidden'
        }).setStyles.pass(before, this);
    },

    getDimensions: function(options){
        options = $merge({computeSize: false},options);
        var dim = {};
        var getSize = function(el, options){
            return (options.computeSize)?el.getComputedSize(options):el.getSize();
        };
        if (this.getStyle('display') == 'none'){
            dim = this.measure(function(){
                return getSize(this, options);
            });
        } else {
            try { //safari sometimes crashes here, so catch it
                dim = getSize(this, options);
            }catch(e){}
        }
        return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
    },

    getComputedSize: function(options){
        options = $merge({
            styles: ['padding','border'],
            plains: {
                height: ['top','bottom'],
                width: ['left','right']
            },
            mode: 'both'
        }, options);
        var size = {width: 0,height: 0};
        switch (options.mode){
            case 'vertical':
                delete size.width;
                delete options.plains.width;
                break;
            case 'horizontal':
                delete size.height;
                delete options.plains.height;
                break;
        }
        var getStyles = [];
        //this function might be useful in other places; perhaps it should be outside this function?
        $each(options.plains, function(plain, key){
            plain.each(function(edge){
                options.styles.each(function(style){
                    getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
                });
            });
        });
        var styles = {};
        getStyles.each(function(style){ styles[style] = this.getComputedStyle(style); }, this);
        var subtracted = [];
        $each(options.plains, function(plain, key){ //keys: width, height, plains: ['left', 'right'], ['top','bottom']
            var capitalized = key.capitalize();
            size['total' + capitalized] = 0;
            size['computed' + capitalized] = 0;
            plain.each(function(edge){ //top, left, right, bottom
                size['computed' + edge.capitalize()] = 0;
                getStyles.each(function(style, i){ //padding, border, etc.
                    //'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
                    if (style.test(edge)){
                        styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
                        size['total' + capitalized] = size['total' + capitalized] + styles[style];
                        size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
                    }
                    //if width != width (so, padding-left, for instance), then subtract that from the total
                    if (style.test(edge) && key != style &&
                        (style.test('border') || style.test('padding')) && !subtracted.contains(style)){
                        subtracted.push(style);
                        size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
                    }
                });
            });
        });

        ['Width', 'Height'].each(function(value){
            var lower = value.toLowerCase();
            if(!$chk(size[lower])) return;

            size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
            size['total' + value] = size[lower] + size['total' + value];
            delete size['computed' + value];
        }, this);

        return $extend(styles, size);
    }

});

/*
Script: Element.Position.js
    Extends the Element native object to include methods useful positioning elements relative to others.

    License:
        MIT-style license.

    Authors:
        Aaron Newton
*/

(function(){

var original = Element.prototype.position;

Element.implement({

    position: function(options){
        //call original position if the options are x/y values
        if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
        $each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
        options = $merge({
            relativeTo: document.body,
            position: {
                x: 'center', //left, center, right
                y: 'center' //top, center, bottom
            },
            edge: false,
            offset: {x: 0, y: 0},
            returnPos: false,
            relFixedPosition: false,
            ignoreMargins: false,
            allowNegative: false
        }, options);
        //compute the offset of the parent positioned element if this element is in one
        var parentOffset = {x: 0, y: 0};
        var parentPositioned = false;
        /* dollar around getOffsetParent should not be necessary, but as it does not return
         * a mootools extended element in IE, an error occurs on the call to expose. See:
         * http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
        var offsetParent = this.measure(function(){
            return $(this.getOffsetParent());
        });
        if (offsetParent && offsetParent != this.getDocument().body){
            parentOffset = offsetParent.measure(function(){
                return this.getPosition();
            });
            parentPositioned = true;
            options.offset.x = options.offset.x - parentOffset.x;
            options.offset.y = options.offset.y - parentOffset.y;
        }
        //upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
        //topRight, topLeft, centerTop, centerBottom, center
        var fixValue = function(option){
            if ($type(option) != 'string') return option;
            option = option.toLowerCase();
            var val = {};
            if (option.test('left')) val.x = 'left';
            else if (option.test('right')) val.x = 'right';
            else val.x = 'center';
            if (option.test('upper') || option.test('top')) val.y = 'top';
            else if (option.test('bottom')) val.y = 'bottom';
            else val.y = 'center';
            return val;
        };
        options.edge = fixValue(options.edge);
        options.position = fixValue(options.position);
        if (!options.edge){
            if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
            else options.edge = {x:'left', y:'top'};
        }

        this.setStyle('position', 'absolute');
        var rel = $(options.relativeTo) || document.body;
        var calc = rel == document.body ? window.getScroll() : rel.getPosition();
        var top = calc.y;
        var left = calc.x;

        if (Browser.Engine.trident){
            var scrolls = rel.getScrolls();
            top += scrolls.y;
            left += scrolls.x;
        }

        var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
        if (options.ignoreMargins){
            options.offset.x = options.offset.x - dim['margin-left'];
            options.offset.y = options.offset.y - dim['margin-top'];
        }
        var pos = {};
        var prefY = options.offset.y;
        var prefX = options.offset.x;
        var winSize = window.getSize();
        switch(options.position.x){
            case 'left':
                pos.x = left + prefX;
                break;
            case 'right':
                pos.x = left + prefX + rel.offsetWidth;
                break;
            default: //center
                pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
                break;
        }
        switch(options.position.y){
            case 'top':
                pos.y = top + prefY;
                break;
            case 'bottom':
                pos.y = top + prefY + rel.offsetHeight;
                break;
            default: //center
                pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
                break;
        }

        if (options.edge){
            var edgeOffset = {};

            switch(options.edge.x){
                case 'left':
                    edgeOffset.x = 0;
                    break;
                case 'right':
                    edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
                    break;
                default: //center
                    edgeOffset.x = -(dim.x/2);
                    break;
            }
            switch(options.edge.y){
                case 'top':
                    edgeOffset.y = 0;
                    break;
                case 'bottom':
                    edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
                    break;
                default: //center
                    edgeOffset.y = -(dim.y/2);
                    break;
            }
            pos.x = pos.x + edgeOffset.x;
            pos.y = pos.y + edgeOffset.y;
        }
        pos = {
            left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
            top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
        };
        if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
            var winScroll = window.getScroll();
            pos.top = pos.top.toInt() + winScroll.y;
            pos.left = pos.left.toInt() + winScroll.x;
        }

        if (options.returnPos) return pos;
        else this.setStyles(pos);
        return this;
    }

});

})();

/*
Script: Element.Shortcuts.js
    Extends the Element native object to include some shortcut methods.

    License:
        MIT-style license.

    Authors:
        Aaron Newton

*/

Element.implement({

    isDisplayed: function(){
        return this.getStyle('display') != 'none';
    },

    toggle: function(){
        return this[this.isDisplayed() ? 'hide' : 'show']();
    },

    hide: function(){
        var d;
        try {
            //IE fails here if the element is not in the dom
            if ('none' != this.getStyle('display')) d = this.getStyle('display');
        } catch(e){}

        return this.store('originalDisplay', d || 'block').setStyle('display', 'none');
    },

    show: function(display){
        return this.setStyle('display', display || this.retrieve('originalDisplay') || 'block');
    },

    swapClass: function(remove, add){
        return this.removeClass(remove).addClass(add);
    }

});


/*
Script: Fx.Elements.js
    Effect to change any number of CSS properties of any number of Elements.

    License:
        MIT-style license.

    Authors:
        Valerio Proietti
*/

Fx.Elements = new Class({

    Extends: Fx.CSS,

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

    compute: function(from, to, delta){
        var now = {};
        for (var i in from){
            var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
            for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
        }
        return now;
    },

    set: function(now){
        for (var i in now){
            var iNow = now[i];
            for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
        }
        return this;
    },

    start: function(obj){
        if (!this.check(obj)) return this;
        var from = {}, to = {};
        for (var i in obj){
            var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
            for (var p in iProps){
                var parsed = this.prepare(this.elements[i], p, iProps[p]);
                iFrom[p] = parsed.from;
                iTo[p] = parsed.to;
            }
        }
        return this.parent(from, to);
    }

});

/*
Script: Fx.Accordion.js
    An Fx.Elements extension which allows you to easily create accordion type controls.

    License:
        MIT-style license.

    Authors:
        Valerio Proietti
*/

var Accordion = Fx.Accordion = new Class({

    Extends: Fx.Elements,

    options: {/*
        onActive: $empty(toggler, section),
        onBackground: $empty(toggler, section),*/
        display: 0,
        show: false,
        height: true,
        width: false,
        opacity: true,
        fixedHeight: false,
        fixedWidth: false,
        wait: false,
        alwaysHide: false,
        trigger: 'click',
        initialDisplayFx: true
    },

    initialize: function(){
        var params = Array.link(arguments, {'container': Element.type, 'options': Object.type, 'togglers': $defined, 'elements': $defined});
        this.parent(params.elements, params.options);
        this.togglers = $$(params.togglers);
        this.container = $(params.container);
        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('active', [this.togglers[i], el]);
            } else {
                for (var fx in this.effects) el.setStyle(fx, 0);
            }
        }, this);
        if ($chk(this.options.display)) this.display(this.options.display, this.options.initialDisplayFx);
    },

    addSection: function(toggler, element){
        toggler = $(toggler);
        element = $(element);
        var test = this.togglers.contains(toggler);
        this.togglers.include(toggler);
        this.elements.include(element);
        var idx = this.togglers.indexOf(toggler);
        toggler.addEvent(this.options.trigger, 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, useFx){
        useFx = $pick(useFx, true);
        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 ? 'background' : 'active', [this.togglers[i], el]);
            for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
        }, this);
        return useFx ? this.start(obj) : this.set(obj);
    }

});

/*
Script: Fx.Move.js
    Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.

    License:
        MIT-style license.

    Authors:
        Aaron Newton

*/

Fx.Move = new Class({

    Extends: Fx.Morph,

    options: {
        relativeTo: document.body,
        position: 'center',
        edge: false,
        offset: {x: 0, y: 0}
    },

    start: function(destination){
        return this.parent(this.element.position($merge(this.options, destination, {returnPos: true})));
    }

});

Element.Properties.move = {

    set: function(options){
        var morph = this.retrieve('move');
        if (morph) morph.cancel();
        return this.eliminate('move').store('move:options', $extend({link: 'cancel'}, options));
    },

    get: function(options){
        if (options || !this.retrieve('move')){
            if (options || !this.retrieve('move:options')) this.set('move', options);
            this.store('move', new Fx.Move(this, this.retrieve('move:options')));
        }
        return this.retrieve('move');
    }

};

Element.implement({

    move: function(options){
        this.get('move').start(options);
        return this;
    }

});


/*
Script: Fx.Reveal.js
    Defines Fx.Reveal, a class that shows and hides elements with a transition.

    License:
        MIT-style license.

    Authors:
        Aaron Newton

*/

Fx.Reveal = new Class({

    Extends: Fx.Morph,

    options: {/*      
        onShow: $empty(thisElemeng),
        onHide: $empty(thisElemeng),
        onComplete: $empty(thisElemeng),
        heightOverride: null,
        widthOverride: null, */
        styles: ['padding', 'border', 'margin'],
        transitionOpacity: !Browser.Engine.trident4,
        mode: 'vertical',
        display: 'block',
        hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
    },

    dissolve: function(){
        try {
            if (!this.hiding && !this.showing){
                if (this.element.getStyle('display') != 'none'){
                    this.hiding = true;
                    this.showing = false;
                    this.hidden = true;
                    var startStyles = this.element.getComputedSize({
                        styles: this.options.styles,
                        mode: this.options.mode
                    });
                    var setToAuto = (this.element.style.height === ''||this.element.style.height == 'auto');
                    this.element.setStyle('display', 'block');
                    if (this.options.transitionOpacity) startStyles.opacity = 1;
                    var zero = {};
                    $each(startStyles, function(style, name){
                        zero[name] = [style, 0];
                    }, this);
                    var overflowBefore = this.element.getStyle('overflow');
                    this.element.setStyle('overflow', 'hidden');
                    var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
                    this.$chain.unshift(function(){
                        if (this.hidden){
                            this.hiding = false;
                            $each(startStyles, function(style, name){
                                startStyles[name] = style;
                            }, this);
                            this.element.setStyles($merge({display: 'none', overflow: overflowBefore}, startStyles));
                            if (setToAuto){
                                if (['vertical', 'both'].contains(this.options.mode)) this.element.style.height = '';
                                if (['width', 'both'].contains(this.options.mode)) this.element.style.width = '';
                            }
                            if (hideThese) hideThese.setStyle('visibility', 'visible');
                        }
                        this.fireEvent('hide', this.element);
                        this.callChain();
                    }.bind(this));
                    if (hideThese) hideThese.setStyle('visibility', 'hidden');
                    this.start(zero);
                } else {
                    this.callChain.delay(10, this);
                    this.fireEvent('complete', this.element);
                    this.fireEvent('hide', this.element);
                }
            } else if (this.options.link == 'chain'){
                this.chain(this.dissolve.bind(this));
            } else if (this.options.link == 'cancel' && !this.hiding){
                this.cancel();
                this.dissolve();
            }
        } catch(e){
            this.hiding = false;
            this.element.setStyle('display', 'none');
            this.callChain.delay(10, this);
            this.fireEvent('complete', this.element);
            this.fireEvent('hide', this.element);
        }
        return this;
    },

    reveal: function(){
        try {
            if (!this.showing && !this.hiding){
                if (this.element.getStyle('display') == 'none' ||
                     this.element.getStyle('visiblity') == 'hidden' ||
                     this.element.getStyle('opacity') == 0){
                    this.showing = true;
                    this.hiding = false;
                    this.hidden = false;
                    var setToAuto, startStyles;
                    //toggle display, but hide it
                    this.element.measure(function(){
                        setToAuto = (this.element.style.height === '' || this.element.style.height == 'auto');
                        //create the styles for the opened/visible state
                        startStyles = this.element.getComputedSize({
                            styles: this.options.styles,
                            mode: this.options.mode
                        });
                    }.bind(this));
                    $each(startStyles, function(style, name){
                        startStyles[name] = style;
                    });
                    //if we're overridding height/width
                    if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
                    if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
                    if (this.options.transitionOpacity) {
                        this.element.setStyle('opacity', 0);
                        startStyles.opacity = 1;
                    }
                    //create the zero state for the beginning of the transition
                    var zero = {
                        height: 0,
                        display: this.options.display
                    };
                    $each(startStyles, function(style, name){ zero[name] = 0; });
                    var overflowBefore = this.element.getStyle('overflow');
                    //set to zero
                    this.element.setStyles($merge(zero, {overflow: 'hidden'}));
                    //hide inputs
                    var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
                    if (hideThese) hideThese.setStyle('visibility', 'hidden');
                    //start the effect
                    this.start(startStyles);
                    this.$chain.unshift(function(){
                        this.element.setStyle('overflow', overflowBefore);
                        if (!this.options.heightOverride && setToAuto){
                            if (['vertical', 'both'].contains(this.options.mode)) this.element.style.height = '';
                            if (['width', 'both'].contains(this.options.mode)) this.element.style.width = '';
                        }
                        if (!this.hidden) this.showing = false;
                        if (hideThese) hideThese.setStyle('visibility', 'visible');
                        this.callChain();
                        this.fireEvent('show', this.element);
                    }.bind(this));
                } else {
                    this.callChain();
                    this.fireEvent('complete', this.element);
                    this.fireEvent('show', this.element);
                }
            } else if (this.options.link == 'chain'){
                this.chain(this.reveal.bind(this));
            } else if (this.options.link == 'cancel' && !this.showing){
                this.cancel();
                this.reveal();
            }
        } catch(e){
            this.element.setStyles({
                display: this.options.display,
                visiblity: 'visible',
                opacity: 1
            });
            this.showing = false;
            this.callChain.delay(10, this);
            this.fireEvent('complete', this.element);
            this.fireEvent('show', this.element);
        }
        return this;
    },

    toggle: function(){
        if (this.element.getStyle('display') == 'none' ||
             this.element.getStyle('visiblity') == 'hidden' ||
             this.element.getStyle('opacity') == 0){
            this.reveal();
        } else {
            this.dissolve();
        }
        return this;
    }

});

Element.Properties.reveal = {

    set: function(options){
        var reveal = this.retrieve('reveal');
        if (reveal) reveal.cancel();
        return this.eliminate('reveal').store('reveal:options', $extend({link: 'cancel'}, options));
    },

    get: function(options){
        if (options || !this.retrieve('reveal')){
            if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
            this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
        }
        return this.retrieve('reveal');
    }

};

Element.Properties.dissolve = Element.Properties.reveal;

Element.implement({

    reveal: function(options){
        this.get('reveal', options).reveal();
        return this;
    },

    dissolve: function(options){
        this.get('reveal', options).dissolve();
        return this;
    },

    nix: function(){
        var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type});
        this.get('reveal', params.options).dissolve().chain(function(){
            this[params.destroy ? 'destroy' : 'dispose']();
        }.bind(this));
        return this;
    },

    wink: function(){
        var params = Array.link(arguments, {duration: Number.type, options: Object.type});
        var reveal = this.get('reveal', params.options);
        reveal.reveal().chain(function(){
            (function(){
                reveal.dissolve();
            }).delay(params.duration || 2000);
        });
    }


});

/*
Script: Fx.Scroll.js
    Effect to smoothly scroll any element, including the window.

    License:
        MIT-style license.

    Authors:
        Valerio Proietti
*/

Fx.Scroll = new Class({

    Extends: Fx,

    options: {
        offset: {x: 0, y: 0},
        wheelStops: true
    },

    initialize: function(element, options){
        this.element = this.subject = $(element);
        this.parent(options);
        var cancel = this.cancel.bind(this, false);

        if ($type(this.element) != 'element') this.element = $(this.element.getDocument().body);

        var stopper = this.element;

        if (this.options.wheelStops){
            this.addEvent('start', function(){
                stopper.addEvent('mousewheel', cancel);
            }, true);
            this.addEvent('complete', function(){
                stopper.removeEvent('mousewheel', cancel);
            }, true);
        }
    },

    set: function(){
        var now = Array.flatten(arguments);
        this.element.scrollTo(now[0], now[1]);
    },

    compute: function(from, to, delta){
        return [0, 1].map(function(i){
            return Fx.compute(from[i], to[i], delta);
        });
    },

    start: function(x, y){
        if (!this.check(x, y)) return this;
        var offsetSize = this.element.getSize(), scrollSize = this.element.getScrollSize();
        var scroll = this.element.getScroll(), values = {x: x, y: y};
        for (var z in values){
            var max = scrollSize[z] - offsetSize[z];
            if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z].limit(0, max) : max;
            else values[z] = scroll[z];
            values[z] += this.options.offset[z];
        }
        return this.parent([scroll.x, scroll.y], [values.x, values.y]);
    },

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

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

    toRight: function(){
        return this.start('right', false);
    },

    toBottom: function(){
        return this.start(false, 'bottom');
    },

    toElement: function(el){
        var position = $(el).getPosition(this.element);
        return this.start(position.x, position.y);
    }

});


/*
Script: Fx.Slide.js
    Effect to slide an element in and out of view.

    License:
        MIT-style license.

    Authors:
        Valerio Proietti
*/

Fx.Slide = new Class({

    Extends: Fx,

    options: {
        mode: 'vertical'
    },

    initialize: function(element, options){
        this.addEvent('complete', function(){
            this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
            if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
        }, true);
        this.element = this.subject = $(element);
        this.parent(options);
        var wrapper = this.element.retrieve('wrapper');
        this.wrapper = wrapper || new Element('div', {
            styles: $extend(this.element.getStyles('margin', 'position'), {overflow: 'hidden'})
        }).wraps(this.element);
        this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
        this.now = [];
        this.open = true;
    },

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

    set: function(now){
        this.element.setStyle(this.margin, now[0]);
        this.wrapper.setStyle(this.layout, now[1]);
        return this;
    },

    compute: function(from, to, delta){
        return [0, 1].map(function(i){
            return Fx.compute(from[i], to[i], delta);
        });
    },

    start: function(how, mode){
        if (!this.check(how, mode)) return this;
        this[mode || this.options.mode]();
        var margin = this.element.getStyle(this.margin).toInt();
        var layout = this.wrapper.getStyle(this.layout).toInt();
        var caseIn = [[margin, layout], [0, this.offset]];
        var caseOut = [[margin, layout], [-this.offset, 0]];
        var start;
        switch (how){
            case 'in': start = caseIn; break;
            case 'out': start = caseOut; break;
            case 'toggle': start = (layout == 0) ? caseIn : caseOut;
        }
        return this.parent(start[0], start[1]);
    },

    slideIn: function(mode){
        return this.start('in', mode);
    },

    slideOut: function(mode){
        return this.start('out', mode);
    },

    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){
        return this.start('toggle', mode);
    }

});

Element.Properties.slide = {

    set: function(options){
        var slide = this.retrieve('slide');
        if (slide) slide.cancel();
        return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
    },

    get: function(options){
        if (options || !this.retrieve('slide')){
            if (options || !this.retrieve('slide:options')) this.set('slide', options);
            this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
        }
        return this.retrieve('slide');
    }

};

Element.implement({

    slide: function(how, mode){
        how = how || 'toggle';
        var slide = this.get('slide'), toggle;
        switch (how){
            case 'hide': slide.hide(mode); break;
            case 'show': slide.show(mode); break;
            case 'toggle':
                var flag = this.retrieve('slide:flag', slide.open);
                slide[flag ? 'slideOut' : 'slideIn'](mode);
                this.store('slide:flag', !flag);
                toggle = true;
            break;
            default: slide.start(how, mode);
        }
        if (!toggle) this.eliminate('slide:flag');
        return this;
    }

});


/*
Script: Fx.SmoothScroll.js
    Class for creating a smooth scrolling effect to all internal links on the page.

    License:
        MIT-style license.

    Authors:
        Valerio Proietti
*/

var SmoothScroll = Fx.SmoothScroll = new Class({

    Extends: Fx.Scroll,

    initialize: function(options, context){
        context = context || document;
        this.doc = context.getDocument();
        var win = context.getWindow();
        this.parent(this.doc, options);
        this.links = this.options.links ? $$(this.options.links) : $$(this.doc.links);
        var location = win.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) this.useLink(link, anchor);
        }, this);
        if (!Browser.Engine.webkit419) {
            this.addEvent('complete', function(){
                win.location.hash = this.anchor;
            }, true);
        }
    },

    useLink: function(link, anchor){
        var el;
        link.addEvent('click', function(event){
            if (el !== false && !el) el = $(anchor) || this.doc.getElement('a[name=' + anchor + ']');
            if (el) {
                event.preventDefault();
                this.anchor = anchor;
                this.toElement(el);
                link.blur();
            }
        }.bind(this));
    }

});

/*
Script: Fx.Sort.js
    Defines Fx.Sort, a class that reorders lists with a transition.

    License:
        MIT-style license.

    Authors:
        Aaron Newton

*/

Fx.Sort = new Class({

    Extends: Fx.Elements,

    options: {
        mode: 'vertical'
    },

    initialize: function(elements, options){
        this.parent(elements, options);
        this.elements.each(function(el){
            if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
        });
        this.setDefaultOrder();
    },

    setDefaultOrder: function(){
        this.currentOrder = this.elements.map(function(el, index){
            return index;
        });
    },

    sort: function(newOrder){
        if ($type(newOrder) != 'array') return false;
        var top = 0;
        var left = 0;
        var zero = {};
        var vert = this.options.mode == 'vertical';
        var current = this.elements.map(function(el, index){
            var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
            var val;
            if (vert){
                val = {
                    top: top,
                    margin: size['margin-top'],
                    height: size.totalHeight
                };
                top += val.height - size['margin-top'];
            } else {
                val = {
                    left: left,
                    margin: size['margin-left'],
                    width: size.totalWidth
                };
                left += val.width;
            }
            var plain = vert ? 'top' : 'left';
            zero[index] = {};
            var start = el.getStyle(plain).toInt();
            zero[index][plain] = start || 0;
            return val;
        }, this);
        this.set(zero);
        newOrder = newOrder.map(function(i){ return i.toInt(); });
        if (newOrder.length != this.elements.length){
            this.currentOrder.each(function(index){
                if (!newOrder.contains(index)) newOrder.push(index);
            });
            if (newOrder.length > this.elements.length)
                newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
        }
        top = 0;
        left = 0;
        var margin = 0;
        var next = {};
        newOrder.each(function(item, index){
            var newPos = {};
            if (vert){
                newPos.top = top - current[item].top - margin;
                top += current[item].height;
            } else {
                newPos.left = left - current[item].left;
                left += current[item].width;
            }
            margin = margin + current[item].margin;
            next[item]=newPos;
        }, this);
        var mapped = {};
        $A(newOrder).sort().each(function(index){
            mapped[index] = next[index];
        });
        this.start(mapped);
        this.currentOrder = newOrder;
        return this;
    },

    rearrangeDOM: function(newOrder){
        newOrder = newOrder || this.currentOrder;
        var parent = this.elements[0].getParent();
        var rearranged = [];
        this.elements.setStyle('opacity', 0);
        //move each element and store the new default order
        newOrder.each(function(index){
            rearranged.push(this.elements[index].inject(parent).setStyles({
                top: 0,
                left: 0
            }));
        }, this);
        this.elements.setStyle('opacity', 1);
        this.elements = $$(rearranged);
        this.setDefaultOrder();
        return this;
    },

    getDefaultOrder: function(){
        return this.elements.map(function(el, index){
            return index;
        });
    },

    forward: function(){
        return this.sort(this.getDefaultOrder());
    },

    backward: function(){
        return this.sort(this.getDefaultOrder().reverse());
    },

    reverse: function(){
        return this.sort(this.currentOrder.reverse());
    },

    sortByElements: function(elements){
        return this.sort(elements.map(function(el){
            return this.elements.indexOf(el);
        }, this));
    },

    swap: function(one, two){
        if ($type(one) == 'element') one = this.elements.indexOf(one);
        if ($type(two) == 'element') two = this.elements.indexOf(two);
        
        var newOrder = $A(this.currentOrder);
        newOrder[this.currentOrder.indexOf(one)] = two;
        newOrder[this.currentOrder.indexOf(two)] = one;
        this.sort(newOrder);
    }

});
