/*
Script: Clientcide.js
  The Clientcide namespace.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var Clientcide = {
  version: '812',
  setAssetLocation: function(baseHref) {
    if (window.StickyWin && StickyWin.ui) {
      StickyWin.UI.refactor({
        options: {
          baseHref: baseHref + '/stickyWinHTML/'
        }
      });
      if (StickyWin.alert) {
        var CGFsimpleErrorPopup = StickyWin.alert.bind(window);
        StickyWin.alert = function(msghdr, msg, base) {
            return CGFsimpleErrorPopup(msghdr, msg, base||baseHref + "/simple.error.popup");
        };
      }
      if (StickyWin.UI.Pointy) {
        StickyWin.UI.Pointy.refactor({
          options: {
            baseHref: baseHref + '/PointyTip/'
          }
        });
      }
    }
    if (window.TagMaker) {
      TagMaker = TagMaker.refactor({
          options: {
              baseHref: baseHref + '/tips/'
          }
      });
    }
    if (window.ProductPicker) {
      ProductPicker.refactor({
          options:{
              baseHref: baseHref + '/Picker'
          }
      });
    }

    if (window.Autocompleter) {
      var AcClientcide = {
          options: {
            baseHref: baseHref + '/autocompleter/'
          }
      };
      Autocompleter.Base.refactor(AcClientcide);
      if (Autocompleter.Ajax) {
        ["Base", "Xhtml", "Json"].each(function(c){
          if(Autocompleter.Ajax[c]) Autocompleter.Ajax[c].refactor(AcClientcide);
        });
      }
      if (Autocompleter.Local) Autocompleter.Local.refactor(AcClientcide);
      if (Autocompleter.JsonP) Autocompleter.JsonP.refactor(AcClientcide);
    }

    if (window.Lightbox) {
      Lightbox.refactor({
          options: {
              assetBaseUrl: baseHref + '/slimbox/'
          }
      });
    }

    if (window.Waiter) {
      Waiter.refactor({
        options: {
          baseHref: baseHref + '/waiter/'
        }
      });
    }
  },
  preLoadCss: function(){
    if (window.DatePicker) new DatePicker();
    if (window.ProductPicker) new ProductPicker();
    if (window.TagMaker) new TagMaker();
    if (window.StickyWin && StickyWin.ui) StickyWin.ui();
    if (window.StickyWin && StickyWin.pointy) StickyWin.pointy();
    Clientcide.preloaded = true;
    return true;
  },
  preloaded: false
};
(function(){
  if (!window.addEvent) return;
  var preload = function(){
    if (window.dbug) dbug.log('preloading clientcide css');
    if (!Clientcide.preloaded) Clientcide.preLoadCss();
  };
  window.addEvent('domready', preload);
  window.addEvent('load', preload);
})();
setCNETAssetBaseHref = Clientcide.setAssetLocation;

/*
Script: dbug.js
  A wrapper for Firebug console.* statements.

License:
  http://www.clientcide.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) {
    var con = window.firebug ? firebug.d.console.cmd : window.console;

    if((!!window.console && !!window.console.warn) || window.firebug) {
      try {
        dbug.enabled = true;
        dbug.log = function(){
            (con.debug || con.log).apply(con, arguments);
        };
        dbug.time = function(){
          con.time.apply(con, arguments);
        };
        dbug.timeEnd = function(){
          con.timeEnd.apply(con, arguments);
        };
        if(!silent) dbug.log('enabling dbug');
        for(var i=0;i<dbug.logged.length;i++){ dbug.log.apply(con, 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((!$defined(set) && debugCookie != 'true') || ($defined(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 = !!window.console || !!window.firebug;
  var con = window.firebug ? window.firebug.d.console.cmd : window.console;
  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 && con[methodList[i]])?con[methodList[i]]:defaultFunction;
    }
  };
  set(debugMethods, dbug.log);
  set(otherMethods, function(){});
})();
if ((!!window.console && !!window.console.warn) || window.firebug){
  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: Class.Refactor.js
  Extends a class onto itself with new property, preserving any items attached to the class's namespace.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
Class.refactor = function(orig, props) {
  props = $extend($unlink(props), { Extends: orig });
  var update = new Class(props);
  $each(orig, function(v, k) {
    update[k] = update[k] || v;
  });
  return update;
};

$extend(Class.prototype, {
  refactor: function(props){
    this.prototype = Class.refactor(this, props).prototype;
    return this;
  }
});

/*
Script: Class.Binds.js
  Automatically binds specified methods in a class to the instance of the class.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
(function(){
  var binder = function(self, binds){
    var oldInit = self.initialize;
    self.initialize = function() {
      Array.flatten(binds).each(function(binder) {
        var original = this[binder];
        this[binder] = function(){
          return original.apply(this, arguments);
        }.bind(this);
        this[binder].parent = original.parent;
      }, this);
      return oldInit.apply(this,arguments);
    };
    return self;
  };
  Class.Mutators.Binds = function(self, binds) {
    if (!self.Binds) return self;
    delete self.Binds;
    return binder(self, binds);
  };
  Class.Mutators.binds = function(self, binds) {
    if (!self.binds) return self;
    delete self['binds'];
    return binder(self, binds);
  };
})();

/*
Script: Chain.Wait.js
  Adds a method to inject pauses between chained events.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
(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){
          this.get(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: Occlude.js
  Prevents a class from being applied to a DOM element twice.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var Occlude = new Class({
  // usage: if(this.occlude()) return this.occluded;
  occlude: function(property, element) {
    element = $(element || this.element);
    var instance = element.retrieve(property || this.property);
    if (instance && (this.occluded === null || this.occluded)) {
      this.occluded = instance;
    } else {
      this.occluded = false;
      element.store(property || this.property, this);
    }
    return this.occluded||false;
  }
});

/*
Script: ToElement.js
  Defines the toElement method for a class.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var ToElement = new Class({
  toElement: function(){
    return this.element;
  }
});

/*
Script: Browser.Extras.js
  Extends the Window native object to include methods useful in managing the window location and urls.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/

$extend(Browser, {
  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;
  },
  redraw: function(element){
    var n = document.createTextNode(' ');
    this.adopt(n);
    (function(){n.dispose()}).delay(1);
    return this;
  }
});
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.getQueryStringValues()) retry();
      else Browser.qs = Browser.getQueryStringValues();
    } catch(e){
      retry();
    }
  }
  setQs();
});

/*
Script: FixPNG.js
  Extends the Browser hash object to include methods useful in managing the window location and urls.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
$extend(Browser, {
  fixPNG: function(el) {
    try {
      if (Browser.Engine.trident4){
        el = $(el);
        if (!el) return el;
        if (el.get('tag') == "img" && el.get('src').test(".png")) {
          var vis = el.isVisible();
          try { //safari sometimes crashes here, so catch it
            dim = el.getSize();
          }catch(e){}
          if(!vis){
            var before = {};
            //use this method instead of getStyles
            ['visibility', 'display', 'position'].each(function(style){
              before[style] = this.style[style]||'';
            }, this);
            //this.getStyles('visibility', 'display', 'position');
            this.setStyles({
              visibility: 'hidden',
              display: 'block',
              position:'absolute'
            });
            dim = el.getSize(); //works now, because the display isn't none
            this.setStyles(before); //put it back where it was
            el.hide();
          }
          var replacement = new Element('span', {
            id:(el.id)?el.id:'',
            'class':(el.className)?el.className:'',
            title:(el.title)?el.title:(el.alt)?el.alt:'',
            styles: {
              display: vis?'inline-block':'none',
              width: dim.x,
              height: dim.y,
              filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader (src='"
                + el.src + "', sizingMethod='scale');"
            },
            src: el.src
          });
          if(el.style.cssText) {
            try {
              var styles = {};
              var s = el.style.cssText.split(';');
              s.each(function(style){
                var n = style.split(':');
                styles[n[0]] = n[1];
              });
              replacement.setStyle(styles);
            } catch(e){ dbug.log('fixPNG1: ', e)}
          }
          if(replacement.cloneEvents) replacement.cloneEvents(el);
          replacement.replaces(el);
        } else if (el.get('tag') != "img") {
          var imgURL = el.getStyle('background-image');
          if (imgURL.test(/\((.+)\)/)){
            el.setStyles({
              background: '',
              filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true', sizingMethod='crop', src=" + imgURL.match(/\((.+)\)/)[1] + ")"
            });
          };
        }
      }
    } catch(e) {dbug.log('fixPNG2: ', e)}
  },
  pngTest: /\.png$/, // saves recreating the regex repeatedly
  scanForPngs: function(el, className) {
    className = className||'fixPNG';
    //TODO: should this also be testing the css background-image property for pngs?
    //Q: should it return an array of all those it has tweaked?
    if (document.getElements){ // more efficient but requires 'selectors'
      el = $(el||document.body);
      el.getElements('img[src$=.png]').addClass(className);
    } else { // scan the whole page
      var els = $$('img').each(function(img) {
        if (Browser.pngTest(img.src)){
          img.addClass(className);
        }
      });
    }
  }
});
if(Browser.Engine.trident4) window.addEvent('domready', function(){$$('img.fixPNG').each(Browser.fixPNG)});


/*
Script: IframeShim.js
  Defines IframeShim, a class for obscuring select lists and flash objects in IE.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var IframeShim = new Class({
  Implements: [Options, Events, Occlude, ToElement],
  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;
  },
  makeShim: function(){
    this.shim = new Element('iframe').store('IframeShim', this);
    if(!this.options.browsers) return;
    var pos = this.element.getStyle('position');
    if (pos == "static" || !pos) this.element.setStyle('position', 'relative');
    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.set({
      src: (window.location.protocol == 'https') ? '://0' : 'javascript:void(0)',
      frameborder:'0',
      scrolling:'no',
      styles: {
        position: 'absolute',
        zIndex: z,
        border: 'none',
        filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
      },
      'class':this.options.className
    });

    var inject = function(){
      this.shim.inject(this.element, 'after');
      if(this.options.display) this.show();
      else this.hide();
      this.fireEvent('onInject');
    };
    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 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;
    }
    this.shim.set({
      'width': size.x,
      'height': size.y
    }).setPosition({
      relativeTo: this.element,
      offset: this.options.offset
    });
    return this;
  },
  hide: function(){
    if(this.options.browsers) this.shim.hide();
    return this;
  },
  show: function(){
    if(!this.options.browsers) return this;
    this.shim.show();
    return this.position();
  },
  dispose: function(){
    if(this.options.browsers) this.shim.dispose();
    return this;
  }
});
window.addEvent('load', function(){
  IframeShim.ready = true;
});


/*
Script: Date.js
  Extends the Date native object to include methods useful in managing dates.

License:
  http://www.clientcide.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;
        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();
    }
  },
  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',

  $language: 'enUS',

  $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('date', bits[Date.$cIndex('date')]);
        d.set('month', bits[Date.$cIndex('month')] - 1);
        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('date', bits[Date.$cIndex('date')]);
        d.set('month', bits[Date.$cIndex('month')] - 1);
        d.set('hr', bits[4]);
        d.set('min', bits[5]);
        d.set('ampm', bits[6]);
        return Date.fixY2K(d);
      }
    },
    {
      //"12.31.08 11:59:59", "12-31-08 11:59:59", "12/31/08 11:59:59", "12.31.2008 11:59:59", "12-31-2008 11:59:59", "12/31/2008 11:59:59"
      re: /^(\d{1,2})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2}):(\d{1,2}):(\d{1,2})/,
      handler: function(bits) {
        var d = new Date();
        var culture = Date.$cultures[Date.$culture];
        d.set('year', bits[Date.$cIndex('year')]);
        d.set('date', bits[Date.$cIndex('date')]);
        d.set('month', bits[Date.$cIndex('month')] - 1);
        d.set('hours', bits[4]);
        d.set('minutes', bits[5]);
        d.set('seconds', 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://www.clientcide.com/wiki/cnet-libraries#license
*/

