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

script: More.js

description: MooTools More

license: MIT-style license

authors:
- Guillermo Rauch
- Thomas Aylott
- Scott Kyle

requires:
- core:1.2.4/MooTools

provides: [MooTools.More]

...
*/

MooTools.More = {
  'version': '1.2.4.2dev',
  'build': '%build%'
};

/*
---

script: MooTools.Lang.js

description: Provides methods for localization.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Events
- /MooTools.More

provides: [MooTools.Lang]

...
*/

(function() {

  var data = {
    language: 'en-US',
    languages: {
      'en-US': {}
    },
    cascades: ['en-US']
  };

  var cascaded;

  MooTools.lang = new Events();

  $extend(MooTools.lang, {

    setLanguage: function(lang) {
      if (!data.languages[lang]) return this;
      data.language = lang;
      this.load();
      this.fireEvent('langChange', lang);
      return this;
    },

    load: function() {
      var langs = this.cascade(this.getCurrentLanguage());
      cascaded = {};
      $each(langs, function(set, setName) {
        cascaded[setName] = this.lambda(set);
      }, this);
    },

    getCurrentLanguage: function() {
      return data.language;
    },

    addLanguage: function(lang) {
      data.languages[lang] = data.languages[lang] || {};
      return this;
    },

    cascade: function(lang) {
      var cascades = (data.languages[lang] || {}).cascades || [];
      cascades.combine(data.cascades);
      cascades.erase(lang).push(lang);
      var langs = cascades.map(function(lng) {
        return data.languages[lng];
      }, this);
      return $merge.apply(this, langs);
    },

    lambda: function(set) {
      (set || {}).get = function(key, args) {
        return $lambda(set[key]).apply(this, $splat(args));
      };
      return set;
    },

    get: function(set, key, args) {
      if (cascaded && cascaded[set]) return (key ? cascaded[set].get(key, args) : cascaded[set]);
    },

    set: function(lang, set, members) {
      this.addLanguage(lang);
      langData = data.languages[lang];
      if (!langData[set]) langData[set] = {};
      $extend(langData[set], members);
      if (lang == this.getCurrentLanguage()) {
        this.load();
        this.fireEvent('langChange', lang);
      }
      return this;
    },

    list: function() {
      return Hash.getKeys(data.languages);
    }

  });

})();

/*
---

script: Class.Refactor.js

description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Class
- /MooTools.More

provides: [Class.refactor]

...
*/

Class.refactor = function(original, refactors) {

  $each(refactors, function(item, name) {
    var origin = original.prototype[name];
    if (origin && (origin = origin._origin) && typeof item == 'function') original.implement(name, function() {
      var old = this.previous;
      this.previous = origin;
      var value = item.apply(this, arguments);
      this.previous = old;
      return value;
    }); else original.implement(name, item);
  });

  return original;

};

/*
---

script: Class.Binds.js

description: Automagically binds specified methods in a class to the instance of the class.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Class
- /MooTools.More

provides: [Class.Binds]

...
*/

Class.Mutators.Binds = function(binds) {
  return binds;
};

Class.Mutators.initialize = function(initialize) {
  return function() {
    $splat(this.Binds).each(function(name) {
      var original = this[name];
      if (original) this[name] = original.bind(this);
    }, this);
    return initialize.apply(this, arguments);
  };
};


/*
---

script: Class.Occlude.js

description: Prevents a class from being applied to a DOM element twice.

license: MIT-style license.

authors:
- Aaron Newton

requires: 
- core/1.2.4/Class
- core:1.2.4/Element
- /MooTools.More

provides: [Class.Occlude]

...
*/

Class.Occlude = new Class({

  occlude: function(property, element) {
    element = document.id(element || this.element);
    var instance = element.retrieve(property || this.property);
    if (instance && !$defined(this.occluded))
      return this.occluded = instance;

    this.occluded = false;
    element.store(property || this.property, this);
    return this.occluded;
  }

});

/*
---

script: Chain.Wait.js

description: value, Adds a method to inject pauses between chained events.

license: MIT-style license.

authors:
- Aaron Newton

requires: 
- core:1.2.4/Chain 
- core:1.2.4/Element
- core:1.2.4/Fx
- /MooTools.More

provides: [Chain.Wait]

...
*/

(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);
    });
  }

  Element.implement({
    chains: function(effects) {
      $splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect) {
        effect = this.get(effect);
        if (!effect) return;
        effect.setOptions({
          link: 'chain'
        });
      }, this);
      return this;
    },
    pauseFx: function(duration, effect) {
      this.chains(effect).get($pick(effect, 'tween')).wait(duration);
      return this;
    }
  });

})();

/*
---

script: Array.Extras.js

description: Extends the Array native object to include useful methods to work with arrays.

license: MIT-style license

authors:
- Christoph Pojer

requires:
- core:1.2.4/Array

provides: [Array.Extras]

...
*/
Array.implement({

  min: function() {
    return Math.min.apply(null, this);
  },

  max: function() {
    return Math.max.apply(null, this);
  },

  average: function() {
    return this.length ? this.sum() / this.length : 0;
  },

  sum: function() {
    var result = 0, l = this.length;
    if (l) {
      do {
        result += this[--l];
      } while (l);
    }
    return result;
  },

  unique: function() {
    return [].combine(this);
  }

});

/*
---

script: Date.js

description: Extends the Date native object to include methods useful in managing dates.

license: MIT-style license

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

requires:
- core:1.2.4/Array
- core:1.2.4/String
- core:1.2.4/Number
- core:1.2.4/Lang
- core:1.2.4/Date.English.US
- /MooTools.More

provides: [Date]

...
*/

