﻿//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: Log.js
Provides basic logging functionality for plugins to implement.

License:
MIT-style license.

Authors:
Guillermo Rauch
*/

var Log = new Class({

    log: function() {
        Log.logger.call(this, arguments);
    }

});

Log.logged = [];

Log.logger = function() {
    if (window.console && console.log) console.log.apply(console, arguments);
    else Log.logged.push(arguments);
};

/*
Script: Class.Refactor.js
Extends a class onto itself with new property, preserving any items attached to the class's namespace.

License:
MIT-style license.

Authors:
Aaron Newton
*/

Class.refactor = function(original, refactors) {

    $each(refactors, function(item, name) {
        var origin = original.prototype[name];
        if (origin && (origin = origin._origin) && typeof item == 'function') original.implement(name, function() {
            var old = this.previous;
            this.previous = origin;
            var value = item.apply(this, arguments);
            this.previous = old;
            return value;
        }); else original.implement(name, item);
    });

    return original;

};

/*
Script: Class.Binds.js
Automagically binds specified methods in a class to the instance of the class.

License:
MIT-style license.

Authors:
Aaron Newton
*/

Class.Mutators.Binds = function(binds) {
    return binds;
};

Class.Mutators.initialize = function(initialize) {
    return function() {
        $splat(this.Binds).each(function(name) {
            var original = this[name];
            if (original) this[name] = original.bind(this);
        }, this);
        return initialize.apply(this, arguments);
    };
};

/*
Script: Class.Occlude.js
Prevents a class from being applied to a DOM element twice.

License:
MIT-style license.

Authors:
Aaron Newton
*/

Class.Occlude = new Class({

    occlude: function(property, element) {
        element = $(element || this.element);
        var instance = element.retrieve(property || this.property);
        if (instance && !$defined(this.occluded)) {
            this.occluded = instance;
        } else {
            this.occluded = false;
            element.store(property || this.property, this);
        }
        return this.occluded;
    }

});

/*
Script: Chain.Wait.js
Adds a method to inject pauses between chained events.

License:
MIT-style license.

Authors:
Aaron Newton
*/

(function() {

    var wait = {
        wait: function(duration) {
            return this.chain(function() {
                this.callChain.delay($pick(duration, 500), this);
            } .bind(this));
        }
    };

    Chain.implement(wait);

    if (window.Fx) {
        Fx.implement(wait);
        ['Css', 'Tween', 'Elements'].each(function(cls) {
            if (Fx[cls]) Fx[cls].implement(wait);
        });
    }

    try {
        Element.implement({
            chains: function(effects) {
                $splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect) {
                    effect = this.get(effect);
                    if (!effect) return;
                    effect.setOptions({
                        link: 'chain'
                    });
                }, this);
                return this;
            },
            pauseFx: function(duration, effect) {
                this.chains(effect).get($pick(effect, 'tween')).wait(duration);
                return this;
            }
        });
    } catch (e) { }

})();

/*
Script: Array.Extras.js
Extends the Array native object to include useful methods to work with arrays.

License:
MIT-style license.

Authors:
Christoph Pojer

*/
Array.implement({

    min: function() {
        return Math.min.apply(null, this);
    },

    max: function() {
        return Math.max.apply(null, this);
    },

    average: function() {
        return this.length ? this.sum() / this.length : 0;
    },

    sum: function() {
        var result = 0, l = this.length;
        if (l) {
            do {
                result += this[--l];
            } while (l);
        }
        return result;
    },

    unique: function() {
        return [].combine(this);
    }

});

/*
Script: Date.js
Extends the Date native object to include methods useful in managing dates.

License:
MIT-style license.

Authors:
Aaron Newton
Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
Harald Kirshner - mail [at] digitarald.de; http://digitarald.de

*/

(function() {

    new Native({ name: 'Date', initialize: Date, protect: true });

    ['now', 'parse', 'UTC'].each(function(method) {
        Native.genericize(Date, method, true);
    });

    Date.Methods = {};

    ['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
	'AMPM', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds'].each(function(method) {
	    Date.Methods[method.toLowerCase()] = method;
	});

    $each({
        ms: 'Milliseconds',
        year: 'FullYear',
        min: 'Minutes',
        mo: 'Month',
        sec: 'Seconds',
        hr: 'Hours'
    }, function(value, key) {
        Date.Methods[key] = value;
    });

    var zeroize = function(what, length) {
        return '0'.repeat(length - what.toString().length) + what;
    };

    Date.implement({

        set: function(prop, value) {
            switch ($type(prop)) {
                case 'object':
                    for (var p in prop) this.set(p, prop[p]);
                    break;
                case 'string':
                    prop = prop.toLowerCase();
                    var m = Date.Methods;
                    if (m[prop]) this['set' + m[prop]](value);
            }
            return this;
        },

        get: function(key) {
            key = key.toLowerCase();
            var m = Date.Methods;
            if (m[key]) return this['get' + m[key]]();
            return null;
        },

        clone: function() {
            return new Date(this.get('time'));
        },

        increment: function(interval, times) {
            return this.multiply(interval, times);
        },

        decrement: function(interval, times) {
            return this.multiply(interval, times, false);
        },

        multiply: function(interval, times, increment) {
            interval = interval || 'day';
            times = $pick(times, 1);
            increment = $pick(increment, true);
            var multiplier = increment ? 1 : -1;
            var month = this.format('%m').toInt() - 1;
            var year = this.format('%Y').toInt();
            var time = this.get('time');
            var offset = 0;
            switch (interval) {
                case 'year':
                    times.times(function(val) {
                        if (Date.isLeapYear(year + val) && month > 1 && multiplier > 0) val++;
                        if (Date.isLeapYear(year + val) && month <= 1 && multiplier < 0) val--;
                        offset += Date.units.year(year + val);
                    });
                    break;
                case 'month':
                    times.times(function(val) {
                        if (multiplier < 0) val++;
                        var mo = month + (val * multiplier);
                        var year = year;
                        if (mo < 0) {
                            year--;
                            mo = 12 + mo;
                        }
                        if (mo > 11 || mo < 0) {
                            year += (mo / 12).toInt() * multiplier;
                            mo = mo % 12;
                        }
                        offset += Date.units.month(mo, year);
                    });
                    break;
                case 'day':
                    return this.set('date', this.get('date') + (multiplier * times));
                default:
                    offset = Date.units[interval]() * times;
                    break;
            }
            this.set('time', time + (offset * multiplier));
            return this;
        },

        isLeapYear: function() {
            return Date.isLeapYear(this.get('year'));
        },

        clearTime: function() {
            ['hr', 'min', 'sec', 'ms'].each(function(t) {
                this.set(t, 0);
            }, this);
            return this;
        },

        diff: function(d, resolution) {
            resolution = resolution || 'day';
            if ($type(d) == 'string') d = Date.parse(d);
            switch (resolution) {
                case 'year':
                    return d.format('%Y').toInt() - this.format('%Y').toInt();
                    break;
                case 'month':
                    var months = (d.format('%Y').toInt() - this.format('%Y').toInt()) * 12;
                    return months + d.format('%m').toInt() - this.format('%m').toInt();
                    break;
                default:
                    var diff = d.get('time') - this.get('time');
                    if (diff < 0 && Date.units[resolution]() > (-1 * (diff))) return 0;
                    else if (diff >= 0 && diff < Date.units[resolution]()) return 0;
                    return ((d.get('time') - this.get('time')) / Date.units[resolution]()).round();
            }
            return null;
        },

        getWeek: function() {
            var day = (new Date(this.get('year'), 0, 1)).get('date');
            return Math.round((this.get('dayofyear') + (day > 3 ? day - 4 : day + 3)) / 7);
        },

        getTimezone: function() {
            return this.toString()
			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
        },

        getGMTOffset: function() {
            var off = this.get('timezoneOffset');
            return ((off > 0) ? '-' : ' + ')
			+ zeroize(Math.floor(Math.abs(off) / 60), 2)
			+ zeroize(off % 60, 2);
        },

        parse: function(str) {
            this.set('time', Date.parse(str));
            return this;
        },

        isValid: function(date) {
            return !!(date || this).valueOf();
        },

        format: function(f) {
            if (!this.isValid()) return 'invalid date';
            f = f || '%x %X';
            //replace short-hand with actual format
            f = ({
                db: '%Y-%m-%d %H:%M:%S',
                compact: '%Y%m%dT%H%M%S',
                iso8601: '%Y-%m-%dT%H:%M:%S%T',
                rfc822: '%a, %d %b %Y %H:%M:%S %Z',
                'short': '%d %b %H:%M',
                'long': '%B %d, %Y %H:%M'
            })[f.toLowerCase()] || f;
            var d = this;
            return f.replace(/\%([aAbBcdHIjmMpSUWwxXyYTZ\%])/g,
			function($1, $2) {
			    switch ($2) {
			        case 'a': return Date.getMsg('days')[d.get('day')].substr(0, 3);
			        case 'A': return Date.getMsg('days')[d.get('day')];
			        case 'b': return Date.getMsg('months')[d.get('month')].substr(0, 3);
			        case 'B': return Date.getMsg('months')[d.get('month')];
			        case 'c': return d.toString();
			        case 'd': return zeroize(d.get('date'), 2);
			        case 'H': return zeroize(d.get('hr'), 2);
			        case 'I': return ((d.get('hr') % 12) || 12);
			        case 'j': return zeroize(d.get('dayofyear'), 3);
			        case 'm': return zeroize((d.get('mo') + 1), 2);
			        case 'M': return zeroize(d.get('min'), 2);
			        case 'p': return Date.getMsg(d.get('hr') < 12 ? 'AM' : 'PM');
			        case 'S': return zeroize(d.get('seconds'), 2);
			        case 'U': return zeroize(d.get('week'), 2);
			        case 'W': throw new Error('%W is not supported yet');
			        case 'w': return d.get('day');
			        case 'x': return d.format(Date.getMsg('shortDate'));
			        case 'X': return d.format(Date.getMsg('shortTime'));
			        case 'y': return d.get('year').toString().substr(2);
			        case 'Y': return d.get('year');
			        case 'T': return d.get('GMTOffset');
			        case 'Z': return d.get('Timezone');
			        case '%': return '%';
			    }
			    return $2;
			}
		);
        },

        setAMPM: function(ampm) {
            ampm = ampm.toUpperCase();
            if (this.format('%H').toInt() > 11 && ampm == 'AM')
                return this.decrement('hour', 12);
            else if (this.format('%H').toInt() < 12 && ampm == 'PM')
                return this.increment('hour', 12);
            return this;
        }

    });

    Date.alias('diff', 'compare');
    Date.alias('format', 'strftime');

    var nativeParse = Date.parse;

    var daysInMonth = function(monthIndex, year) {
        if (Date.isLeapYear(year.toInt()) && monthIndex === 1) return 29;
        return [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][monthIndex];
    };


    $extend(Date, {

        getMsg: function(key, args) {
            return MooTools.lang.get('Date', key, args);
        },

        units: {
            ms: $lambda(1),
            second: $lambda(1000),
            minute: $lambda(60000),
            hour: $lambda(3600000),
            day: $lambda(86400000),
            week: $lambda(608400000),
            month: function(monthIndex, year) {
                var d = new Date();
                return daysInMonth($pick(monthIndex, d.format('%m').toInt()), $pick(year, d.format('%Y').toInt())) * 86400000;
            },
            year: function(year) {
                year = year || new Date().format('%Y').toInt();
                return Date.isLeapYear(year.toInt()) ? 31622400000 : 31536000000;
            }
        },

        isLeapYear: function(yr) {
            return new Date(yr, 1, 29).getDate() == 29;
        },

        fixY2K: function(d) {
            if (!isNaN(d)) {
                var newDate = new Date(d);
                if (newDate.get('year') < 2000 && d.toString().indexOf(newDate.get('year')) < 0) newDate.increment('year', 100);
                return newDate;
            } else {
                return d;
            }
        },

        parse: function(from) {
            var t = $type(from);
            if (t == 'number') return new Date(from);
            if (t != 'string') return from;
            if (!from.length) return null;
            var parsed;
            Date.parsePatterns.each(function(pattern, i) {
                if (parsed) return;
                var r = pattern.re.exec(from);
                if (r) parsed = pattern.handler(r);
            });
            return parsed || new Date(nativeParse(from));
        },

        parseDay: function(day, num) {
            var ret = -1;
            switch ($type(day)) {
                case 'number':
                    ret = Date.getMsg('days')[day - 1] || false;
                    if (!ret) throw new Error('Invalid day index value must be between 1 and 7');
                    break;
                case 'string':
                    var match = Date.getMsg('days').filter(function(name) {
                        return this.test(name);
                    }, new RegExp('^' + day, 'i'));
                    if (!match.length) throw new Error('Invalid day string');
                    if (match.length > 1) throw new Error('Ambiguous day');
                    ret = match[0];
            }
            return (num) ? Date.getMsg('days').indexOf(ret) : ret;
        },

        parseMonth: function(month, num) {
            var ret = -1;
            switch ($type(month)) {
                case 'object':
                    ret = Date.getMsg('months')[month.get('mo')];
                    break;
                case 'number':
                    ret = Date.getMsg('months')[month - 1] || false;
                    if (!ret) throw new Error('Invalid month index value must be between 1 and 12:' + index);
                    break;
                case 'string':
                    var match = Date.getMsg('months').filter(function(name) {
                        return this.test(name);
                    }, new RegExp('^' + month, 'i'));
                    if (!match.length) throw new Error('Invalid month string');
                    if (match.length > 1) throw new Error('Ambiguous month');
                    ret = match[0];
            }
            return (num) ? Date.getMsg('months').indexOf(ret) : ret;
        },

        parseUTC: function(value) {
            var localDate = new Date(value);
            var utcSeconds = Date.UTC(localDate.get('year'), localDate.get('mo'),
		localDate.get('date'), localDate.get('hr'), localDate.get('min'), localDate.get('sec'));
            return new Date(utcSeconds);
        },

        orderIndex: function(unit) {
            return Date.getMsg('dateOrder').indexOf(unit) + 1;
        },

        parsePatterns: [
		{
		    //"1999-12-31"
		    re: /^(\d{4})[\.\-\/](\d{1,2})[\.\-\/](\d{1,2})$/,
		    handler: function(bits) {
		        return new Date(bits[1], bits[2] - 1, bits[3]);
		    }
		},
		{
		    //"1999-12-31 23:59:59"
		    re: /^(\d{4})[\.\-\/](\d{1,2})[\.\-\/](\d{1,2})\s(\d{1,2}):(\d{1,2})(?:\:(\d{1,2}))?(\w{2})?$/,
		    handler: function(bits) {
		        var d = new Date(bits[1], bits[2] - 1, bits[3]);
		        d.set('hr', bits[4]);
		        d.set('min', bits[5]);
		        d.set('sec', bits[6] || 0);
		        if (bits[7]) d.set('ampm', bits[7]);
		        return d;
		    }
		},
		{
		    //"12.31.08", "12-31-08", "12/31/08", "12.31.2008", "12-31-2008", "12/31/2008"
		    re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})$/,
		    handler: function(bits) {
		        var d = new Date(bits[Date.orderIndex('year')],
								 bits[Date.orderIndex('month')] - 1,
								 bits[Date.orderIndex('date')]);
		        return Date.fixY2K(d);
		    }
		},
        //"12.31.08", "12-31-08", "12/31/08", "12.31.2008", "12-31-2008", "12/31/2008"
        //above plus "10:45pm" ex: 12.31.08 10:45pm
		{
		re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2})[:\.](\d{1,2})(?:[\:\.](\d{1,2}))?(\w{2})?$/,
		handler: function(bits) {
		    var d = new Date(bits[Date.orderIndex('year')],
								 bits[Date.orderIndex('month')] - 1,
								 bits[Date.orderIndex('date')]);
		    d.set('hr', bits[4]);
		    d.set('min', bits[5]);
		    d.set('sec', bits[6] || 0);
		    if (bits[7]) d.set('ampm', bits[7]);
		    return Date.fixY2K(d);
		}
}
	]

    });

})();