["LastDayOfMonth", "Ordinal"].each(function(method) {
  Date.$Methods.set(method.toLowerCase(), method);
});

Date.implement({
  timeDiffInWords: function(relative_to){
    return Date.distanceOfTimeInWords(this, relative_to || new Date);
  },
  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();
  }
});

Date.alias('timeDiffInWords', 'timeAgoInWords');

$extend(Date, {
  distanceOfTimeInWords: function(fromTime, toTime) {
    return Date.getTimePhrase(((toTime.getTime() - fromTime.getTime()) / 1000).toInt(), fromTime, toTime);
  },
  getTimePhrase: function(delta, fromTime, toTime) {
    var res = Date.$resources[Date.$language]; //saving bytes
    var getPhrase = function(){
      if (delta >= 0) {
        if (delta < 60) {
          return res.ago.lessThanMinute;
        } else if (delta < 120) {
          return res.ago.minute;
        } else if (delta < (45*60)) {
          delta = (delta / 60).round();
          return res.ago.minutes;
        } else if (delta < (90*60)) {
          return res.ago.hour;
        } else if (delta < (24*60*60)) {
          delta = (delta / 3600).round();
          return res.ago.hours;
        } else if (delta < (48*60*60)) {
          return res.ago.day;
        } else {
          delta = (delta / 86400).round();
          return res.ago.days;
        }
      }
      if (delta < 0) {
        delta = delta * -1;
        if (delta < 60) {
          return res.until.lessThanMinute;
        } else if (delta < 120) {
          return res.until.minute;
        } else if (delta < (45*60)) {
          delta = (delta / 60).round();
          return res.until.minutes;
        } else if (delta < (90*60)) {
          return res.until.hour;
        } else if (delta < (24*60*60)) {
          delta = (delta / 3600).round();
          return res.until.hours;
        } else if (delta < (48*60*60)) {
          return res.until.day;
        } else  {
          delta = (delta / 86400).round();
          return res.until.days;
        }
      }
    };
    return getPhrase().substitute({delta: delta});
  }
});

Date.$resources = {
  enUS: {
    ago: {
      lessThanMinute: 'less than a minute ago',
      minute: 'about a minute ago',
      minutes: '{delta} minutes ago',
      hour: 'about an hour ago',
      hours: 'about {delta} hours ago',
      day: '1 day ago',
      days: '{delta} days ago'
    },
    until: {
      lessThanMinute: 'less than a minute from now',
      minute: 'about a minute from now',
      minutes: '{delta} minutes from now',
      hour: 'about an hour from now',
      hours: 'about {delta} hours from now',
      day: '1 day from now',
      days: '{delta} days from now'
    }
  }
};

Date.$parsePatterns.extend([
  {
    //"1999-12-31 23:59:59"
    re: /^(\d{4})[\.\-\/](\d{1,2})[\.\-\/](\d{2,4})\s(\d{1,2}):(\d{1,2}):(\d{1,2})/,
    handler: function(bits) {
      var d = new Date();
      var culture = Date.$cultures[Date.$culture];
      d.set('year', bits[1]);
      d.set('date', bits[3]);
      d.set('month', bits[2] - 1);
      d.set('hours', bits[4]);
      d.set('minutes', bits[5]);
      d.set('seconds', bits[6]);
      return d;
    }
  },
  {
    // 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) {
      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:
  http://www.clientcide.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;
  },
  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:
  http://www.clientcide.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("&");
  },
  findAllEmails: function(){
      return this.match(new RegExp("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", "gi")) || [];
  }
});