(function() {

  var Date = this.Date;

  if (!Date.now) Date.now = $time;

  Date.Methods = {
    ms: 'Milliseconds',
    year: 'FullYear',
    min: 'Minutes',
    mo: 'Month',
    sec: 'Seconds',
    hr: 'Hours'
  };

  ['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds'].each(function(method) {
	  Date.Methods[method.toLowerCase()] = method;
	});

  var pad = function(what, length) {
    return new Array(length - String(what).length + 1).join('0') + what;
  };

  Date.implement({

    set: function(prop, value) {
      switch ($type(prop)) {
        case 'object':
          for (var p in prop) this.set(p, prop[p]);
          break;
        case 'string':
          prop = prop.toLowerCase();
          var m = Date.Methods;
          if (m[prop]) this['set' + m[prop]](value);
      }
      return this;
    },

    get: function(prop) {
      prop = prop.toLowerCase();
      var m = Date.Methods;
      if (m[prop]) return this['get' + m[prop]]();
      return null;
    },

    clone: function() {
      return new Date(this.get('time'));
    },

    increment: function(interval, times) {
      interval = interval || 'day';
      times = $pick(times, 1);

      switch (interval) {
        case 'year':
          return this.increment('month', times * 12);
        case 'month':
          var d = this.get('date');
          this.set('date', 1).set('mo', this.get('mo') + times);
          return this.set('date', d.min(this.get('lastdayofmonth')));
        case 'week':
          return this.increment('day', times * 7);
        case 'day':
          return this.set('date', this.get('date') + times);
      }

      if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');

      return this.set('time', this.get('time') + times * Date.units[interval]());
    },

    decrement: function(interval, times) {
      return this.increment(interval, -1 * $pick(times, 1));
    },

    isLeapYear: function() {
      return Date.isLeapYear(this.get('year'));
    },

    clearTime: function() {
      return this.set({ hr: 0, min: 0, sec: 0, ms: 0 });
    },

    diff: function(date, resolution) {
      if ($type(date) == 'string') date = Date.parse(date);

      return ((date - this) / Date.units[resolution || 'day'](3, 3)).toInt(); // non-leap year, 30-day month
    },

    getLastDayOfMonth: function() {
      return Date.daysInMonth(this.get('mo'), this.get('year'));
    },

    getDayOfYear: function() {
      return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
			- Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
    },

    getWeek: function() {
      return (this.get('dayofyear') / 7).ceil();
    },

    getOrdinal: function(day) {
      return Date.getMsg('ordinal', day || this.get('date'));
    },

    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) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
    },

    setAMPM: function(ampm) {
      ampm = ampm.toUpperCase();
      var hr = this.get('hr');
      if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
      else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
      return this;
    },

    getAMPM: function() {
      return (this.get('hr') < 12) ? 'AM' : 'PM';
    },

    parse: function(str) {
      this.set('time', Date.parse(str));
      return this;
    },

    isValid: function(date) {
      return !!(date || this).valueOf();
    },

    format: function(f) {
      if (!this.isValid()) return 'invalid date';
      f = f || '%x %X';
      f = formats[f.toLowerCase()] || f; // replace short-hand with actual format
      var d = this;
      return f.replace(/%([a-z%])/gi,
			function($0, $1) {
			  switch ($1) {
			    case 'a': return Date.getMsg('days')[d.get('day')].substr(0, 3);
			    case 'A': return Date.getMsg('days')[d.get('day')];
			    case 'b': return Date.getMsg('months')[d.get('month')].substr(0, 3);
			    case 'B': return Date.getMsg('months')[d.get('month')];
			    case 'c': return d.toString();
			    case 'd': return pad(d.get('date'), 2);
			    case 'H': return pad(d.get('hr'), 2);
			    case 'I': return ((d.get('hr') % 12) || 12);
			    case 'j': return pad(d.get('dayofyear'), 3);
			    case 'm': return pad((d.get('mo') + 1), 2);
			    case 'M': return pad(d.get('min'), 2);
			    case 'o': return d.get('ordinal');
			    case 'p': return Date.getMsg(d.get('ampm'));
			    case 'S': return pad(d.get('seconds'), 2);
			    case 'U': return pad(d.get('week'), 2);
			    case 'w': return d.get('day');
			    case 'x': return d.format(Date.getMsg('shortDate'));
			    case 'X': return d.format(Date.getMsg('shortTime'));
			    case 'y': return d.get('year').toString().substr(2);
			    case 'Y': return d.get('year');
			    case 'T': return d.get('GMTOffset');
			    case 'Z': return d.get('Timezone');
			  }
			  return $1;
			}
		);
    },

    toISOString: function() {
      return this.format('iso8601');
    }

  });

  Date.alias('toISOString', 'toJSON');
  Date.alias('diff', 'compare');
  Date.alias('format', 'strftime');

  var 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'
  };

  var parsePatterns = [];
  var nativeParse = Date.parse;

  var parseWord = function(type, word, num) {
    var ret = -1;
    var translated = Date.getMsg(type + 's');

    switch ($type(word)) {
      case 'object':
        ret = translated[word.get(type)];
        break;
      case 'number':
        ret = translated[month - 1];
        if (!ret) throw new Error('Invalid ' + type + ' index: ' + index);
        break;
      case 'string':
        var match = translated.filter(function(name) {
          return this.test(name);
        }, new RegExp('^' + word, 'i'));
        if (!match.length) throw new Error('Invalid ' + type + ' string');
        if (match.length > 1) throw new Error('Ambiguous ' + type);
        ret = match[0];
    }

    return (num) ? translated.indexOf(ret) : ret;
  };

  Date.extend({

    getMsg: function(key, args) {
      return MooTools.lang.get('Date', key, args);
    },

    units: {
      ms: $lambda(1),
      second: $lambda(1000),
      minute: $lambda(60000),
      hour: $lambda(3600000),
      day: $lambda(86400000),
      week: $lambda(608400000),
      month: function(month, year) {
        var d = new Date;
        return Date.daysInMonth($pick(month, d.get('mo')), $pick(year, d.get('year'))) * 86400000;
      },
      year: function(year) {
        year = year || new Date().get('year');
        return Date.isLeapYear(year) ? 31622400000 : 31536000000;
      }
    },

    daysInMonth: function(month, year) {
      return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
    },

    isLeapYear: function(year) {
      return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
    },

    parse: function(from) {
      var t = $type(from);
      if (t == 'number') return new Date(from);
      if (t != 'string') return from;
      from = from.clean();
      if (!from.length) return null;

      var parsed;
      parsePatterns.some(function(pattern) {
        var bits = pattern.re.exec(from);
        return (bits) ? (parsed = pattern.handler(bits)) : false;
      });

      return parsed || new Date(nativeParse(from));
    },

    parseDay: function(day, num) {
      return parseWord('day', day, num);
    },

    parseMonth: function(month, num) {
      return parseWord('month', month, num);
    },

    parseUTC: function(value) {
      var localDate = new Date(value);
      var utcSeconds = Date.UTC(
			localDate.get('year'),
			localDate.get('mo'),
			localDate.get('date'),
			localDate.get('hr'),
			localDate.get('min'),
			localDate.get('sec')
		);
      return new Date(utcSeconds);
    },

    orderIndex: function(unit) {
      return Date.getMsg('dateOrder').indexOf(unit) + 1;
    },

    defineFormat: function(name, format) {
      formats[name] = format;
    },

    defineFormats: function(formats) {
      for (var name in formats) Date.defineFormat(name, formats[name]);
    },

    parsePatterns: parsePatterns, // this is deprecated

    defineParser: function(pattern) {
      parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
    },

    defineParsers: function() {
      Array.flatten(arguments).each(Date.defineParser);
    },

    define2DigitYearStart: function(year) {
      startYear = year % 100;
      startCentury = year - startYear;
    }

  });

  var startCentury = 1900;
  var startYear = 70;

  var regexOf = function(type) {
    return new RegExp('(?:' + Date.getMsg(type).map(function(name) {
      return name.substr(0, 3);
    }).join('|') + ')[a-z]*');
  };

  var replacers = function(key) {
    switch (key) {
      case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
        return ((Date.orderIndex('month') == 1) ? '%m[.-/]%d' : '%d[.-/]%m') + '([.-/]%y)?';
      case 'X':
        return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?';
    }
    return null;
  };

  var keys = {
    d: /[0-2]?[0-9]|3[01]/,
    H: /[01]?[0-9]|2[0-3]/,
    I: /0?[1-9]|1[0-2]/,
    M: /[0-5]?\d/,
    s: /\d+/,
    o: /[a-z]*/,
    p: /[ap]\.?m\.?/,
    y: /\d{2}|\d{4}/,
    Y: /\d{4}/,
    T: /Z|[+-]\d{2}(?::?\d{2})?/
  };

  keys.m = keys.I;
  keys.S = keys.M;

  var currentLanguage;

  var recompile = function(language) {
    currentLanguage = language;

    keys.a = keys.A = regexOf('days');
    keys.b = keys.B = regexOf('months');

    parsePatterns.each(function(pattern, i) {
      if (pattern.format) parsePatterns[i] = build(pattern.format);
    });
  };

  var build = function(format) {
    if (!currentLanguage) return { format: format };

    var parsed = [];
    var re = (format.source || format) // allow format to be regex
	 .replace(/%([a-z])/gi,
		function($0, $1) {
		  return replacers($1) || $0;
		}
	).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
	 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
	 .replace(/%([a-z%])/gi,
		function($0, $1) {
		  var p = keys[$1];
		  if (!p) return $1;
		  parsed.push($1);
		  return '(' + p.source + ')';
		}
	).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff]'); // handle unicode words

    return {
      format: format,
      re: new RegExp('^' + re + '$', 'i'),
      handler: function(bits) {
        bits = bits.slice(1).associate(parsed);
        var date = new Date().clearTime();
        if ('d' in bits) handle.call(date, 'd', 1);
        if ('m' in bits) handle.call(date, 'm', 1);
        for (var key in bits) handle.call(date, key, bits[key]);
        return date;
      }
    };
  };

  var handle = function(key, value) {
    if (!value) return this;

    switch (key) {
      case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
      case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
      case 'd': return this.set('date', value);
      case 'H': case 'I': return this.set('hr', value);
      case 'm': return this.set('mo', value - 1);
      case 'M': return this.set('min', value);
      case 'p': return this.set('ampm', value.replace(/\./g, ''));
      case 'S': return this.set('sec', value);
      case 's': return this.set('ms', ('0.' + value) * 1000);
      case 'w': return this.set('day', value);
      case 'Y': return this.set('year', value);
      case 'y':
        value = +value;
        if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
        return this.set('year', value);
      case 'T':
        if (value == 'Z') value = '+00';
        var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
        offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
        return this.set('time', this - offset * 60000);
    }

    return this;
  };

  Date.defineParsers(
	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
	'%Y %b( %d%o( %X)?)?' // Same as above with year coming first
);

  MooTools.lang.addEvent('langChange', function(language) {
    if (MooTools.lang.get('Date')) recompile(language);
  }).fireEvent('langChange', MooTools.lang.getCurrentLanguage());

})();

/*
---

script: Date.Extras.js

description: Extends the Date native object to include extra methods (on top of those in Date.js).

license: MIT-style license

authors:
- Aaron Newton
- Scott Kyle

requires:
- /Date

provides: [Date.Extras]

...
*/

Date.implement({

  timeDiffInWords: function(relative_to) {
    return Date.distanceOfTimeInWords(this, relative_to || new Date);
  },

  timeDiff: function(to, joiner) {
    if (to == null) to = new Date;
    var delta = ((to - this) / 1000).toInt();
    if (!delta) return '0s';

    var durations = { s: 60, m: 60, h: 24, d: 365, y: 0 };
    var duration, vals = [];

    for (var step in durations) {
      if (!delta) break;
      if ((duration = durations[step])) {
        vals.unshift((delta % duration) + step);
        delta = (delta / duration).toInt();
      } else {
        vals.unshift(delta + step);
      }
    }

    return vals.join(joiner || ':');
  }

});

Date.alias('timeDiffInWords', 'timeAgoInWords');

Date.extend({

  distanceOfTimeInWords: function(from, to) {
    return Date.getTimePhrase(((to - from) / 1000).toInt());
  },

  getTimePhrase: function(delta) {
    var suffix = (delta < 0) ? 'Until' : 'Ago';
    if (delta < 0) delta *= -1;

    var units = {
      minute: 60,
      hour: 60,
      day: 24,
      week: 7,
      month: 52 / 12,
      year: 12,
      eon: Infinity
    };

    var msg = 'lessThanMinute';

    for (var unit in units) {
      var interval = units[unit];
      if (delta < 1.5 * interval) {
        if (delta > 0.75 * interval) msg = unit;
        break;
      }
      delta /= interval;
      msg = unit + 's';
    }

    return Date.getMsg(msg + suffix).substitute({ delta: delta.round() });
  }

});


Date.defineParsers(

	{
	  // "today", "tomorrow", "yesterday"
	  re: /^(?:tod|tom|yes)/i,
	  handler: function(bits) {
	    var d = new Date().clearTime();
	    switch (bits[0]) {
	      case 'tom': return d.increment();
	      case 'yes': return d.decrement();
	      default: return d;
	    }
	  }
	},

	{
	  // "next Wednesday", "last Thursday"
	  re: /^(next|last) ([a-z]+)$/i,
	  handler: function(bits) {
	    var d = new Date().clearTime();
	    var day = d.getDay();
	    var newDay = Date.parseDay(bits[2], true);
	    var addDays = newDay - day;
	    if (newDay <= day) addDays += 7;
	    if (bits[1] == 'last') addDays -= 7;
	    return d.set('date', d.getDate() + addDays);
	  }
	}

);


/*
---

script: Hash.Extras.js

description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Hash.base
- /MooTools.More

provides: [Hash.Extras]

...
*/

Hash.implement({

  getFromPath: function(notation) {
    var source = this.getClean();
    notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match) {
      if (!source) return null;
      var prop = arguments[2] || arguments[1] || arguments[0];
      source = (prop in source) ? source[prop] : null;
      return match;
    });
    return source;
  },

  cleanValues: function(method) {
    method = method || $defined;
    this.each(function(v, k) {
      if (!method(v)) this.erase(k);
    }, this);
    return this;
  },

  run: function() {
    var args = arguments;
    this.each(function(v, k) {
      if ($type(v) == 'function') v.run(args);
    });
  }

});

/*
---

script: String.Extras.js

description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).

license: MIT-style license

authors:
- Aaron Newton
- Guillermo Rauch

requires:
- core:1.2.4/String
- core:1.2.4/$util
- core:1.2.4/Array

provides: [String.Extras]

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

script: String.QueryString.js

description: Methods for dealing with URI query strings.

license: MIT-style license

authors:
- Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti

requires:
- core:1.2.4/Array
- core:1.2.4/String
- /MooTools.More

provides: [String.QueryString]

...
*/

String.implement({

  parseQueryString: function() {
    var vars = this.split(/[&;]/), res = {};
    if (vars.length) vars.each(function(val) {
      var index = val.indexOf('='),
				keys = index < 0 ? [''] : val.substr(0, index).match(/[^\]\[]+/g),
				value = decodeURIComponent(val.substr(index + 1)),
				obj = res;
      keys.each(function(key, i) {
        var current = obj[key];
        if (i < keys.length - 1)
          obj = obj[key] = current || {};
        else if ($type(current) == 'array')
          current.push(value);
        else
          obj[key] = $defined(current) ? [current, value] : value;
      });
    });
    return res;
  },

  cleanQueryString: function(method) {
    return this.split('&').filter(function(val) {
      var index = val.indexOf('='),
			key = index < 0 ? '' : val.substr(0, index),
			value = val.substr(index + 1);
      return method ? method.run([key, value]) : $chk(value);
    }).join('&');
  }

});

/*
---

script: URI.js

description: Provides methods useful in managing the window location and uris.

license: MIT-style license

authors:
- Sebastian Markb樥
- Aaron Newton

requires:
- core:1.2.4/Selectors
- /String.QueryString

provides: URI

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

script: Element.Forms.js

description: Extends the Element native object to include methods useful in managing inputs.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element
- /MooTools.More

provides: [Element.Forms]

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

script: Elements.From.js

description: Returns a collection of elements from a string of html.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element
- /MooTools.More

provides: [Elements.from]

...
*/