/*
Script: Date.Extras.js
Extends the Date native object to include extra methods (on top of those in Date.js).

License:
MIT-style license.

Authors:
Aaron Newton

*/

['LastDayOfMonth', 'Ordinal'].each(function(method) {
    Date.Methods[method.toLowerCase()] = method;
});


Date.implement({

    timeDiffInWords: function(relative_to) {
        return Date.distanceOfTimeInWords(this, relative_to || new Date);
    },

    getOrdinal: function(dayOfMonth) {
        return Date.getMsg('ordinal', dayOfMonth || this.get('date'));
    },

    getDayOfYear: function() {
        return ((Date.UTC(this.getFullYear(), this.getMonth(), this.getDate() + 1, 0, 0, 0)
			- Date.UTC(this.getFullYear(), 0, 1, 0, 0, 0)) / Date.units.day());
    },

    getLastDayOfMonth: function() {
        var ret = this.clone();
        ret.setMonth(ret.getMonth() + 1, 0);
        return ret.getDate();
    }

});

Date.alias('timeDiffInWords', 'timeAgoInWords');

$extend(Date, {

    distanceOfTimeInWords: function(fromTime, toTime) {
        return this.getTimePhrase(((toTime.getTime() - fromTime.getTime()) / 1000).toInt(), fromTime, toTime);
    },

    getTimePhrase: function(delta, fromTime, toTime) {
        var getPhrase = function() {
            var suffix;
            if (delta >= 0) {
                suffix = 'Ago';
            } else {
                delta = delta * -1;
                suffix = 'Until';
            }
            if (delta < 60) {
                return Date.getMsg('lessThanMinute' + suffix, delta);
            } else if (delta < 120) {
                return Date.getMsg('minute' + suffix, delta);
            } else if (delta < (45 * 60)) {
                delta = (delta / 60).round();
                return Date.getMsg('minutes' + suffix, delta);
            } else if (delta < (90 * 60)) {
                return Date.getMsg('hour' + suffix, delta);
            } else if (delta < (24 * 60 * 60)) {
                delta = (delta / 3600).round();
                return Date.getMsg('hours' + suffix, delta);
            } else if (delta < (48 * 60 * 60)) {
                return Date.getMsg('day' + suffix, delta);
            } else {
                delta = (delta / 86400).round();
                return Date.getMsg('days' + suffix, delta);
            }
        };
        return getPhrase().substitute({ delta: delta });
    }

});


Date.parsePatterns.extend([

	{
	    // yyyy-mm-ddTHH:MM:SS-0500 (ISO8601) i.e.2007-04-17T23:15:22Z
	    // inspired by: http://delete.me.uk/2005/03/iso8601.html
	    re: /^(\d{4})(?:-?(\d{2})(?:-?(\d{2})(?:[T ](\d{2})(?::?(\d{2})(?::?(\d{2})(?:\.(\d+))?)?)?(?:Z|(?:([-+])(\d{2})(?::?(\d{2}))?)?)?)?)?)?$/,
	    handler: function(bits) {
	        var offset = 0;
	        var d = new Date(bits[1], 0, 1);
	        if (bits[3]) d.set('date', bits[3]);
	        if (bits[2]) d.set('mo', bits[2] - 1);
	        if (bits[4]) d.set('hr', bits[4]);
	        if (bits[5]) d.set('min', bits[5]);
	        if (bits[6]) d.set('sec', bits[6]);
	        if (bits[7]) d.set('ms', ('0.' + bits[7]).toInt() * 1000);
	        if (bits[9]) {
	            offset = (bits[9].toInt() * 60) + bits[10].toInt();
	            offset *= ((bits[8] == '-') ? 1 : -1);
	        }
	        //offset -= d.getTimezoneOffset();
	        d.setTime((d * 1) + (offset * 60 * 1000).toInt());
	        return d;
	    }
	},

	{
	    //"today"
	    re: /^tod/i,
	    handler: function() {
	        return new Date();
	    }
	},

	{
	    //"tomorow"
	    re: /^tom/i,
	    handler: function() {
	        return new Date().increment();
	    }
	},

	{
	    //"yesterday"
	    re: /^yes/i,
	    handler: function() {
	        return new Date().decrement();
	    }
	},

	{
	    //4th, 23rd
	    re: /^(\d{1,2})(st|nd|rd|th)?$/i,
	    handler: function(bits) {
	        var d = new Date();
	        d.set('date', bits[1].toInt());
	        return d;
	    }
	},

	{
	    //4th Jan, 23rd May
	    re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i,
	    handler: function(bits) {
	        var d = new Date();
	        d.set('mo', Date.parseMonth(bits[2], true), bits[1].toInt());
	        return d;
	    }
	},

	{
	    //4th Jan 2000, 23rd May 2004
	    re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
	    handler: function(bits) {
	        var d = new Date();
	        d.set('mo', Date.parseMonth(bits[2], true), bits[1].toInt());
	        d.setYear(bits[3]);
	        return d;
	    }
	},

	{
	    //Jan 4th
	    re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
	    handler: function(bits) {
	        var d = new Date();
	        d.set('mo', Date.parseMonth(bits[1], true), bits[2].toInt());
	        d.setYear(bits[3]);
	        return d;
	    }
	},

	{
	    //Jan 4th 2003
	    re: /^next (\w+)$/i,
	    handler: function(bits) {
	        var d = new Date();
	        var day = d.getDay();
	        var newDay = Date.parseDay(bits[1], true);
	        var addDays = newDay - day;
	        if (newDay <= day) {
	            addDays += 7;
	        }
	        d.set('date', d.getDate() + addDays);
	        return d;
	    }
	},

	{
	    //4 May 08:12
	    re: /^\d+\s[a-zA-z]..\s\d.\:\d.$/,
	    handler: function(bits) {
	        var d = new Date();
	        bits = bits[0].split(' ');
	        d.set('date', bits[0]);
	        var m;
	        Date.getMsg('months').each(function(mo, i) {
	            if (new RegExp('^' + bits[1]).test(mo)) m = i;
	        });
	        d.set('mo', m);
	        d.set('hr', bits[2].split(':')[0]);
	        d.set('min', bits[2].split(':')[1]);
	        d.set('ms', 0);
	        return d;
	    }
	},

	{
	    re: /^last (\w+)$/i,
	    handler: function(bits) {
	        return Date.parse('next ' + bits[0]).decrement('day', 7);
	    }
	}

]);

/*
Script: Hash.Extras.js
Extends the Hash native object to include getFromPath which allows a path notation to child elements.

License:
MIT-style license.

Authors:
Aaron Newton
*/

Hash.implement({

    getFromPath: function(notation) {
        var source = this.getClean();
        notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match) {
            if (!source) return null;
            var prop = arguments[2] || arguments[1] || arguments[0];
            source = (prop in source) ? source[prop] : null;
            return match;
        });
        return source;
    },

    cleanValues: function(method) {
        method = method || $defined;
        this.each(function(v, k) {
            if (!method(v)) this.erase(k);
        }, this);
        return this;
    },

    run: function() {
        var args = arguments;
        this.each(function(v, k) {
            if ($type(v) == 'function') v.run(args);
        });
    }

});

/*
Script: String.Extras.js
Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).

License:
MIT-style license.

Authors:
Aaron Newton
Guillermo Rauch

*/

(function() {

    var special = ['À', 'à', 'Á', 'á', 'Â', 'â', 'Ã', 'ã', 'Ä', 'ä', 'Å', 'å', 'Ă', 'ă', 'Ą', 'ą', 'Ć', 'ć', 'Č', 'č', 'Ç', 'ç', 'Ď', 'ď', 'Đ', 'đ', 'È', 'è', 'É', 'é', 'Ê', 'ê', 'Ë', 'ë', 'Ě', 'ě', 'Ę', 'ę', 'Ğ', 'ğ', 'Ì', 'ì', 'Í', 'í', 'Î', 'î', 'Ï', 'ï', 'Ĺ', 'ĺ', 'Ľ', 'ľ', 'Ł', 'ł', 'Ñ', 'ñ', 'Ň', 'ň', 'Ń', 'ń', 'Ò', 'ò', 'Ó', 'ó', 'Ô', 'ô', 'Õ', 'õ', 'Ö', 'ö', 'Ø', 'ø', 'ő', 'Ř', 'ř', 'Ŕ', 'ŕ', 'Š', 'š', 'Ş', 'ş', 'Ś', 'ś', 'Ť', 'ť', 'Ť', 'ť', 'Ţ', 'ţ', 'Ù', 'ù', 'Ú', 'ú', 'Û', 'û', 'Ü', 'ü', 'Ů', 'ů', 'Ÿ', 'ÿ', 'ý', 'Ý', 'Ž', 'ž', 'Ź', 'ź', 'Ż', 'ż', 'Þ', 'þ', 'Ð', 'ð', 'ß', 'Œ', 'œ', 'Æ', 'æ', 'µ'];

    var standard = ['A', 'a', 'A', 'a', 'A', 'a', 'A', 'a', 'Ae', 'ae', 'A', 'a', 'A', 'a', 'A', 'a', 'C', 'c', 'C', 'c', 'C', 'c', 'D', 'd', 'D', 'd', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'G', 'g', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'L', 'l', 'L', 'l', 'L', 'l', 'N', 'n', 'N', 'n', 'N', 'n', 'O', 'o', 'O', 'o', 'O', 'o', 'O', 'o', 'Oe', 'oe', 'O', 'o', 'o', 'R', 'r', 'R', 'r', 'S', 's', 'S', 's', 'S', 's', 'T', 't', 'T', 't', 'T', 't', 'U', 'u', 'U', 'u', 'U', 'u', 'Ue', 'ue', 'U', 'u', 'Y', 'y', 'Y', 'y', 'Z', 'z', 'Z', 'z', 'Z', 'z', 'TH', 'th', 'DH', 'dh', 'ss', 'OE', 'oe', 'AE', 'ae', 'u'];

    var tidymap = {
        "[\xa0\u2002\u2003\u2009]": " ",
        "\xb7": "*",
        "[\u2018\u2019]": "'",
        "[\u201c\u201d]": '"',
        "\u2026": "...",
        "\u2013": "-",
        "\u2014": "--",
        "\uFFFD": "&raquo;"
    };

    String.implement({

        standardize: function() {
            var text = this;
            special.each(function(ch, i) {
                text = text.replace(new RegExp(ch, 'g'), standard[i]);
            });
            return text;
        },

        repeat: function(times) {
            return new Array(times + 1).join(this);
        },

        pad: function(length, str, dir) {
            if (this.length >= length) return this;
            str = str || ' ';
            var pad = str.repeat(length - this.length).substr(0, length - this.length);
            if (!dir || dir == 'right') return this + pad;
            if (dir == 'left') return pad + this;
            return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
        },

        stripTags: function() {
            return this.replace(/<\/?[^>]+>/gi, '');
        },

        tidy: function() {
            var txt = this.toString();
            $each(tidymap, function(value, key) {
                txt = txt.replace(new RegExp(key, 'g'), value);
            });
            return txt;
        }

    });

})();

