//  mootools.js: moo javascript tools
//  by Valerio Proietti (http://mad4milk.net) MIT-style license.

//  CREDITS:

//  Class is slightly based on Base.js : http://dean.edwards.name/weblog/2006/03/base/
//      (c) 2006 Dean Edwards, License: http://creativecommons.org/licenses/LGPL/2.1/

//  Some functions are based on those found in prototype.js : http://prototype.conio.net/
//      (c) 2005 Sam Stephenson <sam@conio.net>, MIT-style license


//moo.js : My Object Oriented javascript - has no dependancies

var Class = function(properties){
    var klass = function(){
        for (p in this) this[p]._proto_ = this;
        if (arguments[0] != 'noinit' && this.initialize) return this.initialize.apply(this, arguments);
    };
    klass.extend = this.extend;
    klass.implement = this.implement;
    klass.prototype = properties;
    return klass;
};

Class.empty = function(){};

Class.create = function(properties){
    return new Class(properties);
};

Class.prototype = {
    extend: function(properties){
        var prototype = new this('noinit');
        for (property in properties){
            var previous = prototype[property];
            var current = properties[property];
            if (previous && previous != current) current = previous.parentize(current) || current;
            prototype[property] = current;
        }
        return new Class(prototype);
    },

    implement: function(properties){
        for (property in properties) this.prototype[property] = properties[property];
    }
}

Object.extend = function(){
    var args = arguments;
    if (args[1]) args = [args[0], args[1]];
    else args = [this, args[0]];
    for (property in args[1]) args[0][property] = args[1][property];
    return args[0];
};

Object.Native = function(){
    for (var i = 0; i < arguments.length; i++) arguments[i].extend = Class.prototype.implement;
};

new Object.Native(Function, Array, String);

Function.extend({
    parentize: function(current){
        var previous = this;
        return function(){
            this.parent = previous;
            return current.apply(this, arguments);
        };
    }
});

//Function.js : Function extension - Depends on Moo.js

Function.extend({

    pass: function(args, bind){
        var fn = this;
        if ($type(args) != 'array') args = [args];
        return function(){
            fn.apply(bind || fn._proto_ || fn, args);
        };
    },

    bind: function(bind){
        var fn = this;
        return function(){
            return fn.apply(bind, arguments);
        };
    },

    bindAsEventListener: function(bind){
        var fn = this;
        return function(event){
            fn.call(bind, event || window.event);
            return false;
        };
    },

    delay: function(ms, bind){
        return setTimeout(this.bind(bind || this._proto_ || this), ms);
    },

    periodical: function(ms, bind){
        return setInterval(this.bind(bind || this._proto_ || this), ms);
    }

});

function $clear(timer){
    clearTimeout(timer);
    clearInterval(timer);
    return null;
};

function $type(obj, types){
    if (!obj) return false;
    var type = false;
    if (obj instanceof Function) type = 'function';
    else if (obj.nodeName){
        if (obj.nodeType == 3 && !/\S/.test(obj.nodeValue)) type = 'textnode';
        else if (obj.nodeType == 1) type = 'element';
    }
    else if (obj instanceof Array) type = 'array';
    else if (typeof obj == 'object') type = 'object';
    else if (typeof obj == 'string') type = 'string';
    else if (typeof obj == 'number' && isFinite(obj)) type = 'number';
    return type;
};

function $check(obj, objTrue, objFalse){
    if (obj) {
        if (objTrue && $type(objTrue) == 'function') return objTrue();
        else return objTrue || obj;
    } else {
        if (objFalse && $type(objFalse) == 'function') return objFalse();
        return objFalse || false;
    }
};

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.splice(0, 1)[0].delay(10, this);
    }

});

//Array.js : Array extension - depends on Moo.js

if (!Array.prototype.forEach){
    Array.prototype.forEach = function(fn, bind){
        for(var i = 0; i < this.length ; i++) fn.call(bind, this[i], i);
    };
}

Array.extend({

    each: Array.prototype.forEach,

    copy: function(){
        var nArray = [];
        for (var i = 0; i < this.length; i++) nArray.push(this[i]);
        return nArray;
    },

    remove: function(item){
        for (var i = 0; i < this.length; i++){
            if (this[i] == item) this.splice(i, 1);
        }
        return this;
    },

    test: function(item){
        for (var i = 0; i < this.length; i++){
            if (this[i] == item) return true;
        };
        return false;
    },

    extend: function(nArray){
        for (var i = 0; i < nArray.length; i++) this.push(nArray[i]);
        return this;
    }

});

function $A(array){
    return Array.prototype.copy.call(array);
};

//String.js : String extension - depends on Moo.js