Elements.from = function(text, excludeScripts) {
  if ($pick(excludeScripts, true)) text = text.stripScripts();

  var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);

  if (match) {
    container = new Element('table');
    var tag = match[1].toLowerCase();
    if (['td', 'th', 'tr'].contains(tag)) {
      container = new Element('tbody').inject(container);
      if (tag != 'tr') container = new Element('tr').inject(container);
    }
  }

  return (container || new Element('div')).set('html', text).getChildren();
};

/*
---

script: Element.Measure.js

description: Extends the Element native object to include methods useful in measuring dimensions.

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

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Style
- core:1.2.4/Element.Dimensions
- /MooTools.More

provides: [Element.Measure]

...
*/

Element.implement({

  measure: function(fn) {
    var vis = function(el) {
      return !!(!el || el.offsetHeight || el.offsetWidth);
    };
    if (vis(this)) return fn.apply(this);
    var parent = this.getParent(),
			restorers = [],
			toMeasure = [];
    while (!vis(parent) && parent != document.body) {
      toMeasure.push(parent.expose());
      parent = parent.getParent();
    }
    var restore = this.expose();
    var result = fn.apply(this);
    restore();
    toMeasure.each(function(restore) {
      restore();
    });
    return result;
  },

  expose: function() {
    if (this.getStyle('display') != 'none') return $empty;
    var before = this.style.cssText;
    this.setStyles({
      display: 'block',
      position: 'absolute',
      visibility: 'hidden'
    });
    return function() {
      this.style.cssText = before;
    } .bind(this);
  },

  getDimensions: function(options) {
    options = $merge({ computeSize: false }, options);
    var dim = {};
    var getSize = function(el, options) {
      return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
    };
    var parent = this.getParent('body');
    if (parent && this.getStyle('display') == 'none') {
      dim = this.measure(function() {
        return getSize(this, options);
      });
    } else if (parent) {
      try { //safari sometimes crashes here, so catch it
        dim = getSize(this, options);
      } catch (e) { }
    } else {
      dim = { x: 0, y: 0 };
    }
    return $chk(dim.x) ? $extend(dim, { width: dim.x, height: dim.y }) : $extend(dim, { x: dim.width, y: dim.height });
  },

  getComputedSize: function(options) {
    options = $merge({
      styles: ['padding', 'border'],
      plains: {
        height: ['top', 'bottom'],
        width: ['left', 'right']
      },
      mode: 'both'
    }, options);
    var size = { width: 0, height: 0 };
    switch (options.mode) {
      case 'vertical':
        delete size.width;
        delete options.plains.width;
        break;
      case 'horizontal':
        delete size.height;
        delete options.plains.height;
        break;
    }
    var getStyles = [];
    //this function might be useful in other places; perhaps it should be outside this function?
    $each(options.plains, function(plain, key) {
      plain.each(function(edge) {
        options.styles.each(function(style) {
          getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
        });
      });
    });
    var styles = {};
    getStyles.each(function(style) { styles[style] = this.getComputedStyle(style); }, this);
    var subtracted = [];
    $each(options.plains, function(plain, key) { //keys: width, height, plains: ['left', 'right'], ['top','bottom']
      var capitalized = key.capitalize();
      size['total' + capitalized] = size['computed' + capitalized] = 0;
      plain.each(function(edge) { //top, left, right, bottom
        size['computed' + edge.capitalize()] = 0;
        getStyles.each(function(style, i) { //padding, border, etc.
          //'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
          if (style.test(edge)) {
            styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
            size['total' + capitalized] = size['total' + capitalized] + styles[style];
            size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
          }
          //if width != width (so, padding-left, for instance), then subtract that from the total
          if (style.test(edge) && key != style &&
						(style.test('border') || style.test('padding')) && !subtracted.contains(style)) {
            subtracted.push(style);
            size['computed' + capitalized] = size['computed' + capitalized] - styles[style];
          }
        });
      });
    });

    ['Width', 'Height'].each(function(value) {
      var lower = value.toLowerCase();
      if (!$chk(size[lower])) return;

      size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
      size['total' + value] = size[lower] + size['total' + value];
      delete size['computed' + value];
    }, this);

    return $extend(styles, size);
  }

});

/*
---

script: Element.Position.js

description: Extends the Element native object to include methods useful positioning elements relative to others.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Dimensions
- /Element.Measure

provides: [Elements.Position]

...
*/

(function() {

  var original = Element.prototype.position;

  Element.implement({

    position: function(options) {
      //call original position if the options are x/y values
      if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
      $each(options || {}, function(v, k) { if (!$defined(v)) delete options[k]; });
      options = $merge({
        // minimum: { x: 0, y: 0 },
        // maximum: { x: 0, y: 0},
        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,
        ignoreScroll: false,
        allowNegative: false
      }, options);
      //compute the offset of the parent positioned element if this element is in one
      var parentOffset = { x: 0, y: 0 },
				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 document.id(this.getOffsetParent());
      });
      if (offsetParent && offsetParent != this.getDocument().body) {
        parentOffset = offsetParent.measure(function() {
          return this.getPosition();
        });
        parentPositioned = offsetParent != document.id(options.relativeTo);
        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 = document.id(options.relativeTo) || document.body,
				calc = rel == document.body ? window.getScroll() : rel.getPosition(),
				top = calc.y, left = calc.x;

      var scrolls = rel.getScrolls();
      top += scrolls.y;
      left += scrolls.x;

      var dim = this.getDimensions({ computeSize: true, styles: ['padding', 'border', 'margin'] });
      var pos = {},
				prefY = options.offset.y,
				prefX = options.offset.x,
				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.totalWidth / 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.totalHeight / 2);
            break;
        }
        pos.x += edgeOffset.x;
        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()
      };
      var xy = { left: 'x', top: 'y' };
      ['minimum', 'maximum'].each(function(minmax) {
        ['left', 'top'].each(function(lr) {
          var val = options[minmax] ? options[minmax][xy[lr]] : null;
          if (val != null && pos[lr] < val) pos[lr] = val;
        });
      });
      if (rel.getStyle('position') == 'fixed' || options.relFixedPosition) {
        var winScroll = window.getScroll();
        pos.top += winScroll.y;
        pos.left += winScroll.x;
      }
      if (options.ignoreScroll) {
        var relScroll = rel.getScroll();
        pos.top -= relScroll.y;
        pos.left -= relScroll.x;
      }
      if (options.ignoreMargins) {
        pos.left += (
				options.edge.x == 'right' ? dim['margin-right'] :
				options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left']) / 2) :
					-dim['margin-left']
			);
        pos.top += (
				options.edge.y == 'bottom' ? dim['margin-bottom'] :
				options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top']) / 2) :
					-dim['margin-top']
			);
      }
      pos.left = Math.ceil(pos.left);
      pos.top = Math.ceil(pos.top);
      if (options.returnPos) return pos;
      else this.setStyles(pos);
      return this;
    }

  });

})();

/*
---

script: Element.Shortcuts.js

description: Extends the Element native object to include some shortcut methods.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Style
- /MooTools.More

provides: [Element.Shortcuts]

...
*/

Element.implement({

  isDisplayed: function() {
    return this.getStyle('display') != 'none';
  },

  isVisible: function() {
    var w = this.offsetWidth,
			h = this.offsetHeight;
    return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.isDisplayed();
  },

  toggle: function() {
    return this[this.isDisplayed() ? 'hide' : 'show']();
  },

  hide: function() {
    var d;
    try {
      // IE fails here if the element is not in the dom
      if ((d = this.getStyle('display')) == 'none') d = null;
    } catch (e) { }

    return this.store('originalDisplay', d || 'block').setStyle('display', 'none');
  },

  show: function(display) {
    return this.setStyle('display', display || this.retrieve('originalDisplay') || 'block');
  },

  swapClass: function(remove, add) {
    return this.removeClass(remove).addClass(add);
  }

});


/*
---

script: Form.Request.js

description: Handles the basic functionality of submitting a form and updating a dom element with the result.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Event
- core:1.2.4/Request.HTML
- /Class.Binds
- /Class.Occlude
- /Spinner
- /String.QueryString

provides: [Form.Request]

...
*/

if (!window.Form) window.Form = {};

(function() {

  Form.Request = new Class({

    Binds: ['onSubmit', 'onFormValidate'],

    Implements: [Options, Events, Class.Occlude],

    options: {
      //onFailure: $empty,
      //onSuccess: #empty, //aliased to onComplete,
      //onSend: $empty
      requestOptions: {
        evalScripts: true,
        useSpinner: true,
        emulation: false,
        link: 'ignore'
      },
      extraData: {},
      resetForm: true
    },

    property: 'form.request',

    initialize: function(form, update, options) {
      this.element = document.id(form);
      if (this.occlude()) return this.occluded;
      this.update = document.id(update);
      this.setOptions(options);
      this.makeRequest();
      if (this.options.resetForm) {
        this.request.addEvent('success', function() {
          $try(function() { this.element.reset(); } .bind(this));
          if (window.OverText) OverText.update();
        } .bind(this));
      }
      this.attach();
    },

    toElement: function() {
      return this.element;
    },

    makeRequest: function() {
      this.request = new Request.HTML($merge({
        url: this.element.get('action'),
        update: this.update,
        emulation: false,
        spinnerTarget: this.element,
        method: this.element.get('method') || 'post'
      }, this.options.requestOptions)).addEvents({
        success: function(text, xml) {
          ['success', 'complete'].each(function(evt) {
            this.fireEvent(evt, [this.update, text, xml]);
          }, this);
        } .bind(this),
        failure: function(xhr) {
          this.fireEvent('failure', xhr);
        } .bind(this),
        exception: function() {
          this.fireEvent('failure', xhr);
        } .bind(this)
      });
    },

    attach: function(attach) {
      attach = $pick(attach, true);
      method = attach ? 'addEvent' : 'removeEvent';

      var fv = this.element.retrieve('validator');
      if (fv) fv[method]('onFormValidate', this.onFormValidate);
      if (!fv || !attach) this.element[method]('submit', this.onSubmit);
    },

    detach: function() {
      this.attach(false);
    },

    //public method
    enable: function() {
      this.attach();
    },

    //public method
    disable: function() {
      this.detach();
    },

    onFormValidate: function(valid, form, e) {
      if (valid || !fv.options.stopOnFailure) {
        if (e && e.stop) e.stop();
        this.send();
      }
    },

    onSubmit: function(e) {
      if (this.element.retrieve('validator')) {
        //form validator was created after Form.Request
        this.detach();
        this.addFormEvent();
        return;
      }
      e.stop();
      this.send();
    },

    send: function() {
      var str = this.element.toQueryString().trim();
      var data = $H(this.options.extraData).toQueryString();
      if (str) str += "&" + data;
      else str = data;
      this.fireEvent('send', [this.element, str]);
      this.request.send({ data: str });
      return this;
    }

  });

  Element.Properties.formRequest = {

    set: function() {
      var opt = Array.link(arguments, { options: Object.type, update: Element.type, updateId: String.type });
      var update = opt.update || opt.updateId;
      var updater = this.retrieve('form.request');
      if (update) {
        if (updater) updater.update = document.id(update);
        this.store('form.request:update', update);
      }
      if (opt.options) {
        if (updater) updater.setOptions(opt.options);
        this.store('form.request:options', opt.options);
      }
      return this;
    },

    get: function() {
      var opt = Array.link(arguments, { options: Object.type, update: Element.type, updateId: String.type });
      var update = opt.update || opt.updateId;
      if (opt.options || update || !this.retrieve('form.request')) {
        if (opt.options || !this.retrieve('form.request:options')) this.set('form.request', opt.options);
        if (update) this.set('form.request', update);
        this.store('form.request', new Form.Request(this, this.retrieve('form.request:update'), this.retrieve('form.request:options')));
      }
      return this.retrieve('form.request');
    }

  };

  Element.implement({

    formUpdate: function(update, options) {
      this.get('form.request', update, options).send();
      return this;
    }

  });

})();