/*
Script: String.QueryString.js
...

License:
MIT-style license.

Authors:
Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti
*/

String.implement({

    parseQueryString: function() {
        var vars = this.split(/[&;]/), res = {};
        if (vars.length) vars.each(function(val) {
            var index = val.indexOf('='),
				keys = index < 0 ? [''] : val.substr(0, index).match(/[^\]\[]+/g),
				value = decodeURIComponent(val.substr(index + 1)),
				obj = res;
            keys.each(function(key, i) {
                var current = obj[key];
                if (i < keys.length - 1)
                    obj = obj[key] = current || {};
                else if ($type(current) == 'array')
                    current.push(value);
                else
                    obj[key] = $defined(current) ? [current, value] : value;
            });
        });
        return res;
    },

    cleanQueryString: function(method) {
        return this.split('&').filter(function(val) {
            var index = val.indexOf('='),
			key = index < 0 ? '' : val.substr(0, index),
			value = val.substr(index + 1);
            return method ? method.run([key, value]) : $chk(value);
        }).join('&');
    }

});

/*
Script: URI.js
Provides methods useful in managing the window location and uris.

License:
MIT-style license.

Authors:
Sebastian Markb�ge, Aaron Newton
*/

var URI = new Class({

    Implements: Options,

    /*
    options: {
    base: false
    },
    */

    regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
    parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
    schemes: { http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0 },

    initialize: function(uri, options) {
        this.setOptions(options);
        var base = this.options.base || URI.base;
        uri = uri || base;
        if (uri && uri.parsed)
            this.parsed = $unlink(uri.parsed);
        else
            this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
    },

    parse: function(value, base) {
        var bits = value.match(this.regex);
        if (!bits) return false;
        bits.shift();
        return this.merge(bits.associate(this.parts), base);
    },

    merge: function(bits, base) {
        if (!bits.scheme && !base.scheme) return false;
        if (base) {
            this.parts.every(function(part) {
                if (bits[part]) return false;
                bits[part] = base[part] || '';
                return true;
            });
        }
        bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
        bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
        return bits;
    },

    parseDirectory: function(directory, baseDirectory) {
        directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
        if (!directory.test(URI.regs.directoryDot)) return directory;
        var result = [];
        directory.replace(URI.regs.endSlash, '').split('/').each(function(dir) {
            if (dir == '..' && result.length > 0) result.pop();
            else if (dir != '.') result.push(dir);
        });
        return result.join('/') + '/';
    },

    combine: function(bits) {
        return bits.value || bits.scheme + '://' +
			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
			(bits.directory || '/') + (bits.file || '') +
			(bits.query ? '?' + bits.query : '') +
			(bits.fragment ? '#' + bits.fragment : '');
    },

    set: function(part, value, base) {
        if (part == 'value') {
            var scheme = value.match(URI.regs.scheme);
            if (scheme) scheme = scheme[1];
            if (scheme && !$defined(this.schemes[scheme.toLowerCase()])) this.parsed = { scheme: scheme, value: value };
            else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value} : { value: value });
        } else {
            this.parsed[part] = value;
        }
        return this;
    },

    get: function(part, base) {
        switch (part) {
            case 'value': return this.combine(this.parsed, base ? base.parsed : false);
            case 'data': return this.getData();
        }
        return this.parsed[part] || undefined;
    },

    go: function() {
        document.location.href = this.toString();
    },

    toURI: function() {
        return this;
    },

    getData: function(key, part) {
        var qs = this.get(part || 'query');
        if (!$chk(qs)) return key ? null : {};
        var obj = qs.parseQueryString();
        return key ? obj[key] : obj;
    },

    setData: function(values, merge, part) {
        if ($type(arguments[0]) == 'string') {
            values = this.getData();
            values[arguments[0]] = arguments[1];
        } else if (merge) {
            values = $merge(this.getData(), values);
        }
        return this.set(part || 'query', Hash.toQueryString(values));
    },

    clearData: function(part) {
        return this.set(part || 'query', '');
    }

});

['toString', 'valueOf'].each(function(method) {
    URI.prototype[method] = function() {
        return this.get('value');
    };
});


URI.regs = {
    endSlash: /\/$/,
    scheme: /^(\w+):/,
    directoryDot: /\.\/|\.$/
};

URI.base = new URI($$('base[href]').getLast(), { base: document.location });

String.implement({

    toURI: function(options) { return new URI(this, options); }

});

/*
Script: URI.Relative.js
Extends the URI class to add methods for computing relative and absolute urls.

License:
MIT-style license.

Authors:
Sebastian Markbåge
*/

URI = Class.refactor(URI, {

    combine: function(bits, base) {
        if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
            return this.previous.apply(this, arguments);
        var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');

        if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;

        var baseDir = base.directory.split('/'),
			relDir = bits.directory.split('/'),
			path = '',
			offset;

        var i = 0;
        for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
        for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
        for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';

        return (path || (bits.file ? '' : './')) + end;
    },

    toAbsolute: function(base) {
        base = new URI(base);
        if (base) base.set('directory', '').set('file', '');
        return this.toRelative(base);
    },

    toRelative: function(base) {
        return this.get('value', new URI(base));
    }

});

/*
Script: Element.Forms.js
Extends the Element native object to include methods useful in managing inputs.

License:
MIT-style license.

Authors:
Aaron Newton

*/
Element.implement({

    tidy: function() {
        this.set('value', this.get('value').tidy());
    },

    getTextInRange: function(start, end) {
        return this.get('value').substring(start, end);
    },

    getSelectedText: function() {
        if (document.selection && document.selection.createRange) return document.selection.createRange().text;
        return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
    },

    getSelectedRange: function() {
        if ($defined(this.selectionStart)) return { start: this.selectionStart, end: this.selectionEnd };
        var pos = { start: 0, end: 0 };
        var range = this.getDocument().selection.createRange();
        if (!range || range.parentElement() != this) return pos;
        var dup = range.duplicate();
        if (this.type == 'text') {
            pos.start = 0 - dup.moveStart('character', -100000);
            pos.end = pos.start + range.text.length;
        } else {
            var value = this.get('value');
            var offset = value.length - value.match(/[\n\r]*$/)[0].length;
            dup.moveToElementText(this);
            dup.setEndPoint('StartToEnd', range);
            pos.end = offset - dup.text.length;
            dup.setEndPoint('StartToStart', range);
            pos.start = offset - dup.text.length;
        }
        return pos;
    },

    getSelectionStart: function() {
        return this.getSelectedRange().start;
    },

    getSelectionEnd: function() {
        return this.getSelectedRange().end;
    },

    setCaretPosition: function(pos) {
        if (pos == 'end') pos = this.get('value').length;
        this.selectRange(pos, pos);
        return this;
    },

    getCaretPosition: function() {
        return this.getSelectedRange().start;
    },

    selectRange: function(start, end) {
        if (this.createTextRange) {
            var value = this.get('value');
            var diff = value.substr(start, end - start).replace(/\r/g, '').length;
            start = value.substr(0, start).replace(/\r/g, '').length;
            var range = this.createTextRange();
            range.collapse(true);
            range.moveEnd('character', start + diff);
            range.moveStart('character', start);
            range.select();
        } else {
            this.focus();
            this.setSelectionRange(start, end);
        }
        return this;
    },

    insertAtCursor: function(value, select) {
        var pos = this.getSelectedRange();
        var text = this.get('value');
        this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
        if ($pick(select, true)) this.selectRange(pos.start, pos.start + value.length);
        else this.setCaretPosition(pos.start + value.length);
        return this;
    },

    insertAroundCursor: function(options, select) {
        options = $extend({
            before: '',
            defaultMiddle: '',
            after: ''
        }, options);
        var value = this.getSelectedText() || options.defaultMiddle;
        var pos = this.getSelectedRange();
        var text = this.get('value');
        if (pos.start == pos.end) {
            this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
            this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
        } else {
            var current = text.substring(pos.start, pos.end);
            this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
            var selStart = pos.start + options.before.length;
            if ($pick(select, true)) this.selectRange(selStart, selStart + current.length);
            else this.setCaretPosition(selStart + text.length);
        }
        return this;
    }

});

/*
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.Pin.js
Extends the Element native object to include the pin method useful for fixed positioning for elements.

License:
MIT-style license.

Authors:
Aaron Newton
*/

(function() {
    var supportsPositionFixed = false;
    window.addEvent('domready', function() {
        var test = new Element('div').setStyles({
            position: 'fixed',
            top: 0,
            right: 0
        }).inject(document.body);
        supportsPositionFixed = (test.offsetTop === 0);
        test.dispose();
    });

    Element.implement({

        pin: function(enable) {
            if (this.getStyle('display') == 'none') return null;

            var p;
            if (enable !== false) {
                p = this.getPosition();
                if (!this.retrieve('pinned')) {
                    var pos = {
                        top: p.y - window.getScroll().y,
                        left: p.x - window.getScroll().x
                    };
                    if (supportsPositionFixed) {
                        this.setStyle('position', 'fixed').setStyles(pos);
                    } else {
                        this.store('pinnedByJS', true);
                        this.setStyles({
                            position: 'absolute',
                            top: p.y,
                            left: p.x
                        });
                        this.store('scrollFixer', (function() {
                            if (this.retrieve('pinned'))
                                this.setStyles({
                                    top: pos.top.toInt() + window.getScroll().y,
                                    left: pos.left.toInt() + window.getScroll().x
                                });
                        }).bind(this));
                        window.addEvent('scroll', this.retrieve('scrollFixer'));
                    }
                    this.store('pinned', true);
                }
            } else {
                var op;
                if (!Browser.Engine.trident) {
                    if (this.getParent().getComputedStyle('position') != 'static') op = this.getParent();
                    else op = this.getParent().getOffsetParent();
                }
                p = this.getPosition(op);
                this.store('pinned', false);
                var reposition;
                if (supportsPositionFixed && !this.retrieve('pinnedByJS')) {
                    reposition = {
                        top: p.y + window.getScroll().y,
                        left: p.x + window.getScroll().x
                    };
                } else {
                    this.store('pinnedByJS', false);
                    window.removeEvent('scroll', this.retrieve('scrollFixer'));
                    reposition = {
                        top: p.y,
                        left: p.x
                    };
                }
                this.setStyles($merge(reposition, { position: 'absolute' }));
            }
            return this.addClass('isPinned');
        },

        unpin: function() {
            return this.pin(false).removeClass('isPinned');
        },

        togglepin: function() {
            this.pin(!this.retrieve('pinned'));
        }

    });

})();

/*
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: FormValidator.js
A css-class based form validation system.

License:
MIT-style license.

Authors:
Aaron Newton
*/
var InputValidator = new Class({

    Implements: [Options],

    options: {
        errorMsg: 'Validation failed.',
        test: function(field) { return true; }
    },

    initialize: function(className, options) {
        this.setOptions(options);
        this.className = className;
    },

    test: function(field, props) {
        if ($(field)) return this.options.test($(field), props || this.getProps(field));
        else return false;
    },

    getError: function(field, props) {
        var err = this.options.errorMsg;
        if ($type(err) == 'function') err = err($(field), props || this.getProps(field));
        return err;
    },

    getProps: function(field) {
        if (!$(field)) return {};
        return field.get('validatorProps');
    }

});