String.extend({

    test: function(value, params){
        return this.match(new RegExp(value, params));
    },

    camelCase: function(){
        return this.replace(/-\D/gi, function(match){
            return match.charAt(match.length - 1).toUpperCase();
        });
    },

    capitalize: function(){
        return this.toLowerCase().replace(/\b[a-z]/g, function(match){
            return match.toUpperCase();
        });
    },

    trim: function(){
        return this.replace(/^\s*|\s*$/g,'');
    },

    clean: function(){
        return this.replace(/\s\s/g, ' ').trim();
    },

    rgbToHex: function(array){
        var rgb = this.test('^[rgba]{3,4}\\(([\\d]{0,3}),[\\s]*([\\d]{0,3}),[\\s]*([\\d]{0,3})\\)$');
        var hex = [];
        for (var i = 1; i < rgb.length; i++) hex.push((rgb[i]-0).toString(16));
        var hexText = '#'+hex.join('');
        if (array) return hex;
        else return hexText;
    },

    hexToRgb: function(array){
        var hex = this.test('^[#]{0,1}([\\w]{1,2})([\\w]{1,2})([\\w]{1,2})$');
        var rgb = [];
        for (var i = 1; i < hex.length; i++){
            if (hex[i].length == 1) hex[i] += hex[i];
            rgb.push(parseInt(hex[i], 16));
        }
        var rgbText = 'rgb('+rgb.join(',')+')';
        if (array) return rgb;
        else return rgbText;
    }

});

//Element.js : Element methods - depends on Moo.js + Native Scripts

var Element = new Class({

    //creation

    initialize: function(el){
        if ($type(el) == 'string') el = document.createElement(el);
        return $(el);
    },

    //injecters

    inject: function(el, where){
        var el = $check($(el), $(el), new Element(el));
        switch(where){
            case "before": $(el.parentNode).insertBefore(this, el); break;
            case "after": {
                    if (!el.getNext()) $(el.parentNode).appendChild(this);
                    else $(el.parentNode).insertBefore(this, el.getNext());
            } break;
            case "inside": el.appendChild(this); break;
        }
        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, 'inside');
    },

    adopt: function(el){
        var el = $check($(el), $(el), new Element(el));
        this.appendChild(el);
        return this;
    },

    //actions

    remove: function(){
        this.parentNode.removeChild(this);
    },

    clone: function(){
        return $(this.cloneNode(true));
    },

    replaceWith: function(el){
        var el = $check($(el), $(el), new Element(el));
        this.parentNode.replaceChild(el, this);
        return el;
    },

    appendText: function(text){
        if (this.getTag() == 'style' && window.ActiveXObject) this.styleSheet.cssText = text;
        else this.appendChild(document.createTextNode(text));
        return this;
    },

    //classnames

    hasClassName: function(className){
        return $check(this.className.test("\\b"+className+"\\b"), true);
    },

    addClassName: function(className){
        if (!this.hasClassName(className)) this.className = (this.className+' '+className.trim()).clean();
        return this;
    },

    removeClassName: function(className){
        if (this.hasClassName(className)) this.className = this.className.replace(className.trim(), '').clean();
        return this;
    },

    toggleClassName: function(className){
        if (this.hasClassName(className)) return this.removeClassName(className);
        else return this.addClassName(className);
    },

    //styles

    setStyle: function(property, value){
        if (property == 'opacity') this.setOpacity(value);
        else this.style[property.camelCase()] = value;
        return this;
    },

    setStyles: function(source){
        if ($type(source) == 'object') {
            for (property in source) this.setStyle(property, source[property]);
        } else if ($type(source) == 'string') this.setAttribute('style', source);
        return this;
    },

    setOpacity: function(opacity){
        if (opacity == 0 && this.style.visibility != "hidden") this.style.visibility = "hidden";
        else if (this.style.visibility != "visible") this.style.visibility = "visible";
        if (window.ActiveXObject) this.style.filter = "alpha(opacity=" + opacity*100 + ")";
        this.style.opacity = opacity;
        return this;
    },

    getStyle: function(property, num){
        var proPerty = property.camelCase();
        var style = $check(this.style[proPerty]);
        if (!style) {
            if (document.defaultView) style = document.defaultView.getComputedStyle(this,null).getPropertyValue(property);
            else if (this.currentStyle) style = this.currentStyle[proPerty];
        }
        if (style && ['color', 'backgroundColor', 'borderColor'].test(proPerty) && style.test('rgb')) style = style.rgbToHex();
        if (['auto', 'transparent'].test(style)) style = 0;
        if (num) return parseInt(style);
        else return style;
    },

    removeStyles: function(){
        $A(arguments).each(function(property){
            this.style[property.camelCase()] = '';
        }, this);
        return this;
    },

    //events

    addEvent: function(action, fn){
        this[action+fn] = fn.bind(this);
        if (this.addEventListener) this.addEventListener(action, fn, false);
        else this.attachEvent('on'+action, this[action+fn]);
        var el = this;
        if (this != window) Unload.functions.push(function(){
            el.removeEvent(action, fn);
            el[action+fn] = null;
        });
        return this;
    },

    removeEvent: function(action, fn){
        if (this.removeEventListener) this.removeEventListener(action, fn, false);
        else this.detachEvent('on'+action, this[action+fn]);
        return this;
    },

    //get non-text elements

    getBrother: function(what){
        var el = this[what+'Sibling'];
        while ($type(el) == 'textnode') el = el[what+'Sibling'];
        return $(el);
    },

    getPrevious: function(){
        return this.getBrother('previous');
    },

    getNext: function(){
        return this.getBrother('next');
    },

    getFirst: function(){
        var el = this.firstChild;
        while ($type(el) == 'textnode') el = el.nextSibling;
        return $(el);
    },

    //properties

    setProperty: function(property, value){
        var el = false;
        switch(property){
            case 'class': this.className = value; break;
            case 'style': this.setStyles(value); break;
            case 'name': if (window.ActiveXObject && this.getTag() == 'input'){
                el = $(document.createElement('<input name="'+value+'" />'));
                $A(this.attributes).each(function(attribute){
                    if (attribute.name != 'name') el.setProperty(attribute.name, attribute.value);

                });
                if (this.parentNode) this.replaceWith(el);
            };
            default: this.setAttribute(property, value);
        }
        return el || this;
    },

    setProperties: function(source){
        for (property in source) this.setProperty(property, source[property]);
        return this;
    },

    setHTML: function(html){
        this.innerHTML = html;
        return this;
    },

    getProperty: function(property){
        return this.getAttribute(property);
    },

    getTag: function(){
        return this.tagName.toLowerCase();
    },

    //position

    getOffset: function(what){
        what = what.capitalize();
        var el = this;
        var offset = 0;
        do {
            offset += el['offset'+what] || 0;
            el = el.offsetParent;
        } while (el);
        return offset;
    },

    getTop: function(){
        return this.getOffset('top');
    },

    getLeft: function(){
        return this.getOffset('left');
    }

});