/*
Script: Element.Forms.js
  Extends the Element native object to include methods useful in managing inputs.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
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(Browser.Engine.trident) return document.selection.createRange().text;
    return this.get('value').substring(this.getSelectionStart(), this.getSelectionEnd());
  },
  getIERanges: function(){
    this.focus();
    var range = document.selection.createRange();
    var re = this.createTextRange();
    var dupe = re.duplicate();
    re.moveToBookmark(range.getBookmark());
    dupe.setEndPoint('EndToStart', re);
    return { start: dupe.text.length, end: dupe.text.length + range.text.length, length: range.text.length, text: range.text };
  },
  getSelectionStart: function() {
    if(Browser.Engine.trident) return this.getIERanges().start;
    return this.selectionStart;
  },
  getSelectionEnd: function() {
    if(Browser.Engine.trident) return this.getIERanges().end;
    return this.selectionEnd;
  },
  getSelectedRange: function() {
    return {
      start: this.getSelectionStart(),
      end: this.getSelectionEnd()
    }
  },
  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) {
    this.focus();
    if(Browser.Engine.trident) {
      var range = this.createTextRange();
      range.collapse(true);
      range.moveStart('character', start);
      range.moveEnd('character', end - start);
      range.select();
      return this;
    }
    this.setSelectionRange(start, end);
    return this;
  },
  insertAtCursor: function(value, select) {
    var start = this.getSelectionStart();
    var end = this.getSelectionEnd();
    this.set('value', this.get('value').substring(0, start) + value + this.get('value').substring(end, this.get('value').length));
    if($pick(select, true)) this.selectRange(start, start + value.length);
    else this.setCaretPosition(start + value.length);
    return this;
  },
  insertAroundCursor: function(options, select) {
    options = $extend({
      before: '',
      defaultMiddle: 'SOMETHING HERE',
      after: ''
    }, options);
    value = this.getSelectedText() || options.defaultMiddle;
    var start = this.getSelectionStart();
    var end = this.getSelectionEnd();
    if(start == end) {
      var text = this.get('value');
      this.set('value', text.substring(0, start) + options.before + value + options.after + text.substring(end, text.length));
      this.selectRange(start + options.before.length, end + options.before.length + value.length);
      text = null;
    } else {
      text = this.get('value').substring(start, end);
      this.set('value', this.get('value').substring(0, start) + options.before + text + options.after + this.get('value').substring(end, this.get('value').length));
      var selStart = start + options.before.length;
      if($pick(select, true)) this.selectRange(selStart, selStart + text.length);
      else this.setCaretPosition(selStart + text.length);
    }
    return this;
  }
});


Element.Properties.inputValue = {

    get: function(){
       switch(this.get('tag')) {
        case 'select':
          vals = this.getSelected().map(function(op){
            var v = $pick(op.get('value'),op.get('text'));
            return (v=="")?op.get('text'):v;
          });
          return this.get('multiple')?vals:vals[0];
        case 'input':
          switch(this.get('type')) {
            case 'checkbox':
              return this.get('checked')?this.get('value'):false;
            case 'radio':
              var checked;
              if (this.get('checked')) return this.get('value');
              $(this.getParent('form')||document.body).getElements('input').each(function(input){
                if (input.get('name') == this.get('name') && input.get('checked')) checked = input.get('value');
              }, this);
              return checked||null;
          }
        case 'input': case 'textarea':
          return this.get('value');
        default:
          return this.get('inputValue');
       }
    },

    set: function(value){
      switch(this.get('tag')){
        case 'select':
          this.getElements('option').each(function(op){
            var v = $pick(op.get('value'), op.get('text'));
            if (v=="") v = op.get('text');
            op.set('selected', $splat(value).contains(v));
          });
          break;
        case 'input':
          if (['radio','checkbox'].contains(this.get('type'))) {
            this.set('checked', $type(value)=="boolean"?value:$splat(value).contains(this.get('value')));
            break;
          }
        case 'textarea': case 'input':
          this.set('value', value);
          break;
        default:
          this.set('inputValue', value);
      }
      return this;
    },

  erase: function() {
    switch(this.get('tag')) {
      case 'select':
        this.getElements('option').each(function(op) {
          op.erase('selected');
        });
        break;
      case 'input':
        if (['radio','checkbox'].contains(this.get('type'))) {
          this.set('checked', false);
          break;
        }
      case 'input': case 'textarea':
        this.set('value', '');
        break;
      default:
        this.set('inputValue', '');
    }
    return this;
  }

};


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

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/

Element.implement({

  // Daniel Steigerwald - MIT licence
  measure: function(fn) {
    var restore = this.expose();
    var result = fn.apply(this);
    restore();
    return result;
  },

  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.MouseOvers.js

Collection of mouseover behaviours (images, class toggles, etc.).

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/

Element.implement({
  autoMouseOvers: function(options){

    options = $extend({
      outString: '_out',
      overString: '_over',
      cssOver: 'hover',
      cssOut: 'hoverOut',
      subSelector: '',
      applyToBoth: false
    }, options);
    el = this;
    if (options.subSelector) el = this.getElements(options.subSelector);
    if (el.retrieve('autoMouseOverSetup')) return this;
    el.store('autoMouseOverSetup', true);
    return el.addEvents({
      mouseenter: function(){
        this.swapClass(options.cssOut, options.cssOver);
        if (this.src && this.src.contains(options.outString))
          this.src = this.src.replace(options.outString, options.overString);
        if(options.applyToBoth && options.subSelector) {
          this.getElements(options.subSelector).each(function(el){
            el.swapClass(options.cssOut, options.cssOver);
          });
        }
      }.bind(this),
      mouseleave: function(){
        this.swapClass(options.cssOver, options.cssOut);
        if (this.src && this.src.contains(options.overString))
          this.src = this.src.replace(options.overString, options.outString);
        if(options.applyToBoth && options.subSelector) {
          this.getElements(options.subSelector).each(function(el){
            el.swapClass(options.cssOver, options.cssOut);
          });
        }
      }.bind(this)
    }).swapClass(options.cssOver, options.cssOut);
    el = null;
  }
});
window.addEvent('domready', function(){
  $$('img.autoMouseOver').each(function(img){
    img.autoMouseOvers();
  });
});


/*
Script: Element.Pin.js
  Extends the Element native object to include the pin method useful for fixed positioning for elements.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/

window.addEvent('domready', function(){
  var test = new Element('div').setStyles({
    position: 'fixed',
    top: 0,
    right: 0
  }).inject(document.body);
  var supported = (test.offsetTop === 0);
  test.dispose();
  Browser.supportsPositionFixed = supported;
});

Element.implement({
  pin: function(enable){
    if(!Browser.loaded) dbug.log('cannot pin ' + this + ' natively because the dom is not ready');
    if (this.getStyle('display') == 'none') {
      dbug.log('cannot pin ' + this + ' because it is hidden');
      return;
    }
    if(enable!==false) {
      var p = this.getPosition();
      if(!this.retrieve('pinned')) {
        var pos = {
          top: (p.y - window.getScroll().y),
          left: (p.x - window.getScroll().x)
        };
        if(Browser.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')) {
              var to = {
                top: (pos.top.toInt() + window.getScroll().y),
                left: (pos.left.toInt() + window.getScroll().x)
              };
              this.setStyles(to);
            }
          }.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();
      }
      var p = this.getPosition(op);
      this.store('pinned', false);
      var reposition;
      if (Browser.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:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/

Element.Properties.position = {

  set: function(options){
    this.setPosition(options);
  },

  get: function(options){
    if (options) this.setPosition(options);
    return this.getPosition();
  }

};

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,
      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:
  http://www.clientcide.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
      if ('none' != this.getStyle('display')) 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;
  }
});


$E = document.getElement.bind(document);

//returns a collection given an id or a selector
$G = function(elements) {
  return $splat($(elements)||$$(elements));
};

/*
Script: Fx.Marquee.js
  Defines Fx.Marquee, a marquee class for animated notifications.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
Fx.Marquee = new Class({
  Extends: Fx.Morph,
  options: {
    mode: 'horizontal', //or vertical
    message: '', //the message to display
    revert: true, //revert back to the previous message after a specified time
    delay: 5000, //how long to wait before reverting
    cssClass: 'msg', //the css class to apply to that message
    showEffect: { opacity: 1 },
    hideEffect: {opacity: 0},
    revertEffect: { opacity: [0,1] },
    currentMessage: null
/*  onRevert: $empty,
    onMessage: $empty */
  },
  initialize: function(container, options){
    container = $(container);
    var msg = this.options.currentMessage || (container.getChildren().length == 1)?container.getFirst():'';
    var wrapper = new Element('div', {
        styles: { position: 'relative' },
        'class':'fxMarqueeWrapper'
      }).inject(container);
    this.parent(wrapper, options);
    this.current = this.wrapMessage(msg);
  },
  wrapMessage: function(msg){
    if($(msg) && $(msg).hasClass('fxMarquee')) { //already set up
      var wrapper = $(msg);
    } else {
      //create the wrapper
      var wrapper = new Element('span', {
        'class':'fxMarquee',
        styles: {
          position: 'relative'
        }
      });
      if($(msg)) wrapper.grab($(msg)); //if the message is a dom element, inject it inside the wrapper
      else if ($type(msg) == "string") wrapper.set('html', msg); //else set it's value as the inner html
    }
    return wrapper.inject(this.element); //insert it into the container
  },
  announce: function(options) {
    this.setOptions(options).showMessage();
    return this;
  },
  showMessage: function(reverting){
    //delay the fuction if we're reverting
    (function(){
      //store a copy of the current chained functions
      var chain = this.$chain?$A(this.$chain):[];
      //clear teh chain
      this.clearChain();
      this.element = $(this.element);
      this.current = $(this.current);
      this.message = $(this.message);
      //execute the hide effect
      this.start(this.options.hideEffect).chain(function(){
        //if we're reverting, hide the message and show the original
        if(reverting) {
          this.message.hide();
          if(this.current) this.current.show();
        } else {
          //else we're showing; remove the current message
          if(this.message) this.message.dispose();
          //create a new one with the message supplied
          this.message = this.wrapMessage(this.options.message);
          //hide the current message
          if(this.current) this.current.hide();
        }
        //if we're reverting, execute the revert effect, else the show effect
        this.start((reverting)?this.options.revertEffect:this.options.showEffect).chain(function(){
          //merge the chains we set aside back into this.$chain
          if (this.$chain) this.$chain.combine(chain);
          else this.$chain = chain;
          this.fireEvent((reverting)?'onRevert':'onMessage');
          //then, if we're reverting, show the original message
          if(!reverting && this.options.revert) this.showMessage(true);
          //if we're done, call the chain stack
          else this.callChain.delay(this.options.delay, this);
        }.bind(this));
      }.bind(this));
    }).delay((reverting)?this.options.delay:10, this);
    return this;
  }
});