Element.Properties.validatorProps = {

    set: function(props) {
        return this.eliminate('validatorProps').store('validatorProps', props);
    },

    get: function(props) {
        if (props) this.set(props);
        if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
        if (this.getProperty('validatorProps')) {
            try {
                this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
            } catch (e) {
                return {};
            }
        } else {
            var vals = this.get('class').split(' ').filter(function(cls) {
                return cls.test(':');
            });
            if (!vals.length) {
                this.store('validatorProps', {});
            } else {
                props = {};
                vals.each(function(cls) {
                    var split = cls.split(':');
                    if (split[1]) {
                        try {
                            props[split[0]] = JSON.decode(split[1]);
                        } catch (e) { }
                    }
                });
                this.store('validatorProps', props);
            }
        }
        return this.retrieve('validatorProps');
    }

};

var FormValidator = new Class({

    Implements: [Options, Events],

    Binds: ['onSubmit'],

    options: {/*
		onFormValidate: $empty(isValid, form, event),
		onElementValidate: $empty(isValid, field, className, warn),
		onElementPass: $empty(field),
		onElementFail: $empty(field, validatorsFailed) */
        fieldSelectors: 'input, select, textarea',
        ignoreHidden: true,
        useTitles: false,
        evaluateOnSubmit: true,
        evaluateFieldsOnBlur: true,
        evaluateFieldsOnChange: true,
        serial: true,
        stopOnFailure: true,
        warningPrefix: function() {
            return FormValidator.getMsg('warningPrefix') || 'Warning: ';
        },
        errorPrefix: function() {
            return FormValidator.getMsg('errorPrefix') || 'Error: ';
        }
    },

    initialize: function(form, options) {
        this.setOptions(options);
        this.element = $(form);
        this.element.store('validator', this);
        this.warningPrefix = $lambda(this.options.warningPrefix)();
        this.errorPrefix = $lambda(this.options.errorPrefix)();
        if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
        if (this.options.evaluateFieldsOnBlur) this.watchFields(this.getFields());
    },

    toElement: function() {
        return this.element;
    },

    getFields: function() {
        return (this.fields = this.element.getElements(this.options.fieldSelectors));
    },

    watchFields: function(fields) {
        fields.each(function(el) {
            el.addEvent('blur', this.validationMonitor.pass([el, false], this));
            if (this.options.evaluateFieldsOnChange)
                el.addEvent('change', this.validationMonitor.pass([el, true], this));
        }, this);
    },

    validationMonitor: function() {
        $clear(this.timer);
        this.timer = this.validateField.delay(50, this, arguments);
    },

    onSubmit: function(event) {
        if (!this.validate(event) && event) event.preventDefault();
        else this.reset();
    },

    reset: function() {
        this.getFields().each(this.resetField, this);
        return this;
    },

    validate: function(event) {
        var result = this.getFields().map(function(field) {
            return this.validateField(field, true);
        }, this).every(function(v) { return v; });
        this.fireEvent('formValidate', [result, this.element, event]);
        if (this.options.stopOnFailure && !result && event) event.preventDefault();
        return result;
    },

    validateField: function(field, force) {
        if (this.paused) return true;
        field = $(field);
        var passed = !field.hasClass('validation-failed');
        var failed, warned;
        if (this.options.serial && !force) {
            failed = this.element.getElement('.validation-failed');
            warned = this.element.getElement('.warning');
        }
        if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))) {
            var validators = field.className.split(' ').some(function(cn) {
                return this.getValidator(cn);
            }, this);
            var validatorsFailed = [];
            field.className.split(' ').each(function(className) {
                if (className && !this.test(className, field)) validatorsFailed.include(className);
            }, this);
            passed = validatorsFailed.length === 0;
            if (validators && !field.hasClass('warnOnly')) {
                if (passed) {
                    field.addClass('validation-passed').removeClass('validation-failed');
                    this.fireEvent('elementPass', field);
                } else {
                    field.addClass('validation-failed').removeClass('validation-passed');
                    this.fireEvent('elementFail', [field, validatorsFailed]);
                }
            }
            if (!warned) {
                var warnings = field.className.split(' ').some(function(cn) {
                    if (cn.test('^warn-') || field.hasClass('warnOnly'))
                        return this.getValidator(cn.replace(/^warn-/, ''));
                    else return null;
                }, this);
                field.removeClass('warning');
                var warnResult = field.className.split(' ').map(function(cn) {
                    if (cn.test('^warn-') || field.hasClass('warnOnly'))
                        return this.test(cn.replace(/^warn-/, ''), field, true);
                    else return null;
                }, this);
            }
        }
        return passed;
    },

    test: function(className, field, warn) {
        var validator = this.getValidator(className);
        field = $(field);
        if (field.hasClass('ignoreValidation')) return true;
        warn = $pick(warn, false);
        if (field.hasClass('warnOnly')) warn = true;
        var isValid = validator ? validator.test(field) : true;
        if (validator && this.isVisible(field)) this.fireEvent('elementValidate', [isValid, field, className, warn]);
        if (warn) return true;
        return isValid;
    },

    isVisible: function(field) {
        if (!this.options.ignoreHidden) return true;
        while (field != document.body) {
            if ($(field).getStyle('display') == 'none') return false;
            field = field.getParent();
        }
        return true;
    },

    resetField: function(field) {
        field = $(field);
        if (field) {
            field.className.split(' ').each(function(className) {
                if (className.test('^warn-')) className = className.replace(/^warn-/, '');
                field.removeClass('validation-failed');
                field.removeClass('warning');
                field.removeClass('validation-passed');
            }, this);
        }
        return this;
    },

    stop: function() {
        this.paused = true;
        return this;
    },

    start: function() {
        this.paused = false;
        return this;
    },

    ignoreField: function(field, warn) {
        field = $(field);
        if (field) {
            this.enforceField(field);
            if (warn) field.addClass('warnOnly');
            else field.addClass('ignoreValidation');
        }
        return this;
    },

    enforceField: function(field) {
        field = $(field);
        if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
        return this;
    }

});

FormValidator.getMsg = function(key) {
    return MooTools.lang.get('FormValidator', key);
};

FormValidator.adders = {

    validators: {},

    add: function(className, options) {
        this.validators[className] = new InputValidator(className, options);
        //if this is a class (this method is used by instances of FormValidator and the FormValidator namespace)
        //extend these validators into it
        //this allows validators to be global and/or per instance
        if (!this.initialize) {
            this.implement({
                validators: this.validators
            });
        }
    },

    addAllThese: function(validators) {
        $A(validators).each(function(validator) {
            this.add(validator[0], validator[1]);
        }, this);
    },

    getValidator: function(className) {
        return this.validators[className.split(':')[0]];
    }

};

$extend(FormValidator, FormValidator.adders);

FormValidator.implement(FormValidator.adders);

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
            return ((element.get('value') == null) || (element.get('value').length == 0));
    }

});

FormValidator.addAllThese([

	['required', {
	    errorMsg: function() {
	        return FormValidator.getMsg('required');
	    },
	    test: function(element) {
	        return !FormValidator.getValidator('IsEmpty').test(element);
	    }
}],

	['minLength', {
	    errorMsg: function(element, props) {
	        if ($type(props.minLength))
	            return FormValidator.getMsg('minLength').substitute({ minLength: props.minLength, length: element.get('value').length });
	        else return '';
	    },
	    test: function(element, props) {
	        if ($type(props.minLength)) return (element.get('value').length >= $pick(props.minLength, 0));
	        else return true;
	    }
}],

	['maxLength', {
	    errorMsg: function(element, props) {
	        //props is {maxLength:10}
	        if ($type(props.maxLength))
	            return FormValidator.getMsg('maxLength').substitute({ maxLength: props.maxLength, length: element.get('value').length });
	        else return '';
	    },
	    test: function(element, props) {
	        //if the value is <= than the maxLength value, element passes test
	        return (element.get('value').length <= $pick(props.maxLength, 10000));
	    }
}],

	['validate-integer', {
	    errorMsg: FormValidator.getMsg.pass('integer'),
	    test: function(element) {
	        return FormValidator.getValidator('IsEmpty').test(element) || (/^-?[1-9]\d*$/).test(element.get('value'));
	    }
}],

	['validate-numeric', {
	    errorMsg: FormValidator.getMsg.pass('numeric'),
	    test: function(element) {
	        return FormValidator.getValidator('IsEmpty').test(element) ||
				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
	    }
}],

	['validate-digits', {
	    errorMsg: FormValidator.getMsg.pass('digits'),
	    test: function(element) {
	        return FormValidator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
	    }
}],

	['validate-alpha', {
	    errorMsg: FormValidator.getMsg.pass('alpha'),
	    test: function(element) {
	        return FormValidator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
	    }
}],

	['validate-alphanum', {
	    errorMsg: FormValidator.getMsg.pass('alphanum'),
	    test: function(element) {
	        return FormValidator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
	    }
}],

	['validate-date', {
	    errorMsg: function(element, props) {
	        if (Date.parse) {
	            var format = props.dateFormat || '%x';
	            return FormValidator.getMsg('dateSuchAs').substitute({ date: new Date().format(format) });
	        } else {
	            return FormValidator.getMsg('dateInFormatMDY');
	        }
	    },
	    test: function(element, props) {
	        if (FormValidator.getValidator('IsEmpty').test(element)) return true;
	        var d;
	        if (Date.parse) {
	            var format = props.dateFormat || '%x';
	            d = Date.parse(element.get('value'));
	            var formatted = d.format(format);
	            if (formatted != 'invalid date') element.set('value', formatted);
	            return !isNaN(d);
	        } else {
	            var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
	            if (!regex.test(element.get('value'))) return false;
	            d = new Date(element.get('value').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());
	        }
	    }
}],

	['validate-email', {
	    errorMsg: FormValidator.getMsg.pass('email'),
	    test: function(element) {
	        return FormValidator.getValidator('IsEmpty').test(element) || (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(element.get('value'));
	    }
}],

	['validate-url', {
	    errorMsg: FormValidator.getMsg.pass('url'),
	    test: function(element) {
	        return FormValidator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
	    }
}],

	['validate-currency-dollar', {
	    errorMsg: FormValidator.getMsg.pass('currencyDollar'),
	    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.get('value'));
	    }
}],

	['validate-one-required', {
	    errorMsg: FormValidator.getMsg.pass('oneRequired'),
	    test: function(element, props) {
	        var p = $(props['validate-one-required']) || element.parentNode;
	        return p.getElements('input').some(function(el) {
	            if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
	            return el.get('value');
	        });
	    }
}]

]);

Element.Properties.validator = {

    set: function(options) {
        var validator = this.retrieve('validator');
        if (validator) validator.setOptions(options);
        return this.store('validator:options');
    },

    get: function(options) {
        if (options || !this.retrieve('validator')) {
            if (options || !this.retrieve('validator:options')) this.set('validator', options);
            this.store('validator', new FormValidator(this, this.retrieve('validator:options')));
        }
        return this.retrieve('validator');
    }

};

Element.implement({

    validate: function(options) {
        this.set('validator', options);
        return this.get('validator', options).validate();
    }

});

/*
Script: FormValidator.Inline.js
Extends FormValidator to add inline messages.

License:
MIT-style license.

Authors:
Aaron Newton
*/