function $Element(el, method, args){
    if ($type(args) != 'array') args = [args];
    return Element.prototype[method].apply(el, args);
};

new Object.Native(Element);

function $(el){
    if ($type(el) == 'string') el = document.getElementById(el);
    if ($type(el) == 'element'){
        if (!el.extend){
            Unload.elements.push(el);
            el.extend = Object.extend;
            el.extend(Element.prototype);
        }
        return el;
    } else return false;
};

//garbage collector

window.addEvent = Element.prototype.addEvent;
window.removeEvent = Element.prototype.removeEvent;

var Unload = {

    elements: [], functions: [], vars: [],

    unload: function(){
        Unload.functions.each(function(fn){
            fn();
        });

        window.removeEvent('unload', window.removeFunction);

        Unload.elements.each(function(el){
            for(p in Element.prototype){
                window[p] = null;
                document[p] = null;
                el[p] = null;
            }
            el.extend = null;
        });
    }

};
window.removeFunction = Unload.unload;
window.addEvent('unload', window.removeFunction);

//Fx.js - depends on Moo.js + Native Scripts

var Fx = fx = {};

Fx.Base = new Class({

    setOptions: function(options){
        this.options = Object.extend({
            duration: 500,
            onComplete: Class.empty,
            onStart: Class.empty,
            unit: 'px',
            wait: true,
            transition: Fx.sinoidal,
            fps: 30
        }, options || {});
    },

    step: function(){
        var currentTime  = (new Date).getTime();
        if (currentTime >= this.options.duration+this.startTime){
            this.clearTimer();
            this.now = this.to;
            this.options.onComplete.pass(this.el, this).delay(10);
            this.callChain();
        } else {
            this.tPos = (currentTime - this.startTime) / this.options.duration;
            this.setNow();
        }
        this.increase();
    },

    setNow: function(){
        this.now = this.compute(this.from, this.to);
    },

    compute: function(from, to){
        return this.options.transition(this.tPos) * (to-from) + from;
    },

    custom: function(from, to){
        if(!this.options.wait) this.clearTimer();
        if (this.timer) return;
        this.options.onStart.pass(this.el, this).delay(10);
        this.from = from;
        this.to = to;
        this.startTime = (new Date).getTime();
        this.timer = this.step.periodical(Math.round(1000/this.options.fps), this);
        return this;
    },

    set: function(to){
        this.now = to;
        this.increase();
        return this;
    },

    clearTimer: function(){
        this.timer = $clear(this.timer);
        return this;
    },

    setStyle: function(el, property, value){
        if (property == 'opacity'){
            if (value == 1 && navigator.userAgent.test('Firefox')) value = 0.9999;
            el.setOpacity(value);
        } else el.setStyle(property, value+this.options.unit);
    }

});

Fx.Base.implement(new Chain);

Fx.Style = Fx.Base.extend({

    initialize: function(el, property, options){
        this.el = $(el);
        this.setOptions(options);
        this.property = property.camelCase();
    },

    hide: function(){
        return this.set(0);
    },

    goTo: function(val){
        return this.custom(this.now || 0, val);
    },

    increase: function(){
        this.setStyle(this.el, this.property, this.now);
    }

});

Fx.Layout = Fx.Style.extend({

    initialize: function(el, layout, options){
        this.parent(el, layout, options);
        this.layout = layout.capitalize();
        this.el.setStyle('overflow', 'hidden');
    },

    toggle: function(){
        if (this.el['offset'+this.layout] > 0) return this.custom(this.el['offset'+this.layout], 0);
        else return this.custom(0, this.el['scroll'+this.layout]);
    },

    show: function(){
        return this.set(this.el['scroll'+this.layout]);
    }

});

Fx.Height = Fx.Layout.extend({

    initialize: function(el, options){
        this.parent(el, 'height', options);
    }

});

Fx.Width = Fx.Layout.extend({

    initialize: function(el, options){
        this.parent(el, 'width', options);
    }

});

Fx.Opacity = Fx.Style.extend({

    initialize: function(el, options){
        this.parent(el, 'opacity', options);
        this.now = 1;
    },

    toggle: function(){
        if (this.now > 0) return this.custom(1, 0);
        else return this.custom(0, 1);
    },

    show: function(){
        this.set(1);
    }

});

Element.extend({

    effect: function(property, options){
        return new Fx.Style(this, property, options);
    }

});