/*
---

script: Form.Request.Append.js

description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.

license: MIT-style license

authors:
- Aaron Newton

requires:
- /Form.Request
- /Fx.Reveal
- /Elements.from

provides: [Form.Request.Append]

...
*/

Form.Request.Append = new Class({

  Extends: Form.Request,

  options: {
    //onBeforeEffect: $empty,
    useReveal: true,
    revealOptions: {},
    inject: 'bottom'
  },

  makeRequest: function() {
    this.request = new Request.HTML($merge({
      url: this.element.get('action'),
      method: this.element.get('method') || 'post',
      spinnerTarget: this.element
    }, this.options.requestOptions, {
      evalScripts: false
    })
		).addEvents({
		  success: function(tree, elements, html, javascript) {
		    var container;
		    var kids = Elements.from(html);
		    if (kids.length == 1) {
		      container = kids[0];
		    } else {
		      container = new Element('div', {
		        styles: {
		          display: 'none'
		        }
		      }).adopt(kids);
		    }
		    container.inject(this.update, this.options.inject);
		    if (this.options.requestOptions.evalScripts) $exec(javascript);
		    this.fireEvent('beforeEffect', container);
		    var finish = function() {
		      this.fireEvent('success', [container, this.update, tree, elements, html, javascript]);
		    } .bind(this);
		    if (this.options.useReveal) {
		      container.get('reveal', this.options.revealOptions).chain(finish);
		      container.reveal();
		    } else {
		      finish();
		    }
		  } .bind(this),
		  failure: function(xhr) {
		    this.fireEvent('failure', xhr);
		  } .bind(this)
		});
  }

});

/*
---

script: OverText.js

description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Options
- core:1.2.4/Events
- core:1.2.4/Element.Event
- /Class.Binds
- /Class.Occlude
- /Element.Position
- /Element.Shortcuts

provides: [OverText]

...
*/

var OverText = new Class({

  Implements: [Options, Events, Class.Occlude],

  Binds: ['reposition', 'assert', 'focus', 'hide'],

  options: {/*
		textOverride: null,
		onFocus: $empty()
		onTextHide: $empty(textEl, inputEl),
		onTextShow: $empty(textEl, inputEl), */
    element: 'label',
    positionOptions: {
      position: 'upperLeft',
      edge: 'upperLeft',
      offset: {
        x: 4,
        y: 2
      }
    },
    poll: false,
    pollInterval: 250,
    wrap: false
  },

  property: 'OverText',

  initialize: function(element, options) {
    this.element = document.id(element);
    if (this.occlude()) return this.occluded;
    this.setOptions(options);
    this.attach(this.element);
    OverText.instances.push(this);
    if (this.options.poll) this.poll();
    return this;
  },

  toElement: function() {
    return this.element;
  },

  attach: function() {
    var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
    if (!val) return;
    this.text = new Element(this.options.element, {
      'class': 'overTxtLabel',
      styles: {
        lineHeight: 'normal',
        position: 'absolute',
        cursor: 'text'
      },
      html: val,
      events: {
        click: this.hide.pass(this.options.element == 'label', this)
      }
    }).inject(this.element, 'after');
    if (this.options.element == 'label') {
      if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
      this.text.set('for', this.element.get('id'));
    }

    if (this.options.wrap) {
      this.textHolder = new Element('div', {
        styles: {
          lineHeight: 'normal',
          position: 'relative'
        },
        'class': 'overTxtWrapper'
      }).adopt(this.text).inject(this.element, 'before');
    }

    this.element.addEvents({
      focus: this.focus,
      blur: this.assert,
      change: this.assert
    }).store('OverTextDiv', this.text);
    window.addEvent('resize', this.reposition.bind(this));
    this.assert(true);
    this.reposition();
  },

  wrap: function() {
    if (this.options.element == 'label') {
      if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
      this.text.set('for', this.element.get('id'));
    }
  },

  startPolling: function() {
    this.pollingPaused = false;
    return this.poll();
  },

  poll: function(stop) {
    //start immediately
    //pause on focus
    //resumeon blur
    if (this.poller && !stop) return this;
    var test = function() {
      if (!this.pollingPaused) this.assert(true);
    } .bind(this);
    if (stop) $clear(this.poller);
    else this.poller = test.periodical(this.options.pollInterval, this);
    return this;
  },

  stopPolling: function() {
    this.pollingPaused = true;
    return this.poll(true);
  },

  focus: function() {
    if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return;
    this.hide();
  },

  hide: function(suppressFocus, force) {
    if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))) {
      this.text.hide();
      this.fireEvent('textHide', [this.text, this.element]);
      this.pollingPaused = true;
      try {
        if (!suppressFocus) this.element.fireEvent('focus');
        this.element.focus();
      } catch (e) { } //IE barfs if you call focus on hidden elements
    }
    return this;
  },

  show: function() {
    if (this.text && !this.text.isDisplayed()) {
      this.text.show();
      this.reposition();
      this.fireEvent('textShow', [this.text, this.element]);
      this.pollingPaused = false;
    }
    return this;
  },

  assert: function(suppressFocus) {
    this[this.test() ? 'show' : 'hide'](suppressFocus);
  },

  test: function() {
    var v = this.element.get('value');
    return !v;
  },

  reposition: function() {
    this.assert(true);
    if (!this.element.isVisible()) return this.stopPolling().hide();
    if (this.text && this.test()) this.text.position($merge(this.options.positionOptions, { relativeTo: this.element }));
    return this;
  }

});

OverText.instances = [];

$extend(OverText, {

  each: function(fn) {
    return OverText.instances.map(function(ot, i) {
      if (ot.element && ot.text) return fn.apply(OverText, [ot, i]);
      return null; //the input or the text was destroyed
    });
  },

  update: function() {

    return OverText.each(function(ot) {
      return ot.reposition();
    });

  },

  hideAll: function() {

    return OverText.each(function(ot) {
      return ot.hide(true, true);
    });

  },

  showAll: function() {
    return OverText.each(function(ot) {
      return ot.show();
    });
  }

});

if (window.Fx && Fx.Reveal) {
  Fx.Reveal.implement({
    hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel' : false
  });
}

/*
---

script: Fx.Elements.js

description: Effect to change any number of CSS properties of any number of Elements.

license: MIT-style license

authors:
- Valerio Proietti

requires:
- core:1.2.4/Fx.CSS
- /MooTools.More

provides: [Fx.Elements]

...
*/

Fx.Elements = new Class({

  Extends: Fx.CSS,

  initialize: function(elements, options) {
    this.elements = this.subject = $$(elements);
    this.parent(options);
  },

  compute: function(from, to, delta) {
    var now = {};
    for (var i in from) {
      var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
      for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
    }
    return now;
  },

  set: function(now) {
    for (var i in now) {
      var iNow = now[i];
      for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
    }
    return this;
  },

  start: function(obj) {
    if (!this.check(obj)) return this;
    var from = {}, to = {};
    for (var i in obj) {
      var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
      for (var p in iProps) {
        var parsed = this.prepare(this.elements[i], p, iProps[p]);
        iFrom[p] = parsed.from;
        iTo[p] = parsed.to;
      }
    }
    return this.parent(from, to);
  }

});

/*
---

script: Fx.Accordion.js

description: An Fx.Elements extension which allows you to easily create accordion type controls.

license: MIT-style license

authors:
- Valerio Proietti

requires:
- core:1.2.4/Element.Event
- /Fx.Elements

provides: [Fx.Accordion]

...
*/

var Accordion = Fx.Accordion = new Class({

  Extends: Fx.Elements,

  options: {/*
		onActive: $empty(toggler, section),
		onBackground: $empty(toggler, section),
		fixedHeight: false,
		fixedWidth: false,
		*/
    display: 0,
    show: false,
    height: true,
    width: false,
    opacity: true,
    alwaysHide: false,
    trigger: 'click',
    initialDisplayFx: true,
    returnHeightToAuto: true
  },

  initialize: function() {
    var params = Array.link(arguments, { 'container': Element.type, 'options': Object.type, 'togglers': $defined, 'elements': $defined });
    this.parent(params.elements, params.options);
    this.togglers = $$(params.togglers);
    this.container = document.id(params.container);
    this.previous = -1;
    this.internalChain = new Chain();
    if (this.options.alwaysHide) this.options.wait = true;
    if ($chk(this.options.show)) {
      this.options.display = false;
      this.previous = this.options.show;
    }
    if (this.options.start) {
      this.options.display = false;
      this.options.show = false;
    }
    this.effects = {};
    if (this.options.opacity) this.effects.opacity = 'fullOpacity';
    if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
    if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
    for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
    this.elements.each(function(el, i) {
      if (this.options.show === i) {
        this.fireEvent('active', [this.togglers[i], el]);
      } else {
        for (var fx in this.effects) el.setStyle(fx, 0);
      }
    }, this);
    if ($chk(this.options.display)) this.display(this.options.display, this.options.initialDisplayFx);
    this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
  },

  addSection: function(toggler, element) {
    toggler = document.id(toggler);
    element = document.id(element);
    var test = this.togglers.contains(toggler);
    this.togglers.include(toggler);
    this.elements.include(element);
    var idx = this.togglers.indexOf(toggler);
    var displayer = this.display.bind(this, idx);
    toggler.store('accordion:display', displayer);
    toggler.addEvent(this.options.trigger, displayer);
    if (this.options.height) element.setStyles({ 'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none' });
    if (this.options.width) element.setStyles({ 'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none' });
    element.fullOpacity = 1;
    if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
    if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
    element.setStyle('overflow', 'hidden');
    if (!test) {
      for (var fx in this.effects) element.setStyle(fx, 0);
    }
    return this;
  },

  detach: function() {
    this.togglers.each(function(toggler) {
      toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
    }, this);
  },

  display: function(index, useFx) {
    if (!this.check(index, useFx)) return this;
    useFx = $pick(useFx, true);
    if (this.options.returnHeightToAuto) {
      var prev = this.elements[this.previous];
      if (prev) {
        for (var fx in this.effects) {
          prev.setStyle(fx, prev[this.effects[fx]]);
        }
      }
    }
    index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
    if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
    this.previous = index;
    var obj = {};
    this.elements.each(function(el, i) {
      obj[i] = {};
      var hide = (i != index) ||
						(this.options.alwaysHide && ((el.offsetHeight > 0 && this.options.height) ||
							el.offsetWidth > 0 && this.options.width));
      this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
      for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
    }, this);
    this.internalChain.chain(function() {
      if (this.options.returnHeightToAuto) {
        var el = this.elements[index];
        el.setStyle('height', 'auto');
      };
    } .bind(this));
    return useFx ? this.start(obj) : this.set(obj);
  }

});