/*
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:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
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.setPosition($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:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
Fx.Reveal = new Class({
  Extends: Fx.Morph,
  options: {
    styles: ['padding','border','margin'],
    transitionOpacity: true,
    mode:'vertical',
    heightOverride: null,
    widthOverride: null
/*  onShow: $empty,
    onHide: $empty */
  },
  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.element.fxOpacityOk() && 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');
          //put the final fx method at the front of the chain
          this.$chain = this.$chain || [];
          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) this.element.setStyle('height', 'auto');
            }
            this.fireEvent('onHide', this.element);
            this.callChain();
          }.bind(this));
          this.start(zero);
        } else {
          this.callChain.delay(10, this);
          this.fireEvent('onComplete', this.element);
          this.fireEvent('onHide', 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.hide();
      this.callChain.delay(10, this);
      this.fireEvent('onComplete', this.element);
      this.fireEvent('onHide', 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;
          //toggle display, but hide it
          var before = this.element.getStyles('visibility', 'display', 'position');
          this.element.setStyles({
            visibility: 'hidden',
            display: 'block',
            position:'absolute'
          });
          var setToAuto = this.element.style.height === ""||this.element.style.height=="auto";
          //enable opacity effects
          if(this.element.fxOpacityOk() && this.options.transitionOpacity) this.element.setStyle('opacity',0);
          //create the styles for the opened/visible state
          var startStyles = this.element.getComputedSize({
            styles: this.options.styles,
            mode: this.options.mode
          });
          //reset the styles back to hidden now
          this.element.setStyles(before);
          $each(startStyles, function(style, name) {
            startStyles[name] = style;
          }, this);
          //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.element.fxOpacityOk() && this.options.transitionOpacity) startStyles.opacity = 1;
          //create the zero state for the beginning of the transition
          var zero = {
            height: 0,
            display: 'block'
          };
          $each(startStyles, function(style, name){ zero[name] = 0 }, this);
          var overflowBefore = this.element.getStyle('overflow');
          //set to zero
          this.element.setStyles($merge(zero, {overflow: 'hidden'}));
          //start the effect
          this.start(startStyles);
          if (!this.$chain) this.$chain = [];
          this.$chain.unshift(function(){
            this.element.setStyle('overflow', overflowBefore);
            if (!this.options.heightOverride && setToAuto) {
              if (["vertical", "both"].contains(this.options.mode)) this.element.setStyle('height', 'auto');
              if (["width", "both"].contains(this.options.mode)) this.element.setStyle('width', 'auto');
            }
            if(!this.hidden) this.showing = false;
            this.callChain();
            this.fireEvent('onShow', this.element);
          }.bind(this));
        } else {
          this.callChain();
          this.fireEvent('onComplete', this.element);
          this.fireEvent('onShow', 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: 'block',
        visiblity: 'visible',
        opacity: 1
      });
      this.showing = false;
      this.callChain.delay(10, this);
      this.fireEvent('onComplete', this.element);
      this.fireEvent('onShow', this.element);
    }
    return this;
  },
  toggle: function(){
    try {
      if(this.element.getStyle('display') == "none" ||
         this.element.getStyle('visiblity') == "hidden" ||
         this.element.getStyle('opacity')==0){
        this.reveal();
      } else {
        this.dissolve();
      }
    } catch(e) { this.show(); }
   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;
  }
});


/*
Script: modalizer.js
  Defines Modalizer: functionality to overlay the window contents with a semi-transparent layer that prevents interaction with page content until it is removed

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var Modalizer = new Class({
  defaultModalStyle: {
    display:'block',
    position:'fixed',
    top:0,
    left:0,
    'z-index':5000,
    'background-color':'#333',
    opacity:0.8
  },
  setModalOptions: function(options){
    this.modalOptions = $merge({
      width:(window.getScrollSize().x),
      height:(window.getScrollSize().y),
      elementsToHide: 'select, embed' + (Browser.Engine.trident ? '': ', object'),
      hideOnClick: true,
      modalStyle: {},
      updateOnResize: true,
      layerId: 'modalOverlay',
      onModalHide: $empty,
      onModalShow: $empty
    }, this.modalOptions, options);
    return this;
  },
  layer: function(){
    if (!this.modalOptions.layerId) this.setModalOptions();
    return $(this.modalOptions.layerId) || new Element('div', {id: this.modalOptions.layerId}).inject(document.body);
  },
  resize: function(){
    if (this.layer()) {
      this.layer().setStyles({
        width:(window.getScrollSize().x),
        height:(window.getScrollSize().y)
      });
    }
  },
  setModalStyle: function (styleObject){
    this.modalOptions.modalStyle = styleObject;
    this.modalStyle = $merge(this.defaultModalStyle, {
      width:this.modalOptions.width,
      height:this.modalOptions.height
    }, styleObject);
    if (this.layer()) this.layer().setStyles(this.modalStyle);
    return(this.modalStyle);
  },
  modalShow: function(options){
    this.setModalOptions(options);
    this.layer().setStyles(this.setModalStyle(this.modalOptions.modalStyle));
    if (Browser.Engine.trident4) this.layer().setStyle('position','absolute');
    this.layer().removeEvents('click').addEvent('click', function(){
      this.modalHide(this.modalOptions.hideOnClick);
    }.bind(this));
    this.bound = this.bound||{};
    if (!this.bound.resize && this.modalOptions.updateOnResize) {
      this.bound.resize = this.resize.bind(this);
      window.addEvent('resize', this.bound.resize);
    }
    if ($type(this.modalOptions.onModalShow)  == "function") this.modalOptions.onModalShow();
    this.togglePopThroughElements(0);
    this.layer().setStyle('display','block');
    return this;
  },
  modalHide: function(override, force){
    if (override === false) return false; //this is internal, you don't need to pass in an argument
    this.togglePopThroughElements(1);
    if ($type(this.modalOptions.onModalHide) == "function") this.modalOptions.onModalHide();
    this.layer().setStyle('display','none');
    if (this.modalOptions.updateOnResize) {
      this.bound = this.bound||{};
      if (!this.bound.resize) this.bound.resize = this.resize.bind(this);
      window.removeEvent('resize', this.bound.resize);
    }
    return this;
  },
  togglePopThroughElements: function(opacity){
    if (Browser.Engine.trident4 || (Browser.Engine.gecko && Browser.Platform.mac)) {
      $$(this.modalOptions.elementsToHide).each(function(sel){
        sel.setStyle('opacity', opacity);
      });
    }
  }
});

/*
Script: Waiter.js

Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var Waiter = new Class({
  Implements: [Options, Events, Chain],
  options: {
    baseHref: 'http://www.cnet.com/html/rb/assets/global/waiter/',
    containerProps: {
      styles: {
        position: 'absolute',
        'text-align': 'center'
      },
      'class':'waiterContainer'
    },
    containerPosition: {},
    msg: false,
    msgProps: {
      styles: {
        'text-align': 'center',
        fontWeight: 'bold'
      },
      'class':'waiterMsg'
    },
    img: {
      src: 'waiter.gif',
      styles: {
        width: 24,
        height: 24
      },
      'class':'waiterImg'
    },
    layer:{
      styles: {
        width: 0,
        height: 0,
        position: 'absolute',
        zIndex: 999,
        display: 'none',
        opacity: 0.9,
        background: '#fff'
      },
      'class': 'waitingDiv'
    },
    useIframeShim: true,
    fxOptions: {},
    injectWhere: null
//  iframeShimOptions: {},
//  onShow: $empty
//  onHide: $empty
  },
  initialize: function(target, options){
    this.target = $(target)||$(document.body);
    this.setOptions(options);
    this.waiterContainer = new Element('div', this.options.containerProps);
    if (this.options.msg) {
      this.msgContainer = new Element('div', this.options.msgProps);
      this.waiterContainer.adopt(this.msgContainer);
      if (!$(this.options.msg)) this.msg = new Element('p').appendText(this.options.msg);
      else this.msg = $(this.options.msg);
      this.msgContainer.adopt(this.msg);
    }
    if (this.options.img) this.waiterImg = $(this.options.img.id) || new Element('img').inject(this.waiterContainer);
    this.waiterOverlay = $(this.options.layer.id) || new Element('div').adopt(this.waiterContainer);
    this.waiterOverlay.set(this.options.layer);
    this.place(target);
    try {
      if (this.options.useIframeShim) this.shim = new IframeShim(this.waiterOverlay, this.options.iframeShimOptions);
    } catch(e) {
      dbug.log("Waiter attempting to use IframeShim but failed; did you include IframeShim? Error: ", e);
      this.options.useIframeShim = false;
    }
    this.waiterFx = this.waiterFx || new Fx.Elements($$(this.waiterContainer, this.waiterOverlay), this.options.fxOptions);
  },
  place: function(target, where){
    var where = where || this.options.injectWhere || target == document.body ? 'inside' : 'after';
    this.waiterOverlay.inject(target, where);
  },
  toggle: function(element, show) {
    //the element or the default
    element = $(element) || $(this.active) || $(this.target);
    this.place(element);
    if (!$(element)) return this;
    if (this.active && element != this.active) return this.stop(this.start.bind(this, element));
    //if it's not active or show is explicit
    //or show is not explicitly set to false
    //start the effect
    if((!this.active || show) && show !== false) this.start(element);
    //else if it's active and show isn't explicitly set to true
    //stop the effect
    else if(this.active && !show) this.stop();
    return this;
  },
  reset: function(){
    this.waiterFx.cancel().set({
      0: { opacity:[0]},
      1: { opacity:[0]}
    });
  },
  start: function(element){
    this.reset();
    element = $(element) || $(this.target);
    this.place(element);
    if (this.options.img) {
      this.waiterImg.set($merge(this.options.img, {
        src: this.options.baseHref + this.options.img.src
      }));
    }

    var start = function() {
      var dim = element.getComputedSize();
      this.active = element;
      this.waiterOverlay.setStyles({
        width: this.options.layer.width||dim.totalWidth,
        height: this.options.layer.height||dim.totalHeight,
        display: 'block'
      }).setPosition({
        relativeTo: element,
        position: 'upperLeft'
      });
      this.waiterContainer.setPosition($merge({
        relativeTo: this.waiterOverlay
      }, this.options.containerPosition));
      if (this.options.useIframeShim) this.shim.show();
      this.waiterFx.start({
        0: { opacity:[1] },
        1: { opacity:[this.options.layer.styles.opacity]}
      }).chain(function(){
        if (this.active == element) this.fireEvent('onShow', element);
        this.callChain();
      }.bind(this));
    }.bind(this);

    if (this.active && this.active != element) this.stop(start);
    else start();

    return this;
  },
  stop: function(callback){
    if (!this.active) {
      if ($type(callback) == "function") callback.attempt();
      return this;
    }
    this.waiterFx.cancel();
    this.waiterFx.clearChain();
    //fade the waiter out
    this.waiterFx.start({
      0: { opacity:[0]},
      1: { opacity:[0]}
    }).chain(function(){
      this.active = null;
      this.waiterOverlay.hide();
      if (this.options.useIframeShim) this.shim.hide();
      this.fireEvent('onHide', this.active);
      this.callChain();
      this.clearChain();
      if ($type(callback) == "function") callback.attempt();
    }.bind(this));
    return this;
  }
});

if (typeof Request != "undefined" && Request.HTML) {
  Request.HTML = Class.refactor(Request.HTML, {
    options: {
      useWaiter: false,
      waiterOptions: {},
      waiterTarget: false
    },
    initialize: function(options){
      this._send = this.send;
      this.send = function(options){
        if(this.waiter) this.waiter.start().chain(this._send.bind(this, options));
        else this._send(options);
        return this;
      };
      this.parent(options);
      if (this.options.useWaiter && ($(this.options.update) || $(this.options.waiterTarget))) {
        this.waiter = new Waiter(this.options.waiterTarget || this.options.update, this.options.waiterOptions);
        ['onComplete', 'onException', 'onCancel'].each(function(event){
          this.addEvent(event, this.waiter.stop.bind(this.waiter));
        }, this);
      }
    }
  });
}

Element.Properties.waiter = {

  set: function(options){
    var waiter = this.retrieve('waiter');
    return this.eliminate('wait').store('waiter:options');
  },

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

};

Element.implement({

  wait: function(options){
    this.get('waiter', options).start();
    return this;
  },

  release: function(){
    var opt = Array.link(arguments, {options: Object.type, callback: Function.type});
    this.get('waiter', opt.options).stop(opt.callback);
    return this;
  }

});

/*
Script: Collapsable.js
  Enables a dom element to, when clicked, hide or show (it toggles) another dom element. Kind of an Accordion for one item.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var Collapsable = new Class({
  Extends: Fx.Reveal,
  initialize: function(clicker, section, options) {
    this.clicker = $(clicker);
    this.section = $(section);
    this.parent(this.section, options);
    this.addEvents();
  },
  addEvents: function(){
    this.clicker.addEvent('click', this.toggle.bind(this));
  }
});

var HoverGroup = new Class({
  Implements: [Options, Events],
  Binds: ['enter', 'leave', 'remain'],
  options: {
    //onEnter: $empty,
    //onLeave: $empty,
    elements: [],
    delay: 300,
    start: ['mouseenter'],
    remain: [],
    end: ['mouseleave']
  },
  initialize: function(options) {
    this.setOptions(options);
    this.attachTo(this.options.elements);
    this.addEvents({
      leave: function(){
        this.active = false;
      },
      enter: function(){
        this.active = true;
      }
    });
  },
  elements: [],
  attachTo: function(elements, detach){
    var starters = {}, remainers = {}, enders = {};
    elements = $G(elements);
    this.options.start.each(function(start) {
      starters[start] = this.enter;
    }, this);
    this.options.end.each(function(end) {
      enders[end] = this.leave;
    }, this);
    this.options.remain.each(function(remain){
      remainers[remain] = this.remain;
    }, this);
    if (detach) {
      elements.each(function(el) {
        el.removeEvents(starters).removeEvents(enders).removeEvents(remainers);
        this.elements.erase(el);
      });
    } else {
      elements.each(function(el){
        el.addEvents(starters).addEvents(enders).addEvents(remainers);
      });
      this.elements.combine(elements);
    }
    return this;
  },
  detachFrom: function(elements){
    this.attachTo(elements, true);
  },
  enter: function(e){
    this.isMoused = true;
    this.assert(e);
  },
  leave: function(e){
    this.isMoused = false;
    this.assert(e);
  },
  remain: function(e){
    if (this.active) this.enter(e);
  },
  assert: function(e){
    $clear(this.assertion);
    this.assertion = (function(){
      if (!this.isMoused && this.active) this.fireEvent('leave', e);
      else if (this.isMoused && !this.active) this.fireEvent('enter', e);
    }).delay(this.options.delay, this);
  }
});

/*
Script: MenuSlider.js
  Slides open a menu when the user mouses over a dom element. leaves it open while the mouse is over that element or the menu.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var MenuSlider = new Class({
  Implements: [Options, Events],
  Binds: ['slideIn', 'slideOut'],
  options: {
  /*  onIn: $empty,
    onInStart: $empty,
    onOut: $empty,
    hoverGroupOptions: {}, */
    fxOptions: {
      duration: 400,
      transition: 'expo:out',
      link: 'cancel'
    },
    useIframeShim: true,
    slideOut: false
  },
  initialize: function(menu, subMenu, options) {
    this.menu = $(menu);
    this.subMenu = $(subMenu);
    this.setOptions(options);
    this.makeSlider();
    this.hoverGroup = new HoverGroup($merge(this.options.hoverGroupOptions, {
      elements: [this.menu, this.subMenu],
      onEnter: this.slideIn,
      onLeave: this.slideOut
    }));
  },
  makeSlider: function(){
    this.slider = new Fx.Slide(this.subMenu, this.options.fxOptions).hide();
    if (this.options.useIframeShim && window.IframeShim) this.shim = new IframeShim(this.subMenu);
  },
  slideIn: function(){
    this.fireEvent('onInStart');
    this.slider.slideIn().chain(function(){
      if (this.shim) this.shim.show();
      this.fireEvent('onIn');
    }.bind(this));
    return this;
  },
  slideOut: function(){
    this.hide();
    this.fireEvent('onOut');
    if (this.shim) this.shim.hide();
    return this;
  },
  hide: function(){
    $clear(this.hoverGroup.assertion);
    this.hoverGroup.active = false;
    this.slider.cancel();
    if (this.options.slideOut) this.slider.slideOut();
    else this.slider.hide();
    if (this.shim) this.shim.hide();
    return this;
  }
});