Fx.sinoidal = function(pos){return ((-Math.cos(pos*Math.PI)/2) + 0.5);}; //this transition is from script.aculo.us

Fx.linear = function(pos){return pos;};

Fx.cubic = function(pos){return Math.pow(pos, 3);};

Fx.circ = function(pos){return Math.sqrt(pos);};

//SuperDom.js - depends on Moo.js + Native Scripts

function $S(){
    var els = [];
    $A(arguments).each(function(sel){
        if ($type(sel) == 'string') els.extend(document.getElementsBySelector(sel));
        else if ($type(sel) == 'element') els.push($(sel));
    });
    return $$(els);
};

function $E(selector, filter){
    return ($(filter) || document).getElement(selector);
};

function $$(elements){
    return Object.extend(elements, new Elements);
};

Element.extend({

    getElements: function(selector){
        var filters = [];
        selector.clean().split(' ').each(function(sel, i){
            var bits = [];
            var param = [];
            var attr = [];
            if (bits = sel.test('^([\\w]*)')) param['tag'] = bits[1] || '*';
            if (bits = sel.test('([.#]{1})([\\w-]*)$')){
                if (bits[1] == '.') param['class'] = bits[2];
                else param['id'] = bits[2];
            }
            if (bits = sel.test('\\[["\'\\s]{0,1}([\\w-]*)["\'\\s]{0,1}([\\W]{0,1}=){0,2}["\'\\s]{0,1}([\\w-]*)["\'\\s]{0,1}\\]$')){
                attr['name'] = bits[1];
                attr['operator'] = bits[2];
                attr['value'] = bits[3];
            }
            if (i == 0){
                if (param['id']){
                    var el = this.getElementById(param['id']);
                    if (el && (param['tag'] == '*' || $(el).getTag() == param['tag'])) filters = [el];
                    else return false;
                } else {
                    filters = $A(this.getElementsByTagName(param['tag']));
                }
            } else {
                filters = $$(filters).filterByTagName(param['tag']);
                if (param['id']) filters = $$(filters).filterById(param['id']);
            }
            if (param['class']) filters = $$(filters).filterByClassName(param['class']);
            if (attr['name']) filters = $$(filters).filterByAttribute(attr['name'], attr['value'], attr['operator']);

        }, this);
        filters.each(function(el){
            $(el);
        });
        return $$(filters);
    },

    getElement: function(selector){
        return this.getElementsBySelector(selector)[0];
    },

    getElementsBySelector: function(selector){
        var els = [];
        selector.split(',').each(function(sel){
            els.extend(this.getElements(sel));
        }, this);
        return $$(els);
    }

});

document.extend = Object.extend;

document.extend({

    getElementsByClassName: function(className){
        return document.getElements('.'+className);
    },
    getElement: Element.prototype.getElement,
    getElements: Element.prototype.getElements,
    getElementsBySelector: Element.prototype.getElementsBySelector

});

var Elements = new Class({

    action: function(actions){
        this.each(function(el){
            el = $(el);
            if (actions.initialize) actions.initialize.apply(el);
            for(action in actions){
                var evt = false;
                if (action.test('^on[\\w]{1,}')) el[action] = actions[action];
                else if (evt = action.test('([\\w-]{1,})event$')) el.addEvent(evt[1], actions[action]);
            }
        });
    },

    filterById: function(id){
        var found = [];
        this.each(function(el){
            if (el.id == id) found.push(el);
        });
        return found;
    },

    filterByClassName: function(className){
        var found = [];
        this.each(function(el){
            if ($Element(el, 'hasClassName', className)) found.push(el);
        });
        return found;
    },

    filterByTagName: function(tagName){
        var found = [];
        this.each(function(el){
            found.extend($A(el.getElementsByTagName(tagName)));
        });
        return found;
    },

    filterByAttribute: function(name, value, operator){
        var found = [];
        this.each(function(el){
            var att = el.getAttribute(name);
            if(!att) return;
            if (!operator) return found.push(el);

            switch(operator){
                case '*=': if (att.test(value)) found.push(el); break;
                case '=': if (att == value) found.push(el); break;
                case '^=': if (att.test('^'+value)) found.push(el); break;
                case '$=': if (att.test(value+'$')) found.push(el);
            }

        });
        return found;
    }

});

new Object.Native(Elements);

//Ajax.js - depends on Moo.js + Native Scripts

var Ajax = ajax = new Class({

    setOptions: function(options){
        this.options = {
            method: 'post',
            postBody: '',
            async: true,
            onComplete: Class.empty,
            update: null,
            evalScripts: false
        };
        Object.extend(this.options, options || {});
    },

    initialize: function(url, options){
        this.setOptions(options);
        this.url = url;
        this.transport = this.getTransport();
    },

    request: function(){
        this.transport.open(this.options.method, this.url, this.options.async);
        this.transport.onreadystatechange = this.onStateChange.bind(this);
        if (this.options.method == 'post'){
            this.transport.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
            if (this.transport.overrideMimeType) this.transport.setRequestHeader('Connection', 'close');
        }
        switch($type(this.options.postBody)){
            case 'element': this.options.postBody = $(this.options.postBody).toQueryString(); break;
            case 'object': this.options.postBody = Object.toQueryString(this.options.postBody);
        }
        if($type(this.options.postBody) == 'string') this.transport.send(this.options.postBody);
        else this.transport.send();
        return this;
    },

    onStateChange: function(){
        if (this.transport.readyState == 4 && this.transport.status == 200){
            if (this.options.update) $(this.options.update).setHTML(this.transport.responseText);
            this.options.onComplete.pass([this.transport.responseText, this.transport.responseXML], this).delay(20);
            if (this.options.evalScripts) this.evalScripts.delay(30, this);
            this.transport.onreadystatechange = Class.empty;
            this.callChain();
        }
    },

    evalScripts: function(){
        if(scripts = this.transport.responseText.match(/<script[^>]*?>.*?<\/script>/g)){
            scripts.each(function(script){
                eval(script.replace(/^<script[^>]*?>/, '').replace(/<\/script>$/, ''));
            });
        }
    },

    getTransport: function(){
        if (window.XMLHttpRequest) return new XMLHttpRequest();
        else if (window.ActiveXObject) return new ActiveXObject('Microsoft.XMLHTTP');
    }

});