/*
---

script: Fx.Move.js

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

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Fx.Morph
- /Element.Position

provides: [Fx.Move]

...
*/

Fx.Move = new Class({

  Extends: Fx.Morph,

  options: {
    relativeTo: document.body,
    position: 'center',
    edge: false,
    offset: { x: 0, y: 0 }
  },

  start: function(destination) {
    return this.parent(this.element.position($merge(this.options, destination, { returnPos: true })));
  }

});

Element.Properties.move = {

  set: function(options) {
    var morph = this.retrieve('move');
    if (morph) morph.cancel();
    return this.eliminate('move').store('move:options', $extend({ link: 'cancel' }, options));
  },

  get: function(options) {
    if (options || !this.retrieve('move')) {
      if (options || !this.retrieve('move:options')) this.set('move', options);
      this.store('move', new Fx.Move(this, this.retrieve('move:options')));
    }
    return this.retrieve('move');
  }

};

Element.implement({

  move: function(options) {
    this.get('move').start(options);
    return this;
  }

});


/*
---

script: Fx.Reveal.js

description: Defines Fx.Reveal, a class that shows and hides elements with a transition.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Fx.Morph
- /Element.Shortcuts
- /Element.Measure

provides: [Fx.Reveal]

...
*/

Fx.Reveal = new Class({

  Extends: Fx.Morph,

  options: {/*	  
		onShow: $empty(thisElement),
		onHide: $empty(thisElement),
		onComplete: $empty(thisElement),
		heightOverride: null,
		widthOverride: null, */
    link: 'cancel',
    styles: ['padding', 'border', 'margin'],
    transitionOpacity: !Browser.Engine.trident4,
    mode: 'vertical',
    display: 'block',
    hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
  },

  dissolve: function() {
    try {
      if (!this.hiding && !this.showing) {
        if (this.element.getStyle('display') != 'none') {
          this.hiding = true;
          this.showing = false;
          this.hidden = true;
          var startStyles = this.element.getComputedSize({
            styles: this.options.styles,
            mode: this.options.mode
          });
          var setToAuto = (this.element.style.height === '' || this.element.style.height == 'auto');
          this.element.setStyle('display', 'block');
          if (this.options.transitionOpacity) startStyles.opacity = 1;
          var zero = {};
          $each(startStyles, function(style, name) {
            zero[name] = [style, 0];
          }, this);
          var overflowBefore = this.element.getStyle('overflow');
          this.element.setStyle('overflow', 'hidden');
          var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
          this.$chain.unshift(function() {
            if (this.hidden) {
              this.hiding = false;
              $each(startStyles, function(style, name) {
                startStyles[name] = style;
              }, this);
              this.element.setStyles($merge({ display: 'none', overflow: overflowBefore }, startStyles));
              if (setToAuto) {
                if (['vertical', 'both'].contains(this.options.mode)) this.element.style.height = '';
                if (['width', 'both'].contains(this.options.mode)) this.element.style.width = '';
              }
              if (hideThese) hideThese.setStyle('visibility', 'visible');
            }
            this.fireEvent('hide', this.element);
            this.callChain();
          } .bind(this));
          if (hideThese) hideThese.setStyle('visibility', 'hidden');
          this.start(zero);
        } else {
          this.callChain.delay(10, this);
          this.fireEvent('complete', this.element);
          this.fireEvent('hide', this.element);
        }
      } else if (this.options.link == 'chain') {
        this.chain(this.dissolve.bind(this));
      } else if (this.options.link == 'cancel' && !this.hiding) {
        this.cancel();
        this.dissolve();
      }
    } catch (e) {
      this.hiding = false;
      this.element.setStyle('display', 'none');
      this.callChain.delay(10, this);
      this.fireEvent('complete', this.element);
      this.fireEvent('hide', this.element);
    }
    return this;
  },

  reveal: function() {
    try {
      if (!this.showing && !this.hiding) {
        if (this.element.getStyle('display') == 'none' ||
					 this.element.getStyle('visiblity') == 'hidden' ||
					 this.element.getStyle('opacity') == 0) {
          this.showing = true;
          this.hiding = this.hidden = false;
          var setToAuto, startStyles;
          //toggle display, but hide it
          this.element.measure(function() {
            setToAuto = (this.element.style.height === '' || this.element.style.height == 'auto');
            //create the styles for the opened/visible state
            startStyles = this.element.getComputedSize({
              styles: this.options.styles,
              mode: this.options.mode
            });
          } .bind(this));
          $each(startStyles, function(style, name) {
            startStyles[name] = style;
          });
          //if we're overridding height/width
          if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
          if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
          if (this.options.transitionOpacity) {
            this.element.setStyle('opacity', 0);
            startStyles.opacity = 1;
          }
          //create the zero state for the beginning of the transition
          var zero = {
            height: 0,
            display: this.options.display
          };
          $each(startStyles, function(style, name) { zero[name] = 0; });
          var overflowBefore = this.element.getStyle('overflow');
          //set to zero
          this.element.setStyles($merge(zero, { overflow: 'hidden' }));
          //hide inputs
          var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
          if (hideThese) hideThese.setStyle('visibility', 'hidden');
          //start the effect
          this.start(startStyles);
          this.$chain.unshift(function() {
            this.element.setStyle('overflow', overflowBefore);
            if (!this.options.heightOverride && setToAuto) {
              if (['vertical', 'both'].contains(this.options.mode)) this.element.style.height = '';
              if (['width', 'both'].contains(this.options.mode)) this.element.style.width = '';
            }
            if (!this.hidden) this.showing = false;
            if (hideThese) hideThese.setStyle('visibility', 'visible');
            this.callChain();
            this.fireEvent('show', this.element);
          } .bind(this));
        } else {
          this.callChain();
          this.fireEvent('complete', this.element);
          this.fireEvent('show', this.element);
        }
      } else if (this.options.link == 'chain') {
        this.chain(this.reveal.bind(this));
      } else if (this.options.link == 'cancel' && !this.showing) {
        this.cancel();
        this.reveal();
      }
    } catch (e) {
      this.element.setStyles({
        display: this.options.display,
        visiblity: 'visible',
        opacity: 1
      });
      this.showing = false;
      this.callChain.delay(10, this);
      this.fireEvent('complete', this.element);
      this.fireEvent('show', this.element);
    }
    return this;
  },

  toggle: function() {
    if (this.element.getStyle('display') == 'none' ||
			 this.element.getStyle('visiblity') == 'hidden' ||
			 this.element.getStyle('opacity') == 0) {
      this.reveal();
    } else {
      this.dissolve();
    }
    return this;
  },

  cancel: function() {
    this.parent.apply(this, arguments);
    this.hidding = false;
    this.showing = false;
  }

});

Element.Properties.reveal = {

  set: function(options) {
    var reveal = this.retrieve('reveal');
    if (reveal) reveal.cancel();
    return this.eliminate('reveal').store('reveal:options', options);
  },

  get: function(options) {
    if (options || !this.retrieve('reveal')) {
      if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
      this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
    }
    return this.retrieve('reveal');
  }

};

Element.Properties.dissolve = Element.Properties.reveal;

Element.implement({

  reveal: function(options) {
    this.get('reveal', options).reveal();
    return this;
  },

  dissolve: function(options) {
    this.get('reveal', options).dissolve();
    return this;
  },

  nix: function() {
    var params = Array.link(arguments, { destroy: Boolean.type, options: Object.type });
    this.get('reveal', params.options).dissolve().chain(function() {
      this[params.destroy ? 'destroy' : 'dispose']();
    } .bind(this));
    return this;
  },

  wink: function() {
    var params = Array.link(arguments, { duration: Number.type, options: Object.type });
    var reveal = this.get('reveal', params.options);
    reveal.reveal().chain(function() {
      (function() {
        reveal.dissolve();
      }).delay(params.duration || 2000);
    });
  }


});

/*
---

script: Fx.Scroll.js

description: Effect to smoothly scroll any element, including the window.

license: MIT-style license

authors:
- Valerio Proietti

requires:
- core:1.2.4/Fx
- core:1.2.4/Element.Event
- core:1.2.4/Element.Dimensions
- /MooTools.More

provides: [Fx.Scroll]

...
*/

Fx.Scroll = new Class({

  Extends: Fx,

  options: {
    offset: { x: 0, y: 0 },
    wheelStops: true
  },

  initialize: function(element, options) {
    this.element = this.subject = document.id(element);
    this.parent(options);
    var cancel = this.cancel.bind(this, false);

    if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);

    var stopper = this.element;

    if (this.options.wheelStops) {
      this.addEvent('start', function() {
        stopper.addEvent('mousewheel', cancel);
      }, true);
      this.addEvent('complete', function() {
        stopper.removeEvent('mousewheel', cancel);
      }, true);
    }
  },

  set: function() {
    var now = Array.flatten(arguments);
    if (Browser.Engine.gecko) now = [Math.round(now[0]), Math.round(now[1])];
    this.element.scrollTo(now[0], now[1]);
  },

  compute: function(from, to, delta) {
    return [0, 1].map(function(i) {
      return Fx.compute(from[i], to[i], delta);
    });
  },

  start: function(x, y) {
    if (!this.check(x, y)) return this;
    var scrollSize = this.element.getScrollSize(),
			scroll = this.element.getScroll(),
			values = { x: x, y: y };
    for (var z in values) {
      var max = scrollSize[z];
      if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z] : max;
      else values[z] = scroll[z];
      values[z] += this.options.offset[z];
    }
    return this.parent([scroll.x, scroll.y], [values.x, values.y]);
  },

  toTop: function() {
    return this.start(false, 0);
  },

  toLeft: function() {
    return this.start(0, false);
  },

  toRight: function() {
    return this.start('right', false);
  },

  toBottom: function() {
    return this.start(false, 'bottom');
  },

  toElement: function(el) {
    var position = document.id(el).getPosition(this.element);
    return this.start(position.x, position.y);
  },

  scrollIntoView: function(el, axes, offset) {
    axes = axes ? $splat(axes) : ['x', 'y'];
    var to = {};
    el = document.id(el);
    var pos = el.getPosition(this.element);
    var size = el.getSize();
    var scroll = this.element.getScroll();
    var containerSize = this.element.getSize();
    var edge = {
      x: pos.x + size.x,
      y: pos.y + size.y
    };
    ['x', 'y'].each(function(axis) {
      if (axes.contains(axis)) {
        if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
        if (pos[axis] < scroll[axis]) to[axis] = pos[axis];
      }
      if (to[axis] == null) to[axis] = scroll[axis];
      if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
    }, this);
    if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
    return this;
  },

  scrollToCenter: function(el, axes, offset) {
    axes = axes ? $splat(axes) : ['x', 'y'];
    el = $(el);
    var to = {},
			pos = el.getPosition(this.element),
			size = el.getSize(),
			scroll = this.element.getScroll(),
			containerSize = this.element.getSize(),
			edge = {
			  x: pos.x + size.x,
			  y: pos.y + size.y
			};

    ['x', 'y'].each(function(axis) {
      if (axes.contains(axis)) {
        to[axis] = pos[axis] - (containerSize[axis] - size[axis]) / 2;
      }
      if (to[axis] == null) to[axis] = scroll[axis];
      if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
    }, this);
    if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
    return this;
  }

});