/*
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://www.clientcide.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'
    },
    hideWhenNoOverflow: true
//    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);

    if (this.options.hideWhenNoOverflow) {
      this.hidden = this.knobSize == this.trackSize;
      this.track.setStyle('opacity', this.hidden?0:1);
    }

    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){
    if (this.hidden) return;
    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: JsonP.js
  Defines JsonP, a class for cross domain javascript via script injection.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var JsonP = new Class({
  Implements: [Options, Events],
  options: {
//  onComplete: $empty,
//  globalFunction: '',
//  abortAfter: 0,
    callBackKey: "callback",
    queryString: "",
    data: {},
    timeout: 5000,
    retries: 0
  },
  initialize: function(url, options){
    this.setOptions(options);
    this.url = this.makeUrl(url).url;
    this.fired = false;
    this.scripts = [];
    this.requests = 0;
    this.triesRemaining = [];
  },
  request: function(url, requestIndex){
    var u = this.makeUrl(url);
    if(!$chk(requestIndex)) {
      requestIndex = this.requests;
      this.requests++;
    }
    if(!$chk(this.triesRemaining[requestIndex])) this.triesRemaining[requestIndex] = this.options.retries;
    var remaining = this.triesRemaining[requestIndex]; //saving bytes
    dbug.log('retrieving by json script method: %s', u.url);
    var dl = (Browser.Engine.trident)?50:0; //for some reason, IE needs a moment here...
    (function(){
      var script = new Element('script', {
        src: u.url,
        type: 'text/javascript',
        id: 'jsonp_'+u.index+'_'+requestIndex
      });
      this.fired = true;
      this.addEvent('onComplete', function(){
        try {script.dispose();}catch(e){}
      }.bind(this));
      script.inject(document.head);

      if ($chk(this.options.abortAfter) && ! remaining) script.dispose.delay(this.options.abortAfter, script);

      if(remaining) {
        (function(){
          this.triesRemaining[requestIndex] = remaining - 1;
          if(script.getParent() && remaining) {
            dbug.log('removing script (%o) and retrying: try: %s, remaining: %s', requestIndex, remaining);
            script.dispose();
            this.request(url, requestIndex);
          }
        }).delay(this.options.timeout, this);
      }
    }.bind(this)).delay(dl);
    return this;
  },
  makeUrl: function(url){
    var index;
    if (JsonP.requestors.contains(this)) {
      index = JsonP.requestors.indexOf(this);
    } else {
      index = JsonP.requestors.push(this) - 1;
      JsonP.requestors['request_'+index] = this;
    }
    if(url) {
      var separator = (url.test('\\?'))?'&':'?';
      var jurl = url + separator + this.options.callBackKey + "=JsonP.requestors.request_" +
        index+".handleResults";
      if(this.options.queryString) jurl += "&"+this.options.queryString;
      jurl += "&"+Hash.toQueryString(this.options.data);
    } else {
      var jurl = this.url;
    }
    if ($chk(this.options.globalFunction)) {
      window[this.options.globalFunction] = function(r){
        JsonP.requestors[index].handleResults(r)
      };
    }
    return {url: jurl, index: index};
  },
  handleResults: function(data){
    dbug.log('jsonp received: ', data);
    this.fireEvent('onComplete', [data, this]);
  }
});
JsonP.requestors = [];


/**
 * Autocompleter
 *
 * @version   1.1.1
 *
 * @todo: Caching, no-result handling!
 *
 *
 * @license   MIT-style license
 * @author    Harald Kirschner <mail [at] digitarald.de>
 * @copyright Author
 */
