/*
Script: dbug.js
    A wrapper for Firebug console.* statements.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/
var dbug = {
    logged: [], 
    timers: {},
    firebug: false, 
    enabled: false, 
    log: function() {
        dbug.logged.push(arguments);
    },
    nolog: function(msg) {
        dbug.logged.push(arguments);
    },
    time: function(name){
        dbug.timers[name] = new Date().getTime();
    },
    timeEnd: function(name){
        if (dbug.timers[name]) {
            var end = new Date().getTime() - dbug.timers[name];
            dbug.timers[name] = false;
            dbug.log('%s: %s', name, end);
        } else dbug.log('no such timer: %s', name);
    },
    enable: function(silent) { 
        if(dbug.firebug) {
            try {
                dbug.enabled = true;
                dbug.log = function(){
                        (console.debug || console.log).apply(console, arguments);
                };
                dbug.time = function(){
                    console.time.apply(console, arguments);
                };
                dbug.timeEnd = function(){
                    console.timeEnd.apply(console, arguments);
                };
                if(!silent) dbug.log('enabling dbug');
                for(var i=0;i<dbug.logged.length;i++){ dbug.log.apply(console, dbug.logged[i]); }
                dbug.logged=[];
            } catch(e) {
                dbug.enable.delay(400);
            }
        }
    },
    disable: function(){ 
        if(dbug.firebug) dbug.enabled = false;
        dbug.log = dbug.nolog;
        dbug.time = function(){};
        dbug.timeEnd = function(){};
    },
    cookie: function(set){
        var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
        var debugCookie = value ? unescape(value[1]) : false;
        if((debugCookie != 'true' || set) && !set) {
            dbug.enable();
            dbug.log('setting debugging cookie');
            var date = new Date();
            date.setTime(date.getTime()+(24*60*60*1000));
            document.cookie = 'jsdebug=true;expires='+date.toGMTString()+';path=/;';
        } else dbug.disableCookie();
    },
    disableCookie: function(){
        dbug.log('disabling debugging cookie');
        document.cookie = 'jsdebug=false;path=/;';
    }
};

(function(){
    var fb = typeof console != "undefined";
    var debugMethods = ['debug','info','warn','error','assert','dir','dirxml'];
    var otherMethods = ['trace','group','groupEnd','profile','profileEnd','count'];
    function set(methodList, defaultFunction) {
        for(var i = 0; i < methodList.length; i++){
            dbug[methodList[i]] = (fb && console[methodList[i]])?console[methodList[i]]:defaultFunction;
        }
    };
    set(debugMethods, dbug.log);
    set(otherMethods, function(){});
})();
if (typeof console != "undefined" && console.warn){
    dbug.firebug = true;
    var value = document.cookie.match('(?:^|;)\\s*jsdebug=([^;]*)');
    var debugCookie = value ? unescape(value[1]) : false;
    if(window.location.href.indexOf("jsdebug=true")>0 || debugCookie=='true') dbug.enable();
    if(debugCookie=='true')dbug.log('debugging cookie enabled');
    if(window.location.href.indexOf("jsdebugCookie=true")>0){
        dbug.cookie();
        if(!dbug.enabled)dbug.enable();
    }
    if(window.location.href.indexOf("jsdebugCookie=false")>0)dbug.disableCookie();
}


/*
Script: Browser.Extras.js
    Extends the Window native object to include methods useful in managing the window location and urls.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/
Browser.combine({
    getHost:function(url){
        url = $pick(url, window.location.href);
        var host = url;
        if(url.test('http://')){
            url = url.substring(url.indexOf('http://')+7,url.length);
            if(url.test(':')) url = url.substring(0, url.indexOf(":"));
            if(url.test('/')) return url.substring(0,url.indexOf('/'));
            return url;
        }
        return false;
    },
    getQueryStringValue: function(key, url) {
        try { 
            return Browser.getQueryStringValues(url)[key];
        }catch(e){return null;}
    },
    getQueryStringValues: function(url){
        var qs = $pick(url, window.location.search, '').split('?')[1]; //get the query string
        if (!$chk(qs)) return {};
        if (qs.test('#')) qs = qs.substring(0, qs.indexOf('#'));
        try {
       if (qs) return qs.parseQuery();
        } catch(e){
            return null;
        }
        return {}; //if there isn't one, return null
    },
    getPort: function(url) {
        url = $pick(url, window.location.href);
        var re = new RegExp(':([0-9]{4})');
        var m = re.exec(url);
      if (m == null) return false;
      else {
            var port = false;
            m.each(function(val){
                if($chk(parseInt(val))) port = val;
            });
      }
        return port;
    }
});
window.addEvent('domready', function(){
    var count = 0;
    //this is in case domready fires before string.extras loads
    function setQs(){
        function retry(){
            count++;
            if (count < 20) setQs.delay(50);
        }; 
        try {
            if (!Browser.set("qs", Browser.getQueryStringValues())) retry();
        } catch(e){
            retry();
        }
    }
    setQs();
});

/*
Script: IframeShim.js
    Defines IframeShim, a class for obscuring select lists and flash objects in IE.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/  
var IframeShim = new Class({
    Implements: [Options, Events],
    options: {
        name: '',
        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))
    },
    initialize: function (element, options){
        this.setOptions(options);
        //legacy
        if(this.options.offset && this.options.offset.top) this.options.offset.y = this.options.offset.top;
        if(this.options.offset && this.options.offset.left) this.options.offset.x = this.options.offset.left;
        this.element = $(element);
        this.makeShim();
        return;
    },
    makeShim: function(){
        this.shim = new Element('iframe');
        this.id = this.options.name || new Date().getTime() + "_shim";
        if(this.element.getStyle('z-Index').toInt()<1 || isNaN(this.element.getStyle('z-Index').toInt()))
            this.element.setStyle('z-Index',5);
        var z = this.element.getStyle('z-Index')-1;
        
        if($chk(this.options.zindex) && 
             this.element.getStyle('z-Index').toInt() > this.options.zindex)
             z = this.options.zindex;
            
        this.shim.setStyles({
            'position': 'absolute',
            'zIndex': z,
            'border': 'none',
            'filter': 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
        }).setProperties({
            'src':'javascript:void(0);',
            'frameborder':'0',
            'scrolling':'no',
            'id':this.id
        }).addClass(this.options.className);
        
        this.element.store('shim', this);

        var inject = function(){
            this.shim.inject(this.element, 'after');
            if(this.options.display) this.show();
            else this.hide();
            this.fireEvent('onInject');
        };
        if(this.options.browsers){
            if(Browser.Engine.trident && !IframeShim.ready) {
                window.addEvent('load', inject.bind(this));
            } else {
                inject.run(null, this);
            }
        }
    },
    position: function(shim){
        if(!this.options.browsers || !IframeShim.ready) return this;
        var before = this.element.getStyles('display', 'visibility', 'position');
        this.element.setStyles({
            display: 'block',
            position: 'absolute',
            visibility: 'hidden'
        });
        var size = this.element.getSize();
        this.element.setStyles(before);
        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;
        }
        this.shim.setStyles({
            'width': size.x,
            'height': size.y
        }).setPosition({
            relativeTo: this.element,
            offset: this.options.offset
        });
        return this;
    },
    hide: function(){
        if(this.options.browsers) this.shim.setStyle('display','none');
        return this;
    },
    show: function(){
        if(!this.options.browsers) return this;
        this.shim.setStyle('display','block');
        return this.position();
    },
    dispose: function(){
        if(this.options.browsers) this.shim.dispose();
        return this;
    }
});
window.addEvent('load', function(){
    IframeShim.ready = true;
});


/*
Script: Popup.js
    Defines the Popup class useful for making popup windows.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/

Browser.set("Popup", new Class({
    Implements:[Options, Events],
    options: {
        width: 500,
        height: 300,
        x: 50,
        y: 50,
        toolbar: 0,
        location: 0,
        directories: 0,
        status: 0,
        scrollbars: 'auto',
        resizable: 1,
        name: 'popup'
//  onBlock: $empty
    },
    initialize: function(url, options){
        this.url = url || false;
        this.setOptions(options);
        if(this.url) this.openWin();
    },
    openWin: function(url){
        url = url || this.url;
        var options = 'toolbar='+this.options.toolbar+
            ',location='+this.options.location+
            ',directories='+this.options.directories+
            ',status='+this.options.status+
            ',scrollbars='+this.options.scrollbars+
            ',resizable='+this.options.resizable+
            ',width='+this.options.width+
            ',height='+this.options.height+
            ',top='+this.options.y+
            ',left='+this.options.x;
        this.window = window.open(url, this.options.name, options);
        if (!this.window) {
            this.window = window.open('', this.options.name, options);
            this.window.location.href = url;
        }
        this.focus.delay(100, this);
        return this;
    },
    focus: function(){
        if (this.window) this.window.focus();
        else if (this.focusTries<10) this.focus.delay(100, this); //try again
        else {
            this.blocked = true;
            this.fireEvent('onBlock');
        }
        return this;
    },
    focusTries: 0,
    blocked: null,
    close: function(){
        this.window.close();
        return this;
    }
}));

/*
Script: Date.js
    Extends the Date native object to include methods useful in managing dates.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/

new Native({name: 'Date', initialize: Date, protect: true});
['now','parse','UTC'].each(function(method){
    Native.genericize(Date, method, true);
});
Date.$Methods = new Hash();
["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.set(method.toLowerCase(), method);
});
$each({
    ms: "Milliseconds",
    year: "FullYear",
    min: "Minutes",
    mo: "Month",
    sec: "Seconds",
    hr: "Hours"
}, function(value, key){
    Date.$Methods.set(key, value);
});


Date.implement({
    set: function(key, value) {
        key = key.toLowerCase();
        var m = Date.$Methods;
        if (m.has(key)) this['set'+m.get(key)](value);
        return this;
    },
    get: function(key) {
        key = key.toLowerCase();
        var m = Date.$Methods;
        if (m.has(key)) return this['get'+m.get(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;
                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() {
        this.set('hr', 0);
        this.set('min',0);
        this.set('sec', 0);
        this.set('ms', 0);
        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();
        }
    },
    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) ? '-' : '+')
            + Math.floor(Math.abs(off) / 60).zeroise(2)
            + (off % 60).zeroise(2);
    },
    parse: function(str) {
        this.set('time', Date.parse(str));
        return this;
    },
    format: function(f) {
        f = f || "%x %X";
        if (!this.valueOf()) return 'invalid date';
        //replace short-hand with actual format
        if (Date.$formats[f.toLowerCase()]) f = Date.$formats[f.toLowerCase()];
        var d = this;
        return f.replace(/\%([aAbBcdHIjmMpSUWwxXyYTZ])/g,
            function($1, $2) {
                switch ($2) {
                    case 'a': return Date.$days[d.get('day')].substr(0, 3);
                    case 'A': return Date.$days[d.get('day')];
                    case 'b': return Date.$months[d.get('month')].substr(0, 3);
                    case 'B': return Date.$months[d.get('month')];
                    case 'c': return d.toString();
                    case 'd': return d.get('date').zeroise(2);
                    case 'H': return d.get('hr').zeroise(2);
                    case 'I': return ((d.get('hr') % 12) || 12);
                    case 'j': return d.get('dayofyear').zeroise(3);
                    case 'm': return (d.get('mo') + 1).zeroise(2);
                    case 'M': return d.get('min').zeroise(2);
                    case 'p': return d.get('hr') < 12 ? 'AM' : 'PM';
                    case 'S': return d.get('seconds').zeroise(2);
                    case 'U': return d.get('week').zeroise(2);
                    case 'W': throw new Error('%W is not supported yet');
                    case 'w': return d.get('day');
                    case 'x': 
                        var c = Date.$cultures[Date.$culture];
                        //return d.format("%{0}{3}%{1}{3}%{2}".substitute(c.map(function(s){return s.substr(0,1)}))); //grr!
                        return d.format('%' + c[0].substr(0,1) +
                            c[3] + '%' + c[1].substr(0,1) +
                            c[3] + '%' + c[2].substr(0,1).toUpperCase());
                    case 'X': return d.format('%I:%M%p');
                    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.prototype.compare = Date.prototype.diff;
Date.prototype.strftime = Date.prototype.format;

Date.$nativeParse = Date.parse;

$extend(Date, {
    $months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
    $days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    $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];
    },
    $epoch: -1,
    $era: -2,
    $units: {
        ms: function(){return 1},
        second: function(){return 1000},
        minute: function(){return 60000},
        hour: function(){return 3600000},
        day: function(){return 86400000},
        week: function(){return 608400000},
        month: function(monthIndex, year) {
            var d = new Date();
            return Date.$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;
        }
    },
    $formats: {
        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'
    },
    
    isLeapYear: function(yr) {
        return new Date(yr,1,29).getDate()==29;
    },

    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);
    },
    
    parse: function(from) {
        var type = $type(from);
        if (type == 'number') return new Date(from);
        if (type != 'string') return from;
        if (!from.length) return null;
        for (var i = 0, j = Date.$parsePatterns.length; i < j; i++) {
            var r = Date.$parsePatterns[i].re.exec(from);
            if (r) {
                try {
                    return Date.$parsePatterns[i].handler(r);
                } catch(e) {
                    dbug.log('date parse error: ', e);
                    return null;
                }
            }
        }
        return new Date(Date.$nativeParse(from));
    },

    parseMonth: function(month, num) {
        var ret = -1;
        switch ($type(month)) {
            case 'object':
                ret = Date.$months[month.get('mo')];
                break;
            case 'number':
                ret = Date.$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.$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.$months.indexOf(ret) : ret;
    },

    parseDay: function(day, num) {
        var ret = -1;
        switch ($type(day)) {
            case 'number':
                ret = Date.$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.$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.$days.indexOf(ret) : ret;
    },
    
    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;
    },

    $cultures: {
        'US': ['month', 'date', 'year', '/'],
        'GB': ['date', 'month', 'year', '/']
    },

    $culture: 'US',
    
    $cIndex: function(unit){
        return Date.$cultures[Date.$culture].indexOf(unit)+1;
    },

    $parsePatterns: [
        {
            //"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();
                var culture = Date.$cultures[Date.$culture];
                d.set('year', bits[Date.$cIndex('year')]);
                d.set('month', bits[Date.$cIndex('month')] - 1);
                d.set('date', bits[Date.$cIndex('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})(\w{2})$/,
            handler: function(bits){
                var d = new Date();
                d.set('year', bits[Date.$cIndex('year')]);
                d.set('month', bits[Date.$cIndex('month')] - 1);
                d.set('date', bits[Date.$cIndex('date')]);
                d.set('hr', bits[4]);
                d.set('min', bits[5]);
                d.set('ampm', bits[6]);
                return Date.fixY2K(d);
            }
        }
    ]
});

Number.implement({
    zeroise: function(length) {
        return String(this).zeroise(length);
    }
});

String.implement({
    repeat: function(times) {
        var ret = [];
        for (var i = 0; i < times; i++) ret.push(this);
        return ret.join('');
    },
    zeroise: function(length) {
        return '0'.repeat(length - this.length) + this;
    }

});


/*
Script: Date.Extras.js
    Extends the Date native object to include extra methods (on top of those in Date.js).

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/

["LastDayOfMonth", "Ordinal"].each(function(method) {
    Date.$Methods.set(method.toLowerCase(), method);
});

Date.implement({
    timeAgoInWords: function(){
        var relative_to = (arguments.length > 0) ? arguments[1] : new Date();
        return Date.distanceOfTimeInWords(this, relative_to, arguments[2]);
    },
    getOrdinal: function() {
        var test = this.get('date');
        return (test > 3 && test < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(test % 10, 4)];
    },
    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();
    }
});

$extend(Date, {
// http://twitter.pbwiki.com/RelativeTimeScripts
    distanceOfTimeInWords: function(fromTime, toTime, includeTime) {
        var delta = ((toTime.getTime() - fromTime.getTime()) / 1000).toInt();
        if(delta < 60) {
            return 'less than a minute ago';
        } else if(delta < 120) {
            return 'about a minute ago';
        } else if(delta < (45*60)) {
            return (delta / 60).round() + ' minutes ago';
        } else if(delta < (90*60)) {
            return 'about an hour ago';
        } else if(delta < (24*60*60)) {
            return 'about ' + (delta / 3600).round() + ' hours ago';
        } else if(delta < (48*60*60)) {
            return '1 day ago';
        } else {
            var days = (delta / 86400).round();
            if(days > 30) {
                    var fmt  = '%B %d';
                    if(toTime.getYear() != fromTime.getYear()) { fmt += ', %Y'; }
                    if(includeTime) fmt += ' %I:%M %p';
                    return fromTime.strftime(fmt);
            } else {
                return days + " days ago";
            }
        }
    }
});

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[2]) d.setMonth(bits[2] - 1);
            if (bits[3]) d.setDate(bits[3]);
            if (bits[4]) d.setHours(bits[4]);
            if (bits[5]) d.setMinutes(bits[5]);
            if (bits[6]) d.setSeconds(bits[6]);
            if (bits[7]) d.setMilliseconds(('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.setDate(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.setMonth(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.setMonth(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.setMonth(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.setDate(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.setDate(bits[0]);
            var m;
            Date.$months.each(function(mo, i){
                if (new RegExp("^"+bits[1]).test(mo)) m = i;
            });
            d.setMonth(m);
            d.setHours(bits[2].split(":")[0]);
            d.setMinutes(bits[2].split(":")[1]);
            d.setMilliseconds(0);
            return d;
        }
    }
/*   {
        re: /^last (\w+)$/i,
        handler: function(bits) {
            throw new Error('Not yet implemented');
        }
    }   */
]);