/*
---

script: Fx.Slide.js

description: Effect to slide an element in and out of view.

license: MIT-style license

authors:
- Valerio Proietti

requires:
- core:1.2.4/Fx Element.Style
- /MooTools.More

provides: [Fx.Slide]

...
*/

Fx.Slide = new Class({

  Extends: Fx,

  options: {
    mode: 'vertical',
    hideOverflow: true
  },

  initialize: function(element, options) {
    this.addEvent('complete', function() {
      this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
      if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
    }, true);
    this.element = this.subject = document.id(element);
    this.parent(options);
    var wrapper = this.element.retrieve('wrapper');
    var styles = this.element.getStyles('margin', 'position', 'overflow')
    if (this.options.hideOverflow) styles = $extend(styles, { overflow: 'hidden' });
    this.wrapper = wrapper || new Element('div', {
      styles: styles
    }).wraps(this.element);
    this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
    this.now = [];
    this.open = true;
  },

  vertical: function() {
    this.margin = 'margin-top';
    this.layout = 'height';
    this.offset = this.element.offsetHeight;
  },

  horizontal: function() {
    this.margin = 'margin-left';
    this.layout = 'width';
    this.offset = this.element.offsetWidth;
  },

  set: function(now) {
    this.element.setStyle(this.margin, now[0]);
    this.wrapper.setStyle(this.layout, now[1]);
    return this;
  },

  compute: function(from, to, delta) {
    return [0, 1].map(function(i) {
      return Fx.compute(from[i], to[i], delta);
    });
  },

  start: function(how, mode) {
    if (!this.check(how, mode)) return this;
    this[mode || this.options.mode]();
    var margin = this.element.getStyle(this.margin).toInt();
    var layout = this.wrapper.getStyle(this.layout).toInt();
    var caseIn = [[margin, layout], [0, this.offset]];
    var caseOut = [[margin, layout], [-this.offset, 0]];
    var start;
    switch (how) {
      case 'in': start = caseIn; break;
      case 'out': start = caseOut; break;
      case 'toggle': start = (layout == 0) ? caseIn : caseOut;
    }
    return this.parent(start[0], start[1]);
  },

  slideIn: function(mode) {
    return this.start('in', mode);
  },

  slideOut: function(mode) {
    return this.start('out', mode);
  },

  hide: function(mode) {
    this[mode || this.options.mode]();
    this.open = false;
    return this.set([-this.offset, 0]);
  },

  show: function(mode) {
    this[mode || this.options.mode]();
    this.open = true;
    return this.set([0, this.offset]);
  },

  toggle: function(mode) {
    return this.start('toggle', mode);
  }

});

Element.Properties.slide = {

  set: function(options) {
    var slide = this.retrieve('slide');
    if (slide) slide.cancel();
    return this.eliminate('slide').store('slide:options', $extend({ link: 'cancel' }, options));
  },

  get: function(options) {
    if (options || !this.retrieve('slide')) {
      if (options || !this.retrieve('slide:options')) this.set('slide', options);
      this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
    }
    return this.retrieve('slide');
  }

};

Element.implement({

  slide: function(how, mode) {
    how = how || 'toggle';
    var slide = this.get('slide'), toggle;
    switch (how) {
      case 'hide': slide.hide(mode); break;
      case 'show': slide.show(mode); break;
      case 'toggle':
        var flag = this.retrieve('slide:flag', slide.open);
        slide[flag ? 'slideOut' : 'slideIn'](mode);
        this.store('slide:flag', !flag);
        toggle = true;
        break;
      default: slide.start(how, mode);
    }
    if (!toggle) this.eliminate('slide:flag');
    return this;
  }

});


/*
---

script: Fx.SmoothScroll.js

description: Class for creating a smooth scrolling effect to all internal links on the page.

license: MIT-style license

authors:
- Valerio Proietti

requires:
- core:1.2.4/Selectors
- /Fx.Scroll

provides: [Fx.SmoothScroll]

...
*/

var SmoothScroll = Fx.SmoothScroll = new Class({

  Extends: Fx.Scroll,

  initialize: function(options, context) {
    context = context || document;
    this.doc = context.getDocument();
    var win = context.getWindow();
    this.parent(this.doc, options);
    this.links = $$(this.options.links || this.doc.links);
    var location = win.location.href.match(/^[^#]*/)[0] + '#';
    this.links.each(function(link) {
      if (link.href.indexOf(location) != 0) { return; }
      var anchor = link.href.substr(location.length);
      if (anchor) this.useLink(link, anchor);
    }, this);
    if (!Browser.Engine.webkit419) {
      this.addEvent('complete', function() {
        win.location.hash = this.anchor;
      }, true);
    }
  },

  useLink: function(link, anchor) {
    var el;
    link.addEvent('click', function(event) {
      if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
      if (el) {
        event.preventDefault();
        this.anchor = anchor;
        this.toElement(el);
        link.blur();
      }
    } .bind(this));
  }
});

/*
---

script: Fx.Sort.js

description: Defines Fx.Sort, a class that reorders lists with a transition.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Dimensions
- /Fx.Elements
- /Element.Measure

provides: [Fx.Sort]

...
*/

Fx.Sort = new Class({

  Extends: Fx.Elements,

  options: {
    mode: 'vertical'
  },

  initialize: function(elements, options) {
    this.parent(elements, options);
    this.elements.each(function(el) {
      if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
    });
    this.setDefaultOrder();
  },

  setDefaultOrder: function() {
    this.currentOrder = this.elements.map(function(el, index) {
      return index;
    });
  },

  sort: function(newOrder) {
    if ($type(newOrder) != 'array') return false;
    var top = 0,
			left = 0,
			next = {},
			zero = {},
			vert = this.options.mode == 'vertical';
    var current = this.elements.map(function(el, index) {
      var size = el.getComputedSize({ styles: ['border', 'padding', 'margin'] });
      var val;
      if (vert) {
        val = {
          top: top,
          margin: size['margin-top'],
          height: size.totalHeight
        };
        top += val.height - size['margin-top'];
      } else {
        val = {
          left: left,
          margin: size['margin-left'],
          width: size.totalWidth
        };
        left += val.width;
      }
      var plain = vert ? 'top' : 'left';
      zero[index] = {};
      var start = el.getStyle(plain).toInt();
      zero[index][plain] = start || 0;
      return val;
    }, this);
    this.set(zero);
    newOrder = newOrder.map(function(i) { return i.toInt(); });
    if (newOrder.length != this.elements.length) {
      this.currentOrder.each(function(index) {
        if (!newOrder.contains(index)) newOrder.push(index);
      });
      if (newOrder.length > this.elements.length)
        newOrder.splice(this.elements.length - 1, newOrder.length - this.elements.length);
    }
    var margin = top = left = 0;
    newOrder.each(function(item, index) {
      var newPos = {};
      if (vert) {
        newPos.top = top - current[item].top - margin;
        top += current[item].height;
      } else {
        newPos.left = left - current[item].left;
        left += current[item].width;
      }
      margin = margin + current[item].margin;
      next[item] = newPos;
    }, this);
    var mapped = {};
    $A(newOrder).sort().each(function(index) {
      mapped[index] = next[index];
    });
    this.start(mapped);
    this.currentOrder = newOrder;
    return this;
  },

  rearrangeDOM: function(newOrder) {
    newOrder = newOrder || this.currentOrder;
    var parent = this.elements[0].getParent();
    var rearranged = [];
    this.elements.setStyle('opacity', 0);
    //move each element and store the new default order
    newOrder.each(function(index) {
      rearranged.push(this.elements[index].inject(parent).setStyles({
        top: 0,
        left: 0
      }));
    }, this);
    this.elements.setStyle('opacity', 1);
    this.elements = $$(rearranged);
    this.setDefaultOrder();
    return this;
  },

  getDefaultOrder: function() {
    return this.elements.map(function(el, index) {
      return index;
    });
  },

  forward: function() {
    return this.sort(this.getDefaultOrder());
  },

  backward: function() {
    return this.sort(this.getDefaultOrder().reverse());
  },

  reverse: function() {
    return this.sort(this.currentOrder.reverse());
  },

  sortByElements: function(elements) {
    return this.sort(elements.map(function(el) {
      return this.elements.indexOf(el);
    }, this));
  },

  swap: function(one, two) {
    if ($type(one) == 'element') one = this.elements.indexOf(one);
    if ($type(two) == 'element') two = this.elements.indexOf(two);

    var newOrder = $A(this.currentOrder);
    newOrder[this.currentOrder.indexOf(one)] = two;
    newOrder[this.currentOrder.indexOf(two)] = one;
    return this.sort(newOrder);
  }

});

/*
---

script: Drag.js

description: The base Drag Class. Can be used to drag and resize Elements using mouse events.

license: MIT-style license

authors:
- Valerio Proietti
- Tom Occhinno
- Jan Kassens

requires:
- core:1.2.4/Events
- core:1.2.4/Options
- core:1.2.4/Element.Event
- core:1.2.4/Element.Style
- /MooTools.More

provides: [Drag]

*/

var Drag = new Class({

  Implements: [Events, Options],

  options: {/*
		onBeforeStart: $empty(thisElement),
		onStart: $empty(thisElement, event),
		onSnap: $empty(thisElement)
		onDrag: $empty(thisElement, event),
		onCancel: $empty(thisElement),
		onComplete: $empty(thisElement, event),*/
    snap: 6,
    unit: 'px',
    grid: false,
    style: true,
    limit: false,
    handle: false,
    invert: false,
    preventDefault: false,
    modifiers: { x: 'left', y: 'top' }
  },

  initialize: function() {
    var params = Array.link(arguments, { 'options': Object.type, 'element': $defined });
    this.element = document.id(params.element);
    this.document = this.element.getDocument();
    this.setOptions(params.options || {});
    var htype = $type(this.options.handle);
    this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
    this.mouse = { 'now': {}, 'pos': {} };
    this.value = { 'start': {}, 'now': {} };

    this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';

    this.bound = {
      start: this.start.bind(this),
      check: this.check.bind(this),
      drag: this.drag.bind(this),
      stop: this.stop.bind(this),
      cancel: this.cancel.bind(this),
      eventStop: $lambda(false)
    };
    this.attach();
  },

  attach: function() {
    this.handles.addEvent('mousedown', this.bound.start);
    return this;
  },

  detach: function() {
    this.handles.removeEvent('mousedown', this.bound.start);
    return this;
  },

  start: function(event) {
    if (event.rightClick) return;
    if (this.options.preventDefault) event.preventDefault();
    this.mouse.start = event.page;
    this.fireEvent('beforeStart', this.element);
    var limit = this.options.limit;
    this.limit = { x: [], y: [] };
    for (var z in this.options.modifiers) {
      if (!this.options.modifiers[z]) continue;
      if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
      else this.value.now[z] = this.element[this.options.modifiers[z]];
      if (this.options.invert) this.value.now[z] *= -1;
      this.mouse.pos[z] = event.page[z] - this.value.now[z];
      if (limit && limit[z]) {
        for (var i = 2; i--; i) {
          if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
        }
      }
    }
    if ($type(this.options.grid) == 'number') this.options.grid = { x: this.options.grid, y: this.options.grid };
    this.document.addEvents({ mousemove: this.bound.check, mouseup: this.bound.cancel });
    this.document.addEvent(this.selection, this.bound.eventStop);
  },

  check: function(event) {
    if (this.options.preventDefault) event.preventDefault();
    var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
    if (distance > this.options.snap) {
      this.cancel();
      this.document.addEvents({
        mousemove: this.bound.drag,
        mouseup: this.bound.stop
      });
      this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
    }
  },

  drag: function(event) {
    if (this.options.preventDefault) event.preventDefault();
    this.mouse.now = event.page;
    for (var z in this.options.modifiers) {
      if (!this.options.modifiers[z]) continue;
      this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
      if (this.options.invert) this.value.now[z] *= -1;
      if (this.options.limit && this.limit[z]) {
        if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])) {
          this.value.now[z] = this.limit[z][1];
        } else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])) {
          this.value.now[z] = this.limit[z][0];
        }
      }
      if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0] || 0)) % this.options.grid[z]);
      if (this.options.style) {
        this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
      } else {
        this.element[this.options.modifiers[z]] = this.value.now[z];
      }
    }
    this.fireEvent('drag', [this.element, event]);
  },

  cancel: function(event) {
    this.document.removeEvent('mousemove', this.bound.check);
    this.document.removeEvent('mouseup', this.bound.cancel);
    if (event) {
      this.document.removeEvent(this.selection, this.bound.eventStop);
      this.fireEvent('cancel', this.element);
    }
  },

  stop: function(event) {
    this.document.removeEvent(this.selection, this.bound.eventStop);
    this.document.removeEvent('mousemove', this.bound.drag);
    this.document.removeEvent('mouseup', this.bound.stop);
    if (event) this.fireEvent('complete', [this.element, event]);
  }

});