FormValidator.Inline = new Class({

    Extends: FormValidator,

    options: {
        scrollToErrorsOnSubmit: true,
        scrollFxOptions: {
            offset: {
                y: -20
            }
        }
    },

    initialize: function(form, options) {
        this.parent(form, options);
        this.addEvent('onElementValidate', function(isValid, field, className, warn) {
            var validator = this.getValidator(className);
            if (!isValid && validator.getError(field)) {
                if (warn) field.addClass('warning');
                var advice = this.makeAdvice(className, field, validator.getError(field), warn);
                this.insertAdvice(advice, field);
                this.showAdvice(className, field);
            } else {
                this.hideAdvice(className, field);
            }
        });
    },

    makeAdvice: function(className, field, error, warn) {
        var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
        errorMsg += (this.options.useTitles) ? field.title || error : error;
        var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
        var advice = this.getAdvice(className, field);
        if (advice) {
            advice = advice.clone(true, true).set('html', errorMsg).replaces(advice);
        } else {
            advice = new Element('div', {
                html: errorMsg,
                styles: { display: 'none' },
                id: 'advice-' + className + '-' + this.getFieldId(field)
            }).addClass(cssClass);
        }
        field.store('advice-' + className, advice);
        return advice;
    },

    getFieldId: function(field) {
        return field.id ? field.id : field.id = 'input_' + field.name;
    },

    showAdvice: function(className, field) {
        var advice = this.getAdvice(className, field);
        if (advice && !field.retrieve(this.getPropName(className))
				&& (advice.getStyle('display') == 'none'
				|| advice.getStyle('visiblity') == 'hidden'
				|| advice.getStyle('opacity') == 0)) {
            field.store(this.getPropName(className), true);
            if (advice.reveal) advice.reveal();
            else advice.setStyle('display', 'block');
        }
    },

    hideAdvice: function(className, field) {
        var advice = this.getAdvice(className, field);
        if (advice && field.retrieve(this.getPropName(className))) {
            field.store(this.getPropName(className), false);
            //if Fx.Reveal.js is present, transition the advice out
            if (advice.dissolve) advice.dissolve();
            else advice.setStyle('display', 'none');
        }
    },

    getPropName: function(className) {
        return 'advice' + className;
    },

    resetField: function(field) {
        field = $(field);
        if (!field) return this;
        this.parent(field);
        field.className.split(' ').each(function(className) {
            this.hideAdvice(className, field);
        }, this);
        return this;
    },

    getAllAdviceMessages: function(field, force) {
        var advice = [];
        if (field.hasClass('ignoreValidation') && !force) return advice;
        var validators = field.className.split(' ').some(function(cn) {
            var warner = cn.test('^warn-') || field.hasClass('warnOnly');
            if (warner) cn = cn.replace(/^warn-/, '');
            var validator = this.getValidator(cn);
            if (!validator) return;
            advice.push({
                message: validator.getError(field),
                warnOnly: warner,
                passed: validator.test(),
                validator: validator
            });
        }, this);
        return advice;
    },

    getAdvice: function(className, field) {
        return field.retrieve('advice-' + className);
    },

    insertAdvice: function(advice, field) {
        //Check for error position prop
        var props = field.get('validatorProps');
        //Build advice
        if (!props.msgPos || !$(props.msgPos)) {
            if (field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
            else advice.inject($(field), 'after');
        } else {
            $(props.msgPos).grab(advice);
        }
    },

    validateField: function(field, force) {
        var result = this.parent(field, force);
        if (this.options.scrollToErrorsOnSubmit && !result) {
            var failed = $(this).getElement('.validation-failed');
            var par = $(this).getParent();
            var isScrolled = function(p) {
                return p.getScrollSize().y != p.getSize().y;
            };
            var scrolls;
            while (par != document.body && !isScrolled(par)) {
                par = par.getParent();
            }
            var fx = par.retrieve('fvScroller');
            if (!fx && window.Fx && Fx.Scroll) {
                fx = new Fx.Scroll(par, {
                    transition: 'quad:out',
                    offset: {
                        y: -20
                    }
                });
                par.store('fvScroller', fx);
            }
            if (failed) {
                if (fx) fx.toElement(failed);
                else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
            }
        }
        return result;
    }

});


/*
Script: FormValidator.Extras.js
Additional validators for the FormValidator class.

License:
MIT-style license.

Authors:
Aaron Newton
*/
FormValidator.addAllThese([

	['validate-enforce-oncheck', {
	    test: function(element, props) {
	        if (element.checked) {
	            var fv = element.getParent('form').retrieve('validator');
	            if (!fv) return true;
	            (props.toEnforce || $(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item) {
	                fv.enforceField(item);
	            });
	        }
	        return true;
	    }
}],

	['validate-ignore-oncheck', {
	    test: function(element, props) {
	        if (element.checked) {
	            var fv = element.getParent('form').retrieve('validator');
	            if (!fv) return true;
	            (props.toIgnore || $(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item) {
	                fv.ignoreField(item);
	                fv.resetField(item);
	            });
	        }
	        return true;
	    }
}],

	['validate-nospace', {
	    errorMsg: function() {
	        return FormValidator.getMsg('noSpace');
	    },
	    test: function(element, props) {
	        return !element.get('value').test(/\s/);
	    }
}],

	['validate-toggle-oncheck', {
	    test: function(element, props) {
	        var fv = element.getParent('form').retrieve('validator');
	        if (!fv) return true;
	        var eleArr = props.toToggle || $(props.toToggleChildrenOf).getElements('input, select, textarea');
	        if (!element.checked) {
	            eleArr.each(function(item) {
	                fv.ignoreField(item);
	                fv.resetField(item);
	            });
	        } else {
	            eleArr.each(function(item) {
	                fv.enforceField(item);
	            });
	        }
	        return true;
	    }
}],

	['validate-reqchk-bynode', {
	    errorMsg: function() {
	        return FormValidator.getMsg('reqChkByNode');
	    },
	    test: function(element, props) {
	        return ($(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item) {
	            return item.checked;
	        });
	    }
}],

	['validate-required-check', {
	    errorMsg: function(element, props) {
	        return props.useTitle ? element.get('title') : FormValidator.getMsg('requiredChk');
	    },
	    test: function(element, props) {
	        return !!element.checked;
	    }
}],

	['validate-reqchk-byname', {
	    errorMsg: function(element, props) {
	        return FormValidator.getMsg('reqChkByName').substitute({ label: props.label || element.get('type') });
	    },
	    test: function(element, props) {
	        var grpName = props.groupName || element.get('name');
	        var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index) {
	            return item.checked;
	        });
	        var fv = element.getParent('form').retrieve('validator');
	        if (oneCheckedItem && fv) fv.resetField(element);
	        return oneCheckedItem;
	    }
}],

	['validate-match', {
	    errorMsg: function(element, props) {
	        return FormValidator.getMsg('match').substitute({ matchName: props.matchName || $(props.matchInput).get('name') });
	    },
	    test: function(element, props) {
	        var eleVal = element.get('value');
	        var matchVal = $(props.matchInput) && $(props.matchInput).get('value');
	        return eleVal && matchVal ? eleVal == matchVal : true;
	    }
}],

	['validate-after-date', {
	    errorMsg: function(element, props) {
	        return FormValidator.getMsg('afterDate').substitute({
	            label: props.afterLabel || (props.afterElement ? FormValidator.getMsg('startDate') : FormValidator.getMsg('currentDate'))
	        });
	    },
	    test: function(element, props) {
	        var start = $(props.afterElement) ? Date.parse($(props.afterElement).get('value')) : new Date();
	        var end = Date.parse(element.get('value'));
	        return end && start ? end >= start : true;
	    }
}],

	['validate-before-date', {
	    errorMsg: function(element, props) {
	        return FormValidator.getMsg('beforeDate').substitute({
	            label: props.beforeLabel || (props.beforeElement ? FormValidator.getMsg('endDate') : FormValidator.getMsg('currentDate'))
	        });
	    },
	    test: function(element, props) {
	        var start = Date.parse(element.get('value'));
	        var end = $(props.beforeElement) ? Date.parse($(props.beforeElement).get('value')) : new Date();
	        return end && start ? end >= start : true;
	    }
}],

	['validate-custom-required', {
	    errorMsg: function() {
	        return FormValidator.getMsg('required');
	    },
	    test: function(element, props) {
	        return element.get('value') != props.emptyValue;
	    }
}],

	['validate-same-month', {
	    errorMsg: function(element, props) {
	        var startMo = $(props.sameMonthAs) && $(props.sameMonthAs).get('value');
	        var eleVal = element.get('value');
	        if (eleVal != '') return FormValidator.getMsg(startMo ? 'sameMonth' : 'startMonth');
	    },
	    test: function(element, props) {
	        var d1 = Date.parse(element.get('value'));
	        var d2 = Date.parse($(props.sameMonthAs) && $(props.sameMonthAs).get('value'));
	        return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
	    }
}]

]);

/*
Script: OverText.js
Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.

License:
MIT-style license.

Authors:
Aaron Newton
*/

var OverText = new Class({

    Implements: [Options, Events, Class.Occlude],

    Binds: ['reposition', 'assert', 'focus'],

    options: {/*
		textOverride: null,
		onFocus: $empty()
		onTextHide: $empty(textEl, inputEl),
		onTextShow: $empty(textEl, inputEl), */
        element: 'label',
        positionOptions: {
            position: 'upperLeft',
            edge: 'upperLeft',
            offset: {
                x: 4,
                y: 2
            }
        },
        poll: false,
        pollInterval: 250
    },

    property: 'OverText',

    initialize: function(element, options) {
        this.element = $(element);
        if (this.occlude()) return this.occluded;
        this.setOptions(options);
        this.attach(this.element);
        OverText.instances.push(this);
        if (this.options.poll) this.poll();
        return this;
    },

    toElement: function() {
        return this.element;
    },

    attach: function() {
        var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
        if (!val) return;
        this.text = new Element(this.options.element, {
            'class': 'overTxtDiv',
            styles: {
                lineHeight: 'normal',
                position: 'absolute'
            },
            html: val,
            events: {
                click: this.hide.pass(true, this)
            }
        }).inject(this.element, 'after');
        if (this.options.element == 'label') this.text.set('for', this.element.get('id'));
        this.element.addEvents({
            focus: this.focus,
            blur: this.assert,
            change: this.assert
        }).store('OverTextDiv', this.text);
        window.addEvent('resize', this.reposition.bind(this));
        this.assert();
        this.reposition();
    },

    startPolling: function() {
        this.pollingPaused = false;
        return this.poll();
    },

    poll: function(stop) {
        //start immediately
        //pause on focus
        //resumeon blur
        if (this.poller && !stop) return this;
        var test = function() {
            if (!this.pollingPaused) this.assert();
        } .bind(this);
        if (stop) $clear(this.poller);
        else this.poller = test.periodical(this.options.pollInterval, this);
        return this;
    },

    stopPolling: function() {
        this.pollingPaused = true;
        return this.poll(true);
    },

    focus: function() {
        if (!this.text.isDisplayed() || this.element.get('disabled')) return;
        this.hide();
    },

    hide: function() {
        if (this.text.isDisplayed() && !this.element.get('disabled')) {
            this.text.hide();
            this.fireEvent('textHide', [this.text, this.element]);
            this.pollingPaused = true;
            try {
                this.element.fireEvent('focus').focus();
            } catch (e) { } //IE barfs if you call focus on hidden elements
        }
        return this;
    },

    show: function() {
        if (!this.text.isDisplayed()) {
            this.text.show();
            this.reposition();
            this.fireEvent('textShow', [this.text, this.element]);
            this.pollingPaused = false;
        }
        return this;
    },

    assert: function() {
        this[this.test() ? 'show' : 'hide']();
    },

    test: function() {
        var v = this.element.get('value');
        return !v;
    },

    reposition: function() {
        try {
            this.assert();
            if (!this.element.getParent() || !this.element.offsetHeight) return this.hide();
            if (this.test()) this.text.position($merge(this.options.positionOptions, { relativeTo: this.element }));
        } catch (e) { }
        return this;
    }

});

OverText.instances = [];

OverText.update = function() {

    return OverText.instances.map(function(ot) {
        if (ot.element && ot.text) return ot.reposition();
        return null; //the input or the text was destroyed
    });

};

if (window.Fx && Fx.Reveal) {
    Fx.Reveal.implement({
        hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtDiv' : false
    });
}

/*
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);
    }

});

/*
Script: Drag.js
The base Drag Class. Can be used to drag and resize Elements using mouse events.

License:
MIT-style license.

Authors:
Valerio Proietti
Tom Occhinno
Jan Kassens
*/