var Autocompleter = {};

var OverlayFix = IframeShim;

Autocompleter.Base = new Class({

  Implements: [Options, Events],

  options: {
    minLength: 1,
    markQuery: true,
    width: 'inherit',
    maxChoices: 10,
//    injectChoice: null,
//    customChoices: null,
    className: 'autocompleter-choices',
    zIndex: 42,
    delay: 400,
    observerOptions: {},
    fxOptions: {},
//    onSelection: $empty,
//    onShow: $empty,
//    onHide: $empty,
//    onBlur: $empty,
//    onFocus: $empty,

    autoSubmit: false,
    overflow: false,
    overflowMargin: 25,
    selectFirst: false,
    filter: null,
    filterCase: false,
    filterSubset: false,
    forceSelect: false,
    selectMode: true,
    choicesMatch: null,

    multiple: false,
    separator: ', ',
    separatorSplit: /\s*[,;]\s*/,
    autoTrim: true,
    allowDupes: false,

    cache: true,
    relative: false
  },

  initialize: function(element, options) {
    this.element = $(element);
    this.setOptions(options);
    this.build();
    this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({
      'delay': this.options.delay
    }, this.options.observerOptions));
    this.queryValue = null;
    if (this.options.filter) this.filter = this.options.filter.bind(this);
    var mode = this.options.selectMode;
    this.typeAhead = (mode == 'type-ahead');
    this.selectMode = (mode === true) ? 'selection' : mode;
    this.cached = [];
  },

  /**
   * build - Initialize DOM
   *
   * Builds the html structure for choices and appends the events to the element.
   * Override this function to modify the html generation.
   */
  build: function() {
    if ($(this.options.customChoices)) {
      this.choices = this.options.customChoices;
    } else {
      this.choices = new Element('ul', {
        'class': this.options.className,
        'styles': {
          'zIndex': this.options.zIndex
        }
      }).inject(document.body);
      this.relative = false;
      if (this.options.relative || this.element.getOffsetParent() != document.body) {
        this.choices.inject(this.element, 'after');
        this.relative = this.element.getOffsetParent();
      }
      this.fix = new OverlayFix(this.choices);
    }
    if (!this.options.separator.test(this.options.separatorSplit)) {
      this.options.separatorSplit = this.options.separator;
    }
    this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, $merge({
      'property': 'opacity',
      'link': 'cancel',
      'duration': 200
    }, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0);
    this.element.setProperty('autocomplete', 'off')
      .addEvent((Browser.Engine.trident || Browser.Engine.webkit) ? 'keydown' : 'keypress', this.onCommand.bind(this))
      .addEvent('click', this.onCommand.bind(this, [false]))
      .addEvent('focus', this.toggleFocus.create({bind: this, arguments: true, delay: 100}));
      //.addEvent('blur', this.toggleFocus.create({bind: this, arguments: false, delay: 100}));
    document.addEvent('click', function(e){
      if (e.target != this.choices) this.toggleFocus(false);
    }.bind(this));
  },

  destroy: function() {
    if (this.fix) this.fix.dispose();
    this.choices = this.selected = this.choices.destroy();
  },

  toggleFocus: function(state) {
    this.focussed = state;
    if (!state) this.hideChoices(true);
    this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]);
  },

  onCommand: function(e) {
    if (!e && this.focussed) return this.prefetch();
    if (e && e.key && !e.shift) {
      switch (e.key) {
        case 'enter':
          if (this.element.value != this.opted) return true;
          if (this.selected && this.visible) {
            this.choiceSelect(this.selected);
            return !!(this.options.autoSubmit);
          }
          break;
        case 'up': case 'down':
          if (!this.prefetch() && this.queryValue !== null) {
            var up = (e.key == 'up');
            this.choiceOver((this.selected || this.choices)[
              (this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst')
            ](this.options.choicesMatch), true);
          }
          return false;
        case 'esc': case 'tab':
          this.hideChoices(true);
          break;
      }
    }
    return true;
  },

  setSelection: function(finish) {
    var input = this.selected.inputValue, value = input;
    var start = this.queryValue.length, end = input.length;
    if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0;
    if (this.options.multiple) {
      var split = this.options.separatorSplit;
      value = this.element.value;
      start += this.queryIndex;
      end += this.queryIndex;
      var old = value.substr(this.queryIndex).split(split, 1)[0];
      value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length);
      if (finish) {
        var space = /[^\s,]+/;
        var tokens = value.split(this.options.separatorSplit).filter(space.test, space);
        if (!this.options.allowDupes) tokens = [].combine(tokens);
        var sep = this.options.separator;
        value = tokens.join(sep) + sep;
        end = value.length;
      }
    }
    this.observer.setValue(value);
    this.opted = value;
    if (finish || this.selectMode == 'pick') start = end;
    this.element.selectRange(start, end);
    this.fireEvent('onSelection', [this.element, this.selected, value, input]);
  },

  showChoices: function() {
    var match = this.options.choicesMatch, first = this.choices.getFirst(match);
    this.selected = this.selectedValue = null;
    if (this.fix) {
      var pos = this.element.getCoordinates(this.relative), width = this.options.width || 'auto';
      this.choices.setStyles({
        'left': pos.left,
        'top': pos.bottom,
        'width': (width === true || width == 'inherit') ? pos.width : width
      });
    }
    if (!first) return;
    if (!this.visible) {
      this.visible = true;
      this.choices.setStyle('display', '');
      if (this.fx) this.fx.start(1);
      this.fireEvent('onShow', [this.element, this.choices]);
    }
    if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead);
    var items = this.choices.getChildren(match), max = this.options.maxChoices;
    var styles = {'overflowY': 'hidden', 'height': ''};
    this.overflown = false;
    if (items.length > max) {
      var item = items[max - 1];
      styles.overflowY = 'scroll';
      styles.height = item.getCoordinates(this.choices).bottom;
      this.overflown = true;
    };
    this.choices.setStyles(styles);
    this.fix.show();
  },

  hideChoices: function(clear) {
    if (clear) {
      var value = this.element.value;
      if (this.options.forceSelect) value = this.opted;
      if (this.options.autoTrim) {
        value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator);
      }
      this.observer.setValue(value);
    }
    if (!this.visible) return;
    this.visible = false;
    this.observer.clear();
    var hide = function(){
      this.choices.setStyle('display', 'none');
      this.fix.hide();
    }.bind(this);
    if (this.fx) this.fx.start(0).chain(hide);
    else hide();
    this.fireEvent('onHide', [this.element, this.choices]);
  },

  prefetch: function() {
    var value = this.element.value, query = value;
    if (this.options.multiple) {
      var split = this.options.separatorSplit;
      var values = value.split(split);
      var index = this.element.getCaretPosition();
      var toIndex = value.substr(0, index).split(split);
      var last = toIndex.length - 1;
      index -= toIndex[last].length;
      query = values[last];
    }
    if (query.length < this.options.minLength) {
      this.hideChoices();
    } else {
      if (query === this.queryValue || (this.visible && query == this.selectedValue)) {
        if (this.visible) return false;
        this.showChoices();
      } else {
        this.queryValue = query;
        this.queryIndex = index;
        if (!this.fetchCached()) this.query();
      }
    }
    return true;
  },

  fetchCached: function() {
    return false;
    if (!this.options.cache
      || !this.cached
      || !this.cached.length
      || this.cached.length >= this.options.maxChoices
      || this.queryValue) return false;
    this.update(this.filter(this.cached));
    return true;
  },

  update: function(tokens) {
    this.choices.empty();
    this.cached = tokens;
    if (!tokens || !tokens.length) {
      this.hideChoices();
    } else {
      if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices;
      tokens.each(this.options.injectChoice || function(token){
        var choice = new Element('li', {'html': this.markQueryValue(token)});
        choice.inputValue = token;
        this.addChoiceEvents(choice).inject(this.choices);
      }, this);
      this.showChoices();
    }
  },

  choiceOver: function(choice, selection) {
    if (!choice || choice == this.selected) return;
    if (this.selected) this.selected.removeClass('autocompleter-selected');
    this.selected = choice.addClass('autocompleter-selected');
    this.fireEvent('onSelect', [this.element, this.selected, selection]);
    if (!selection) return;
    this.selectedValue = this.selected.inputValue;
    if (this.overflown) {
      var coords = this.selected.getCoordinates(this.choices), margin = this.options.overflowMargin,
        top = this.choices.scrollTop, height = this.choices.offsetHeight, bottom = top + height;
      if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0);
      else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom);
    }
    if (this.selectMode) this.setSelection();
  },

  choiceSelect: function(choice) {
    if (choice) this.choiceOver(choice);
    this.setSelection(true);
    this.queryValue = false;
    this.hideChoices();
  },

  filter: function(tokens) {
    var regex = new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i');
    return (tokens || this.tokens).filter(regex.test, regex);
  },

  /**
   * markQueryValue
   *
   * Marks the queried word in the given string with <span class="autocompleter-queried">*</span>
   * Call this i.e. from your custom parseChoices, same for addChoiceEvents
   *
   * @param   {String} Text
   * @return    {String} Text
   */
  markQueryValue: function(str) {
    return (!this.options.markQuery || !this.queryValue) ? str
      : str.replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>');
  },

  /**
   * addChoiceEvents
   *
   * Appends the needed event handlers for a choice-entry to the given element.
   *
   * @param   {Element} Choice entry
   * @return    {Element} Choice entry
   */
  addChoiceEvents: function(el) {
    return el.addEvents({
      'mouseover': this.choiceOver.bind(this, [el]),
      'click': this.choiceSelect.bind(this, [el])
    });
  }
});