Element.implement({

  makeResizable: function(options) {
    var drag = new Drag(this, $merge({ modifiers: { x: 'width', y: 'height'} }, options));
    this.store('resizer', drag);
    return drag.addEvent('drag', function() {
      this.fireEvent('resize', drag);
    } .bind(this));
  }

});


/*
---

script: Drag.Move.js

description: A Drag extension that provides support for the constraining of draggables to containers and droppables.

license: MIT-style license

authors:
- Valerio Proietti
- Tom Occhinno
- Jan Kassens
- Aaron Newton
- Scott Kyle

requires:
- core:1.2.4/Element.Dimensions
- /Drag

provides: [Drag.Move]

...
*/

Drag.Move = new Class({

  Extends: Drag,

  options: {/*
		onEnter: $empty(thisElement, overed),
		onLeave: $empty(thisElement, overed),
		onDrop: $empty(thisElement, overed, event),*/
    droppables: [],
    container: false,
    precalculate: false,
    includeMargins: true,
    checkDroppables: true
  },

  initialize: function(element, options) {
    this.parent(element, options);
    element = this.element;

    this.droppables = $$(this.options.droppables);
    this.container = document.id(this.options.container);

    if (this.container && $type(this.container) != 'element')
      this.container = document.id(this.container.getDocument().body);

    var styles = element.getStyles('left', 'right', 'position');
    if (styles.left == 'auto' || styles.top == 'auto')
      element.setPosition(element.getPosition(element.getOffsetParent()));

    if (styles.position == 'static')
      element.setStyle('position', 'absolute');

    this.addEvent('start', this.checkDroppables, true);

    this.overed = null;
  },

  start: function(event) {
    if (this.container) this.options.limit = this.calculateLimit();

    if (this.options.precalculate) {
      this.positions = this.droppables.map(function(el) {
        return el.getCoordinates();
      });
    }

    this.parent(event);
  },

  calculateLimit: function() {
    var offsetParent = this.element.getOffsetParent(),
			containerCoordinates = this.container.getCoordinates(offsetParent),
			containerBorder = {},
			elementMargin = {},
			elementBorder = {},
			containerMargin = {},
			offsetParentPadding = {};

    ['top', 'right', 'bottom', 'left'].each(function(pad) {
      containerBorder[pad] = this.container.getStyle('border-' + pad).toInt();
      elementBorder[pad] = this.element.getStyle('border-' + pad).toInt();
      elementMargin[pad] = this.element.getStyle('margin-' + pad).toInt();
      containerMargin[pad] = this.container.getStyle('margin-' + pad).toInt();
      offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
    }, this);

    var width = this.element.offsetWidth + elementMargin.left + elementMargin.right,
			height = this.element.offsetHeight + elementMargin.top + elementMargin.bottom,
			left = 0,
			top = 0,
			right = containerCoordinates.right - containerBorder.right - width,
			bottom = containerCoordinates.bottom - containerBorder.bottom - height;

    if (this.options.includeMargins) {
      left += elementMargin.left;
      top += elementMargin.top;
    } else {
      right += elementMargin.right;
      bottom += elementMargin.bottom;
    }

    if (this.element.getStyle('position') == 'relative') {
      var coords = this.element.getCoordinates(offsetParent);
      coords.left -= this.element.getStyle('left').toInt();
      coords.top -= this.element.getStyle('top').toInt();

      left += containerBorder.left - coords.left;
      top += containerBorder.top - coords.top;
      right += elementMargin.left - coords.left;
      bottom += elementMargin.top - coords.top;

      if (this.container != offsetParent) {
        left += containerMargin.left + offsetParentPadding.left;
        top += (Browser.Engine.trident4 ? 0 : containerMargin.top) + offsetParentPadding.top;
      }
    } else {
      left -= elementMargin.left;
      top -= elementMargin.top;

      if (this.container == offsetParent) {
        right -= containerBorder.left;
        bottom -= containerBorder.top;
      } else {
        left += containerCoordinates.left + containerBorder.left;
        top += containerCoordinates.top + containerBorder.top;
      }
    }

    return {
      x: [left, right],
      y: [top, bottom]
    };
  },

  checkAgainst: function(el, i) {
    el = (this.positions) ? this.positions[i] : el.getCoordinates();
    var now = this.mouse.now;
    return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
  },

  checkDroppables: function() {
    var overed = this.droppables.filter(this.checkAgainst, this).getLast();
    if (this.overed != overed) {
      if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
      if (overed) this.fireEvent('enter', [this.element, overed]);
      this.overed = overed;
    }
  },

  drag: function(event) {
    this.parent(event);
    if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
  },

  stop: function(event) {
    this.checkDroppables();
    this.fireEvent('drop', [this.element, this.overed, event]);
    this.overed = null;
    return this.parent(event);
  }

});

Element.implement({

  makeDraggable: function(options) {
    var drag = new Drag.Move(this, options);
    this.store('dragger', drag);
    return drag;
  }

});


/*
---

script: Slider.js

description: Class for creating horizontal and vertical slider controls.

license: MIT-style license

authors:
- Valerio Proietti

requires:
- core:1.2.4/Element.Dimensions
- /Class.Binds
- /Drag
- /Element.Dimensions
- /Element.Measure

provides: [Slider]

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

script: Sortables.js

description: Class for creating a drag and drop sorting interface for lists of items.

license: MIT-style license

authors:
- Tom Occhino

requires:
- /Drag.Move

provides: [Slider]

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

script: Request.Queue.js

description: Controls several instances of Request and its variants to run only one request at a time.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element
- core:1.2.4/Request
- /Log

provides: [Request.Queue]

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

script: Request.Periodical.js

description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load

license: MIT-style license

authors:
- Christoph Pojer

requires:
- core:1.2.4/Request
- /MooTools.More

provides: [Request.Periodical]

...
*/

Request.implement({

  options: {
    initialDelay: 5000,
    delay: 5000,
    limit: 60000
  },

  startTimer: function(data) {
    var fn = function() {
      if (!this.running) this.send({ data: data });
    };
    this.timer = fn.delay(this.options.initialDelay, this);
    this.lastDelay = this.options.initialDelay;
    this.completeCheck = function(response) {
      $clear(this.timer);
      this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
      this.timer = fn.delay(this.lastDelay, this);
    };
    return this.addEvent('complete', this.completeCheck);
  },

  stopTimer: function() {
    $clear(this.timer);
    return this.removeEvent('complete', this.completeCheck);
  }

});

/*
---

script: Assets.js

description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.

license: MIT-style license

authors:
- Valerio Proietti

requires:
- core:1.2.4/Element.Event
- /MooTools.More

provides: [Assets]

...
*/

var Asset = {

  javascript: function(source, properties) {
    properties = $extend({
      onload: $empty,
      document: document,
      check: $lambda(true)
    }, properties);

    var script = new Element('script', { src: source, type: 'text/javascript' });

    var load = properties.onload.bind(script),
			check = properties.check,
			doc = properties.document;
    delete properties.onload;
    delete properties.check;
    delete properties.document;

    script.addEvents({
      load: load,
      readystatechange: function() {
        if (['loaded', 'complete'].contains(this.readyState)) load();
      }
    }).set(properties);

    if (Browser.Engine.webkit419) var checker = (function() {
      if (!$try(check)) return;
      $clear(checker);
      load();
    }).periodical(50);

    return script.inject(doc.head);
  },

  css: function(source, properties) {
    return new Element('link', $merge({
      rel: 'stylesheet',
      media: 'screen',
      type: 'text/css',
      href: source
    }, properties)).inject(document.head);
  },

  image: function(source, properties) {
    properties = $merge({
      onload: $empty,
      onabort: $empty,
      onerror: $empty
    }, properties);
    var image = new Image();
    var element = document.id(image) || new Element('img');
    ['load', 'abort', 'error'].each(function(name) {
      var type = 'on' + name;
      var event = properties[type];
      delete properties[type];
      image[type] = function() {
        if (!image) return;
        if (!element.parentNode) {
          element.width = image.width;
          element.height = image.height;
        }
        image = image.onload = image.onabort = image.onerror = null;
        event.delay(1, element, element);
        element.fireEvent(name, element, 1);
      };
    });
    image.src = element.src = source;
    if (image && image.complete) image.onload.delay(1);
    return element.set(properties);
  },

  images: function(sources, options) {
    options = $merge({
      onComplete: $empty,
      onProgress: $empty,
      onError: $empty,
      properties: {}
    }, options);
    sources = $splat(sources);
    var images = [];
    var counter = 0;
    return new Elements(sources.map(function(source) {
      return Asset.image(source, $extend(options.properties, {
        onload: function() {
          options.onProgress.call(this, counter, sources.indexOf(source));
          counter++;
          if (counter == sources.length) options.onComplete();
        },
        onerror: function() {
          options.onError.call(this, counter, sources.indexOf(source));
          counter++;
          if (counter == sources.length) options.onComplete();
        }
      }));
    }));
  }

};