Ajax.implement(new Chain);

Object.toQueryString = function(source){
    var queryString = [];
    for (property in source) queryString.push(encodeURIComponent(property)+'='+encodeURIComponent(source[property]));
    return queryString.join('&');
};

Element.extend({

    send: function(options){
        options = Object.extend(options, {postBody: this.toQueryString(), method: 'post'});
        return new Ajax(this.getProperty('action'), options).request();
    },

    toQueryString: function(){
        var queryString = [];
        $A(this.getElementsByTagName('*')).each(function(el){
            $(el);
            var name = $check(el.name);
            if (!name) return;
            var value = false;
            switch(el.getTag()){
                case 'select': value = el.getElementsByTagName('option')[el.selectedIndex].value; break;
                case 'input': if ( (el.checked && ['checkbox', 'radio'].test(el.type)) || (['hidden', 'text', 'password'].test(el.type)) )
                    value = el.value; break;
                case 'textarea': value = el.value;
            }
            if (value) queryString.push(encodeURIComponent(name)+'='+encodeURIComponent(value));
        });
        return queryString.join('&');
    }

});

//DragDrop.js - depends on Moo.js + Native Scripts

var Drag = {};

Drag.Base = new Class({

    setOptions: function(options){
        this.options = Object.extend({
            handle: false,
            unit: 'px',
            onStart: Class.empty,
            onComplete: Class.empty,
            onDrag: Class.empty
        }, options || {});
    },

    initialize: function(el, xModifier, yModifier, options){
        this.setOptions(options);
        this.el = $(el);
        this.handle = $(this.options.handle) || el;
        if (xModifier) this.xp = xModifier.camelCase();
        if (yModifier) this.yp = yModifier.camelCase();
        this.handle.onmousedown = this.start.bind(this);
    },

    start: function(evt){
        evt = evt || window.event;
        this.startX = evt.clientX;
        this.startY = evt.clientY;
        this.options.onStart.pass(this.el, this).delay(10);
        document.onmousemove = this.drag.bind(this);
        document.onmouseup = this.end.bind(this);
        return false;
    },

    addStyles: function(x, y){
        if (this.xp) this.el.setStyle(this.xp, (this.el.getStyle(this.xp, true)+x)+this.options.unit);
        if (this.yp) this.el.setStyle(this.yp, (this.el.getStyle(this.yp, true)+y)+this.options.unit);
    },

    drag: function(evt){
        evt = evt || window.event;
        this.clientX = evt.clientX;
        this.clientY = evt.clientY;
        this.options.onDrag.pass(this.el, this).delay(5);
        this.addStyles((this.clientX-this.lastMouseX), (this.clientY-this.lastMouseY));
        this.set(evt);
        return false;
    },

    pause: function(){
        this.handle.onmousedown = null;
    },

    resume: function(){
        this.handle.onmousedown = this.start.bind(this);
    },

    set: function(evt){
        this.lastMouseX = evt.clientX;
        this.lastMouseY = evt.clientY;
        return false;
    },

    end: function(){
        document.onmousemove = null;
        document.onmouseup = null;
        this.options.onComplete.pass(this.el, this).delay(10);
    }

});

Drag.Move = Drag.Base.extend({

    extendOptions: function(options){
        this.options = Object.extend(this.options || {}, Object.extend({
            onSnap: Class.empty,
            droppables: [],
            snapDistance: 8,
            snap: true,
            xModifier: 'left',
            yModifier: 'top'
        }, options || {}));
    },

    initialize: function(el, options){
        this.extendOptions(options);
        this.parent(el, this.options.xModifier, this.options.yModifier, this.options);
    },

    start: function(evt){
        this.parent(evt);
        if (this.options.snap){
            document.onmousemove = this.checkAndDrag.bind(this);
        } else {
            this.set(evt);
            document.onmousemove = this.drag.bind(this);
        }
        return false;
    },

    drag: function(evt){
        this.parent(evt);
        this.options.droppables.each(function(drop){
            if (this.checkAgainst(drop)){
                if (drop.onOver && !drop.dropping) drop.onOver.pass([this.el, this], drop).delay(10);
                drop.dropping = true;
            } else {
                if (drop.onLeave && drop.dropping) drop.onLeave.pass([this.el, this], drop).delay(10);
                drop.dropping = false;
            }
        }, this);
        return false;
    },

    checkAndDrag: function(evt){
        evt = evt || window.event;
        var distance = Math.round(Math.sqrt(Math.pow(evt.clientX - this.startX, 2)+Math.pow(evt.clientY - this.startY, 2)));
        if (distance > this.options.snapDistance){
            this.set(evt);
            this.options.onSnap.pass(this.el, this).delay(10);
            document.onmousemove = this.drag.bind(this);
            this.addStyles(-(this.startX-evt.clientX), -(this.startY-evt.clientY));
        }
        return false;
    },

    checkAgainst: function(drop){
        x = this.clientX+Window.getScrollLeft();
        y = this.clientY+Window.getScrollTop();
        drop = $(drop);
        var h = drop.offsetHeight;
        var w = drop.offsetWidth;
        var t = drop.getTop();
        var l = drop.getLeft();
        return $check((x > l && x < l+w && y < t+h && y > t));
    },

    end: function(){
        this.parent();
        this.options.droppables.each(function(drop){
            if (drop.onDrop && this.checkAgainst(drop)) drop.onDrop.pass([this.el, this], drop).delay(10);
        }, this);
    }

});