var Drag = new Class({

    Implements: [Events, Options],

    options: {/*
		onBeforeStart: $empty(thisElement),
		onStart: $empty(thisElement, event),
		onSnap: $empty(thisElement)
		onDrag: $empty(thisElement, event),
		onCancel: $empty(thisElement),
		onComplete: $empty(thisElement, event),*/
        snap: 6,
        unit: 'px',
        grid: false,
        style: true,
        limit: false,
        handle: false,
        invert: false,
        preventDefault: false,
        modifiers: { x: 'left', y: 'top' }
    },

    initialize: function() {
        var params = Array.link(arguments, { 'options': Object.type, 'element': $defined });
        this.element = $(params.element);
        this.document = this.element.getDocument();
        this.setOptions(params.options || {});
        var htype = $type(this.options.handle);
        this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : $(this.options.handle)) || this.element;
        this.mouse = { 'now': {}, 'pos': {} };
        this.value = { 'start': {}, 'now': {} };

        this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';

        this.bound = {
            start: this.start.bind(this),
            check: this.check.bind(this),
            drag: this.drag.bind(this),
            stop: this.stop.bind(this),
            cancel: this.cancel.bind(this),
            eventStop: $lambda(false)
        };
        this.attach();
    },

    attach: function() {
        this.handles.addEvent('mousedown', this.bound.start);
        return this;
    },

    detach: function() {
        this.handles.removeEvent('mousedown', this.bound.start);
        return this;
    },

    start: function(event) {
        if (this.options.preventDefault) event.preventDefault();
        this.mouse.start = event.page;
        this.fireEvent('beforeStart', this.element);
        var limit = this.options.limit;
        this.limit = { x: [], y: [] };
        for (var z in this.options.modifiers) {
            if (!this.options.modifiers[z]) continue;
            if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
            else this.value.now[z] = this.element[this.options.modifiers[z]];
            if (this.options.invert) this.value.now[z] *= -1;
            this.mouse.pos[z] = event.page[z] - this.value.now[z];
            if (limit && limit[z]) {
                for (var i = 2; i--; i) {
                    if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
                }
            }
        }
        if ($type(this.options.grid) == 'number') this.options.grid = { x: this.options.grid, y: this.options.grid };
        this.document.addEvents({ mousemove: this.bound.check, mouseup: this.bound.cancel });
        this.document.addEvent(this.selection, this.bound.eventStop);
    },

    check: function(event) {
        if (this.options.preventDefault) event.preventDefault();
        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) {
            this.cancel();
            this.document.addEvents({
                mousemove: this.bound.drag,
                mouseup: this.bound.stop
            });
            this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
        }
    },

    drag: function(event) {
        if (this.options.preventDefault) event.preventDefault();
        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.options.invert) this.value.now[z] *= -1;
            if (this.options.limit && 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];
                } else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])) {
                    this.value.now[z] = this.limit[z][0];
                }
            }
            if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - this.limit[z][0]) % this.options.grid[z]);
            if (this.options.style) this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
            else this.element[this.options.modifiers[z]] = this.value.now[z];
        }
        this.fireEvent('drag', [this.element, event]);
    },

    cancel: function(event) {
        this.document.removeEvent('mousemove', this.bound.check);
        this.document.removeEvent('mouseup', this.bound.cancel);
        if (event) {
            this.document.removeEvent(this.selection, this.bound.eventStop);
            this.fireEvent('cancel', this.element);
        }
    },

    stop: function(event) {
        this.document.removeEvent(this.selection, this.bound.eventStop);
        this.document.removeEvent('mousemove', this.bound.drag);
        this.document.removeEvent('mouseup', this.bound.stop);
        if (event) this.fireEvent('complete', [this.element, event]);
    }

});

Element.implement({

    makeResizable: function(options) {
        var drag = new Drag(this, $merge({ modifiers: { x: 'width', y: 'height'} }, options));
        this.store('resizer', drag);
        return drag.addEvent('drag', function() {
            this.fireEvent('resize', drag);
        } .bind(this));
    }

});


/*
Script: Drag.Move.js
A Drag extension that provides support for the constraining of draggables to containers and droppables.

License:
MIT-style license.

Authors:
Valerio Proietti
Tom Occhinno
Jan Kassens*/

Drag.Move = new Class({

    Extends: Drag,

    options: {/*
		onEnter: $empty(thisElement, overed),
		onLeave: $empty(thisElement, overed),
		onDrop: $empty(thisElement, overed, event),*/
        droppables: [],
        container: false,
        precalculate: false,
        includeMargins: true,
        checkDroppables: true
    },

    initialize: function(element, options) {
        this.parent(element, options);
        this.droppables = $$(this.options.droppables);
        this.container = $(this.options.container);
        if (this.container && $type(this.container) != 'element') this.container = $(this.container.getDocument().body);

        var position = this.element.getStyle('position');
        if (position == 'static') position = 'absolute';
        if ([this.element.getStyle('left'), this.element.getStyle('top')].contains('auto')) this.element.position(this.element.getPosition(this.element.offsetParent));
        this.element.setStyle('position', position);

        this.addEvent('start', this.checkDroppables, true);

        this.overed = null;
    },

    start: function(event) {
        if (this.container) {
            var ccoo = this.container.getCoordinates(this.element.getOffsetParent()), cbs = {}, ems = {};

            ['top', 'right', 'bottom', 'left'].each(function(pad) {
                cbs[pad] = this.container.getStyle('border-' + pad).toInt();
                ems[pad] = this.element.getStyle('margin-' + pad).toInt();
            }, this);

            var width = this.element.offsetWidth + ems.left + ems.right;
            var height = this.element.offsetHeight + ems.top + ems.bottom;

            if (this.options.includeMargins) {
                $each(ems, function(value, key) {
                    ems[key] = 0;
                });
            }
            if (this.container == this.element.getOffsetParent()) {
                this.options.limit = {
                    x: [0 - ems.left, ccoo.right - cbs.left - cbs.right - width + ems.right],
                    y: [0 - ems.top, ccoo.bottom - cbs.top - cbs.bottom - height + ems.bottom]
                };
            } else {
                this.options.limit = {
                    x: [ccoo.left + cbs.left - ems.left, ccoo.right - cbs.right - width + ems.right],
                    y: [ccoo.top + cbs.top - ems.top, ccoo.bottom - cbs.bottom - height + ems.bottom]
                };
            }

        }
        if (this.options.precalculate) {
            this.positions = this.droppables.map(function(el) {
                return el.getCoordinates();
            });
        }
        this.parent(event);
    },

    checkAgainst: function(el, i) {
        el = (this.positions) ? this.positions[i] : el.getCoordinates();
        var now = this.mouse.now;
        return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
    },

    checkDroppables: function() {
        var overed = this.droppables.filter(this.checkAgainst, this).getLast();
        if (this.overed != overed) {
            if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
            if (overed) this.fireEvent('enter', [this.element, overed]);
            this.overed = overed;
        }
    },

    drag: function(event) {
        this.parent(event);
        if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
    },

    stop: function(event) {
        this.checkDroppables();
        this.fireEvent('drop', [this.element, this.overed, event]);
        this.overed = null;
        return this.parent(event);
    }

});

Element.implement({

    makeDraggable: function(options) {
        var drag = new Drag.Move(this, options);
        this.store('dragger', drag);
        return drag;
    }

});


/*
Script: Slider.js
Class for creating horizontal and vertical slider controls.

License:
MIT-style license.

Authors:
Valerio Proietti
*/

var Slider = new Class({

    Implements: [Events, Options],

    Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],

    options: {/*
		onTick: $empty(intPosition),
		onChange: $empty(intStep),
		onComplete: $empty(strStep),*/
        onTick: function(position) {
            if (this.options.snap) position = this.toPosition(this.step);
            this.knob.setStyle(this.property, position);
        },
        snap: false,
        offset: 0,
        range: false,
        wheel: false,
        steps: 100,
        mode: 'horizontal'
    },

    initialize: function(element, knob, options) {
        this.setOptions(options);
        this.element = $(element);
        this.knob = $(knob);
        this.previousChange = this.previousEnd = this.step = -1;
        var offset, limit = {}, modifiers = { 'x': false, 'y': false };
        switch (this.options.mode) {
            case 'vertical':
                this.axis = 'y';
                this.property = 'top';
                offset = 'offsetHeight';
                break;
            case 'horizontal':
                this.axis = 'x';
                this.property = 'left';
                offset = 'offsetWidth';
        }
        this.half = this.knob[offset] / 2;
        this.full = this.element[offset] - this.knob[offset] + (this.options.offset * 2);
        this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
        this.max = $chk(this.options.range[1]) ? this.options.range[1] : this.options.steps;
        this.range = this.max - this.min;
        this.steps = this.options.steps || this.full;
        this.stepSize = Math.abs(this.range) / this.steps;
        this.stepWidth = this.stepSize * this.full / Math.abs(this.range);

        this.knob.setStyle('position', 'relative').setStyle(this.property, -this.options.offset);
        modifiers[this.axis] = this.property;
        limit[this.axis] = [-this.options.offset, this.full - this.options.offset];

        this.bound = {
            clickedElement: this.clickedElement.bind(this),
            scrolledElement: this.scrolledElement.bindWithEvent(this),
            draggedKnob: this.draggedKnob.bind(this)
        };

        var dragOptions = {
            snap: 0,
            limit: limit,
            modifiers: modifiers,
            onDrag: this.bound.draggedKnob,
            onStart: this.bound.draggedKnob,
            onBeforeStart: (function() {
                this.isDragging = true;
            }).bind(this),
            onComplete: function() {
                this.isDragging = false;
                this.draggedKnob();
                this.end();
            } .bind(this)
        };
        if (this.options.snap) {
            dragOptions.grid = Math.ceil(this.stepWidth);
            dragOptions.limit[this.axis][1] = this.full;
        }

        this.drag = new Drag(this.knob, dragOptions);
        this.attach();
    },

    attach: function() {
        this.element.addEvent('mousedown', this.bound.clickedElement);
        if (this.options.wheel) this.element.addEvent('mousewheel', this.bound.scrolledElement);
        this.drag.attach();
        return this;
    },

    detach: function() {
        this.element.removeEvent('mousedown', this.bound.clickedElement);
        this.element.removeEvent('mousewheel', this.bound.scrolledElement);
        this.drag.detach();
        return this;
    },

    set: function(step) {
        if (!((this.range > 0) ^ (step < this.min))) step = this.min;
        if (!((this.range > 0) ^ (step > this.max))) step = this.max;

        this.step = Math.round(step);
        this.checkStep();
        this.fireEvent('tick', this.toPosition(this.step));
        this.end();
        return this;
    },

    clickedElement: function(event) {
        if (this.isDragging || event.target == this.knob) return;

        var dir = this.range < 0 ? -1 : 1;
        var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
        position = position.limit(-this.options.offset, this.full - this.options.offset);

        this.step = Math.round(this.min + dir * this.toStep(position));
        this.checkStep();
        this.fireEvent('tick', position);
        this.end();
    },

    scrolledElement: function(event) {
        var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
        this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
        event.stop();
    },

    draggedKnob: function() {
        var dir = this.range < 0 ? -1 : 1;
        var position = this.drag.value.now[this.axis];
        position = position.limit(-this.options.offset, this.full - this.options.offset);
        this.step = Math.round(this.min + dir * this.toStep(position));
        this.checkStep();
    },

    checkStep: function() {
        if (this.previousChange != this.step) {
            this.previousChange = this.step;
            this.fireEvent('change', this.step);
        }
    },

    end: function() {
        if (this.previousEnd !== this.step) {
            this.previousEnd = this.step;
            this.fireEvent('complete', this.step + '');
        }
    },

    toStep: function(position) {
        var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
        return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
    },

    toPosition: function(step) {
        return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
    }

});

/*
Script: Sortables.js
Class for creating a drag and drop sorting interface for lists of items.

License:
MIT-style license.

Authors:
Tom Occhino
*/