/*
---

script: IframeShim.js

description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Event
- core:1.2.4/Element.Style
- core:1.2.4/Options Events
- /Element.Position
- /Class.Occlude

provides: [IframeShim]

...
*/

var IframeShim = new Class({

  Implements: [Options, Events, Class.Occlude],

  options: {
    className: 'iframeShim',
    src: 'javascript:false;document.write("");',
    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 = document.id(element);
    if (this.occlude()) return this.occluded;
    this.setOptions(options);
    this.makeShim();
    return this;
  },

  makeShim: function() {
    if (this.options.browsers) {
      var zIndex = this.element.getStyle('zIndex').toInt();

      if (!zIndex) {
        zIndex = 1;
        var pos = this.element.getStyle('position');
        if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
        this.element.setStyle('zIndex', zIndex);
      }
      zIndex = ($chk(this.options.zIndex) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
      if (zIndex < 0) zIndex = 1;
      this.shim = new Element('iframe', {
        src: this.options.src,
        scrolling: 'no',
        frameborder: 0,
        styles: {
          zIndex: zIndex,
          position: 'absolute',
          border: 'none',
          filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
        },
        'class': this.options.className
      }).store('IframeShim', this);
      var inject = (function() {
        this.shim.inject(this.element, 'after');
        this[this.options.display ? 'show' : 'hide']();
        this.fireEvent('inject');
      }).bind(this);
      if (IframeShim.ready) window.addEvent('load', inject);
      else inject();
    } else {
      this.position = this.hide = this.show = this.dispose = $lambda(this);
    }
  },

  position: function() {
    if (!IframeShim.ready || !this.shim) return this;
    var size = this.element.measure(function() {
      return this.getSize();
    });
    if (this.options.margin != undefined) {
      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 }).position({
      relativeTo: this.element,
      offset: this.options.offset
    });
    return this;
  },

  hide: function() {
    if (this.shim) this.shim.setStyle('display', 'none');
    return this;
  },

  show: function() {
    if (this.shim) this.shim.setStyle('display', 'block');
    return this.position();
  },

  dispose: function() {
    if (this.shim) this.shim.dispose();
    return this;
  },

  destroy: function() {
    if (this.shim) this.shim.destroy();
    return this;
  }

});

window.addEvent('load', function() {
  IframeShim.ready = true;
});

/*
---

script: Mask.js

description: Creates a mask element to cover another.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Options
- core:1.2.4/Events
- core:1.2.4/Element.Event
- /Class.Binds
- /Element.Position
- /IframeShim

provides: [Mask]

...
*/

var Mask = new Class({

  Implements: [Options, Events],

  Binds: ['resize'],

  options: {
    // onShow: $empty,
    // onHide: $empty,
    // onDestroy: $empty,
    // onClick: $empty,
    //inject: {
    //  where: 'after',
    //  target: null,
    //},
    // hideOnClick: false,
    // id: null,
    // destroyOnHide: false,
    style: {},
    'class': 'mask',
    maskMargins: false,
    useIframeShim: true
  },

  initialize: function(target, options) {
    this.target = document.id(target) || document.body;
    this.target.store('mask', this);
    this.setOptions(options);
    this.render();
    this.inject();
  },

  render: function() {
    this.element = new Element('div', {
      'class': this.options['class'],
      id: this.options.id || 'mask-' + $time(),
      styles: $merge(this.options.style, {
        display: 'none'
      }),
      events: {
        click: function() {
          this.fireEvent('click');
          if (this.options.hideOnClick) this.hide();
        } .bind(this)
      }
    });
    this.hidden = true;
  },

  toElement: function() {
    return this.element;
  },

  inject: function(target, where) {
    where = where || this.options.inject ? this.options.inject.where : '' || this.target == document.body ? 'inside' : 'after';
    target = target || this.options.inject ? this.options.inject.target : '' || this.target;
    this.element.inject(target, where);
    if (this.options.useIframeShim) {
      this.shim = new IframeShim(this.element);
      this.addEvents({
        show: this.shim.show.bind(this.shim),
        hide: this.shim.hide.bind(this.shim),
        destroy: this.shim.destroy.bind(this.shim)
      });
    }
  },

  position: function() {
    this.resize(this.options.width, this.options.height);
    this.element.position({
      relativeTo: this.target,
      position: 'topLeft',
      ignoreMargins: !this.options.maskMargins,
      ignoreScroll: this.target == document.body
    });
    return this;
  },

  resize: function(x, y) {
    var opt = {
      styles: ['padding', 'border']
    };
    if (this.options.maskMargins) opt.styles.push('margin');
    var dim = this.target.getComputedSize(opt);
    if (this.target == document.body) {
      var win = window.getSize();
      if (dim.totalHeight < win.y) dim.totalHeight = win.y;
      if (dim.totalWidth < win.x) dim.totalWidth = win.x;
    }
    this.element.setStyles({
      width: $pick(x, dim.totalWidth, dim.x),
      height: $pick(y, dim.totalHeight, dim.y)
    });
    return this;
  },

  show: function() {
    if (!this.hidden) return this;
    this.target.addEvent('resize', this.resize);
    if (this.target != document.body) document.id(document.body).addEvent('resize', this.resize);
    this.position();
    this.showMask.apply(this, arguments);
    return this;
  },

  showMask: function() {
    this.element.setStyle('display', 'block');
    this.hidden = false;
    this.fireEvent('show');
  },

  hide: function() {
    if (this.hidden) return this;
    this.target.removeEvent('resize', this.resize);
    this.hideMask.apply(this, arguments);
    if (this.options.destroyOnHide) return this.destroy();
    return this;
  },

  hideMask: function() {
    this.element.setStyle('display', 'none');
    this.hidden = true;
    this.fireEvent('hide');
  },

  toggle: function() {
    this[this.hidden ? 'show' : 'hide']();
  },

  destroy: function() {
    this.hide();
    this.element.destroy();
    this.fireEvent('destroy');
    this.target.eliminate('mask');
  }

});

Element.Properties.mask = {

  set: function(options) {
    var mask = this.retrieve('mask');
    return this.eliminate('mask').store('mask:options', options);
  },

  get: function(options) {
    if (options || !this.retrieve('mask')) {
      if (this.retrieve('mask')) this.retrieve('mask').destroy();
      if (options || !this.retrieve('mask:options')) this.set('mask', options);
      this.store('mask', new Mask(this, this.retrieve('mask:options')));
    }
    return this.retrieve('mask');
  }

};

Element.implement({

  mask: function(options) {
    this.get('mask', options).show();
    return this;
  },

  unmask: function() {
    this.get('mask').hide();
    return this;
  }

});

/*
---

script: Spinner.js

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

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Fx.Tween
- /Class.refactor
- /Mask

provides: [Spinner]

...
*/

var Spinner = new Class({

  Extends: Mask,

  options: {
    /*message: false,*/
    'class': 'spinner',
    containerPosition: {},
    content: {
      'class': 'spinner-content'
    },
    messageContainer: {
      'class': 'spinner-msg'
    },
    img: {
      'class': 'spinner-img'
    },
    fxOptions: {
      link: 'chain'
    }
  },

  initialize: function() {
    this.parent.apply(this, arguments);
    this.target.store('spinner', this);

    //add this to events for when noFx is true; parent methods handle hide/show
    var deactivate = function() { this.active = false; } .bind(this);
    this.addEvents({
      hide: deactivate,
      show: deactivate
    });
  },

  render: function() {
    this.parent();
    this.element.set('id', this.options.id || 'spinner-' + $time());
    this.content = document.id(this.options.content) || new Element('div', this.options.content);
    this.content.inject(this.element);
    if (this.options.message) {
      this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
      this.msg.inject(this.content);
    }
    if (this.options.img) {
      this.img = document.id(this.options.img) || new Element('div', this.options.img);
      this.img.inject(this.content);
    }
    this.element.set('tween', this.options.fxOptions);
  },

  show: function(noFx) {
    if (this.active) return this.chain(this.show.bind(this));
    if (!this.hidden) {
      this.callChain.delay(20, this);
      return this;
    }
    this.active = true;
    return this.parent(noFx);
  },

  showMask: function(noFx) {
    var pos = function() {
      this.content.position($merge({
        relativeTo: this.element
      }, this.options.containerPosition));
    } .bind(this);
    if (noFx) {
      this.parent();
      pos();
    } else {
      this.element.setStyles({
        display: 'block',
        opacity: 0
      }).tween('opacity', this.options.style.opacity || 0.9);
      pos();
      this.hidden = false;
      this.fireEvent('show');
      this.callChain();
    }
  },

  hide: function(noFx) {
    if (this.active) return this.chain(this.hide.bind(this));
    if (this.hidden) {
      this.callChain.delay(20, this);
      return this;
    }
    this.active = true;
    return this.parent();
  },

  hideMask: function(noFx) {
    if (noFx) return this.parent();
    this.element.tween('opacity', 0).get('tween').chain(function() {
      this.element.setStyle('display', 'none');
      this.hidden = true;
      this.fireEvent('hide');
      this.callChain();
    } .bind(this));
  },

  destroy: function() {
    this.content.destroy();
    this.parent();
    this.target.eliminate('spinner');
  }

});

Spinner.implement(new Chain);

if (window.Request) {
  Request = Class.refactor(Request, {
    options: {
      useSpinner: false,
      spinnerOptions: {},
      spinnerTarget: false
    },
    initialize: function(options) {
      this._send = this.send;
      this.send = function(options) {
        if (this.spinner) this.spinner.chain(this._send.bind(this, options)).show();
        else this._send(options);
        return this;
      };
      this.previous(options);
      var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
      if (this.options.useSpinner && update) {
        this.spinner = update.get('spinner', this.options.spinnerOptions);
        ['onComplete', 'onException', 'onCancel'].each(function(event) {
          this.addEvent(event, this.spinner.hide.bind(this.spinner));
        }, this);
      }
    }
  });
}

Element.Properties.spinner = {

  set: function(options) {
    var spinner = this.retrieve('spinner');
    return this.eliminate('spinner').store('spinner:options', options);
  },

  get: function(options) {
    if (options || !this.retrieve('spinner')) {
      if (this.retrieve('spinner')) this.retrieve('spinner').destroy();
      if (options || !this.retrieve('spinner:options')) this.set('spinner', options);
      new Spinner(this, this.retrieve('spinner:options'));
    }
    return this.retrieve('spinner');
  }

};

Element.implement({

  spin: function(options) {
    this.get('spinner', options).show();
    return this;
  },

  unspin: function() {
    var opt = Array.link(arguments, { options: Object.type, callback: Function.type });
    this.get('spinner', opt.options).hide(opt.callback);
    return this;
  }

});

/*
---

script: Date.English.US.js

description: Date messages for US English.

license: MIT-style license

authors:
- Aaron Newton

requires:
- /Lang
- /Date

provides: [Date.English.US]

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