Element.extend({

    makeDraggable: function(options){
        return new Drag.Move(this, options);
    },

    makeResizable: function(options){
        return new Drag.Base(this, 'width', 'height', options);
    }

});

//Window.js : additional Window methods - depends on Moo.js + Function.js

var Window = {

    extend: Object.extend,

    getWidth: function(){
        return window.innerWidth || document.documentElement.clientWidth || 0;
    },

    getHeight: function(){
        return window.innerHeight || document.documentElement.clientHeight || 0;
    },

    getScrollHeight: function(){
        return document.documentElement.scrollHeight;
    },

    getScrollWidth: function(){
        return document.documentElement.scrollWidth;
    },

    getScrollTop: function(){
        return document.documentElement.scrollTop || window.pageYOffset || 0;
    },

    getScrollLeft: function(){
        return document.documentElement.scrollLeft || window.pageXOffset || 0;
    },

    onLoad: function(fn){
        if (!document.body) return Window.onLoad.pass(fn).delay(50);
        else return fn();
    }
};

//Cookie.js : Cookie creator. yummy! - depends on Moo.js + Function.js
//Credits: based on the functions by Peter-Paul Koch (http://quirksmode.org)

var Cookie = {

    set: function(key, value, duration){
        var date = new Date();
        date.setTime(date.getTime()+((duration || 365)*86400000));
        document.cookie = key+"="+value+"; expires="+date.toGMTString()+"; path=/";
    },

    get: function(key){
        var myValue, myVal;
        document.cookie.split(';').each(function(cookie){
            if(myVal = cookie.trim().test(key+'=(.*)')) myValue = myVal[1];
        });
        return myValue;
    },

    remove: function(key){
        this.set(key, '', -1);
    }

};

//Json.js - depends on Moo.js + Native Scripts

var Json = {
    toString: function(el){
        var string = [];

        var isArray = function(array){
            var string = [];
            array.each(function(ar){
                string.push(Json.toString(ar));
            });
            return string.join(',');
        };

        var isObject = function(object){
            var string = [];
            for (property in object) string.push('"'+property+'":'+Json.toString(object[property]));
            return string.join(',');
        };

        switch($type(el)){
            case 'string': string.push('"'+el+'"'); break;
            case 'function': string.push(el); break;
            case 'object': string.push('{'+isObject(el)+'}'); break;
            case 'array': string.push('['+isArray(el)+']');
        }

        return string.join(',');
    },

    evaluate: function(str){
        return eval('(' + str + ')');
    }
};

//Sortables.js : Make any list sortable. Depends on Moo.js + Native Scripts + DragDrop.js + Fx.js

var Sortables = new Class({

    setOptions: function(options) {
        this.options = {
            handles: false,
            fxDuration: 250,
            fxTransition: Fx.sinoidal,
            maxOpacity: 0.5
        };
        Object.extend(this.options, options || {});
    },

    initialize: function(elements, options){
        this.setOptions(options);
        this.options.handles = this.options.handles || elements;
        var trash = new Element('div').injectInside($(document.body));
        $A(elements).each(function(el, i){
            var copy = $(el).clone().setStyles({
                'position': 'absolute',
                'opacity': '0',
                'display': 'none'
            }).injectInside(trash);
            var elEffect = el.effect('opacity', {duration: this.options.fxDuration, wait: false, transition: this.options.fxTransition}).set(1);
            var copyEffects = copy.effects({
                duration: this.options.fxDuration,
                wait: false,
                transition: this.options.fxTransition,
                onComplete: function(){
                    copy.setStyle('display', 'none');
                }
            });
            var dragger = new Drag.Move(copy, {
                xModifier: false,
                onStart: function(){
                    copy.setHTML(el.innerHTML).setStyles({
                        'display': 'block',
                        'opacity': this.options.maxOpacity,
                        'top': el.getTop()+'px',
                        'left': el.getLeft()+'px'
                    });
                    elEffect.custom(elEffect.now, this.options.maxOpacity);
                }.bind(this),
                onComplete: function(){
                    copyEffects.custom({'opacity': [this.options.maxOpacity, 0], 'top': [copy.getTop(), el.getTop()]});
                    elEffect.custom(elEffect.now, 1);
                }.bind(this),
                onDrag: function(){
                    if ( el.getPrevious() && copy.getTop() < (el.getPrevious().getTop()) ) el.injectBefore(el.getPrevious());
                    else if ( el.getNext() && copy.getTop() > (el.getNext().getTop()) ) el.injectAfter(el.getNext());
                }
            });
            this.options.handles[i].onmousedown = dragger.start.bind(dragger);
        }, this);
    }

});