var Sortables = new Class({

    Implements: [Events, Options],

    options: {/*
		onSort: $empty(element, clone),
		onStart: $empty(element, clone),
		onComplete: $empty(element),*/
        snap: 4,
        opacity: 1,
        clone: false,
        revert: false,
        handle: false,
        constrain: false
    },

    initialize: function(lists, options) {
        this.setOptions(options);
        this.elements = [];
        this.lists = [];
        this.idle = true;

        this.addLists($$($(lists) || lists));
        if (!this.options.clone) this.options.revert = false;
        if (this.options.revert) this.effect = new Fx.Morph(null, $merge({ duration: 250, link: 'cancel' }, this.options.revert));
    },

    attach: function() {
        this.addLists(this.lists);
        return this;
    },

    detach: function() {
        this.lists = this.removeLists(this.lists);
        return this;
    },

    addItems: function() {
        Array.flatten(arguments).each(function(element) {
            this.elements.push(element);
            var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
            (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
        }, this);
        return this;
    },

    addLists: function() {
        Array.flatten(arguments).each(function(list) {
            this.lists.push(list);
            this.addItems(list.getChildren());
        }, this);
        return this;
    },

    removeItems: function() {
        return $$(Array.flatten(arguments).map(function(element) {
            this.elements.erase(element);
            var start = element.retrieve('sortables:start');
            (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);

            return element;
        }, this));
    },

    removeLists: function() {
        return $$(Array.flatten(arguments).map(function(list) {
            this.lists.erase(list);
            this.removeItems(list.getChildren());

            return list;
        }, this));
    },

    getClone: function(event, element) {
        if (!this.options.clone) return new Element('div').inject(document.body);
        if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
        return element.clone(true).setStyles({
            margin: '0px',
            position: 'absolute',
            visibility: 'hidden',
            'width': element.getStyle('width')
        }).inject(this.list).position(element.getPosition(element.getOffsetParent()));
    },

    getDroppables: function() {
        var droppables = this.list.getChildren();
        if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
        return droppables.erase(this.clone).erase(this.element);
    },

    insert: function(dragging, element) {
        var where = 'inside';
        if (this.lists.contains(element)) {
            this.list = element;
            this.drag.droppables = this.getDroppables();
        } else {
            where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
        }
        this.element.inject(element, where);
        this.fireEvent('sort', [this.element, this.clone]);
    },

    start: function(event, element) {
        if (!this.idle) return;
        this.idle = false;
        this.element = element;
        this.opacity = element.get('opacity');
        this.list = element.getParent();
        this.clone = this.getClone(event, element);

        this.drag = new Drag.Move(this.clone, {
            snap: this.options.snap,
            container: this.options.constrain && this.element.getParent(),
            droppables: this.getDroppables(),
            onSnap: function() {
                event.stop();
                this.clone.setStyle('visibility', 'visible');
                this.element.set('opacity', this.options.opacity || 0);
                this.fireEvent('start', [this.element, this.clone]);
            } .bind(this),
            onEnter: this.insert.bind(this),
            onCancel: this.reset.bind(this),
            onComplete: this.end.bind(this)
        });

        this.clone.inject(this.element, 'before');
        this.drag.start(event);
    },

    end: function() {
        this.drag.detach();
        this.element.set('opacity', this.opacity);
        if (this.effect) {
            var dim = this.element.getStyles('width', 'height');
            var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
            this.effect.element = this.clone;
            this.effect.start({
                top: pos.top,
                left: pos.left,
                width: dim.width,
                height: dim.height,
                opacity: 0.25
            }).chain(this.reset.bind(this));
        } else {
            this.reset();
        }
    },

    reset: function() {
        this.idle = true;
        this.clone.destroy();
        this.fireEvent('complete', this.element);
    },

    serialize: function() {
        var params = Array.link(arguments, { modifier: Function.type, index: $defined });
        var serial = this.lists.map(function(list) {
            return list.getChildren().map(params.modifier || function(element) {
                return element.get('id');
            }, this);
        }, this);

        var index = params.index;
        if (this.lists.length == 1) index = 0;
        return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
    }

});


/*
Script: Request.JSONP.js
Defines Request.JSONP, a class for cross domain javascript via script injection.

License:
MIT-style license.

Authors:
Aaron Newton
Guillermo Rauch
*/

Request.JSONP = new Class({

    Implements: [Chain, Events, Options, Log],

    options: {/*
		onRetry: $empty(intRetries),
		onRequest: $empty(scriptElement),
		onComplete: $empty(data),
		onSuccess: $empty(data),
		onCancel: $empty(),*/
        url: '',
        data: {},
        retries: 0,
        timeout: 0,
        link: 'ignore',
        callbackKey: 'callback',
        injectScript: document.head
    },

    initialize: function(options) {
        this.setOptions(options);
        this.running = false;
        this.requests = 0;
        this.triesRemaining = [];
    },

    check: function() {
        if (!this.running) return true;
        switch (this.options.link) {
            case 'cancel': this.cancel(); return true;
            case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
        }
        return false;
    },

    send: function(options) {
        if (!$chk(arguments[1]) && !this.check(options)) return this;

        var type = $type(options), old = this.options, index = $chk(arguments[1]) ? arguments[1] : this.requests++;
        if (type == 'string' || type == 'element') options = { data: options };

        options = $extend({ data: old.data, url: old.url }, options);

        if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
        var remaining = this.triesRemaining[index];

        (function() {
            var script = this.getScript(options);
            this.log('JSONP retrieving script with url: ' + script.get('src'));
            this.fireEvent('request', script);
            this.running = true;

            (function() {
                if (remaining) {
                    this.triesRemaining[index] = remaining - 1;
                    if (script) {
                        script.destroy();
                        this.request(options, index);
                        this.fireEvent('retry', this.triesRemaining[index]);
                    }
                } else if (script && this.options.timeout) {
                    script.destroy();
                    this.cancel();
                    this.fireEvent('failure');
                }
            }).delay(this.options.timeout, this);
        }).delay(Browser.Engine.trident ? 50 : 0, this);
        return this;
    },

    cancel: function() {
        if (!this.running) return this;
        this.running = false;
        this.fireEvent('cancel');
        return this;
    },

    getScript: function(options) {
        var index = Request.JSONP.counter, data;
        Request.JSONP.counter++;

        switch ($type(options.data)) {
            case 'element': data = $(options.data).toQueryString(); break;
            case 'object': case 'hash': data = Hash.toQueryString(options.data);
        }

        var src = options.url +
			 (options.url.test('\\?') ? '&' : '?') +
			 (options.callbackKey || this.options.callbackKey) +
			 '=Request.JSONP.request_map.request_' + index +
			 (data ? '&' + data : '');
        if (src.length > 2083) this.log('JSONP ' + src + ' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');

        var script = new Element('script', { type: 'text/javascript', src: src });
        Request.JSONP.request_map['request_' + index] = function(data) { this.success(data, script); } .bind(this);
        return script.inject(this.options.injectScript);
    },

    success: function(data, script) {
        if (script) script.destroy();
        this.running = false;
        this.log('JSONP successfully retrieved: ', data);
        this.fireEvent('complete', [data]).fireEvent('success', [data]).callChain();
    }

});

Request.JSONP.counter = 0;
Request.JSONP.request_map = {};

/*
Script: Request.Queue.js
Controls several instances of Request and its variants to run only one request at a time.

License:
MIT-style license.

Authors:
Aaron Newton
*/

Request.Queue = new Class({

    Implements: [Options, Events],

    Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],

    options: {/*
		onRequest: $empty(argsPassedToOnRequest),
		onSuccess: $empty(argsPassedToOnSuccess),
		onComplete: $empty(argsPassedToOnComplete),
		onCancel: $empty(argsPassedToOnCancel),
		onException: $empty(argsPassedToOnException),
		onFailure: $empty(argsPassedToOnFailure),*/
        stopOnFailure: true,
        autoAdvance: true,
        concurrent: 1,
        requests: {}
    },

    initialize: function(options) {
        this.setOptions(options);
        this.requests = new Hash;
        this.addRequests(this.options.requests);
        this.queue = [];
        this.reqBinders = {};
    },

    addRequest: function(name, request) {
        this.requests.set(name, request);
        this.attach(name, request);
        return this;
    },

    addRequests: function(obj) {
        $each(obj, function(req, name) {
            this.addRequest(name, req);
        }, this);
        return this;
    },

    getName: function(req) {
        return this.requests.keyOf(req);
    },

    attach: function(name, req) {
        if (req._groupSend) return this;
        ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt) {
            if (!this.reqBinders[name]) this.reqBinders[name] = {};
            this.reqBinders[name][evt] = function() {
                this['on' + evt.capitalize()].apply(this, [name, req].extend(arguments));
            } .bind(this);
            req.addEvent(evt, this.reqBinders[name][evt]);
        }, this);
        req._groupSend = req.send;
        req.send = function(options) {
            this.send(name, options);
            return req;
        } .bind(this);
        return this;
    },

    removeRequest: function(req) {
        var name = $type(req) == 'object' ? this.getName(req) : req;
        if (!name && $type(name) != 'string') return this;
        req = this.requests.get(name);
        if (!req) return this;
        ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt) {
            req.removeEvent(evt, this.reqBinders[name][evt]);
        }, this);
        req.send = req._groupSend;
        delete req._groupSend;
        return this;
    },

    getRunning: function() {
        return this.requests.filter(function(r) { return r.running; });
    },

    isRunning: function() {
        return !!this.getRunning().getKeys().length;
    },

    send: function(name, options) {
        var q = function() {
            this.requests.get(name)._groupSend(options);
            this.queue.erase(q);
        } .bind(this);
        q.name = name;
        if (this.getRunning().getKeys().length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
        else q();
        return this;
    },

    hasNext: function(name) {
        return (!name) ? !!this.queue.length : !!this.queue.filter(function(q) { return q.name == name; }).length;
    },

    resume: function() {
        this.error = false;
        (this.options.concurrent - this.getRunning().getKeys().length).times(this.runNext, this);
        return this;
    },

    runNext: function(name) {
        if (!this.queue.length) return this;
        if (!name) {
            this.queue[0]();
        } else {
            var found;
            this.queue.each(function(q) {
                if (!found && q.name == name) {
                    found = true;
                    q();
                }
            });
        }
        return this;
    },

    runAll: function() {
        this.queue.each(function(q) {
            q();
        });
        return this;
    },

    clear: function(name) {
        if (!name) {
            this.queue.empty();
        } else {
            this.queue = this.queue.map(function(q) {
                if (q.name != name) return q;
                else return false;
            }).filter(function(q) { return q; });
        }
        return this;
    },

    cancel: function(name) {
        this.requests.get(name).cancel();
        return this;
    },

    onRequest: function() {
        this.fireEvent('request', arguments);
    },

    onComplete: function() {
        this.fireEvent('complete', arguments);
    },

    onCancel: function() {
        if (this.options.autoAdvance && !this.error) this.runNext();
        this.fireEvent('cancel', arguments);
    },

    onSuccess: function() {
        if (this.options.autoAdvance && !this.error) this.runNext();
        this.fireEvent('success', arguments);
    },

    onFailure: function() {
        this.error = true;
        if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
        this.fireEvent('failure', arguments);
    },

    onException: function() {
        this.error = true;
        if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
        this.fireEvent('exception', arguments);
    }

});


/*
Script: Request.Periodical.js
Requests the same url at a time interval that increases when no data is returned from the requested server

License:
MIT-style license.

Authors:
Christoph Pojer

*/

Request.implement({

    options: {
        initialDelay: 5000,
        delay: 5000,
        limit: 60000
    },

    startTimer: function(data) {
        var fn = (function() {
            if (!this.running) this.send({ data: data });
        });
        this.timer = fn.delay(this.options.initialDelay, this);
        this.lastDelay = this.options.initialDelay;
        this.completeCheck = function(j) {
            $clear(this.timer);
            if (j) this.lastDelay = this.options.delay;
            else this.lastDelay = (this.lastDelay + this.options.delay).min(this.options.limit);
            this.timer = fn.delay(this.lastDelay, this);
        };
        this.addEvent('complete', this.completeCheck);
        return this;
    },

    stopTimer: function() {
        $clear(this.timer);
        this.removeEvent('complete', this.completeCheck);
        return this;
    }

});

/*
Script: Assets.js
Provides methods to dynamically load JavaScript, CSS, and Image files into the document.

License:
MIT-style license.

Authors:
Valerio Proietti
*/

var Asset = {

    javascript: function(source, properties) {
        properties = $extend({
            onload: $empty,
            document: document,
            check: $lambda(true)
        }, properties);

        var script = new Element('script', { src: source, type: 'text/javascript' });

        var load = properties.onload.bind(script), check = properties.check, doc = properties.document;
        delete properties.onload; delete properties.check; delete properties.document;

        script.addEvents({
            load: load,
            readystatechange: function() {
                if (['loaded', 'complete'].contains(this.readyState)) load();
            }
        }).set(properties);

        if (Browser.Engine.webkit419) var checker = (function() {
            if (!$try(check)) return;
            $clear(checker);
            load();
        }).periodical(50);

        return script.inject(doc.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: $empty,
            onabort: $empty,
            onerror: $empty
        }, properties);
        var image = new Image();
        var element = $(image) || new Element('img');
        ['load', 'abort', 'error'].each(function(name) {
            var type = 'on' + name;
            var event = properties[type];
            delete properties[type];
            image[type] = function() {
                if (!image) return;
                if (!element.parentNode) {
                    element.width = image.width;
                    element.height = image.height;
                }
                image = image.onload = image.onabort = image.onerror = null;
                event.delay(1, element, element);
                element.fireEvent(name, element, 1);
            };
        });
        image.src = element.src = source;
        if (image && image.complete) image.onload.delay(1);
        return element.set(properties);
    },

    images: function(sources, options) {
        options = $merge({
            onComplete: $empty,
            onProgress: $empty,
            onError: $empty
        }, options);
        sources = $splat(sources);
        var images = [];
        var counter = 0;
        return new Elements(sources.map(function(source) {
            return Asset.image(source, {
                onload: function() {
                    options.onProgress.call(this, counter, sources.indexOf(source));
                    counter++;
                    if (counter == sources.length) options.onComplete();
                },
                onerror: function() {
                    options.onError.call(this, counter, sources.indexOf(source));
                    counter++;
                    if (counter == sources.length) options.onComplete();
                }
            });
        }));
    }

};

/*
Script: Color.js
Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.

License:
MIT-style license.

Authors:
Valerio Proietti
*/

var Color = new Native({

    initialize: function(color, type) {
        if (arguments.length >= 3) {
            type = 'rgb'; color = Array.slice(arguments, 0, 3);
        } else if (typeof color == 'string') {
            if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
            else if (color.match(/hsb/)) color = color.hsbToRgb();
            else color = color.hexToRgb(true);
        }
        type = type || 'rgb';
        switch (type) {
            case 'hsb':
                var old = color;
                color = color.hsbToRgb();
                color.hsb = old;
                break;
            case 'hex': color = color.hexToRgb(true); break;
        }
        color.rgb = color.slice(0, 3);
        color.hsb = color.hsb || color.rgbToHsb();
        color.hex = color.rgbToHex();
        return $extend(color, this);
    }

});

Color.implement({

    mix: function() {
        var colors = Array.slice(arguments);
        var alpha = ($type(colors.getLast()) == 'number') ? colors.pop() : 50;
        var rgb = this.slice();
        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');
    }

});