/**
 * Autocompleter.Local
 *
 * @version   1.1.1
 *
 * @todo: Caching, no-result handling!
 *
 *
 * @license   MIT-style license
 * @author    Harald Kirschner <mail [at] digitarald.de>
 * @copyright Author
 */
Autocompleter.Local = new Class({

  Extends: Autocompleter.Base,

  options: {
    minLength: 0,
    delay: 200
  },

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

  query: function() {
    this.update(this.filter());
  }

});


/**
 * Autocompleter.Remote
 *
 * @version   1.1.1
 *
 * @todo: Caching, no-result handling!
 *
 *
 * @license   MIT-style license
 * @author    Harald Kirschner <mail [at] digitarald.de>
 * @copyright Author
 */

Autocompleter.Ajax = {};

Autocompleter.Ajax.Base = new Class({

  Extends: Autocompleter.Base,

  options: {
    postVar: 'value',
    postData: {},
    ajaxOptions: {},
    onRequest: $empty,
    onComplete: $empty
  },

  initialize: function(element, options) {
    this.parent(element, options);
    var indicator = $(this.options.indicator);
    if (indicator) {
      this.addEvents({
        'onRequest': indicator.show.bind(indicator),
        'onComplete': indicator.hide.bind(indicator)
      }, true);
    }
  },

  query: function(){
    var data = $unlink(this.options.postData);
    data[this.options.postVar] = this.queryValue;
    this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]);
    this.request.send({'data': data});
  },

  /**
   * queryResponse - abstract
   *
   * Inherated classes have to extend this function and use this.parent(resp)
   *
   * @param   {String} Response
   */
  queryResponse: function() {
    this.fireEvent('onComplete', [this.element, this.request, this.response]);
  }

});