//FxPack.js - depends on Moo.js + Native Scripts + Fx.js

Fx.Styles = Fx.Base.extend({

    initialize: function(el, options){
        this.el = $(el);
        this.setOptions(options);
        this.now = {};
    },

    setNow: function(){
        for (p in this.from) this.now[p] = this.compute(this.from[p], this.to[p]);
    },

    custom: function(objFromTo){
        var from = {};
        var to = {};
        for (p in objFromTo){
            from[p] = objFromTo[p][0];
            to[p] = objFromTo[p][1];
        }
        return this.parent(from, to);
    },

    resizeTo: function(hto, wto){
        return this.custom({'height': [this.el.offsetHeight, hto], 'width': [this.el.offsetWidth, wto]});
    },

    resizeBy: function(hby, wby){
        return this.custom({'height': [this.el.offsetHeight, this.el.offsetHeight+hby], 'width': [this.el.offsetWidth, this.el.offsetWidth+wby]});
    },

    increase: function(){
        for (p in this.now) this.setStyle(this.el, p, this.now[p]);
    }

});

//fx.Color, originally by Tom Jensen (http://neuemusic.com) MIT-style LICENSE.

Fx.Color = Fx.Base.extend({

    initialize: function(el, property, options){
        this.el = $(el);
        this.setOptions(options);
        this.property = property.camelCase();
        this.now = [];
    },

    custom: function(from, to){
        return this.parent(from.hexToRgb(true), to.hexToRgb(true));
    },

    setNow: function(){
        [0,1,2].each(function(i){
            this.now[i] = Math.round(this.compute(this.from[i], this.to[i]));
        }, this);
    },

    increase: function(){
        this.el.setStyle(this.property, "rgb("+this.now[0]+","+this.now[1]+","+this.now[2]+")");
    },

    fromColor: function(color){
        return this.custom(color, this.el.getStyle(this.property));
    },

    toColor: function(color){
        return this.custom(this.el.getStyle(this.property), color);
    }

});

Element.extend({

    effects: function(options){
        return new Fx.Styles(this, options);
    }

});

//Easing Equations (c) 2003 Robert Penner, all rights reserved.
//This work is subject to the terms in http://www.robertpenner.com/easing_terms_of_use.html.

Fx.expoIn = function(pos){return Math.pow(2, 10 * (pos - 1))};
Fx.expoOut = function(pos){return (-Math.pow(2, -10 * pos) + 1)};

Fx.quadIn = function(pos){return Math.pow(pos, 2)};
Fx.quadOut = function(pos){return -(pos)*(pos-2)};

Fx.circOut = function(pos){return Math.sqrt(1 - Math.pow(pos-1,2))};
Fx.circIn = function(pos){return -(Math.sqrt(1 - Math.pow(pos, 2)) - 1)};

Fx.backIn = function(pos){return (pos)*pos*((2.7)*pos - 1.7)};
Fx.backOut = function(pos){return ((pos-1)*(pos-1)*((2.7)*(pos-1) + 1.7) + 1)};

Fx.sineOut = function(pos){return Math.sin(pos * (Math.PI/2))};
Fx.sineIn = function(pos){return -Math.cos(pos * (Math.PI/2)) + 1};
Fx.sineInOut = function(pos){return -(Math.cos(Math.PI*pos) - 1)/2};

//scriptaculous transitions
Fx.wobble = function(pos){return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5};
Fx.pulse = function(pos){return (Math.floor(pos*10) % 2 == 0 ? (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)))};

//Tips.js : Display a tip on any element with a title and/or href - depends on Moo.js + Native Scripts +  Fx.js
//Credits : Tips.js is based on Bubble Tooltips (http://web-graphics.com/mtarchive/001717.php) by Alessandro Fulcitiniti (http://web-graphics.com)

var Tips = new Class({

    setOptions: function(options){
        this.options = {
            transitionStart: fx.sinoidal,
            transitionEnd: fx.sinoidal,
            maxTitleChars: 30,
            fxDuration: 50,
            maxOpacity: 1,
            timeOut: 100,
            className: 'tooltip'
        }
        Object.extend(this.options, options || {});
    },

    initialize: function(elements, options){
        this.elements = elements;
        this.setOptions(options);
        this.toolTip = new Element('div').addClassName(this.options.className).setStyle('position', 'absolute').injectInside(document.body);
        this.toolTitle = new Element('H4').injectInside(this.toolTip);
        this.toolText = new Element('p').injectInside(this.toolTip);
        this.fx = new fx.Style(this.toolTip, 'opacity', {duration: this.options.fxDuration, wait: false}).hide();
        $A(elements).each(function(el){
            $(el).myText = $check(el.title);
            if (el.myText) el.removeAttribute('title');
            if (el.href){
                if (el.href.test('http://')) el.myTitle = el.href.replace('http://', '');
                if (el.href.length > this.options.maxTitleChars) el.myTitle = el.href.substr(0,this.options.maxTitleChars-3)+"...";
            }
            if (el.myText && el.myText.test('::')){
                var dual = el.myText.split('::');
                el.myTitle = dual[0].trim();
                el.myText = dual[1].trim();
            }
            el.onmouseover = function(){
                this.show(el);
                return false;
            }.bind(this);
            el.onmousemove = this.locate.bindAsEventListener(this);
            el.onmouseout = function(){
                this.timer = $clear(this.timer);
                this.disappear();
            }.bind(this);
        }, this);
    },

    show: function(el){
        this.toolTitle.innerHTML = el.myTitle;
        this.toolText.innerHTML = el.myText;
        this.timer = $clear(this.timer);
        this.fx.options.transition = this.options.transitionStart;
        this.timer = this.appear.delay(this.options.timeOut, this);
    },

    appear: function(){
        this.fx.custom(this.fx.now, this.options.maxOpacity);
    },

    locate: function(evt){
        var doc = document.documentElement;
        this.toolTip.setStyles({'top': evt.clientY + doc.scrollTop + 15 + 'px', 'left': evt.clientX + doc.scrollLeft - 30 + 'px'});
    },

    disappear: function(){
        this.fx.options.transition = this.options.transitionEnd;
        this.fx.custom(this.fx.now, 0);
    }

});