/*
Script: Hash.Extras.js
    Extends the Hash native object to include getFromPath which allows a path notation to child elements.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/

Hash.implement({
    getFromPath: function(notation) {
        var source = this.getClean();
        notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match) {
            if (!source) return;
            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;
    }
});

/*
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:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/
String.implement({
    stripTags: function() {
        return this.replace(/<\/?[^>]+>/gi, '');
  },
    parseQuery: function(encodeKeys, encodeValues) {
        encodeKeys = $pick(encodeKeys, true);
        encodeValues = $pick(encodeValues, true);
        var vars = this.split(/[&;]/);
        var rs = {};
        if (vars.length) vars.each(function(val) {
            var keys = val.split('=');
            if (keys.length && keys.length == 2) {
                rs[(encodeKeys)?encodeURIComponent(keys[0]):keys[0]] = (encodeValues)?encodeURIComponent(keys[1]):keys[1];
            }
        });
        return rs;
    },
    tidy: function() {
        var txt = this.toString();
        $each({
            "[\xa0\u2002\u2003\u2009]": " ",
            "\xb7": "*",
            "[\u2018\u2019]": "'",
            "[\u201c\u201d]": '"',
            "\u2026": "...",
            "\u2013": "-",
            "\u2014": "--",
            "\uFFFD": "&raquo;"
        }, function(value, key){
            txt = txt.replace(new RegExp(key, 'g'), value);
        });
        return txt;
    },
    cleanQueryString: function(method){
        return this.split("&").filter(method||function(set){
            return $chk(set.split("=")[1]);
        }).join("&");
    }
});


/*
Script: Element.Measure.js
    Extends the Element native object to include methods useful in measuring dimensions.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/

Element.implement({

    expose: function(){
        if (this.getStyle('display') != 'none') return $empty;
        var before = {};
        var styles = { visibility: 'hidden', display: 'block', position:'absolute' };
        //use this method instead of getStyles 
        $each(styles, function(value, style){
            before[style] = this.style[style]||'';
        }, this);
        //this.getStyles('visibility', 'display', 'position');
        this.setStyles(styles);
        return (function(){ this.setStyles(before); }).bind(this);
    },
    
    getDimensions: function(options) {
        options = $merge({computeSize: false},options);
        var dim = {};
        function getSize(el, options){
            return (options.computeSize)?el.getComputedSize(options):el.getSize();
        };
        if(this.getStyle('display') == 'none'){
            var restore = this.expose();
            dim = getSize(this, options); //works now, because the display isn't none
            restore(); //put it back where it was
        } 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 = this.getStyles.apply(this, getStyles);
        var subtracted = [];
        $each(options.plains, function(plain, key){ //keys: width, height, plains: ['left','right'], ['top','bottom']
            size['total'+key.capitalize()] = 0;
            size['computed'+key.capitalize()] = 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(); //styles['padding-left'] = 5;
                        if(isNaN(styles[style]))styles[style]=0;
                        size['total'+key.capitalize()] = size['total'+key.capitalize()]+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'+key.capitalize()] = size['computed'+key.capitalize()]-styles[style];
                    }
                });
            });
        });
        if($chk(size.width)) {
            size.width = size.width+this.offsetWidth+size.computedWidth;
            size.totalWidth = size.width + size.totalWidth;
            delete size.computedWidth;
        }
        if($chk(size.height)) {
            size.height = size.height+this.offsetHeight+size.computedHeight;
            size.totalHeight = size.height + size.totalHeight;
            delete size.computedHeight;
        }
        return $extend(styles, size);
    }
});


/*
Script: Element.Position.js
    Extends the Element native object to include methods useful positioning elements relative to others.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/

Element.implement({

    setPosition: function(options){
        $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
        }, options);
        //compute the offset of the parent positioned element if this element is in one
        var parentOffset = {x: 0, y: 0};
        var parentPositioned = false;
        var putItBack = this.expose();
        var offsetParent = this.getOffsetParent();
        putItBack();
        if(offsetParent && offsetParent != this.getDocument().body) {
            var putItBack = offsetParent.expose();
            parentOffset = offsetParent.getPosition();
            putItBack();
            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
        function fixValue(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 top = (rel == document.body)?window.getScroll().y:rel.getPosition().y;
    var left = (rel == document.body)?window.getScroll().x:rel.getPosition().x;
        
        if (top < 0) top = 0;
    if (left < 0) left = 0;
        var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
        if (options.ignoreMargins) {
            options.offset.x = options.offset.x - parentOffset.x - offsetParent.getStyle('border-left-width').toInt()||0;
            options.offset.y = options.offset.y - parentOffset.y - offsetParent.getStyle('border-top-width').toInt()||0;
        }
        var pos = {};
        var prefY = options.offset.y.toInt();
        var prefX = options.offset.x.toInt();
        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)?window.getSize().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)?window.getSize().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)?pos.x:0).toInt(),
            top: ((pos.y >= 0 || parentPositioned)?pos.y:0).toInt()
        };
        if(rel.getStyle('position') == "fixed"||options.relFixedPosition) {
            pos.top = pos.top.toInt() + window.getScroll().y;
            pos.left = pos.left.toInt() + window.getScroll().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:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/

Element.implement({
    isVisible: function() {
        return this.getStyle('display') != 'none';
    },
    toggle: function() {
        return this[this.isVisible() ? 'hide' : 'show']();
    },
    hide: function() {
        var d;
        try {
            //IE fails here if the element is not in the dom
            d = this.getStyle('display');
        } catch(e){}
        this.store('originalDisplay', d||'block'); 
        this.setStyle('display','none');
        return this;
    },
    show: function(display) {
        original = this.retrieve('originalDisplay')?this.retrieve('originalDisplay'):this.get('originalDisplay');
        this.setStyle('display',(display || original || 'block'));
        return this;
    },
  swapClass: function(remove, add) {
    return this.removeClass(remove).addClass(add);
  },
    //TODO
    //DO NOT USE THIS METHOD
    //it is temporary, as Mootools 1.1 will negate its requirement
    fxOpacityOk: function(){
        return !Browser.Engine.trident4;
    }
});

/*
Script: MooScroller.js

Recreates the standard scrollbar behavior for elements with overflow but using DOM elements so that the scroll bar elements are completely styleable by css.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/
var MooScroller = new Class({
    Implements: [Options, Events],
    options: {
        maxThumbSize: 10,
        mode: 'vertical',
        width: 0, //required only for mode: horizontal
        scrollSteps: 10,
        wheel: true,
        scrollLinks: {
            forward: 'scrollForward',
            back: 'scrollBack'
        }
//      onScroll: $empty,
//      onPage: $empty
    },

    initialize: function(content, knob, options){
        this.setOptions(options);
        this.horz = (this.options.mode == "horizontal");

        this.content = $(content).setStyle('overflow', 'hidden');
        this.knob = $(knob);
        this.track = this.knob.getParent();
        this.setPositions();
        
        if(this.horz && this.options.width) {
            this.wrapper = new Element('div');
            this.content.getChildren().each(function(child){
                this.wrapper.adopt(child);
            }, this);
            this.wrapper.inject(this.content).setStyle('width', this.options.width);
        }
        

        this.bound = {
            'start': this.start.bind(this),
            'end': this.end.bind(this),
            'drag': this.drag.bind(this),
            'wheel': this.wheel.bind(this),
            'page': this.page.bind(this)
        };

        this.position = {};
        this.mouse = {};
        this.update();
        this.attach();
        
        var clearScroll = function (){
            $clear(this.scrolling);
        }.bind(this);
        ['forward','back'].each(function(direction) {
            var lnk = $(this.options.scrollLinks[direction]);
            if(lnk) {
                lnk.addEvents({
                    mousedown: function() {
                        this.scrolling = this[direction].periodical(50, this);
                    }.bind(this),
                    mouseup: clearScroll.bind(this),
                    click: clearScroll.bind(this)
                });
            }
        }, this);
        this.knob.addEvent('click', clearScroll.bind(this));
        window.addEvent('domready', function(){
            try {
                $(document.body).addEvent('mouseup', clearScroll.bind(this));
            }catch(e){}
        }.bind(this));
    },
    setPositions: function(){
        [this.track, this.knob].each(function(el){
            if (el.getStyle('position') == 'static') el.setStyle('position','relative');
        });

    },
    toElement: function(){
        return this.content;
    },
    update: function(){
        var plain = this.horz?'Width':'Height';
        this.contentSize = this.content['offset'+plain];
        this.contentScrollSize = this.content['scroll'+plain];
        this.trackSize = this.track['offset'+plain];

        this.contentRatio = this.contentSize / this.contentScrollSize;

        this.knobSize = (this.trackSize * this.contentRatio).limit(this.options.maxThumbSize, this.trackSize);

        this.scrollRatio = this.contentScrollSize / this.trackSize;
        this.knob.setStyle(plain.toLowerCase(), this.knobSize);

        this.updateThumbFromContentScroll();
        this.updateContentFromThumbPosition();
    },

    updateContentFromThumbPosition: function(){
        this.content[this.horz?'scrollLeft':'scrollTop'] = this.position.now * this.scrollRatio;
    },

    updateThumbFromContentScroll: function(){
        this.position.now = (this.content[this.horz?'scrollLeft':'scrollTop'] / this.scrollRatio).limit(0, (this.trackSize - this.knobSize));
        this.knob.setStyle(this.horz?'left':'top', this.position.now);
    },

    attach: function(){
        this.knob.addEvent('mousedown', this.bound.start);
        if (this.options.scrollSteps) this.content.addEvent('mousewheel', this.bound.wheel);
        this.track.addEvent('mouseup', this.bound.page);
    },

    wheel: function(event){
        this.scroll(-(event.wheel * this.options.scrollSteps));
        this.updateThumbFromContentScroll();
        event.stop();
    },

    scroll: function(steps){
        steps = steps||this.options.scrollSteps;
        this.content[this.horz?'scrollLeft':'scrollTop'] += steps;
        this.updateThumbFromContentScroll();
        this.fireEvent('onScroll', steps);
    },
    forward: function(steps){
        this.scroll(steps);
    },
    back: function(steps){
        steps = steps||this.options.scrollSteps;
        this.scroll(-steps);
    },

    page: function(event){
        var axis = this.horz?'x':'y';
        var forward = (event.page[axis] > this.knob.getPosition()[axis]);
        this.scroll((forward?1:-1)*this.content['offset'+(this.horz?'Width':'Height')]);
        this.updateThumbFromContentScroll();
        this.fireEvent('onPage', forward);
        event.stop();
    },

    
    start: function(event){
        var axis = this.horz?'x':'y';
        this.mouse.start = event.page[axis];
        this.position.start = this.knob.getStyle(this.horz?'left':'top').toInt();
        document.addEvent('mousemove', this.bound.drag);
        document.addEvent('mouseup', this.bound.end);
        this.knob.addEvent('mouseup', this.bound.end);
        event.stop();
    },

    end: function(event){
        document.removeEvent('mousemove', this.bound.drag);
        document.removeEvent('mouseup', this.bound.end);
        this.knob.removeEvent('mouseup', this.bound.end);
        event.stop();
    },

    drag: function(event){
        var axis = this.horz?'x':'y';
        this.mouse.now = event.page[axis];
        this.position.now = (this.position.start + (this.mouse.now - this.mouse.start)).limit(0, (this.trackSize - this.knobSize));
        this.updateContentFromThumbPosition();
        this.updateThumbFromContentScroll();
        event.stop();
    }

});


/*
Script: SimpleCarousel.js

Builds a carousel object that manages the basic functions of a generic carousel (a carousel here being a collection of "slides" that play from one to the next, with a collection of "buttons" that reference each slide).

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/
var SimpleCarousel = new Class({
    Implements: [Options, Events],
    options: {
//      onRotate: $empty,
//      onStop: $empty,
//      onAutoPlay: $empty,
//      onShowSlide: $empty,
        slideInterval: 4000,
        transitionDuration: 700,
        startIndex: Math.floor(Math.random()*4),
        buttonOnClass: "selected",
        buttonOffClass: "off",
        rotateAction: "none",
        rotateActionDuration: 100,
        autoplay: true
    },
    initialize: function(container, slides, buttons, options){
        this.container = $(container);
        if(this.container.hasClass('hasCarousel')) return false;
        this.setOptions(options);
        this.container.addClass('hasCarousel');
        this.slides = $$(slides);
        this.buttons = $$(buttons);
        this.createFx();
        this.showSlide(this.options.startIndex);
        if(this.options.autoplay) this.autoplay();
        if(this.options.rotateAction != 'none') this.setupAction(this.options.rotateAction);
        return this;
    },
    toElement: function(){
        return this.container;
    },
    setupAction: function(action) {
        this.buttons.each(function(el, idx){
            $(el).addEvent(action, function() {
                this.slideFx.setOptions(this.slideFx.options, {duration: this.options.rotateActionDuration});
                if(this.currentSlide != idx) this.showSlide(idx);
                this.stop();
            }.bind(this));
        }, this);
    },
    createFx: function(){
        if (!this.slideFx) this.slideFx = new Fx.Elements(this.slides, {duration: this.options.transitionDuration});
        this.slides.each(function(slide){
            slide.setStyle('opacity',0);
        });
    },
    showSlide: function(slideIndex){
        var action = {};
        this.slides.each(function(slide, index){
            if(index == slideIndex && index != this.currentSlide){ //show
                $(this.buttons[index]).swapClass(this.options.buttonOffClass, this.options.buttonOnClass);
                action[index.toString()] = {
                    opacity: 1
                };
            } else {
                $(this.buttons[index]).swapClass(this.options.buttonOnClass, this.options.buttonOffClass);
                action[index.toString()] = {
                    opacity:0
                };
            }
        }, this);
        this.fireEvent('onShowSlide', slideIndex);
        this.currentSlide = slideIndex;
        this.slideFx.start(action);
        return this;
    },
    autoplay: function(){
        this.slideshowInt = this.rotate.periodical(this.options.slideInterval, this);
        this.fireEvent('onAutoPlay');
        return this;
    },
    stop: function(){
        $clear(this.slideshowInt);
        this.fireEvent('onStop');
        return this;
    },
    rotate: function(){
        current = this.currentSlide;
        next = (current+1 >= this.slides.length) ? 0 : current+1;
        this.showSlide(next);
        this.fireEvent('onRotate', next);
        return this;
    }
});

/*
Script: SimpleSlideShow.js

Makes a very, very simple slideshow gallery with a collection of dom elements and previous and next buttons.

License:
    http://clientside.cnet.com/wiki/cnet-libraries#license
*/
    var SimpleSlideShow = new Class({
        Implements: [Events, Options, Chain],
        options: {
            startIndex: 0,
            slides: [],
            currentSlideClass: 'currentSlide',
            currentIndexContainer: false,
            maxContainer: false,
            nextLink: false,
            prevLink: false,
            wrap: true,
            disabledLinkClass: 'disabled',
//          onNext: $empty,
//          onPrev: $empty,
//          onSlideClick: $empty,
            crossFadeOptions: {}
        },
        initialize: function(options){
            this.setOptions(options);
            this.slides = this.options.slides;
            this.makeSlides();
            this.setCounters();
            this.setUpNav();
            this.now = this.options.startIndex;
            if(this.slides.length > 0) this.show(this.now);
        },
        setCounters: function(){
            if($(this.options.currentIndexContainer))$(this.options.currentIndexContainer).set('html', this.now+1);
            if($(this.options.maxContainer))$(this.options.maxContainer).set('html', this.slides.length);
        },
        makeSlides: function(){
            //hide them all
            this.slides.each(function(slide, index){
                if(index != this.now) slide.setStyle('display', 'none');
                else slide.setStyle('display', 'block');
                this.makeSlide(slide);
            }, this);
        },
        makeSlide: function(slide){
            slide.addEvent('click', function(){ this.fireEvent('onSlideClick'); }.bind(this));
        },
        setUpNav: function(){   
            if($(this.options.nextLink)) $(this.options.nextLink).addEvent('click', function(){
                    this.forward();
                }.bind(this));
            if($(this.options.prevLink)) $(this.options.prevLink).addEvent('click', function(){
                    this.back();
                }.bind(this));
        },
        forward: function(){
            var fireEvent = false;
            if($type(this.now) && this.now < this.slides.length-1) fireEvent = this.show(this.now+1);
            else if($type(this.now) && this.options.wrap) fireEvent = this.show(0);
            else if(!$type(this.now)) fireEvent = this.show(this.options.startIndex);
            if (fireEvent) this.fireEvent('onNext');
            if(this.now == this.slides.length && !this.options.wrap && $(this.options.nextLink))
                $(this.options.nextLink).addClass(this.options.disabledLinkClass);
            else if ($(this.options.nextLink)) $(this.options.nextLink).removeClass(this.options.disabledLinkClass);
            return this;
        },
        back: function(){
            if(this.now > 0) {
                this.show(this.now-1);
                this.fireEvent('onPrev');
            } else if(this.options.wrap && this.slides.length > 1) {
                this.show(this.slides.length-1);
                this.fireEvent('onPrev');
            }
            if(this.now == 0 && !this.options.wrap && $(this.options.prevSlide))
                $(this.options.prevSlide).addClass(this.options.disabledLinkClass);
            else if ($(this.options.prevSlide)) 
                $(this.options.prevSlide).removeClass(this.options.disabledLinkClass);
            return this;
        },
        show: function(index){
            if (this.showing) return this.chain(this.show.bind(this, index));
            var now = this.now;
            var s = this.slides[index]; //saving bytes
            function fadeIn(s, resetOpacity){
                s.setStyle('display','block');
                if(s.fxOpacityOk()) {
                    if(resetOpacity) s.setStyle('opacity', 0);
                    s.set('tween', this.options.crossFadeOptions).get('tween').start('opacity', 1).chain(function(){
                        this.showing = false;
                        this.callChain();
                    }.bind(this));
                }
            };
            if(s) {
                if($type(this.now) && this.now != index){
                    if(s.fxOpacityOk()) {
                        var fx = this.slides[this.now].get('tween');
                        fx.setOptions(this.options.crossFadeOptions);
                        this.showing = true;
                        fx.start('opacity', 0).chain(function(){
                            this.slides[now].setStyle('display','none');
                            s.addClass(this.options.currentSlideClass);
                            fadeIn.run([s, true], this);
                        }.bind(this));
                    } else {
                        this.slides[this.now].setStyle('display','none');
                        fadeIn.run(s, this);
                    }
                } else fadeIn.run(s, this);
                this.now = index;
                this.setCounters();
            }
        },
        slideClick: function(){
            this.fireEvent('onSlideClick', [this.slides[this.now], this.now]);
        }
    });

    var SimpleImageSlideShow = new Class({
        Extends: SimpleSlideShow,
        options: {
            imgUrls: [],
            imgClass: 'screenshot',
            container: false
        },
        initialize: function(options){
            this.parent(options);
            this.options.imgUrls.each(function(url){
                this.addImg(url);
            }, this);
            this.show(this.options.startIndex);
        },
        addImg: function(url){
            if($(this.options.container)) {
                var img = new Element('img', {
                    'src': url,
                    'id': this.options.imgClass+this.slides.length
                }).addClass(this.options.imgClass).setStyle(
                    'display', 'none').inject($(this.options.container)).addEvent(
                    'click', this.slideClick.bind(this));
                this.slides.push(img);
                this.makeSlide(img);
                this.setCounters();
            }
            return this;
        }
    });