Autocompleter.Ajax.Json = new Class({

  Extends: Autocompleter.Ajax.Base,

  initialize: function(el, url, options) {
    this.parent(el, options);
    this.request = new Request.JSON($merge({
      'url': url,
      'link': 'cancel'
    }, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
  },

  queryResponse: function(response) {
    this.parent();
    this.update(response);
  }

});

Autocompleter.Ajax.Xhtml = new Class({

  Extends: Autocompleter.Ajax.Base,

  initialize: function(el, url, options) {
    this.parent(el, options);
    this.request = new Request.HTML($merge({
      'url': url,
      'link': 'cancel',
      'update': this.choices
    }, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
  },

  queryResponse: function(tree, elements) {
    this.parent();
    if (!elements || !elements.length) {
      this.hideChoices();
    } else {
      this.choices.getChildren(this.options.choicesMatch).each(this.options.injectChoice || function(choice) {
        var value = choice.innerHTML;
        choice.inputValue = value;
        this.addChoiceEvents(choice.set('html', this.markQueryValue(value)));
      }, this);
      this.showChoices();
    }

  }

});


/*
Script: Autocompleter.JsonP.js
  Implements JsonP support for the Autocompleter class.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/

Autocompleter.JsonP = new Class({

  Extends: Autocompleter.Ajax.Json,

  options: {
    postVar: 'query',
    jsonpOptions: {},
//    onRequest: $empty,
//    onComplete: $empty,
//    filterResponse: $empty
    minLength: 1
  },

  initialize: function(el, url, options) {
    this.url = url;
    this.setOptions(options);
    this.parent(el, options);
  },

  query: function(){
    var data = $unlink(this.options.jsonpOptions.data||{});
    data[this.options.postVar] = this.queryValue;
    this.jsonp = new JsonP(this.url, $merge({data: data}, this.options.jsonpOptions));
    this.jsonp.addEvent('onComplete', this.queryResponse.bind(this));
    this.fireEvent('onRequest', [this.element, this.jsonp, data, this.queryValue]);
    this.jsonp.request();
  },

  queryResponse: function(response) {
    this.parent();
    var data = (this.options.filter)?this.options.filter.run([response], this):response;
    this.update(data);
  }

});

/**
 * Observer - Observe formelements for changes
 *
 * @version   1.0rc3
 *
 * @license   MIT-style license
 * @author    Harald Kirschner <mail [at] digitarald.de>
 * @copyright Author
 */
var Observer = new Class({

  Implements: [Options, Events],

  options: {
    periodical: false,
    delay: 1000
  },

  initialize: function(el, onFired, options){
    this.setOptions(options);
    this.addEvent('onFired', onFired);
    this.element = $(el) || $$(el);
    /* Clientcide change */
    this.boundChange = this.changed.bind(this);
    this.resume();
  },

  changed: function() {
    var value = this.element.get('value');
    if ($equals(this.value, value)) return;
    this.clear();
    this.value = value;
    this.timeout = this.onFired.delay(this.options.delay, this);
  },

  setValue: function(value) {
    this.value = value;
    this.element.set('value', value);
    return this.clear();
  },

  onFired: function() {
    this.fireEvent('onFired', [this.value, this.element]);
  },

  clear: function() {
    $clear(this.timeout || null);
    return this;
  },
  /* Clientcide change */
  pause: function(){
    $clear(this.timeout);
    $clear(this.timer);
    this.element.removeEvent('keyup', this.boundChange);
    return this;
  },
  resume: function(){
    this.value = this.element.get('value');
    if (this.options.periodical) this.timer = this.changed.periodical(this.options.periodical, this);
    else this.element.addEvent('keyup', this.boundChange);
    return this;
  }

});

var $equals = function(obj1, obj2) {
  return (obj1 == obj2 || JSON.encode(obj1) == JSON.encode(obj2));
};

/*
Script: AutoCompleter.Clientcide.js
  Adds Clientcide css assets to autocompleter automatically.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
(function(){
  var AcClientcide = function(){
    return {
      options: {
        baseHref: 'http://www.cnet.com/html/rb/assets/global/autocompleter/'
      },
      initialize: function() {
        this.parent.apply(this,arguments);
        this.writeStyle();
      },
      writeStyle: function(){
        window.addEvent('domready', function(){
          if($('AutocompleterCss')) return;
          new Element('link', {
            rel: 'stylesheet',
            media: 'screen',
            type: 'text/css',
            href: this.options.baseHref+'Autocompleter.css',
            id: 'AutocompleterCss'
          }).inject(document.head);
        }.bind(this));
      }
    };
  };
  Autocompleter.Base.refactor(AcClientcide());
  if (Autocompleter.Ajax) {
    ["Base", "Xhtml", "Json"].each(function(c){
      if(Autocompleter.Ajax[c]) Autocompleter.Ajax[c] = Autocompleter.Ajax[c].refactor(AcClientcide());
    });
  }
  if (Autocompleter.Local) Autocompleter.Local.refactor(AcClientcide());
  if (Autocompleter.JsonP) Autocompleter.JsonP.refactor(AcClientcide());
})();


/*
Script: Lightbox.js
  A lightbox clone for MooTools.

* Christophe Beyls (http://www.digitalia.be); MIT-style license.
* Inspired by the original Lightbox v2 by Lokesh Dhakar: http://www.huddletogether.com/projects/lightbox2/.
* Refactored by Aaron Newton

*/
var Lightbox = new Class({
  Implements: [Options, Events, Modalizer],
  binds: ['click', 'keyboardListener', 'addHtmlElements'],
  options: {
//  anchors: null,
    resizeDuration: 400,
//  resizeTransition: false,  // default transition
    initialWidth: 250,
    initialHeight: 250,
    zIndex: 5000,
    animateCaption: true,
    showCounter: true,
    autoScanLinks: true,
    relString: 'lightbox',
    useDefaultCss: true,
    assetBaseUrl: 'http://www.cnet.com/html/rb/assets/global/slimbox/',
    overlayStyles: {
      opacity: 0.8
    }
//  onImageShow: $empty,
//  onDisplay: $empty,
//  onHide: $empty
  },

  initialize: function(){
    var args = Array.link(arguments, {options: Object.type, links: Array.type});
    this.setOptions(args.options);
    var anchors = args.links || this.options.anchors;
    if (this.options.autoScanLinks && !anchors) anchors = $$('a[rel^='+this.options.relString+']');
    if(!$$(anchors).length) return; //no links!
    this.addAnchors(anchors);
    if(this.options.useDefaultCss) this.addCss();
    window.addEvent('domready', this.addHtmlElements.bind(this));
  },

  anchors: [],

  addAnchors: function(anchors){
    $$(anchors).each(function(el){
      if(!el.retrieve('lightbox')) {
        el.store('lightbox', this);
        this.attach(el);
      }
    }.bind(this));
  },

  attach: function(el) {
    el.addEvent('click', this.click.pass(el, this));
    this.anchors.include(el);
  },

  addHtmlElements: function(){
    this.container = new Element('div', {
      'class':'lbContainer'
    }).inject(document.body);
    this.setModalOptions({
      onModalHide: this.close.bind(this)
    });
    this.overlay = this.layer().addClass('lbOverlay');
    this.setModalStyle($merge(this.options.overlayStyles, {
        opacity: 0
      })
    );
    this.popup = new Element('div', {
      'class':'lbPopup'
    }).inject(this.container);
    this.overlay.inject(this.popup);
    this.center = new Element('div', {
      styles: {
        width: this.options.initialWidth,
        height: this.options.initialHeight,
        marginLeft: (-(this.options.initialWidth/2)),
        display: 'none',
        zIndex:this.options.zIndex+1
      }
    }).inject(this.popup).addClass('lbCenter');
    this.image = new Element('div', {
      'class': 'lbImage'
    }).inject(this.center);

    this.prevLink = new Element('a', {
      'class': 'lbPrevLink',
      href: 'javascript:void(0);',
      styles: {'display': 'none'}
    }).inject(this.image);
    this.nextLink = this.prevLink.clone().removeClass('lbPrevLink').addClass('lbNextLink').inject(this.image);
    this.prevLink.addEvent('click', this.previous.bind(this));
    this.nextLink.addEvent('click', this.next.bind(this));

    this.bottomContainer = new Element('div', {
      'class': 'lbBottomContainer',
      styles: {
        display: 'none',
        zIndex:this.options.zIndex+1
    }}).inject(this.popup);
    this.bottom = new Element('div', {'class': 'lbBottom'}).inject(this.bottomContainer);
    new Element('a', {
      'class': 'lbCloseLink',
      href: 'javascript:void(0);'
    }).inject(this.bottom).addEvent('click', this.close.bind(this));
    this.overlay.addEvent('click', this.close.bind(this));
    this.caption = new Element('div', {'class': 'lbCaption'}).inject(this.bottom);
    this.number = new Element('div', {'class': 'lbNumber'}).inject(this.bottom);
    new Element('div', {'styles': {'clear': 'both'}}).inject(this.bottom);
    var nextEffect = this.nextEffect.bind(this);
    this.fx = {
      overlay: new Fx.Tween(this.overlay, {property: 'opacity', duration: 500}).set(0),
      resize: new Fx.Morph(this.center, $extend({
        duration: this.options.resizeDuration,
        onComplete: nextEffect},
        this.options.resizeTransition ? {transition: this.options.resizeTransition} : {})),
      image: new Fx.Tween(this.image, {property: 'opacity', duration: 500, onComplete: nextEffect}),
      bottom: new Fx.Tween(this.bottom, {property: 'margin-top', duration: 400, onComplete: nextEffect})
    };

    this.preloadPrev = new Element('img');
    this.preloadNext = new Element('img');
  },

  addCss: function(){
    window.addEvent('domready', function(){
      if($('LightboxCss')) return;
      new Element('link', {
        rel: 'stylesheet',
        media: 'screen',
        type: 'text/css',
        href: this.options.assetBaseUrl + 'slimbox.css',
        id: 'LightboxCss'
      }).inject(document.head);
    }.bind(this));
  },

  click: function(el){
    link = $(el);
    var rel = link.get('rel')||this.options.relString;
    if (rel == this.options.relString) return this.show(link.get('href'), link.get('title'));

    var j, imageNum, images = [];
    this.anchors.each(function(el){
      if (el.get('rel') == link.get('rel')){
        for (j = 0; j < images.length; j++) if(images[j][0] == el.get('href')) break;
        if (j == images.length){
          images.push([el.get('href'), el.get('title')]);
          if (el.get('href') == link.get('href')) imageNum = j;
        }
      }
    }, this);
    return this.open(images, imageNum);
  },

  show: function(url, title){
    return this.open([[url, title]], 0);
  },

  open: function(images, imageNum){
    this.fireEvent('onDisplay');
    this.images = images;
    this.setup(true);
    this.top = (window.getScroll().y + (window.getSize().y / 15)).toInt();
    this.center.setStyles({
      top: this.top,
      display: ''
    });
    this.modalShow();
    this.fx.overlay.start(0, this.options.overlayStyles.opacity);
    return this.changeImage(imageNum);
  },

  setup: function(open){
    var elements = $$('iframe');
    elements.extend($$(Browser.Engine.trident ? 'select' : 'embed, object'));
    elements.reverse().each(function(el){
      if (open) el.store('lbBackupStyle', el.getStyle('visibility') || 'visible');
      var vis = (open ? 'hidden' : el.retrieve('lbBackupStyle') || 'visible');
      el.setStyle('visibility', vis);
    });
    var fn = open ? 'addEvent' : 'removeEvent';
    document[fn]('keydown', this.keyboardListener);
    this.step = 0;
  },

  keyboardListener: function(event){
    switch (event.code){
      case 27: case 88: case 67: this.close(); break;
      case 37: case 80: this.previous(); break;
      case 39: case 78: this.next();
    }
  },

  previous: function(){
    return this.changeImage(this.activeImage-1);
  },

  next: function(){
    return this.changeImage(this.activeImage+1);
  },

  changeImage: function(imageNum){
    this.fireEvent('onImageShow', [imageNum, this.images[imageNum]]);
    if (this.step || (imageNum < 0) || (imageNum >= this.images.length)) return false;
    this.step = 1;
    this.activeImage = imageNum;

    this.center.setStyle('backgroundColor', '');
    this.bottomContainer.setStyle('display', 'none');
    this.prevLink.setStyle('display', 'none');
    this.nextLink.setStyle('display', 'none');
    this.fx.image.set(0);
    this.center.addClass('lbLoading');
    this.preload = new Element('img', {
      events: {
        load: function(){
          this.nextEffect.delay(100, this)
        }.bind(this)
      }
    });
    this.preload.set('src', this.images[imageNum][0]);
    return false;
  },

  nextEffect: function(){
    switch (this.step++){
    case 1:
      this.image.setStyle('backgroundImage', 'url('+this.images[this.activeImage][0]+')');
      this.image.setStyle('width', this.preload.width);
      this.bottom.setStyle('width',this.preload.width);
      this.image.setStyle('height', this.preload.height);
      this.prevLink.setStyle('height', this.preload.height);
      this.nextLink.setStyle('height', this.preload.height);

      this.caption.set('html',this.images[this.activeImage][1] || '');
      this.number.set('html',(!this.options.showCounter || (this.images.length == 1)) ? '' : 'Image '+(this.activeImage+1)+' of '+this.images.length);

      if (this.activeImage) $(this.preloadPrev).set('src', this.images[this.activeImage-1][0]);
      if (this.activeImage != (this.images.length - 1))
        $(this.preloadNext).set('src',  this.images[this.activeImage+1][0]);
      if (this.center.clientHeight != this.image.offsetHeight){
        this.fx.resize.start({height: this.image.offsetHeight});
        break;
      }
      this.step++;
    case 2:
      if (this.center.clientWidth != this.image.offsetWidth){
        this.fx.resize.start({width: this.image.offsetWidth, marginLeft: -this.image.offsetWidth/2});
        break;
      }
      this.step++;
    case 3:
      this.bottomContainer.setStyles({
        top: (this.top + this.center.getSize().y),
        height: 0,
        marginLeft: this.center.getStyle('margin-left'),
        display: ''
      });
      this.fx.image.start(1);
      break;
    case 4:
      this.center.style.backgroundColor = '#000';
      if (this.options.animateCaption){
        this.fx.bottom.set(-this.bottom.offsetHeight);
        this.bottomContainer.setStyle('height', '');
        this.fx.bottom.start(0);
        break;
      }
      this.bottomContainer.style.height = '';
    case 5:
      if (this.activeImage) this.prevLink.setStyle('display', '');
      if (this.activeImage != (this.images.length - 1)) this.nextLink.setStyle('display', '');
      this.step = 0;
    }
  },

  close: function(){
    this.fireEvent('onHide');
    if (this.step < 0) return;
    this.step = -1;
    if (this.preload) this.preload.destroy();
    for (var f in this.fx) this.fx[f].cancel();
    this.center.setStyle('display', 'none');
    this.bottomContainer.setStyle('display', 'none');
    this.fx.overlay.chain(this.setup.pass(false, this)).start(0);
    return;
  }
});
window.addEvent('domready', function(){if($(document.body).get('html').match(/rel=?.lightbox/i)) new Lightbox()});



/*
Script: InputFocus.js
  Adds a focused css class to inputs when they have focus.

License:
  http://www.clientcide.com/wiki/cnet-libraries#license
*/
var InputFocus = new Class({
  Implements: [Options, Occlude, ToElement],
  Binds: ['focus', 'blur'],
  options: {
    focusedClass: 'focused'
  },
  initialize: function(input, options) {
    this.element = $(input);
    if (this.occlude('focuser')) return this.occluded;
    this.setOptions(options);
    this.element.addEvents({
      focus: this.focus,
      blur: this.blur
    });
  },
  focus: function(){
    $(this).addClass(this.options.focusedClass);
  },
  blur: function(){
    $(this).removeClass(this.options.focusedClass);
  }
});