//Accordion.js - depends on Moo.js + Native Scripts + Fx.js

Fx.Elements = Fx.Base.extend({

    initialize: function(elements, options){
        this.elements = [];
        elements.each(function(el){
            this.elements.push($(el));
        }, this);
        this.setOptions(options);
        this.now = {};
    },

    setNow: function(){
        for (i in this.from){
            var iFrom = this.from[i];
            var iTo = this.to[i];
            var iNow = this.now[i] = {};
            for (p in iFrom) iNow[p] = this.compute(iFrom[p], iTo[p]);
        }
    },

    custom: function(objObjs){
        var from = {};
        var to = {};
        for (i in objObjs){
            var iProps = objObjs[i];
            var iFrom = from[i] = {};
            var iTo = to[i] = {};
            for (prop in iProps){
                iFrom[prop] = iProps[prop][0];
                iTo[prop] = iProps[prop][1];
            }
        }
        return this.parent(from, to);
    },

    increase: function(){
        for (i in this.now){
            var iNow = this.now[i];
            for (p in iNow) this.setStyle(this.elements[parseInt(i)-1], p, iNow[p]);
        }
    }

});

Fx.Accordion = Fx.Elements.extend({

    extendOptions: function(options){
        Object.extend(this.options, Object.extend({
            start: 'open-first',
            fixedHeight: false,
            fixedWidth: false,
            alwaysHide: false,
            wait: false,
            onActive: Class.empty,
            onBackground: Class.empty,
            height: true,
            opacity: true,
            width: false
        }, options || {}));
    },

    initialize: function(togglers, elements, options){
        this.parent(elements, options);
        this.extendOptions(options);
        this.previousClick = 'nan';
        togglers.each(function(tog, i){
            $(tog).addEvent('click', function(){this.showThisHideOpen(i)}.bind(this));
        }, this);
        this.togglers = togglers;
        this.h = {}; this.w = {}; this.o = {};
        this.elements.each(function(el, i){
            this.now[i+1] = {};
            $(el).setStyles({'height': 0, 'overflow': 'hidden'});
        }, this);
        switch(this.options.start){
            case 'first-open': this.elements[0].setStyle('height', this.elements[0].scrollHeight); break;
            case 'open-first': this.showThisHideOpen(0); break;
        }
    },

    hideThis: function(i){
        if (this.options.height) this.h = {'height': [this.elements[i].offsetHeight, 0]};
        if (this.options.width) this.w = {'width': [this.elements[i].offsetWidth, 0]};
        if (this.options.opacity) this.o = {'opacity': [this.now[i+1]['opacity'] || 1, 0]};
    },

    showThis: function(i){
        if (this.options.height) this.h = {'height': [this.elements[i].offsetHeight, this.options.fixedHeight || this.elements[i].scrollHeight]};
        if (this.options.width) this.w = {'width': [this.elements[i].offsetWidth, this.options.fixedWidth || this.elements[i].scrollWidth]};
        if (this.options.opacity) this.o = {'opacity': [this.now[i+1]['opacity'] || 0, 1]};
    },

    showThisHideOpen: function(iToShow){
        if (iToShow != this.previousClick || this.options.alwaysHide){
            this.previousClick = iToShow;
            var objObjs = {};
            var err = false;
            var madeInactive = false;
            this.elements.each(function(el, i){
                this.now[i] = this.now[i] || {};
                if (i != iToShow){
                    this.hideThis(i);
                } else if (this.options.alwaysHide){
                    if (el.offsetHeight == el.scrollHeight){
                        this.hideThis(i);
                        madeInactive = true;
                    } else if (el.offsetHeight == 0){
                        this.showThis(i);
                    } else {
                        err = true;
                    }
                } else if (this.options.wait && this.timer){
                    this.previousClick = 'nan';
                    err = true;
                } else {
                    this.showThis(i);
                }
                objObjs[i+1] = Object.extend(this.h, Object.extend(this.o, this.w));
            }, this);
            if (err) return;
            if (!madeInactive) this.options.onActive.call(this, this.togglers[iToShow]);
            this.togglers.each(function(tog, i){
                if (i != iToShow || madeInactive) this.options.onBackground.call(this, tog);
            }, this);
            return this.custom(objObjs);
        }
    }

});