var $RGB = function(r, g, b) {
    return new Color([r, g, b], 'rgb');
};

var $HSB = function(h, s, b) {
    return new Color([h, s, b], 'hsb');
};

var $HEX = function(hex) {
    return new Color(hex, 'hex');
};

Array.implement({

    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;
    }

});

String.implement({

    rgbToHsb: function() {
        var rgb = this.match(/\d{1,3}/g);
        return (rgb) ? rgb.rgbToHsb() : null;
    },

    hsbToRgb: function() {
        var hsb = this.match(/\d{1,3}/g);
        return (hsb) ? hsb.hsbToRgb() : null;
    }

});


/*
Script: Group.js
Class for monitoring collections of events

License:
MIT-style license.

Authors:
Valerio Proietti
*/

var Group = new Class({

    initialize: function() {
        this.instances = Array.flatten(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);
    }

});


/*
Script: Hash.Cookie.js
Class for creating, reading, and deleting Cookies in JSON format.

License:
MIT-style license.

Authors:
Valerio Proietti
Aaron Newton
*/

Hash.Cookie = new Class({

    Extends: Cookie,

    options: {
        autoSave: true
    },

    initialize: function(name, options) {
        this.parent(name, options);
        this.load();
    },

    save: function() {
        var value = JSON.encode(this.hash);
        if (!value || value.length > 4096) return false; //cookie would be truncated!
        if (value == '{}') this.dispose();
        else this.write(value);
        return true;
    },

    load: function() {
        this.hash = new Hash(JSON.decode(this.read(), true));
        return this;
    }

});

Hash.each(Hash.prototype, function(method, name) {
    if (typeof method == 'function') Hash.Cookie.implement(name, function() {
        var value = method.apply(this.hash, arguments);
        if (this.options.autoSave) this.save();
        return value;
    });
});

/*
Script: IframeShim.js
Defines IframeShim, a class for obscuring select lists and flash objects in IE.

License:
MIT-style license.

Authors:
Aaron Newton
*/

var IframeShim = new Class({

    Implements: [Options, Events, Class.Occlude],

    options: {
        className: 'iframeShim',
        display: false,
        zIndex: null,
        margin: 0,
        offset: { x: 0, y: 0 },
        browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
    },

    property: 'IframeShim',

    initialize: function(element, options) {
        this.element = $(element);
        if (this.occlude()) return this.occluded;
        this.setOptions(options);
        this.makeShim();
        return this;
    },

    makeShim: function() {
        if (this.options.browsers) {
            var zIndex = this.element.getStyle('zIndex').toInt();

            if (!zIndex) {
                var pos = this.element.getStyle('position');
                if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
                this.element.setStyle('zIndex', zIndex || 1);
            }
            zIndex = ($chk(this.options.zIndex) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
            if (zIndex < 0) zIndex = 1;
            this.shim = new Element('iframe', {
                src: (window.location.protocol == 'https') ? '://0' : 'javascript:void(0)',
                scrolling: 'no',
                frameborder: 0,
                styles: {
                    zIndex: zIndex,
                    position: 'absolute',
                    border: 'none',
                    filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
                },
                'class': this.options.className
            }).store('IframeShim', this);
            var inject = (function() {
                this.shim.inject(this.element, 'after');
                this[this.options.display ? 'show' : 'hide']();
                this.fireEvent('inject');
            }).bind(this);
            if (Browser.Engine.trident && !IframeShim.ready) window.addEvent('load', inject);
            else inject();
        } else {
            this.position = this.hide = this.show = this.dispose = $lambda(this);
        }
    },

    position: function() {
        if (!IframeShim.ready) return this;
        var size = this.element.measure(function() { return this.getSize(); });
        if ($type(this.options.margin)) {
            size.x = size.x - (this.options.margin * 2);
            size.y = size.y - (this.options.margin * 2);
            this.options.offset.x += this.options.margin;
            this.options.offset.y += this.options.margin;
        }
        if (this.shim) {
            this.shim.set({ width: size.x, height: size.y }).position({
                relativeTo: this.element,
                offset: this.options.offset
            });
        }
        return this;
    },

    hide: function() {
        if (this.shim) this.shim.setStyle('display', 'none');
        return this;
    },

    show: function() {
        if (this.shim) this.shim.setStyle('display', 'block');
        return this.position();
    },

    dispose: function() {
        if (this.shim) this.shim.dispose();
        return this;
    },

    destroy: function() {
        if (this.shim) this.shim.destroy();
        return this;
    }

});

window.addEvent('load', function() {
    IframeShim.ready = true;
});

/*
Script: Scroller.js
Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.

License:
MIT-style license.

Authors:
Valerio Proietti
*/

var Scroller = new Class({

    Implements: [Events, Options],

    options: {
        area: 20,
        velocity: 1,
        onChange: function(x, y) {
            this.element.scrollTo(x, y);
        },
        fps: 50
    },

    initialize: function(element, options) {
        this.setOptions(options);
        this.element = $(element);
        this.listener = ($type(this.element) != 'element') ? $(this.element.getDocument().body) : this.element;
        this.timer = null;
        this.bound = {
            attach: this.attach.bind(this),
            detach: this.detach.bind(this),
            getCoords: this.getCoords.bind(this)
        };
    },

    start: function() {
        this.listener.addEvents({
            mouseenter: this.bound.attach,
            mouseleave: this.bound.detach
        });
    },

    stop: function() {
        this.listener.removeEvents({
            mouseenter: this.bound.attach,
            mouseleave: this.bound.detach
        });
        this.timer = $clear(this.timer);
    },

    attach: function() {
        this.listener.addEvent('mousemove', this.bound.getCoords);
    },

    detach: function() {
        this.listener.removeEvent('mousemove', this.bound.getCoords);
        this.timer = $clear(this.timer);
    },

    getCoords: function(event) {
        this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
        if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
    },

    scroll: function() {
        var size = this.element.getSize(),
			scroll = this.element.getScroll(),
			pos = this.element.getOffsets(),
			scrollSize = this.element.getScrollSize(),
			change = { x: 0, y: 0 };
        for (var z in this.page) {
            if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0)
                change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
            else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z])
                change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
        }
        if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
    }

});

/*
Script: Tips.js
Class for creating nice tips that follow the mouse cursor when hovering an element.

License:
MIT-style license.

Authors:
Valerio Proietti
Christoph Pojer
*/

var Tips = new Class({

    Implements: [Events, Options],

    options: {
        onShow: function(tip) {
            tip.setStyle('visibility', 'visible');
        },
        onHide: function(tip) {
            tip.setStyle('visibility', 'hidden');
        },
        title: 'title',
        text: function(el) {
            return el.get('rel') || el.get('href');
        },
        showDelay: 100,
        hideDelay: 100,
        className: null,
        offset: { x: 16, y: 16 },
        fixed: false
    },

    initialize: function() {
        var params = Array.link(arguments, { options: Object.type, elements: $defined });
        if (params.options && params.options.offsets) params.options.offset = params.options.offsets;
        this.setOptions(params.options);
        this.container = new Element('div', { 'class': 'tip' });
        this.tip = this.getTip();

        if (params.elements) this.attach(params.elements);
    },

    getTip: function() {
        return new Element('div', {
            'class': this.options.className,
            styles: {
                visibility: 'hidden',
                display: 'none',
                position: 'absolute',
                top: 0,
                left: 0
            }
        }).adopt(
			new Element('div', { 'class': 'tip-top' }),
			this.container,
			new Element('div', { 'class': 'tip-bottom' })
		).inject(document.body);
    },

    attach: function(elements) {
        var read = function(option, element) {
            if (option == null) return '';
            return $type(option) == 'function' ? option(element) : element.get(option);
        };
        $$(elements).each(function(element) {
            var title = read(this.options.title, element);
            element.erase('title').store('tip:native', title).retrieve('tip:title', title);
            element.retrieve('tip:text', read(this.options.text, element));

            var events = ['enter', 'leave'];
            if (!this.options.fixed) events.push('move');

            events.each(function(value) {
                element.addEvent('mouse' + value, element.retrieve('tip:' + value, this['element' + value.capitalize()].bindWithEvent(this, element)));
            }, this);
        }, this);

        return this;
    },

    detach: function(elements) {
        $$(elements).each(function(element) {
            ['enter', 'leave', 'move'].each(function(value) {
                element.removeEvent('mouse' + value, element.retrieve('tip:' + value) || $empty);
            });

            element.eliminate('tip:enter').eliminate('tip:leave').eliminate('tip:move');

            if ($type(this.options.title) == 'string' && this.options.title == 'title') {
                var original = element.retrieve('tip:native');
                if (original) element.set('title', original);
            }
        }, this);

        return this;
    },

    elementEnter: function(event, element) {
        $A(this.container.childNodes).each(Element.dispose);

        ['title', 'text'].each(function(value) {
            var content = element.retrieve('tip:' + value);
            if (!content) return;

            this[value + 'Element'] = new Element('div', { 'class': 'tip-' + value }).inject(this.container);
            this.fill(this[value + 'Element'], content);
        }, this);

        this.timer = $clear(this.timer);
        this.timer = this.show.delay(this.options.showDelay, this, element);
        this.tip.setStyle('display', 'block');
        this.position((!this.options.fixed) ? event : { page: element.getPosition() });
    },

    elementLeave: function(event, element) {
        $clear(this.timer);
        this.tip.setStyle('display', 'none');
        this.timer = this.hide.delay(this.options.hideDelay, this, element);
    },

    elementMove: function(event) {
        this.position(event);
    },

    position: function(event) {
        var size = window.getSize(), scroll = window.getScroll(),
			tip = { x: this.tip.offsetWidth, y: this.tip.offsetHeight },
			props = { x: 'left', y: 'top' },
			obj = {};

        for (var z in props) {
            obj[props[z]] = event.page[z] + this.options.offset[z];
            if ((obj[props[z]] + tip[z] - scroll[z]) > size[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
        }

        this.tip.setStyles(obj);
    },

    fill: function(element, contents) {
        if (typeof contents == 'string') element.set('html', contents);
        else element.adopt(contents);
    },

    show: function(el) {
        this.fireEvent('show', [this.tip, el]);
    },

    hide: function(el) {
        this.fireEvent('hide', [this.tip, el]);
    }

});

/*
Script: Date.English.US.js
Date messages for US English.

License:
MIT-style license.

Authors:
Aaron Newton

*/

MooTools.lang.set('en-US', 'Date', {

    months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
    days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    //culture's date order: MM/DD/YYYY
    dateOrder: ['month', 'date', 'year'],
    shortDate: '%m/%d/%Y',
    shortTime: '%I:%M%p',
    AM: 'AM',
    PM: 'PM',

    /* Date.Extras */
    ordinal: function(dayOfMonth) {
        //1st, 2nd, 3rd, etc.
        return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
    },

    lessThanMinuteAgo: 'less than a minute ago',
    minuteAgo: 'about a minute ago',
    minutesAgo: '{delta} minutes ago',
    hourAgo: 'about an hour ago',
    hoursAgo: 'about {delta} hours ago',
    dayAgo: '1 day ago',
    daysAgo: '{delta} days ago',
    lessThanMinuteUntil: 'less than a minute from now',
    minuteUntil: 'about a minute from now',
    minutesUntil: '{delta} minutes from now',
    hourUntil: 'about an hour from now',
    hoursUntil: 'about {delta} hours from now',
    dayUntil: '1 day from now',
    daysUntil: '{delta} days from now'

});

/*
Script: FormValidator.English.js
Date messages for English.

License:
MIT-style license.

Authors:
Aaron Newton

*/

MooTools.lang.set('en-US', 'FormValidator', {

    required: 'This field is required.',
    minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
    maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
    integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
    numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
    digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
    alpha: 'Please use letters only (a-z) with in this field. No spaces or other characters are allowed.',
    alphanum: 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.',
    dateSuchAs: 'Please enter a valid date such as {date}',
    dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
    email: 'Please enter a valid email address. For example "fred@domain.com".',
    url: 'Please enter a valid URL such as http://www.google.com.',
    currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
    oneRequired: 'Please enter something for at least one of these inputs.',
    errorPrefix: 'Error: ',
    warningPrefix: 'Warning: ',

    //FormValidator.Extras

    noSpace: 'There can be no spaces in this input.',
    reqChkByNode: 'No items are selected.',
    requiredChk: 'This field is required.',
    reqChkByName: 'Please select a {label}.',
    match: 'This field needs to match the {matchName} field',
    startDate: 'the start date',
    endDate: 'the end date',
    currendDate: 'the current date',
    afterDate: 'The date should be the same or after {label}.',
    beforeDate: 'The date should be the same or before {label}.',
    startMonth: 'Please select a start month',
    sameMonth: 'These two dates must be in the same month - you must change one or the other.'

});
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();