/*
  MinuteDock - Application

*/

/*  Prototype JavaScript framework, version 1.6.0.3
 *  (c) 2005-2008 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.6.0.3',

  Browser: {
    IE:     !!(window.attachEvent &&
      navigator.userAgent.indexOf('Opera') === -1),
    Opera:  navigator.userAgent.indexOf('Opera') > -1,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&
      navigator.userAgent.indexOf('KHTML') === -1,
    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    SelectorsAPI: !!document.querySelector,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div')['__proto__'] &&
      document.createElement('div')['__proto__'] !==
        document.createElement('form')['__proto__']
  },

  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

  emptyFunction: function() { },
  K: function(x) { return x }
};

if (Prototype.Browser.MobileSafari)
  Prototype.BrowserFeatures.SpecificElementExtensions = false;


/* Based on Alex Arnell's inheritance implementation. */
var Class = {
  create: function() {
    var parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

    function klass() {
      this.initialize.apply(this, arguments);
    }

    Object.extend(klass, Class.Methods);
    klass.superclass = parent;
    klass.subclasses = [];

    if (parent) {
      var subclass = function() { };
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

    for (var i = 0; i < properties.length; i++)
      klass.addMethods(properties[i]);

    if (!klass.prototype.initialize)
      klass.prototype.initialize = Prototype.emptyFunction;

    klass.prototype.constructor = klass;

    return klass;
  }
};

Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value;
        value = (function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method);

        value.valueOf = method.valueOf.bind(method);
        value.toString = method.toString.bind(method);
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

var Abstract = { };

Object.extend = function(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
};

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (Object.isUndefined(object)) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : String(object);
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  toJSON: function(object) {
    var type = typeof object;
    switch (type) {
      case 'undefined':
      case 'function':
      case 'unknown': return;
      case 'boolean': return object.toString();
    }

    if (object === null) return 'null';
    if (object.toJSON) return object.toJSON();
    if (Object.isElement(object)) return;

    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (!Object.isUndefined(value))
        results.push(property.toJSON() + ': ' + value);
    }

    return '{' + results.join(', ') + '}';
  },

  toQueryString: function(object) {
    return $H(object).toQueryString();
  },

  toHTML: function(object) {
    return object && object.toHTML ? object.toHTML() : String.interpret(object);
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({ }, object);
  },

  isElement: function(object) {
    return !!(object && object.nodeType == 1);
  },

  isArray: function(object) {
    return object != null && typeof object == "object" &&
      'splice' in object && 'join' in object;
  },

  isHash: function(object) {
    return object instanceof Hash;
  },

  isFunction: function(object) {
    return typeof object == "function";
  },

  isString: function(object) {
    return typeof object == "string";
  },

  isNumber: function(object) {
    return typeof object == "number";
  },

  isUndefined: function(object) {
    return typeof object == "undefined";
  }
});

Object.extend(Function.prototype, {
  argumentNames: function() {
    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
      .replace(/\s+/g, '').split(',');
    return names.length == 1 && !names[0] ? [] : names;
  },

  bind: function() {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = $A(arguments), object = args.shift();
    return function() {
      return __method.apply(object, args.concat($A(arguments)));
    }
  },

  bindAsEventListener: function() {
    var __method = this, args = $A(arguments), object = args.shift();
    return function(event) {
      return __method.apply(object, [event || window.event].concat(args));
    }
  },

  curry: function() {
    if (!arguments.length) return this;
    var __method = this, args = $A(arguments);
    return function() {
      return __method.apply(this, args.concat($A(arguments)));
    }
  },

  delay: function() {
    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  },

  defer: function() {
    var args = [0.01].concat($A(arguments));
    return this.delay.apply(this, args);
  },

  wrap: function(wrapper) {
    var __method = this;
    return function() {
      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
    }
  },

  methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      return __method.apply(null, [this].concat($A(arguments)));
    };
  }
});

Date.prototype.toJSON = function() {
  return '"' + this.getUTCFullYear() + '-' +
    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
    this.getUTCDate().toPaddedString(2) + 'T' +
    this.getUTCHours().toPaddedString(2) + ':' +
    this.getUTCMinutes().toPaddedString(2) + ':' +
    this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) { }
    }

    return returnValue;
  }
};

RegExp.prototype.match = RegExp.prototype.test;

RegExp.escape = function(str) {
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create({
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  execute: function() {
    this.callback(this);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.execute();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
});
Object.extend(String, {
  interpret: function(value) {
    return value == null ? '' : String(value);
  },
  specialChar: {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '\\': '\\\\'
  }
});

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = Object.isUndefined(count) ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return String(this);
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = Object.isUndefined(truncation) ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  },

  escapeHTML: function() {
    var self = arguments.callee;
    self.text.data = this;
    return self.div.innerHTML;
  },

  unescapeHTML: function() {
    var div = new Element('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return { };

    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var key = decodeURIComponent(pair.shift());
        var value = pair.length > 1 ? pair.join('=') : pair[0];
        if (value != undefined) value = decodeURIComponent(value);

        if (key in hash) {
          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
          hash[key].push(value);
        }
        else hash[key] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  times: function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
      var character = String.specialChar[match[0]];
      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
    });
    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  },

  toJSON: function() {
    return this.inspect(true);
  },

  unfilterJSON: function(filter) {
    return this.sub(filter || Prototype.JSONFilter, '#{1}');
  },

  isJSON: function() {
    var str = this;
    if (str.blank()) return false;
    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  },

  include: function(pattern) {
    return this.indexOf(pattern) > -1;
  },

  startsWith: function(pattern) {
    return this.indexOf(pattern) === 0;
  },

  endsWith: function(pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
  },

  empty: function() {
    return this == '';
  },

  blank: function() {
    return /^\s*$/.test(this);
  },

  interpolate: function(object, pattern) {
    return new Template(this, pattern).evaluate(object);
  }
});

if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
  escapeHTML: function() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },
  unescapeHTML: function() {
    return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (Object.isFunction(replacement)) return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
};

String.prototype.parseQuery = String.prototype.toQueryParams;

Object.extend(String.prototype.escapeHTML, {
  div:  document.createElement('div'),
  text: document.createTextNode('')
});

String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);

var Template = Class.create({
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

    return this.template.gsub(this.pattern, function(match) {
      if (object == null) return '';

      var before = match[1] || '';
      if (before == '\\') return match[2];

      var ctx = object, expr = match[3];
      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
      match = pattern.exec(expr);
      if (match == null) return before;

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
        ctx = ctx[comp];
        if (null == ctx || '' == match[3]) break;
        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
        match = pattern.exec(expr);
      }

      return before + String.interpret(ctx);
    });
  }
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

var $break = { };

var Enumerable = {
  each: function(iterator, context) {
    var index = 0;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    var index = -number, slices = [], array = this.toArray();
    if (number < 1) return array;
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator.call(context, value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator.call(context, value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator.call(context, value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    var result;
    this.each(function(value, index) {
      if (iterator.call(context, value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator.call(context, value, index));
    });
    return results;
  },

  include: function(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator, context) {
    this.each(function(value, index) {
      memo = iterator.call(context, memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator.call(context, value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    return this.map(function(value, index) {
      return {
        value: value,
        criteria: iterator.call(context, value, index)
      };
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

if (Prototype.Browser.WebKit) {
  $A = function(iterable) {
    if (!iterable) return [];
    if (!(typeof iterable === 'function' && typeof iterable.length ===
        'number' && typeof iterable.item === 'function') && iterable.toArray)
      return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
  };
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(Object.isArray(value) ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function(sorted) {
    return this.inject([], function(array, value, index) {
      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
        array.push(value);
      return array;
    });
  },

  intersect: function(array) {
    return this.uniq().findAll(function(item) {
      return array.detect(function(value) { return item === value });
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  },

  toJSON: function() {
    var results = [];
    this.each(function(object) {
      var value = Object.toJSON(object);
      if (!Object.isUndefined(value)) results.push(value);
    });
    return '[' + results.join(', ') + ']';
  }
});

if (Object.isFunction(Array.prototype.forEach))
  Array.prototype._each = Array.prototype.forEach;

if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
};

if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
  var n = this.slice(0, i).reverse().indexOf(item);
  return (n < 0) ? n : i - n - 1;
};

Array.prototype.toArray = Array.prototype.clone;

function $w(string) {
  if (!Object.isString(string)) return [];
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if (Prototype.Browser.Opera){
  Array.prototype.concat = function() {
    var array = [];
    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for (var i = 0, length = arguments.length; i < length; i++) {
      if (Object.isArray(arguments[i])) {
        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  };
}
Object.extend(Number.prototype, {
  toColorPart: function() {
    return this.toPaddedString(2, 16);
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator, context) {
    $R(0, this, true).each(iterator, context);
    return this;
  },

  toPaddedString: function(length, radix) {
    var string = this.toString(radix || 10);
    return '0'.times(length - string.length) + string;
  },

  toJSON: function() {
    return isFinite(this) ? this.toString() : 'null';
  }
});

$w('abs round ceil floor').each(function(method){
  Number.prototype[method] = Math[method].methodize();
});
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {

  function toQueryPair(key, value) {
    if (Object.isUndefined(value)) return key;
    return key + '=' + encodeURIComponent(String.interpret(value));
  }

  return {
    initialize: function(object) {
      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
    },

    _each: function(iterator) {
      for (var key in this._object) {
        var value = this._object[key], pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    },

    set: function(key, value) {
      return this._object[key] = value;
    },

    get: function(key) {
      if (this._object[key] !== Object.prototype[key])
        return this._object[key];
    },

    unset: function(key) {
      var value = this._object[key];
      delete this._object[key];
      return value;
    },

    toObject: function() {
      return Object.clone(this._object);
    },

    keys: function() {
      return this.pluck('key');
    },

    values: function() {
      return this.pluck('value');
    },

    index: function(value) {
      var match = this.detect(function(pair) {
        return pair.value === value;
      });
      return match && match.key;
    },

    merge: function(object) {
      return this.clone().update(object);
    },

    update: function(object) {
      return new Hash(object).inject(this, function(result, pair) {
        result.set(pair.key, pair.value);
        return result;
      });
    },

    toQueryString: function() {
      return this.inject([], function(results, pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return results.concat(values.map(toQueryPair.curry(key)));
        } else results.push(toQueryPair(key, values));
        return results;
      }).join('&');
    },

    inspect: function() {
      return '#<Hash:{' + this.map(function(pair) {
        return pair.map(Object.inspect).join(': ');
      }).join(', ') + '}>';
    },

    toJSON: function() {
      return Object.toJSON(this.toObject());
    },

    clone: function() {
      return new Hash(this);
    }
  }
})());

Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
};

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responder) {
    if (!this.include(responder))
      this.responders.push(responder);
  },

  unregister: function(responder) {
    this.responders = this.responders.without(responder);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (Object.isFunction(responder[callback])) {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) { }
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate:   function() { Ajax.activeRequestCount++ },
  onComplete: function() { Ajax.activeRequestCount-- }
});

Ajax.Base = Class.create({
  initialize: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   '',
      evalJSON:     true,
      evalJS:       true
    };
    Object.extend(this.options, options || { });

    this.options.method = this.options.method.toLowerCase();

    if (Object.isString(this.options.parameters))
      this.options.parameters = this.options.parameters.toQueryParams();
    else if (Object.isHash(this.options.parameters))
      this.options.parameters = this.options.parameters.toObject();
  }
});

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,

  initialize: function($super, url, options) {
    $super(options);
    this.transport = Ajax.getTransport();
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Object.toQueryString(params)) {
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';
    }

    try {
      var response = new Ajax.Response(this);
      if (this.options.onCreate) this.options.onCreate(response);
      Ajax.Responders.dispatch('onCreate', this, response);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
      this.transport.send(this.body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete))
      this.respondToReadyState(this.transport.readyState);
  },

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (Object.isFunction(extras.push))
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  },

  success: function() {
    var status = this.getStatus();
    return !status || (status >= 200 && status < 300);
  },

  getStatus: function() {
    try {
      return this.transport.status || 0;
    } catch (e) { return 0 }
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

    if (state == 'Complete') {
      try {
        this._complete = true;
        (this.options['on' + response.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(response, response.headerJSON);
      } catch (e) {
        this.dispatchException(e);
      }

      var contentType = response.getHeader('Content-type');
      if (this.options.evalJS == 'force'
          || (this.options.evalJS && this.isSameOrigin() && contentType
          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
        this.evalResponse();
    }

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
    } catch (e) {
      this.dispatchException(e);
    }

    if (state == 'Complete') {
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

  isSameOrigin: function() {
    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
      protocol: location.protocol,
      domain: document.domain,
      port: location.port ? ':' + location.port : ''
    }));
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name) || null;
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval((this.transport.responseText || '').unfilterJSON());
    } catch (e) {
      this.dispatchException(e);
    }
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Response = Class.create({
  initialize: function(request){
    this.request = request;
    var transport  = this.transport  = request.transport,
        readyState = this.readyState = transport.readyState;

    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
      this.status       = this.getStatus();
      this.statusText   = this.getStatusText();
      this.responseText = String.interpret(transport.responseText);
      this.headerJSON   = this._getHeaderJSON();
    }

    if(readyState == 4) {
      var xml = transport.responseXML;
      this.responseXML  = Object.isUndefined(xml) ? null : xml;
      this.responseJSON = this._getResponseJSON();
    }
  },

  status:      0,
  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

  getStatusText: function() {
    try {
      return this.transport.statusText || '';
    } catch (e) { return '' }
  },

  getHeader: Ajax.Request.prototype.getHeader,

  getAllHeaders: function() {
    try {
      return this.getAllResponseHeaders();
    } catch (e) { return null }
  },

  getResponseHeader: function(name) {
    return this.transport.getResponseHeader(name);
  },

  getAllResponseHeaders: function() {
    return this.transport.getAllResponseHeaders();
  },

  _getHeaderJSON: function() {
    var json = this.getHeader('X-JSON');
    if (!json) return null;
    json = decodeURIComponent(escape(json));
    try {
      return json.evalJSON(this.request.options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

  _getResponseJSON: function() {
    var options = this.request.options;
    if (!options.evalJSON || (options.evalJSON != 'force' &&
      !(this.getHeader('Content-type') || '').include('application/json')) ||
        this.responseText.blank())
          return null;
    try {
      return this.responseText.evalJSON(options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  }
});

Ajax.Updater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = Object.clone(options);
    var onComplete = options.onComplete;
    options.onComplete = (function(response, json) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, json);
    }).bind(this);

    $super(url, options);
  },

  updateContent: function(responseText) {
    var receiver = this.container[this.success() ? 'success' : 'failure'],
        options = this.options;

    if (!options.evalScripts) responseText = responseText.stripScripts();

    if (receiver = $(receiver)) {
      if (options.insertion) {
        if (Object.isString(options.insertion)) {
          var insertion = { }; insertion[options.insertion] = responseText;
          receiver.insert(insertion);
        }
        else options.insertion(receiver, responseText);
      }
      else receiver.update(responseText);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
  initialize: function($super, container, url, options) {
    $super(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = { };
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(response) {
    if (this.options.decay) {
      this.decay = (response.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = response.responseText;
    }
    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element))
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(Element.extend(query.snapshotItem(i)));
    return results;
  };
}

/*--------------------------------------------------------------------------*/

if (!window.Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  Object.extend(Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });
}

(function() {
  var element = this.Element;
  this.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;
    if (Prototype.Browser.IE && attributes.name) {
      tagName = '<' + tagName + ' name="' + attributes.name + '">';
      delete attributes.name;
      return Element.writeAttribute(document.createElement(tagName), attributes);
    }
    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
  };
  Object.extend(this.Element, element || { });
  if (element) this.Element.prototype = element.prototype;
}).call(window);

Element.cache = { };

Element.Methods = {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    element = $(element);
    element.style.display = 'none';
    return element;
  },

  show: function(element) {
    element = $(element);
    element.style.display = '';
    return element;
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  update: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);
    content = Object.toHTML(content);
    element.innerHTML = content.stripScripts();
    content.evalScripts.bind(content).defer();
    return element;
  },

  replace: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    else if (!Object.isElement(content)) {
      content = Object.toHTML(content);
      var range = element.ownerDocument.createRange();
      range.selectNode(element);
      content.evalScripts.bind(content).defer();
      content = range.createContextualFragment(content.stripScripts());
    }
    element.parentNode.replaceChild(content, element);
    return element;
  },

  insert: function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = {bottom:insertions};

    var content, insert, tagName, childNodes;

    for (var position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      insert = Element._insertionTranslations[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        insert(element, content);
        continue;
      }

      content = Object.toHTML(content);

      tagName = ((position == 'before' || position == 'after')
        ? element.parentNode : element).tagName.toUpperCase();

      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

      if (position == 'top' || position == 'after') childNodes.reverse();
      childNodes.each(insert.curry(element));

      content.evalScripts.bind(content).defer();
    }

    return element;
  },

  wrap: function(element, wrapper, attributes) {
    element = $(element);
    if (Object.isElement(wrapper))
      $(wrapper).writeAttribute(attributes || { });
    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
    else wrapper = new Element('div', wrapper);
    if (element.parentNode)
      element.parentNode.replaceChild(wrapper, element);
    wrapper.appendChild(element);
    return wrapper;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $(element).select("*");
  },

  firstDescendant: function(element) {
    element = $(element).firstChild;
    while (element && element.nodeType != 1) element = element.nextSibling;
    return $(element);
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (Object.isString(selector))
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(element.parentNode);
    var ancestors = element.ancestors();
    return Object.isNumber(expression) ? ancestors[expression] :
      Selector.findElement(ancestors, expression, index);
  },

  down: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return element.firstDescendant();
    return Object.isNumber(expression) ? element.descendants()[expression] :
      Element.select(element, expression)[index || 0];
  },

  previous: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
    var previousSiblings = element.previousSiblings();
    return Object.isNumber(expression) ? previousSiblings[expression] :
      Selector.findElement(previousSiblings, expression, index);
  },

  next: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
    var nextSiblings = element.nextSiblings();
    return Object.isNumber(expression) ? nextSiblings[expression] :
      Selector.findElement(nextSiblings, expression, index);
  },

  select: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  adjacent: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element.parentNode, args).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = element.readAttribute('id'), self = arguments.callee;
    if (id) return id;
    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
    element.writeAttribute('id', id);
    return id;
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (Prototype.Browser.IE) {
      var t = Element._attributeTranslations.read;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name]) name = t.names[name];
      if (name.include(':')) {
        return (!element.attributes || !element.attributes[name]) ? null :
         element.attributes[name].value;
      }
    }
    return element.getAttribute(name);
  },

  writeAttribute: function(element, name, value) {
    element = $(element);
    var attributes = { }, t = Element._attributeTranslations.write;

    if (typeof name == 'object') attributes = name;
    else attributes[name] = Object.isUndefined(value) ? true : value;

    for (var attr in attributes) {
      name = t.names[attr] || attr;
      value = attributes[attr];
      if (t.values[attr]) name = t.values[attr](element, value);
      if (value === false || value === null)
        element.removeAttribute(name);
      else if (value === true)
        element.setAttribute(name, name);
      else element.setAttribute(name, value);
    }
    return element;
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    return (elementClassName.length > 0 && (elementClassName == className ||
      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    if (!element.hasClassName(className))
      element.className += (element.className ? ' ' : '') + className;
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    element.className = element.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    return element[element.hasClassName(className) ?
      'removeClassName' : 'addClassName'](className);
  },

  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.blank();
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);

    if (element.compareDocumentPosition)
      return (element.compareDocumentPosition(ancestor) & 8) === 8;

    if (ancestor.contains)
      return ancestor.contains(element) && ancestor !== element;

    while (element = element.parentNode)
      if (element == ancestor) return true;

    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = element.cumulativeOffset();
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value || value == 'auto') {
      var css = document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    }
    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
  },

  getOpacity: function(element) {
    return $(element).getStyle('opacity');
  },

  setStyle: function(element, styles) {
    element = $(element);
    var elementStyle = element.style, match;
    if (Object.isString(styles)) {
      element.style.cssText += ';' + styles;
      return styles.include('opacity') ?
        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
    }
    for (var property in styles)
      if (property == 'opacity') element.setOpacity(styles[property]);
      else
        elementStyle[(property == 'float' || property == 'cssFloat') ?
          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
            property] = styles[property];

    return element;
  },

  setOpacity: function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    var display = element.getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      if (Prototype.Browser.Opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
    if (element._overflow !== 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (element.tagName.toUpperCase() == 'BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p !== 'static') break;
      }
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return element;

    var offsets = element.positionedOffset();
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
    return element;
  },

  relativize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'relative') return element;

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
    return element;
  },

  cumulativeScrollOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  getOffsetParent: function(element) {
    if (element.offsetParent) return $(element.offsetParent);
    if (element == document.body) return $(element);

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return $(element);

    return $(document.body);
  },

  viewportOffset: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      if (element.offsetParent == document.body &&
        Element.getStyle(element, 'position') == 'absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return Element._returnOffset(valueL, valueT);
  },

  clonePosition: function(element, source) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || { });

    source = $(source);
    var p = source.viewportOffset();

    element = $(element);
    var delta = [0, 0];
    var parent = null;
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
    return element;
  }
};

Element.Methods.identify.counter = 1;

Object.extend(Element.Methods, {
  getElementsBySelector: Element.Methods.select,
  childElements: Element.Methods.immediateDescendants
});

Element._attributeTranslations = {
  write: {
    names: {
      className: 'class',
      htmlFor:   'for'
    },
    values: { }
  }
};

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
    function(proceed, element, style) {
      switch (style) {
        case 'left': case 'top': case 'right': case 'bottom':
          if (proceed(element, 'position') === 'static') return null;
        case 'height': case 'width':
          if (!Element.visible(element)) return null;

          var dim = parseInt(proceed(element, style), 10);

          if (dim !== element['offset' + style.capitalize()])
            return dim + 'px';

          var properties;
          if (style === 'height') {
            properties = ['border-top-width', 'padding-top',
             'padding-bottom', 'border-bottom-width'];
          }
          else {
            properties = ['border-left-width', 'padding-left',
             'padding-right', 'border-right-width'];
          }
          return properties.inject(dim, function(memo, property) {
            var val = proceed(element, property);
            return val === null ? memo : memo - parseInt(val, 10);
          }) + 'px';
        default: return proceed(element, style);
      }
    }
  );

  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
    function(proceed, element, attribute) {
      if (attribute === 'title') return element.title;
      return proceed(element, attribute);
    }
  );
}

else if (Prototype.Browser.IE) {
  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
    function(proceed, element) {
      element = $(element);
      try { element.offsetParent }
      catch(e) { return $(document.body) }
      var position = element.getStyle('position');
      if (position !== 'static') return proceed(element);
      element.setStyle({ position: 'relative' });
      var value = proceed(element);
      element.setStyle({ position: position });
      return value;
    }
  );

  $w('positionedOffset viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        try { element.offsetParent }
        catch(e) { return Element._returnOffset(0,0) }
        var position = element.getStyle('position');
        if (position !== 'static') return proceed(element);
        var offsetParent = element.getOffsetParent();
        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
          offsetParent.setStyle({ zoom: 1 });
        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );
  });

  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
    function(proceed, element) {
      try { element.offsetParent }
      catch(e) { return Element._returnOffset(0,0) }
      return proceed(element);
    }
  );

  Element.Methods.getStyle = function(element, style) {
    element = $(element);
    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
    var value = element.style[style];
    if (!value && element.currentStyle) value = element.currentStyle[style];

    if (style == 'opacity') {
      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if (value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }

    if (value == 'auto') {
      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
        return element['offset' + style.capitalize()] + 'px';
      return null;
    }
    return value;
  };

  Element.Methods.setOpacity = function(element, value) {
    function stripAlpha(filter){
      return filter.replace(/alpha\([^\)]*\)/gi,'');
    }
    element = $(element);
    var currentStyle = element.currentStyle;
    if ((currentStyle && !currentStyle.hasLayout) ||
      (!currentStyle && element.style.zoom == 'normal'))
        element.style.zoom = 1;

    var filter = element.getStyle('filter'), style = element.style;
    if (value == 1 || value === '') {
      (filter = stripAlpha(filter)) ?
        style.filter = filter : style.removeAttribute('filter');
      return element;
    } else if (value < 0.00001) value = 0;
    style.filter = stripAlpha(filter) +
      'alpha(opacity=' + (value * 100) + ')';
    return element;
  };

  Element._attributeTranslations = {
    read: {
      names: {
        'class': 'className',
        'for':   'htmlFor'
      },
      values: {
        _getAttr: function(element, attribute) {
          return element.getAttribute(attribute, 2);
        },
        _getAttrNode: function(element, attribute) {
          var node = element.getAttributeNode(attribute);
          return node ? node.value : "";
        },
        _getEv: function(element, attribute) {
          attribute = element.getAttribute(attribute);
          return attribute ? attribute.toString().slice(23, -2) : null;
        },
        _flag: function(element, attribute) {
          return $(element).hasAttribute(attribute) ? attribute : null;
        },
        style: function(element) {
          return element.style.cssText.toLowerCase();
        },
        title: function(element) {
          return element.title;
        }
      }
    }
  };

  Element._attributeTranslations.write = {
    names: Object.extend({
      cellpadding: 'cellPadding',
      cellspacing: 'cellSpacing'
    }, Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

      style: function(element, value) {
        element.style.cssText = value ? value : '';
      }
    }
  };

  Element._attributeTranslations.has = {};

  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
  });

  (function(v) {
    Object.extend(v, {
      href:        v._getAttr,
      src:         v._getAttr,
      type:        v._getAttr,
      action:      v._getAttrNode,
      disabled:    v._flag,
      checked:     v._flag,
      readonly:    v._flag,
      multiple:    v._flag,
      onload:      v._getEv,
      onunload:    v._getEv,
      onclick:     v._getEv,
      ondblclick:  v._getEv,
      onmousedown: v._getEv,
      onmouseup:   v._getEv,
      onmouseover: v._getEv,
      onmousemove: v._getEv,
      onmouseout:  v._getEv,
      onfocus:     v._getEv,
      onblur:      v._getEv,
      onkeypress:  v._getEv,
      onkeydown:   v._getEv,
      onkeyup:     v._getEv,
      onsubmit:    v._getEv,
      onreset:     v._getEv,
      onselect:    v._getEv,
      onchange:    v._getEv
    });
  })(Element._attributeTranslations.read.values);
}

else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

else if (Prototype.Browser.WebKit) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;

    if (value == 1)
      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
        element.width++; element.width--;
      } else try {
        var n = document.createTextNode(' ');
        element.appendChild(n);
        element.removeChild(n);
      } catch (e) { }

    return element;
  };

  Element.Methods.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return Element._returnOffset(valueL, valueT);
  };
}

if (Prototype.Browser.IE || Prototype.Browser.Opera) {
  Element.Methods.update = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);

    content = Object.toHTML(content);
    var tagName = element.tagName.toUpperCase();

    if (tagName in Element._insertionTranslations.tags) {
      $A(element.childNodes).each(function(node) { element.removeChild(node) });
      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
        .each(function(node) { element.appendChild(node) });
    }
    else element.innerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

if ('outerHTML' in document.createElement('div')) {
  Element.Methods.replace = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) {
      element.parentNode.replaceChild(content, element);
      return element;
    }

    content = Object.toHTML(content);
    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

    if (Element._insertionTranslations.tags[tagName]) {
      var nextSibling = element.next();
      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
      parent.removeChild(element);
      if (nextSibling)
        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
      else
        fragments.each(function(node) { parent.appendChild(node) });
    }
    else element.outerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

Element._returnOffset = function(l, t) {
  var result = [l, t];
  result.left = l;
  result.top = t;
  return result;
};

Element._getContentFromAnonymousElement = function(tagName, html) {
  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
  if (t) {
    div.innerHTML = t[0] + html + t[1];
    t[2].times(function() { div = div.firstChild });
  } else div.innerHTML = html;
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: function(element, node) {
    element.parentNode.insertBefore(node, element);
  },
  top: function(element, node) {
    element.insertBefore(node, element.firstChild);
  },
  bottom: function(element, node) {
    element.appendChild(node);
  },
  after: function(element, node) {
    element.parentNode.insertBefore(node, element.nextSibling);
  },
  tags: {
    TABLE:  ['<table>',                '</table>',                   1],
    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
    SELECT: ['<select>',               '</select>',                  1]
  }
};

(function() {
  Object.extend(this.tags, {
    THEAD: this.tags.TBODY,
    TFOOT: this.tags.TBODY,
    TH:    this.tags.TD
  });
}).call(Element._insertionTranslations);

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    attribute = Element._attributeTranslations.has[attribute] || attribute;
    var node = $(element).getAttributeNode(attribute);
    return !!(node && node.specified);
  }
};

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

if (!Prototype.BrowserFeatures.ElementExtensions &&
    document.createElement('div')['__proto__']) {
  window.HTMLElement = { };
  window.HTMLElement.prototype = document.createElement('div')['__proto__'];
  Prototype.BrowserFeatures.ElementExtensions = true;
}

Element.extend = (function() {
  if (Prototype.BrowserFeatures.SpecificElementExtensions)
    return Prototype.K;

  var Methods = { }, ByTag = Element.Methods.ByTag;

  var extend = Object.extend(function(element) {
    if (!element || element._extendedByPrototype ||
        element.nodeType != 1 || element == window) return element;

    var methods = Object.clone(Methods),
      tagName = element.tagName.toUpperCase(), property, value;

    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
        element[property] = value.methodize();
    }

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

  extend.refresh();
  return extend;
})();

Element.hasAttribute = function(element, attribute) {
  if (element.hasAttribute) return element.hasAttribute(attribute);
  return Element.Methods.Simulated.hasAttribute(element, attribute);
};

Element.addMethods = function(methods) {
  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

  if (!methods) {
    Object.extend(Form, Form.Methods);
    Object.extend(Form.Element, Form.Element.Methods);
    Object.extend(Element.Methods.ByTag, {
      "FORM":     Object.clone(Form.Methods),
      "INPUT":    Object.clone(Form.Element.Methods),
      "SELECT":   Object.clone(Form.Element.Methods),
      "TEXTAREA": Object.clone(Form.Element.Methods)
    });
  }

  if (arguments.length == 2) {
    var tagName = methods;
    methods = arguments[1];
  }

  if (!tagName) Object.extend(Element.Methods, methods || { });
  else {
    if (Object.isArray(tagName)) tagName.each(extend);
    else extend(tagName);
  }

  function extend(tagName) {
    tagName = tagName.toUpperCase();
    if (!Element.Methods.ByTag[tagName])
      Element.Methods.ByTag[tagName] = { };
    Object.extend(Element.Methods.ByTag[tagName], methods);
  }

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    for (var property in methods) {
      var value = methods[property];
      if (!Object.isFunction(value)) continue;
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = value.methodize();
    }
  }

  function findDOMClass(tagName) {
    var klass;
    var trans = {
      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
      "FrameSet", "IFRAME": "IFrame"
    };
    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName.capitalize() + 'Element';
    if (window[klass]) return window[klass];

    window[klass] = { };
    window[klass].prototype = document.createElement(tagName)['__proto__'];
    return window[klass];
  }

  if (F.ElementExtensions) {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
  }

  if (F.SpecificElementExtensions) {
    for (var tag in Element.Methods.ByTag) {
      var klass = findDOMClass(tag);
      if (Object.isUndefined(klass)) continue;
      copy(T[tag], klass.prototype);
    }
  }

  Object.extend(Element, Element.Methods);
  delete Element.ByTag;

  if (Element.extend.refresh) Element.extend.refresh();
  Element.cache = { };
};

document.viewport = {
  getDimensions: function() {
    var dimensions = { }, B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      if (B.WebKit && !document.evaluate) {
        dimensions[d] = self['inner' + D];
      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
        dimensions[d] = document.body['client' + D]
      } else {
        dimensions[d] = document.documentElement['client' + D];
      }
    });
    return dimensions;
  },

  getWidth: function() {
    return this.getDimensions().width;
  },

  getHeight: function() {
    return this.getDimensions().height;
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
  }
};
/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
 * license.  Please see http://www.yui-ext.com/ for more information. */

var Selector = Class.create({
  initialize: function(expression) {
    this.expression = expression.strip();

    if (this.shouldUseSelectorsAPI()) {
      this.mode = 'selectorsAPI';
    } else if (this.shouldUseXPath()) {
      this.mode = 'xpath';
      this.compileXPathMatcher();
    } else {
      this.mode = "normal";
      this.compileMatcher();
    }

  },

  shouldUseXPath: function() {
    if (!Prototype.BrowserFeatures.XPath) return false;

    var e = this.expression;

    if (Prototype.Browser.WebKit &&
     (e.include("-of-type") || e.include(":empty")))
      return false;

    if ((/(\[[\w-]*?:|:checked)/).test(e))
      return false;

    return true;
  },

  shouldUseSelectorsAPI: function() {
    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;

    if (!Selector._div) Selector._div = new Element('div');

    try {
      Selector._div.querySelector(this.expression);
    } catch(e) {
      return false;
    }

    return true;
  },

  compileMatcher: function() {
    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
        c = Selector.criteria, le, p, m;

    if (Selector._cache[e]) {
      this.matcher = Selector._cache[e];
      return;
    }

    this.matcher = ["this.matcher = function(root) {",
                    "var r = root, h = Selector.handlers, c = false, n;"];

    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
            new Template(c[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.matcher.push("return h.unique(n);\n}");
    eval(this.matcher.join('\n'));
    Selector._cache[this.expression] = this.matcher;
  },

  compileXPathMatcher: function() {
    var e = this.expression, ps = Selector.patterns,
        x = Selector.xpath, le, m;

    if (Selector._cache[e]) {
      this.xpath = Selector._cache[e]; return;
    }

    this.matcher = ['.//*'];
    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        if (m = e.match(ps[i])) {
          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
            new Template(x[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.xpath = this.matcher.join('');
    Selector._cache[this.expression] = this.xpath;
  },

  findElements: function(root) {
    root = root || document;
    var e = this.expression, results;

    switch (this.mode) {
      case 'selectorsAPI':
        if (root !== document) {
          var oldId = root.id, id = $(root).identify();
          e = "#" + id + " " + e;
        }

        results = $A(root.querySelectorAll(e)).map(Element.extend);
        root.id = oldId;

        return results;
      case 'xpath':
        return document._getElementsByXPath(this.xpath, root);
      default:
       return this.matcher(root);
    }
  },

  match: function(element) {
    this.tokens = [];

    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
    var le, p, m;

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          if (as[i]) {
            this.tokens.push([i, Object.clone(m)]);
            e = e.replace(m[0], '');
          } else {
            return this.findElements(document).include(element);
          }
        }
      }
    }

    var match = true, name, matches;
    for (var i = 0, token; token = this.tokens[i]; i++) {
      name = token[0], matches = token[1];
      if (!Selector.assertions[name](element, matches)) {
        match = false; break;
      }
    }

    return match;
  },

  toString: function() {
    return this.expression;
  },

  inspect: function() {
    return "#<Selector:" + this.expression.inspect() + ">";
  }
});

Object.extend(Selector, {
  _cache: { },

  xpath: {
    descendant:   "//*",
    child:        "/*",
    adjacent:     "/following-sibling::*[1]",
    laterSibling: '/following-sibling::*',
    tagName:      function(m) {
      if (m[1] == '*') return '';
      return "[local-name()='" + m[1].toLowerCase() +
             "' or local-name()='" + m[1].toUpperCase() + "']";
    },
    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
    id:           "[@id='#{1}']",
    attrPresence: function(m) {
      m[1] = m[1].toLowerCase();
      return new Template("[@#{1}]").evaluate(m);
    },
    attr: function(m) {
      m[1] = m[1].toLowerCase();
      m[3] = m[5] || m[6];
      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
    },
    pseudo: function(m) {
      var h = Selector.xpath.pseudos[m[1]];
      if (!h) return '';
      if (Object.isFunction(h)) return h(m);
      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
    },
    operators: {
      '=':  "[@#{1}='#{3}']",
      '!=': "[@#{1}!='#{3}']",
      '^=': "[starts-with(@#{1}, '#{3}')]",
      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
      '*=': "[contains(@#{1}, '#{3}')]",
      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
    },
    pseudos: {
      'first-child': '[not(preceding-sibling::*)]',
      'last-child':  '[not(following-sibling::*)]',
      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
      'checked':     "[@checked]",
      'disabled':    "[(@disabled) and (@type!='hidden')]",
      'enabled':     "[not(@disabled) and (@type!='hidden')]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, v;

        var exclusion = [];
        while (e && le != e && (/\S/).test(e)) {
          le = e;
          for (var i in p) {
            if (m = e.match(p[i])) {
              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
              e = e.replace(m[0], '');
              break;
            }
          }
        }
        return "[not(" + exclusion.join(" and ") + ")]";
      },
      'nth-child':      function(m) {
        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
      },
      'nth-last-child': function(m) {
        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
      },
      'nth-of-type':    function(m) {
        return Selector.xpath.pseudos.nth("position() ", m);
      },
      'nth-last-of-type': function(m) {
        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
      },
      'first-of-type':  function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
      },
      'last-of-type':   function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
      },
      'only-of-type':   function(m) {
        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
      },
      nth: function(fragment, m) {
        var mm, formula = m[6], predicate;
        if (formula == 'even') formula = '2n+0';
        if (formula == 'odd')  formula = '2n+1';
        if (mm = formula.match(/^(\d+)$/)) // digit only
          return '[' + fragment + "= " + mm[1] + ']';
        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
          if (mm[1] == "-") mm[1] = -1;
          var a = mm[1] ? Number(mm[1]) : 1;
          var b = mm[2] ? Number(mm[2]) : 0;
          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
          "((#{fragment} - #{b}) div #{a} >= 0)]";
          return new Template(predicate).evaluate({
            fragment: fragment, a: a, b: b });
        }
      }
    }
  },

  criteria: {
    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
    },
    pseudo: function(m) {
      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
    },
    descendant:   'c = "descendant";',
    child:        'c = "child";',
    adjacent:     'c = "adjacent";',
    laterSibling: 'c = "laterSibling";'
  },

  patterns: {
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
    id:           /^#([\w\-\*]+)(\b|$)/,
    className:    /^\.([\w\-\*]+)(\b|$)/,
    pseudo:
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
    attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
  },

  assertions: {
    tagName: function(element, matches) {
      return matches[1].toUpperCase() == element.tagName.toUpperCase();
    },

    className: function(element, matches) {
      return Element.hasClassName(element, matches[1]);
    },

    id: function(element, matches) {
      return element.id === matches[1];
    },

    attrPresence: function(element, matches) {
      return Element.hasAttribute(element, matches[1]);
    },

    attr: function(element, matches) {
      var nodeValue = Element.readAttribute(element, matches[1]);
      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
    }
  },

  handlers: {
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        a.push(node);
      return a;
    },

    mark: function(nodes) {
      var _true = Prototype.emptyFunction;
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = _true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = undefined;
      return nodes;
    },

    index: function(parentNode, reverse, ofType) {
      parentNode._countedByPrototype = Prototype.emptyFunction;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          var node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
      }
    },

    unique: function(nodes) {
      if (nodes.length == 0) return nodes;
      var results = [], n;
      for (var i = 0, l = nodes.length; i < l; i++)
        if (!(n = nodes[i])._countedByPrototype) {
          n._countedByPrototype = Prototype.emptyFunction;
          results.push(Element.extend(n));
        }
      return Selector.handlers.unmark(results);
    },

    descendant: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, node.getElementsByTagName('*'));
      return results;
    },

    child: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        for (var j = 0, child; child = node.childNodes[j]; j++)
          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
      }
      return results;
    },

    adjacent: function(nodes) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        var next = this.nextElementSibling(node);
        if (next) results.push(next);
      }
      return results;
    },

    laterSibling: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, Element.nextSiblings(node));
      return results;
    },

    nextElementSibling: function(node) {
      while (node = node.nextSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    previousElementSibling: function(node) {
      while (node = node.previousSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    tagName: function(nodes, root, tagName, combinator) {
      var uTagName = tagName.toUpperCase();
      var results = [], h = Selector.handlers;
      if (nodes) {
        if (combinator) {
          if (combinator == "descendant") {
            for (var i = 0, node; node = nodes[i]; i++)
              h.concat(results, node.getElementsByTagName(tagName));
            return results;
          } else nodes = this[combinator](nodes);
          if (tagName == "*") return nodes;
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName.toUpperCase() === uTagName) results.push(node);
        return results;
      } else return root.getElementsByTagName(tagName);
    },

    id: function(nodes, root, id, combinator) {
      var targetNode = $(id), h = Selector.handlers;
      if (!targetNode) return [];
      if (!nodes && root == document) return [targetNode];
      if (nodes) {
        if (combinator) {
          if (combinator == 'child') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (targetNode.parentNode == node) return [targetNode];
          } else if (combinator == 'descendant') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Element.descendantOf(targetNode, node)) return [targetNode];
          } else if (combinator == 'adjacent') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Selector.handlers.previousElementSibling(targetNode) == node)
                return [targetNode];
          } else nodes = h[combinator](nodes);
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node == targetNode) return [targetNode];
        return [];
      }
      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
    },

    className: function(nodes, root, className, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      return Selector.handlers.byClassName(nodes, root, className);
    },

    byClassName: function(nodes, root, className) {
      if (!nodes) nodes = Selector.handlers.descendant([root]);
      var needle = ' ' + className + ' ';
      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
        nodeClassName = node.className;
        if (nodeClassName.length == 0) continue;
        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
          results.push(node);
      }
      return results;
    },

    attrPresence: function(nodes, root, attr, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var results = [];
      for (var i = 0, node; node = nodes[i]; i++)
        if (Element.hasAttribute(node, attr)) results.push(node);
      return results;
    },

    attr: function(nodes, root, attr, value, operator, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var handler = Selector.operators[operator], results = [];
      for (var i = 0, node; node = nodes[i]; i++) {
        var nodeValue = Element.readAttribute(node, attr);
        if (nodeValue === null) continue;
        if (handler(nodeValue, value)) results.push(node);
      }
      return results;
    },

    pseudo: function(nodes, name, value, root, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      if (!nodes) nodes = root.getElementsByTagName("*");
      return Selector.pseudos[name](nodes, value, root);
    }
  },

  pseudos: {
    'first-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.previousElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'last-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.nextElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'only-child': function(nodes, value, root) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
          results.push(node);
      return results;
    },
    'nth-child':        function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root);
    },
    'nth-last-child':   function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true);
    },
    'nth-of-type':      function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, false, true);
    },
    'nth-last-of-type': function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true, true);
    },
    'first-of-type':    function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, false, true);
    },
    'last-of-type':     function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, true, true);
    },
    'only-of-type':     function(nodes, formula, root) {
      var p = Selector.pseudos;
      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
    },

    getIndices: function(a, b, total) {
      if (a == 0) return b > 0 ? [b] : [];
      return $R(1, total).inject([], function(memo, i) {
        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
        return memo;
      });
    },

    nth: function(nodes, formula, root, reverse, ofType) {
      if (nodes.length == 0) return [];
      if (formula == 'even') formula = '2n+0';
      if (formula == 'odd')  formula = '2n+1';
      var h = Selector.handlers, results = [], indexed = [], m;
      h.mark(nodes);
      for (var i = 0, node; node = nodes[i]; i++) {
        if (!node.parentNode._countedByPrototype) {
          h.index(node.parentNode, reverse, ofType);
          indexed.push(node.parentNode);
        }
      }
      if (formula.match(/^\d+$/)) { // just a number
        formula = Number(formula);
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.nodeIndex == formula) results.push(node);
      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
        if (m[1] == "-") m[1] = -1;
        var a = m[1] ? Number(m[1]) : 1;
        var b = m[2] ? Number(m[2]) : 0;
        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
          for (var j = 0; j < l; j++)
            if (node.nodeIndex == indices[j]) results.push(node);
        }
      }
      h.unmark(nodes);
      h.unmark(indexed);
      return results;
    },

    'empty': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (node.tagName == '!' || node.firstChild) continue;
        results.push(node);
      }
      return results;
    },

    'not': function(nodes, selector, root) {
      var h = Selector.handlers, selectorType, m;
      var exclusions = new Selector(selector).findElements(root);
      h.mark(exclusions);
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node._countedByPrototype) results.push(node);
      h.unmark(exclusions);
      return results;
    },

    'enabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node.disabled && (!node.type || node.type !== 'hidden'))
          results.push(node);
      return results;
    },

    'disabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.disabled) results.push(node);
      return results;
    },

    'checked': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.checked) results.push(node);
      return results;
    }
  },

  operators: {
    '=':  function(nv, v) { return nv == v; },
    '!=': function(nv, v) { return nv != v; },
    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
    '$=': function(nv, v) { return nv.endsWith(v); },
    '*=': function(nv, v) { return nv.include(v); },
    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
     '-').include('-' + (v || "").toUpperCase() + '-'); }
  },

  split: function(expression) {
    var expressions = [];
    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    return expressions;
  },

  matchElements: function(elements, expression) {
    var matches = $$(expression), h = Selector.handlers;
    h.mark(matches);
    for (var i = 0, results = [], element; element = elements[i]; i++)
      if (element._countedByPrototype) results.push(element);
    h.unmark(matches);
    return results;
  },

  findElement: function(elements, expression, index) {
    if (Object.isNumber(expression)) {
      index = expression; expression = false;
    }
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    expressions = Selector.split(expressions.join(','));
    var results = [], h = Selector.handlers;
    for (var i = 0, l = expressions.length, selector; i < l; i++) {
      selector = new Selector(expressions[i].strip());
      h.concat(results, selector.findElements(element));
    }
    return (l > 1) ? h.unique(results) : results;
  }
});

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        if (node.tagName !== "!") a.push(node);
      return a;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node.removeAttribute('_countedByPrototype');
      return nodes;
    }
  });
}

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, options) {
    if (typeof options != 'object') options = { hash: !!options };
    else if (Object.isUndefined(options.hash)) options.hash = true;
    var key, value, submitted = false, submit = options.submit;

    var data = elements.inject({ }, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          if (key in result) {
            if (!Object.isArray(result[key])) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return options.hash ? data : Object.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, options) {
    return Form.serializeElements(Form.getElements(form), options);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('disable');
    return form;
  },

  enable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('enable');
    return form;
  },

  findFirstElement: function(form) {
    var elements = $(form).getElements().findAll(function(element) {
      return 'hidden' != element.type && !element.disabled;
    });
    var firstByIndex = elements.findAll(function(element) {
      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
    }).sortBy(function(element) { return element.tabIndex }).first();

    return firstByIndex ? firstByIndex : elements.find(function(element) {
      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  },

  request: function(form, options) {
    form = $(form), options = Object.clone(options || { });

    var params = options.parameters, action = form.readAttribute('action') || '';
    if (action.blank()) action = window.location.href;
    options.parameters = form.serialize(true);

    if (params) {
      if (Object.isString(params)) params = params.toQueryParams();
      Object.extend(options.parameters, params);
    }

    if (form.hasAttribute('method') && !options.method)
      options.method = form.method;

    return new Ajax.Request(action, options);
  }
};

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
};

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = { };
        pair[element.name] = value;
        return Object.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  setValue: function(element, value) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    Form.Element.Serializers[method](element, value);
    return element;
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    try {
      element.focus();
      if (element.select && (element.tagName.toLowerCase() != 'input' ||
          !['button', 'reset', 'submit'].include(element.type)))
        element.select();
    } catch (e) { }
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.disabled = false;
    return element;
  }
};

/*--------------------------------------------------------------------------*/

var Field = Form.Element;
var $F = Form.Element.Methods.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element, value) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element, value);
      default:
        return Form.Element.Serializers.textarea(element, value);
    }
  },

  inputSelector: function(element, value) {
    if (Object.isUndefined(value)) return element.checked ? element.value : null;
    else element.checked = !!value;
  },

  textarea: function(element, value) {
    if (Object.isUndefined(value)) return element.value;
    else element.value = value;
  },

  select: function(element, value) {
    if (Object.isUndefined(value))
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, currentValue, single = !Object.isArray(value);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        currentValue = this.optionValue(opt);
        if (single) {
          if (currentValue == value) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = value.include(currentValue);
      }
    }
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
};

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
  initialize: function($super, element, frequency, callback) {
    $super(callback, frequency);
    this.element   = $(element);
    this.lastValue = this.getValue();
  },

  execute: function() {
    var value = this.getValue();
    if (Object.isString(this.lastValue) && Object.isString(value) ?
        this.lastValue != value : String(this.lastValue) != String(value)) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
});

Form.Element.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = Class.create({
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback, this);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
});

Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) var Event = { };

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,

  cache: { },

  relatedTarget: function(event) {
    var element;
    switch(event.type) {
      case 'mouseover': element = event.fromElement; break;
      case 'mouseout':  element = event.toElement;   break;
      default: return null;
    }
    return Element.extend(element);
  }
});

Event.Methods = (function() {
  var isButton;

  if (Prototype.Browser.IE) {
    var buttonMap = { 0: 1, 1: 4, 2: 2 };
    isButton = function(event, code) {
      return event.button == buttonMap[code];
    };

  } else if (Prototype.Browser.WebKit) {
    isButton = function(event, code) {
      switch (code) {
        case 0: return event.which == 1 && !event.metaKey;
        case 1: return event.which == 1 && event.metaKey;
        default: return false;
      }
    };

  } else {
    isButton = function(event, code) {
      return event.which ? (event.which === code + 1) : (event.button === code);
    };
  }

  return {
    isLeftClick:   function(event) { return isButton(event, 0) },
    isMiddleClick: function(event) { return isButton(event, 1) },
    isRightClick:  function(event) { return isButton(event, 2) },

    element: function(event) {
      event = Event.extend(event);

      var node          = event.target,
          type          = event.type,
          currentTarget = event.currentTarget;

      if (currentTarget && currentTarget.tagName) {
        if (type === 'load' || type === 'error' ||
          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
            && currentTarget.type === 'radio'))
              node = currentTarget;
      }
      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
      return Element.extend(node);
    },

    findElement: function(event, expression) {
      var element = Event.element(event);
      if (!expression) return element;
      var elements = [element].concat(element.ancestors());
      return Selector.findElement(elements, expression, 0);
    },

    pointer: function(event) {
      var docElement = document.documentElement,
      body = document.body || { scrollLeft: 0, scrollTop: 0 };
      return {
        x: event.pageX || (event.clientX +
          (docElement.scrollLeft || body.scrollLeft) -
          (docElement.clientLeft || 0)),
        y: event.pageY || (event.clientY +
          (docElement.scrollTop || body.scrollTop) -
          (docElement.clientTop || 0))
      };
    },

    pointerX: function(event) { return Event.pointer(event).x },
    pointerY: function(event) { return Event.pointer(event).y },

    stop: function(event) {
      Event.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

Event.extend = (function() {
  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
    m[name] = Event.Methods[name].methodize();
    return m;
  });

  if (Prototype.Browser.IE) {
    Object.extend(methods, {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
      inspect: function() { return "[object Event]" }
    });

    return function(event) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;

      event._extendedByPrototype = Prototype.emptyFunction;
      var pointer = Event.pointer(event);
      Object.extend(event, {
        target: event.srcElement,
        relatedTarget: Event.relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });
      return Object.extend(event, methods);
    };

  } else {
    Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
    Object.extend(Event.prototype, methods);
    return Prototype.K;
  }
})();

Object.extend(Event, (function() {
  var cache = Event.cache;

  function getEventID(element) {
    if (element._prototypeEventID) return element._prototypeEventID[0];
    arguments.callee.id = arguments.callee.id || 1;
    return element._prototypeEventID = [++arguments.callee.id];
  }

  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return eventName;
  }

  function getCacheForID(id) {
    return cache[id] = cache[id] || { };
  }

  function getWrappersForEventName(id, eventName) {
    var c = getCacheForID(id);
    return c[eventName] = c[eventName] || [];
  }

  function createWrapper(element, eventName, handler) {
    var id = getEventID(element);
    var c = getWrappersForEventName(id, eventName);
    if (c.pluck("handler").include(handler)) return false;

    var wrapper = function(event) {
      if (!Event || !Event.extend ||
        (event.eventName && event.eventName != eventName))
          return false;

      Event.extend(event);
      handler.call(element, event);
    };

    wrapper.handler = handler;
    c.push(wrapper);
    return wrapper;
  }

  function findWrapper(id, eventName, handler) {
    var c = getWrappersForEventName(id, eventName);
    return c.find(function(wrapper) { return wrapper.handler == handler });
  }

  function destroyWrapper(id, eventName, handler) {
    var c = getCacheForID(id);
    if (!c[eventName]) return false;
    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
  }

  function destroyCache() {
    for (var id in cache)
      for (var eventName in cache[id])
        cache[id][eventName] = null;
  }


  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  if (Prototype.Browser.WebKit) {
    window.addEventListener('unload', Prototype.emptyFunction, false);
  }

  return {
    observe: function(element, eventName, handler) {
      element = $(element);
      var name = getDOMEventName(eventName);

      var wrapper = createWrapper(element, eventName, handler);
      if (!wrapper) return element;

      if (element.addEventListener) {
        element.addEventListener(name, wrapper, false);
      } else {
        element.attachEvent("on" + name, wrapper);
      }

      return element;
    },

    stopObserving: function(element, eventName, handler) {
      element = $(element);
      var id = getEventID(element), name = getDOMEventName(eventName);

      if (!handler && eventName) {
        getWrappersForEventName(id, eventName).each(function(wrapper) {
          element.stopObserving(eventName, wrapper.handler);
        });
        return element;

      } else if (!eventName) {
        Object.keys(getCacheForID(id)).each(function(eventName) {
          element.stopObserving(eventName);
        });
        return element;
      }

      var wrapper = findWrapper(id, eventName, handler);
      if (!wrapper) return element;

      if (element.removeEventListener) {
        element.removeEventListener(name, wrapper, false);
      } else {
        element.detachEvent("on" + name, wrapper);
      }

      destroyWrapper(id, eventName, handler);

      return element;
    },

    fire: function(element, eventName, memo) {
      element = $(element);
      if (element == document && document.createEvent && !element.dispatchEvent)
        element = document.documentElement;

      var event;
      if (document.createEvent) {
        event = document.createEvent("HTMLEvents");
        event.initEvent("dataavailable", true, true);
      } else {
        event = document.createEventObject();
        event.eventType = "ondataavailable";
      }

      event.eventName = eventName;
      event.memo = memo || { };

      if (document.createEvent) {
        element.dispatchEvent(event);
      } else {
        element.fireEvent(event.eventType, event);
      }

      return Event.extend(event);
    }
  };
})());

Object.extend(Event, Event.Methods);

Element.addMethods({
  fire:          Event.fire,
  observe:       Event.observe,
  stopObserving: Event.stopObserving
});

Object.extend(document, {
  fire:          Element.Methods.fire.methodize(),
  observe:       Element.Methods.observe.methodize(),
  stopObserving: Element.Methods.stopObserving.methodize(),
  loaded:        false
});

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards and John Resig. */

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearInterval(timer);
    document.fire("dom:loaded");
    document.loaded = true;
  }

  if (document.addEventListener) {
    if (Prototype.Browser.WebKit) {
      timer = window.setInterval(function() {
        if (/loaded|complete/.test(document.readyState))
          fireContentLoadedEvent();
      }, 0);

      Event.observe(window, "load", fireContentLoadedEvent);

    } else {
      document.addEventListener("DOMContentLoaded",
        fireContentLoadedEvent, false);
    }

  } else {
    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
    $("__onDOMContentLoaded").onreadystatechange = function() {
      if (this.readyState == "complete") {
        this.onreadystatechange = null;
        fireContentLoadedEvent();
      }
    };
  }
})();
/*------------------------------- DEPRECATED -------------------------------*/

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

Element.Methods.childOf = Element.Methods.descendantOf;

var Insertion = {
  Before: function(element, content) {
    return Element.insert(element, {before:content});
  },

  Top: function(element, content) {
    return Element.insert(element, {top:content});
  },

  Bottom: function(element, content) {
    return Element.insert(element, {bottom:content});
  },

  After: function(element, content) {
    return Element.insert(element, {after:content});
  }
};

var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

var Position = {
  includeScrollOffsets: false,

  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = Element.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = Element.cumulativeScrollOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = Element.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },


  cumulativeOffset: Element.Methods.cumulativeOffset,

  positionedOffset: Element.Methods.positionedOffset,

  absolutize: function(element) {
    Position.prepare();
    return Element.absolutize(element);
  },

  relativize: function(element) {
    Position.prepare();
    return Element.relativize(element);
  },

  realOffset: Element.Methods.cumulativeScrollOffset,

  offsetParent: Element.Methods.getOffsetParent,

  page: Element.Methods.viewportOffset,

  clone: function(source, target, options) {
    options = options || { };
    return Element.clonePosition(target, source, options);
  }
};

/*--------------------------------------------------------------------------*/

if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
  function iter(name) {
    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
  }

  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
  function(element, className) {
    className = className.toString().strip();
    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
  } : function(element, className) {
    className = className.toString().strip();
    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
    if (!classNames && !className) return elements;

    var nodes = $(element).getElementsByTagName('*');
    className = ' ' + className + ' ';

    for (var i = 0, child, cn; child = nodes[i]; i++) {
      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
          (classNames && classNames.all(function(name) {
            return !name.toString().blank() && cn.include(' ' + name + ' ');
          }))))
        elements.push(Element.extend(child));
    }
    return elements;
  };

  return function(className, parentElement) {
    return $(parentElement || document.body).getElementsByClassName(className);
  };
}(Element.Methods);

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);

/*--------------------------------------------------------------------------*/

Element.addMethods();

String.prototype.parseColor = function() {
  var color = '#';
  if (this.slice(0,4) == 'rgb(') {
    var cols = this.slice(4,this.length-1).split(',');
    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
  } else {
    if (this.slice(0,1) == '#') {
      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
      if (this.length==7) color = this.toLowerCase();
    }
  }
  return (color.length==7 ? color : (arguments[0] || this));
};

/*--------------------------------------------------------------------------*/

Element.collectTextNodes = function(element) {
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue :
      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
  }).flatten().join('');
};

Element.collectTextNodesIgnoreClass = function(element, className) {
  return $A($(element).childNodes).collect( function(node) {
    return (node.nodeType==3 ? node.nodeValue :
      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
        Element.collectTextNodesIgnoreClass(node, className) : ''));
  }).flatten().join('');
};

Element.setContentZoom = function(element, percent) {
  element = $(element);
  element.setStyle({fontSize: (percent/100) + 'em'});
  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
  return element;
};

Element.getInlineOpacity = function(element){
  return $(element).style.opacity || '';
};

Element.forceRerendering = function(element) {
  try {
    element = $(element);
    var n = document.createTextNode(' ');
    element.appendChild(n);
    element.removeChild(n);
  } catch(e) { }
};

/*--------------------------------------------------------------------------*/

var Effect = {
  _elementDoesNotExistError: {
    name: 'ElementDoesNotExistError',
    message: 'The specified DOM element does not exist, but is required for this effect to operate'
  },
  Transitions: {
    linear: Prototype.K,
    sinoidal: function(pos) {
      return (-Math.cos(pos*Math.PI)/2) + .5;
    },
    reverse: function(pos) {
      return 1-pos;
    },
    flicker: function(pos) {
      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
      return pos > 1 ? 1 : pos;
    },
    wobble: function(pos) {
      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
    },
    pulse: function(pos, pulses) {
      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
    },
    spring: function(pos) {
      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
    },
    none: function(pos) {
      return 0;
    },
    full: function(pos) {
      return 1;
    }
  },
  DefaultOptions: {
    duration:   1.0,   // seconds
    fps:        100,   // 100= assume 66fps max.
    sync:       false, // true for combining
    from:       0.0,
    to:         1.0,
    delay:      0.0,
    queue:      'parallel'
  },
  tagifyText: function(element) {
    var tagifyStyle = 'position:relative';
    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';

    element = $(element);
    $A(element.childNodes).each( function(child) {
      if (child.nodeType==3) {
        child.nodeValue.toArray().each( function(character) {
          element.insertBefore(
            new Element('span', {style: tagifyStyle}).update(
              character == ' ' ? String.fromCharCode(160) : character),
              child);
        });
        Element.remove(child);
      }
    });
  },
  multiple: function(element, effect) {
    var elements;
    if (((typeof element == 'object') ||
        Object.isFunction(element)) &&
       (element.length))
      elements = element;
    else
      elements = $(element).childNodes;

    var options = Object.extend({
      speed: 0.1,
      delay: 0.0
    }, arguments[2] || { });
    var masterDelay = options.delay;

    $A(elements).each( function(element, index) {
      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
    });
  },
  PAIRS: {
    'slide':  ['SlideDown','SlideUp'],
    'blind':  ['BlindDown','BlindUp'],
    'appear': ['Appear','Fade']
  },
  toggle: function(element, effect) {
    element = $(element);
    effect = (effect || 'appear').toLowerCase();
    var options = Object.extend({
      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
    }, arguments[2] || { });
    Effect[element.visible() ?
      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
  }
};

Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;

/* ------------- core effects ------------- */

Effect.ScopedQueue = Class.create(Enumerable, {
  initialize: function() {
    this.effects  = [];
    this.interval = null;
  },
  _each: function(iterator) {
    this.effects._each(iterator);
  },
  add: function(effect) {
    var timestamp = new Date().getTime();

    var position = Object.isString(effect.options.queue) ?
      effect.options.queue : effect.options.queue.position;

    switch(position) {
      case 'front':
        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
            e.startOn  += effect.finishOn;
            e.finishOn += effect.finishOn;
          });
        break;
      case 'with-last':
        timestamp = this.effects.pluck('startOn').max() || timestamp;
        break;
      case 'end':
        timestamp = this.effects.pluck('finishOn').max() || timestamp;
        break;
    }

    effect.startOn  += timestamp;
    effect.finishOn += timestamp;

    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
      this.effects.push(effect);

    if (!this.interval)
      this.interval = setInterval(this.loop.bind(this), 15);
  },
  remove: function(effect) {
    this.effects = this.effects.reject(function(e) { return e==effect });
    if (this.effects.length == 0) {
      clearInterval(this.interval);
      this.interval = null;
    }
  },
  loop: function() {
    var timePos = new Date().getTime();
    for(var i=0, len=this.effects.length;i<len;i++)
      this.effects[i] && this.effects[i].loop(timePos);
  }
});

Effect.Queues = {
  instances: $H(),
  get: function(queueName) {
    if (!Object.isString(queueName)) return queueName;

    return this.instances.get(queueName) ||
      this.instances.set(queueName, new Effect.ScopedQueue());
  }
};
Effect.Queue = Effect.Queues.get('global');

Effect.Base = Class.create({
  position: null,
  start: function(options) {
    function codeForEvent(options,eventName){
      return (
        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
      );
    }
    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
    this.currentFrame = 0;
    this.state        = 'idle';
    this.startOn      = this.options.delay*1000;
    this.finishOn     = this.startOn+(this.options.duration*1000);
    this.fromToDelta  = this.options.to-this.options.from;
    this.totalTime    = this.finishOn-this.startOn;
    this.totalFrames  = this.options.fps*this.options.duration;

    this.render = (function() {
      function dispatch(effect, eventName) {
        if (effect.options[eventName + 'Internal'])
          effect.options[eventName + 'Internal'](effect);
        if (effect.options[eventName])
          effect.options[eventName](effect);
      }

      return function(pos) {
        if (this.state === "idle") {
          this.state = "running";
          dispatch(this, 'beforeSetup');
          if (this.setup) this.setup();
          dispatch(this, 'afterSetup');
        }
        if (this.state === "running") {
          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
          this.position = pos;
          dispatch(this, 'beforeUpdate');
          if (this.update) this.update(pos);
          dispatch(this, 'afterUpdate');
        }
      };
    })();

    this.event('beforeStart');
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ?
        'global' : this.options.queue.scope).add(this);
  },
  loop: function(timePos) {
    if (timePos >= this.startOn) {
      if (timePos >= this.finishOn) {
        this.render(1.0);
        this.cancel();
        this.event('beforeFinish');
        if (this.finish) this.finish();
        this.event('afterFinish');
        return;
      }
      var pos   = (timePos - this.startOn) / this.totalTime,
          frame = (pos * this.totalFrames).round();
      if (frame > this.currentFrame) {
        this.render(pos);
        this.currentFrame = frame;
      }
    }
  },
  cancel: function() {
    if (!this.options.sync)
      Effect.Queues.get(Object.isString(this.options.queue) ?
        'global' : this.options.queue.scope).remove(this);
    this.state = 'finished';
  },
  event: function(eventName) {
    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
    if (this.options[eventName]) this.options[eventName](this);
  },
  inspect: function() {
    var data = $H();
    for(property in this)
      if (!Object.isFunction(this[property])) data.set(property, this[property]);
    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
  }
});

Effect.Parallel = Class.create(Effect.Base, {
  initialize: function(effects) {
    this.effects = effects || [];
    this.start(arguments[1]);
  },
  update: function(position) {
    this.effects.invoke('render', position);
  },
  finish: function(position) {
    this.effects.each( function(effect) {
      effect.render(1.0);
      effect.cancel();
      effect.event('beforeFinish');
      if (effect.finish) effect.finish(position);
      effect.event('afterFinish');
    });
  }
});

Effect.Tween = Class.create(Effect.Base, {
  initialize: function(object, from, to) {
    object = Object.isString(object) ? $(object) : object;
    var args = $A(arguments), method = args.last(),
      options = args.length == 5 ? args[3] : null;
    this.method = Object.isFunction(method) ? method.bind(object) :
      Object.isFunction(object[method]) ? object[method].bind(object) :
      function(value) { object[method] = value };
    this.start(Object.extend({ from: from, to: to }, options || { }));
  },
  update: function(position) {
    this.method(position);
  }
});

Effect.Event = Class.create(Effect.Base, {
  initialize: function() {
    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
  },
  update: Prototype.emptyFunction
});

Effect.Opacity = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
      this.element.setStyle({zoom: 1});
    var options = Object.extend({
      from: this.element.getOpacity() || 0.0,
      to:   1.0
    }, arguments[1] || { });
    this.start(options);
  },
  update: function(position) {
    this.element.setOpacity(position);
  }
});

Effect.Move = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      x:    0,
      y:    0,
      mode: 'relative'
    }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    this.element.makePositioned();
    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
    if (this.options.mode == 'absolute') {
      this.options.x = this.options.x - this.originalLeft;
      this.options.y = this.options.y - this.originalTop;
    }
  },
  update: function(position) {
    this.element.setStyle({
      left: (this.options.x  * position + this.originalLeft).round() + 'px',
      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
    });
  }
});

Effect.MoveBy = function(element, toTop, toLeft) {
  return new Effect.Move(element,
    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
};

Effect.Scale = Class.create(Effect.Base, {
  initialize: function(element, percent) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      scaleX: true,
      scaleY: true,
      scaleContent: true,
      scaleFromCenter: false,
      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
      scaleFrom: 100.0,
      scaleTo:   percent
    }, arguments[2] || { });
    this.start(options);
  },
  setup: function() {
    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
    this.elementPositioning = this.element.getStyle('position');

    this.originalStyle = { };
    ['top','left','width','height','fontSize'].each( function(k) {
      this.originalStyle[k] = this.element.style[k];
    }.bind(this));

    this.originalTop  = this.element.offsetTop;
    this.originalLeft = this.element.offsetLeft;

    var fontSize = this.element.getStyle('font-size') || '100%';
    ['em','px','%','pt'].each( function(fontSizeType) {
      if (fontSize.indexOf(fontSizeType)>0) {
        this.fontSize     = parseFloat(fontSize);
        this.fontSizeType = fontSizeType;
      }
    }.bind(this));

    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;

    this.dims = null;
    if (this.options.scaleMode=='box')
      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
    if (/^content/.test(this.options.scaleMode))
      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
    if (!this.dims)
      this.dims = [this.options.scaleMode.originalHeight,
                   this.options.scaleMode.originalWidth];
  },
  update: function(position) {
    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
    if (this.options.scaleContent && this.fontSize)
      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
  },
  finish: function(position) {
    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
  },
  setDimensions: function(height, width) {
    var d = { };
    if (this.options.scaleX) d.width = width.round() + 'px';
    if (this.options.scaleY) d.height = height.round() + 'px';
    if (this.options.scaleFromCenter) {
      var topd  = (height - this.dims[0])/2;
      var leftd = (width  - this.dims[1])/2;
      if (this.elementPositioning == 'absolute') {
        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
      } else {
        if (this.options.scaleY) d.top = -topd + 'px';
        if (this.options.scaleX) d.left = -leftd + 'px';
      }
    }
    this.element.setStyle(d);
  }
});

Effect.Highlight = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
    this.start(options);
  },
  setup: function() {
    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
    this.oldStyle = { };
    if (!this.options.keepBackgroundImage) {
      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
      this.element.setStyle({backgroundImage: 'none'});
    }
    if (!this.options.endcolor)
      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
    if (!this.options.restorecolor)
      this.options.restorecolor = this.element.getStyle('background-color');
    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
  },
  update: function(position) {
    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
  },
  finish: function() {
    this.element.setStyle(Object.extend(this.oldStyle, {
      backgroundColor: this.options.restorecolor
    }));
  }
});

Effect.ScrollTo = function(element) {
  var options = arguments[1] || { },
  scrollOffsets = document.viewport.getScrollOffsets(),
  elementOffsets = $(element).cumulativeOffset();

  if (options.offset) elementOffsets[1] += options.offset;

  return new Effect.Tween(null,
    scrollOffsets.top,
    elementOffsets[1],
    options,
    function(p){ scrollTo(scrollOffsets.left, p.round()); }
  );
};

/* ------------- combination effects ------------- */

Effect.Fade = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  var options = Object.extend({
    from: element.getOpacity() || 1.0,
    to:   0.0,
    afterFinishInternal: function(effect) {
      if (effect.options.to!=0) return;
      effect.element.hide().setStyle({opacity: oldOpacity});
    }
  }, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Appear = function(element) {
  element = $(element);
  var options = Object.extend({
  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
  to:   1.0,
  afterFinishInternal: function(effect) {
    effect.element.forceRerendering();
  },
  beforeSetup: function(effect) {
    effect.element.setOpacity(effect.options.from).show();
  }}, arguments[1] || { });
  return new Effect.Opacity(element,options);
};

Effect.Puff = function(element) {
  element = $(element);
  var oldStyle = {
    opacity: element.getInlineOpacity(),
    position: element.getStyle('position'),
    top:  element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height
  };
  return new Effect.Parallel(
   [ new Effect.Scale(element, 200,
      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
     Object.extend({ duration: 1.0,
      beforeSetupInternal: function(effect) {
        Position.absolutize(effect.effects[0].element);
      },
      afterFinishInternal: function(effect) {
         effect.effects[0].element.hide().setStyle(oldStyle); }
     }, arguments[1] || { })
   );
};

Effect.BlindUp = function(element) {
  element = $(element);
  element.makeClipping();
  return new Effect.Scale(element, 0,
    Object.extend({ scaleContent: false,
      scaleX: false,
      restoreAfterFinish: true,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping();
      }
    }, arguments[1] || { })
  );
};

Effect.BlindDown = function(element) {
  element = $(element);
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: 0,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping();
    }
  }, arguments[1] || { }));
};

Effect.SwitchOff = function(element) {
  element = $(element);
  var oldOpacity = element.getInlineOpacity();
  return new Effect.Appear(element, Object.extend({
    duration: 0.4,
    from: 0,
    transition: Effect.Transitions.flicker,
    afterFinishInternal: function(effect) {
      new Effect.Scale(effect.element, 1, {
        duration: 0.3, scaleFromCenter: true,
        scaleX: false, scaleContent: false, restoreAfterFinish: true,
        beforeSetup: function(effect) {
          effect.element.makePositioned().makeClipping();
        },
        afterFinishInternal: function(effect) {
          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
        }
      });
    }
  }, arguments[1] || { }));
};

Effect.DropOut = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left'),
    opacity: element.getInlineOpacity() };
  return new Effect.Parallel(
    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
    Object.extend(
      { duration: 0.5,
        beforeSetup: function(effect) {
          effect.effects[0].element.makePositioned();
        },
        afterFinishInternal: function(effect) {
          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
        }
      }, arguments[1] || { }));
};

Effect.Shake = function(element) {
  element = $(element);
  var options = Object.extend({
    distance: 20,
    duration: 0.5
  }, arguments[1] || {});
  var distance = parseFloat(options.distance);
  var split = parseFloat(options.duration) / 10.0;
  var oldStyle = {
    top: element.getStyle('top'),
    left: element.getStyle('left') };
    return new Effect.Move(element,
      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
    new Effect.Move(effect.element,
      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
        effect.element.undoPositioned().setStyle(oldStyle);
  }}); }}); }}); }}); }}); }});
};

Effect.SlideDown = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, 100, Object.extend({
    scaleContent: false,
    scaleX: false,
    scaleFrom: window.opera ? 0 : 1,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().setStyle({height: '0px'}).show();
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
    }, arguments[1] || { })
  );
};

Effect.SlideUp = function(element) {
  element = $(element).cleanWhitespace();
  var oldInnerBottom = element.down().getStyle('bottom');
  var elementDimensions = element.getDimensions();
  return new Effect.Scale(element, window.opera ? 0 : 1,
   Object.extend({ scaleContent: false,
    scaleX: false,
    scaleMode: 'box',
    scaleFrom: 100,
    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
    restoreAfterFinish: true,
    afterSetup: function(effect) {
      effect.element.makePositioned();
      effect.element.down().makePositioned();
      if (window.opera) effect.element.setStyle({top: ''});
      effect.element.makeClipping().show();
    },
    afterUpdateInternal: function(effect) {
      effect.element.down().setStyle({bottom:
        (effect.dims[0] - effect.element.clientHeight) + 'px' });
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping().undoPositioned();
      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
    }
   }, arguments[1] || { })
  );
};

Effect.Squish = function(element) {
  return new Effect.Scale(element, window.opera ? 1 : 0, {
    restoreAfterFinish: true,
    beforeSetup: function(effect) {
      effect.element.makeClipping();
    },
    afterFinishInternal: function(effect) {
      effect.element.hide().undoClipping();
    }
  });
};

Effect.Grow = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.full
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var initialMoveX, initialMoveY;
  var moveX, moveY;

  switch (options.direction) {
    case 'top-left':
      initialMoveX = initialMoveY = moveX = moveY = 0;
      break;
    case 'top-right':
      initialMoveX = dims.width;
      initialMoveY = moveY = 0;
      moveX = -dims.width;
      break;
    case 'bottom-left':
      initialMoveX = moveX = 0;
      initialMoveY = dims.height;
      moveY = -dims.height;
      break;
    case 'bottom-right':
      initialMoveX = dims.width;
      initialMoveY = dims.height;
      moveX = -dims.width;
      moveY = -dims.height;
      break;
    case 'center':
      initialMoveX = dims.width / 2;
      initialMoveY = dims.height / 2;
      moveX = -dims.width / 2;
      moveY = -dims.height / 2;
      break;
  }

  return new Effect.Move(element, {
    x: initialMoveX,
    y: initialMoveY,
    duration: 0.01,
    beforeSetup: function(effect) {
      effect.element.hide().makeClipping().makePositioned();
    },
    afterFinishInternal: function(effect) {
      new Effect.Parallel(
        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
          new Effect.Scale(effect.element, 100, {
            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
        ], Object.extend({
             beforeSetup: function(effect) {
               effect.effects[0].element.setStyle({height: '0px'}).show();
             },
             afterFinishInternal: function(effect) {
               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
             }
           }, options)
      );
    }
  });
};

Effect.Shrink = function(element) {
  element = $(element);
  var options = Object.extend({
    direction: 'center',
    moveTransition: Effect.Transitions.sinoidal,
    scaleTransition: Effect.Transitions.sinoidal,
    opacityTransition: Effect.Transitions.none
  }, arguments[1] || { });
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    height: element.style.height,
    width: element.style.width,
    opacity: element.getInlineOpacity() };

  var dims = element.getDimensions();
  var moveX, moveY;

  switch (options.direction) {
    case 'top-left':
      moveX = moveY = 0;
      break;
    case 'top-right':
      moveX = dims.width;
      moveY = 0;
      break;
    case 'bottom-left':
      moveX = 0;
      moveY = dims.height;
      break;
    case 'bottom-right':
      moveX = dims.width;
      moveY = dims.height;
      break;
    case 'center':
      moveX = dims.width / 2;
      moveY = dims.height / 2;
      break;
  }

  return new Effect.Parallel(
    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
    ], Object.extend({
         beforeStartInternal: function(effect) {
           effect.effects[0].element.makePositioned().makeClipping();
         },
         afterFinishInternal: function(effect) {
           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
       }, options)
  );
};

Effect.Pulsate = function(element) {
  element = $(element);
  var options    = arguments[1] || { },
    oldOpacity = element.getInlineOpacity(),
    transition = options.transition || Effect.Transitions.linear,
    reverser   = function(pos){
      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
    };

  return new Effect.Opacity(element,
    Object.extend(Object.extend({  duration: 2.0, from: 0,
      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
    }, options), {transition: reverser}));
};

Effect.Fold = function(element) {
  element = $(element);
  var oldStyle = {
    top: element.style.top,
    left: element.style.left,
    width: element.style.width,
    height: element.style.height };
  element.makeClipping();
  return new Effect.Scale(element, 5, Object.extend({
    scaleContent: false,
    scaleX: false,
    afterFinishInternal: function(effect) {
    new Effect.Scale(element, 1, {
      scaleContent: false,
      scaleY: false,
      afterFinishInternal: function(effect) {
        effect.element.hide().undoClipping().setStyle(oldStyle);
      } });
  }}, arguments[1] || { }));
};

Effect.Morph = Class.create(Effect.Base, {
  initialize: function(element) {
    this.element = $(element);
    if (!this.element) throw(Effect._elementDoesNotExistError);
    var options = Object.extend({
      style: { }
    }, arguments[1] || { });

    if (!Object.isString(options.style)) this.style = $H(options.style);
    else {
      if (options.style.include(':'))
        this.style = options.style.parseStyle();
      else {
        this.element.addClassName(options.style);
        this.style = $H(this.element.getStyles());
        this.element.removeClassName(options.style);
        var css = this.element.getStyles();
        this.style = this.style.reject(function(style) {
          return style.value == css[style.key];
        });
        options.afterFinishInternal = function(effect) {
          effect.element.addClassName(effect.options.style);
          effect.transforms.each(function(transform) {
            effect.element.style[transform.style] = '';
          });
        };
      }
    }
    this.start(options);
  },

  setup: function(){
    function parseColor(color){
      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
      color = color.parseColor();
      return $R(0,2).map(function(i){
        return parseInt( color.slice(i*2+1,i*2+3), 16 );
      });
    }
    this.transforms = this.style.map(function(pair){
      var property = pair[0], value = pair[1], unit = null;

      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
        value = value.parseColor();
        unit  = 'color';
      } else if (property == 'opacity') {
        value = parseFloat(value);
        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
          this.element.setStyle({zoom: 1});
      } else if (Element.CSS_LENGTH.test(value)) {
          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
          value = parseFloat(components[1]);
          unit = (components.length == 3) ? components[2] : null;
      }

      var originalValue = this.element.getStyle(property);
      return {
        style: property.camelize(),
        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
        targetValue: unit=='color' ? parseColor(value) : value,
        unit: unit
      };
    }.bind(this)).reject(function(transform){
      return (
        (transform.originalValue == transform.targetValue) ||
        (
          transform.unit != 'color' &&
          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
        )
      );
    });
  },
  update: function(position) {
    var style = { }, transform, i = this.transforms.length;
    while(i--)
      style[(transform = this.transforms[i]).style] =
        transform.unit=='color' ? '#'+
          (Math.round(transform.originalValue[0]+
            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
          (Math.round(transform.originalValue[1]+
            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
          (Math.round(transform.originalValue[2]+
            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
        (transform.originalValue +
          (transform.targetValue - transform.originalValue) * position).toFixed(3) +
            (transform.unit === null ? '' : transform.unit);
    this.element.setStyle(style, true);
  }
});

Effect.Transform = Class.create({
  initialize: function(tracks){
    this.tracks  = [];
    this.options = arguments[1] || { };
    this.addTracks(tracks);
  },
  addTracks: function(tracks){
    tracks.each(function(track){
      track = $H(track);
      var data = track.values().first();
      this.tracks.push($H({
        ids:     track.keys().first(),
        effect:  Effect.Morph,
        options: { style: data }
      }));
    }.bind(this));
    return this;
  },
  play: function(){
    return new Effect.Parallel(
      this.tracks.map(function(track){
        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
        var elements = [$(ids) || $$(ids)].flatten();
        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
      }).flatten(),
      this.options
    );
  }
});

Element.CSS_PROPERTIES = $w(
  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
  'fontSize fontWeight height left letterSpacing lineHeight ' +
  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
  'right textIndent top width wordSpacing zIndex');

Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;

String.__parseStyleElement = document.createElement('div');
String.prototype.parseStyle = function(){
  var style, styleRules = $H();
  if (Prototype.Browser.WebKit)
    style = new Element('div',{style:this}).style;
  else {
    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
    style = String.__parseStyleElement.childNodes[0].style;
  }

  Element.CSS_PROPERTIES.each(function(property){
    if (style[property]) styleRules.set(property, style[property]);
  });

  if (Prototype.Browser.IE && this.include('opacity'))
    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);

  return styleRules;
};

if (document.defaultView && document.defaultView.getComputedStyle) {
  Element.getStyles = function(element) {
    var css = document.defaultView.getComputedStyle($(element), null);
    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
      styles[property] = css[property];
      return styles;
    });
  };
} else {
  Element.getStyles = function(element) {
    element = $(element);
    var css = element.currentStyle, styles;
    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
      results[property] = css[property];
      return results;
    });
    if (!styles.opacity) styles.opacity = element.getOpacity();
    return styles;
  };
}

Effect.Methods = {
  morph: function(element, style) {
    element = $(element);
    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
    return element;
  },
  visualEffect: function(element, effect, options) {
    element = $(element);
    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
    new Effect[klass](element, options);
    return element;
  },
  highlight: function(element, options) {
    element = $(element);
    new Effect.Highlight(element, options);
    return element;
  }
};

$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
  'pulsate shake puff squish switchOff dropOut').each(
  function(effect) {
    Effect.Methods[effect] = function(element, options){
      element = $(element);
      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
      return element;
    };
  }
);

$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
  function(f) { Effect.Methods[f] = Element[f]; }
);

Element.addMethods(Effect.Methods);

if(Object.isUndefined(Effect))
  throw("dragdrop.js requires including script.aculo.us' effects.js library");

var Droppables = {
  drops: [],

  remove: function(element) {
    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
  },

  add: function(element) {
    element = $(element);
    var options = Object.extend({
      greedy:     true,
      hoverclass: null,
      tree:       false
    }, arguments[1] || { });

    if(options.containment) {
      options._containers = [];
      var containment = options.containment;
      if(Object.isArray(containment)) {
        containment.each( function(c) { options._containers.push($(c)) });
      } else {
        options._containers.push($(containment));
      }
    }

    if(options.accept) options.accept = [options.accept].flatten();

    Element.makePositioned(element); // fix IE
    options.element = element;

    this.drops.push(options);
  },

  findDeepestChild: function(drops) {
    deepest = drops[0];

    for (i = 1; i < drops.length; ++i)
      if (Element.isParent(drops[i].element, deepest.element))
        deepest = drops[i];

    return deepest;
  },

  isContained: function(element, drop) {
    var containmentNode;
    if(drop.tree) {
      containmentNode = element.treeNode;
    } else {
      containmentNode = element.parentNode;
    }
    return drop._containers.detect(function(c) { return containmentNode == c });
  },

  isAffected: function(point, element, drop) {
    return (
      (drop.element!=element) &&
      ((!drop._containers) ||
        this.isContained(element, drop)) &&
      ((!drop.accept) ||
        (Element.classNames(element).detect(
          function(v) { return drop.accept.include(v) } ) )) &&
      Position.within(drop.element, point[0], point[1]) );
  },

  deactivate: function(drop) {
    if(drop.hoverclass)
      Element.removeClassName(drop.element, drop.hoverclass);
    this.last_active = null;
  },

  activate: function(drop) {
    if(drop.hoverclass)
      Element.addClassName(drop.element, drop.hoverclass);
    this.last_active = drop;
  },

  show: function(point, element) {
    if(!this.drops.length) return;
    var drop, affected = [];

    this.drops.each( function(drop) {
      if(Droppables.isAffected(point, element, drop))
        affected.push(drop);
    });

    if(affected.length>0)
      drop = Droppables.findDeepestChild(affected);

    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
    if (drop) {
      Position.within(drop.element, point[0], point[1]);
      if(drop.onHover)
        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));

      if (drop != this.last_active) Droppables.activate(drop);
    }
  },

  fire: function(event, element) {
    if(!this.last_active) return;
    Position.prepare();

    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
      if (this.last_active.onDrop) {
        this.last_active.onDrop(element, this.last_active.element, event);
        return true;
      }
  },

  reset: function() {
    if(this.last_active)
      this.deactivate(this.last_active);
  }
};

var Draggables = {
  drags: [],
  observers: [],

  register: function(draggable) {
    if(this.drags.length == 0) {
      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
      this.eventKeypress  = this.keyPress.bindAsEventListener(this);

      Event.observe(document, "mouseup", this.eventMouseUp);
      Event.observe(document, "mousemove", this.eventMouseMove);
      Event.observe(document, "keypress", this.eventKeypress);
    }
    this.drags.push(draggable);
  },

  unregister: function(draggable) {
    this.drags = this.drags.reject(function(d) { return d==draggable });
    if(this.drags.length == 0) {
      Event.stopObserving(document, "mouseup", this.eventMouseUp);
      Event.stopObserving(document, "mousemove", this.eventMouseMove);
      Event.stopObserving(document, "keypress", this.eventKeypress);
    }
  },

  activate: function(draggable) {
    if(draggable.options.delay) {
      this._timeout = setTimeout(function() {
        Draggables._timeout = null;
        window.focus();
        Draggables.activeDraggable = draggable;
      }.bind(this), draggable.options.delay);
    } else {
      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
      this.activeDraggable = draggable;
    }
  },

  deactivate: function() {
    this.activeDraggable = null;
  },

  updateDrag: function(event) {
    if(!this.activeDraggable) return;
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
    this._lastPointer = pointer;

    this.activeDraggable.updateDrag(event, pointer);
  },

  endDrag: function(event) {
    if(this._timeout) {
      clearTimeout(this._timeout);
      this._timeout = null;
    }
    if(!this.activeDraggable) return;
    this._lastPointer = null;
    this.activeDraggable.endDrag(event);
    this.activeDraggable = null;
  },

  keyPress: function(event) {
    if(this.activeDraggable)
      this.activeDraggable.keyPress(event);
  },

  addObserver: function(observer) {
    this.observers.push(observer);
    this._cacheObserverCallbacks();
  },

  removeObserver: function(element) {  // element instead of observer fixes mem leaks
    this.observers = this.observers.reject( function(o) { return o.element==element });
    this._cacheObserverCallbacks();
  },

  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
    if(this[eventName+'Count'] > 0)
      this.observers.each( function(o) {
        if(o[eventName]) o[eventName](eventName, draggable, event);
      });
    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
  },

  _cacheObserverCallbacks: function() {
    ['onStart','onEnd','onDrag'].each( function(eventName) {
      Draggables[eventName+'Count'] = Draggables.observers.select(
        function(o) { return o[eventName]; }
      ).length;
    });
  }
};

/*--------------------------------------------------------------------------*/

var Draggable = Class.create({
  initialize: function(element) {
    var defaults = {
      handle: false,
      reverteffect: function(element, top_offset, left_offset) {
        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
          queue: {scope:'_draggable', position:'end'}
        });
      },
      endeffect: function(element) {
        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
          queue: {scope:'_draggable', position:'end'},
          afterFinish: function(){
            Draggable._dragging[element] = false
          }
        });
      },
      zindex: 1000,
      revert: false,
      quiet: false,
      scroll: false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
      delay: 0
    };

    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
      Object.extend(defaults, {
        starteffect: function(element) {
          element._opacity = Element.getOpacity(element);
          Draggable._dragging[element] = true;
          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
        }
      });

    var options = Object.extend(defaults, arguments[1] || { });

    this.element = $(element);

    if(options.handle && Object.isString(options.handle))
      this.handle = this.element.down('.'+options.handle, 0);

    if(!this.handle) this.handle = $(options.handle);
    if(!this.handle) this.handle = this.element;

    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
      options.scroll = $(options.scroll);
      this._isScrollChild = Element.childOf(this.element, options.scroll);
    }

    Element.makePositioned(this.element); // fix IE

    this.options  = options;
    this.dragging = false;

    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
    Event.observe(this.handle, "mousedown", this.eventMouseDown);

    Draggables.register(this);
  },

  destroy: function() {
    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
    Draggables.unregister(this);
  },

  currentDelta: function() {
    return([
      parseInt(Element.getStyle(this.element,'left') || '0'),
      parseInt(Element.getStyle(this.element,'top') || '0')]);
  },

  initDrag: function(event) {
    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
      Draggable._dragging[this.element]) return;
    if(Event.isLeftClick(event)) {
      var src = Event.element(event);
      if((tag_name = src.tagName.toUpperCase()) && (
        tag_name=='INPUT' ||
        tag_name=='SELECT' ||
        tag_name=='OPTION' ||
        tag_name=='BUTTON' ||
        tag_name=='TEXTAREA')) return;

      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      var pos     = Position.cumulativeOffset(this.element);
      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });

      Draggables.activate(this);
      Event.stop(event);
    }
  },

  startDrag: function(event) {
    this.dragging = true;
    if(!this.delta)
      this.delta = this.currentDelta();

    if(this.options.zindex) {
      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
      this.element.style.zIndex = this.options.zindex;
    }

    if(this.options.ghosting) {
      this._clone = this.element.cloneNode(true);
      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
      if (!this._originallyAbsolute)
        Position.absolutize(this.element);
      this.element.parentNode.insertBefore(this._clone, this.element);
    }

    if(this.options.scroll) {
      if (this.options.scroll == window) {
        var where = this._getWindowScroll(this.options.scroll);
        this.originalScrollLeft = where.left;
        this.originalScrollTop = where.top;
      } else {
        this.originalScrollLeft = this.options.scroll.scrollLeft;
        this.originalScrollTop = this.options.scroll.scrollTop;
      }
    }

    Draggables.notify('onStart', this, event);

    if(this.options.starteffect) this.options.starteffect(this.element);
  },

  updateDrag: function(event, pointer) {
    if(!this.dragging) this.startDrag(event);

    if(!this.options.quiet){
      Position.prepare();
      Droppables.show(pointer, this.element);
    }

    Draggables.notify('onDrag', this, event);

    this.draw(pointer);
    if(this.options.change) this.options.change(this);

    if(this.options.scroll) {
      this.stopScrolling();

      var p;
      if (this.options.scroll == window) {
        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
      } else {
        p = Position.page(this.options.scroll);
        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
        p[1] += this.options.scroll.scrollTop + Position.deltaY;
        p.push(p[0]+this.options.scroll.offsetWidth);
        p.push(p[1]+this.options.scroll.offsetHeight);
      }
      var speed = [0,0];
      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
      this.startScrolling(speed);
    }

    if(Prototype.Browser.WebKit) window.scrollBy(0,0);

    Event.stop(event);
  },

  finishDrag: function(event, success) {
    this.dragging = false;

    if(this.options.quiet){
      Position.prepare();
      var pointer = [Event.pointerX(event), Event.pointerY(event)];
      Droppables.show(pointer, this.element);
    }

    if(this.options.ghosting) {
      if (!this._originallyAbsolute)
        Position.relativize(this.element);
      delete this._originallyAbsolute;
      Element.remove(this._clone);
      this._clone = null;
    }

    var dropped = false;
    if(success) {
      dropped = Droppables.fire(event, this.element);
      if (!dropped) dropped = false;
    }
    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
    Draggables.notify('onEnd', this, event);

    var revert = this.options.revert;
    if(revert && Object.isFunction(revert)) revert = revert(this.element);

    var d = this.currentDelta();
    if(revert && this.options.reverteffect) {
      if (dropped == 0 || revert != 'failure')
        this.options.reverteffect(this.element,
          d[1]-this.delta[1], d[0]-this.delta[0]);
    } else {
      this.delta = d;
    }

    if(this.options.zindex)
      this.element.style.zIndex = this.originalZ;

    if(this.options.endeffect)
      this.options.endeffect(this.element);

    Draggables.deactivate(this);
    Droppables.reset();
  },

  keyPress: function(event) {
    if(event.keyCode!=Event.KEY_ESC) return;
    this.finishDrag(event, false);
    Event.stop(event);
  },

  endDrag: function(event) {
    if(!this.dragging) return;
    this.stopScrolling();
    this.finishDrag(event, true);
    Event.stop(event);
  },

  draw: function(point) {
    var pos = Position.cumulativeOffset(this.element);
    if(this.options.ghosting) {
      var r   = Position.realOffset(this.element);
      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
    }

    var d = this.currentDelta();
    pos[0] -= d[0]; pos[1] -= d[1];

    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
    }

    var p = [0,1].map(function(i){
      return (point[i]-pos[i]-this.offset[i])
    }.bind(this));

    if(this.options.snap) {
      if(Object.isFunction(this.options.snap)) {
        p = this.options.snap(p[0],p[1],this);
      } else {
      if(Object.isArray(this.options.snap)) {
        p = p.map( function(v, i) {
          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
      } else {
        p = p.map( function(v) {
          return (v/this.options.snap).round()*this.options.snap }.bind(this));
      }
    }}

    var style = this.element.style;
    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
      style.left = p[0] + "px";
    if((!this.options.constraint) || (this.options.constraint=='vertical'))
      style.top  = p[1] + "px";

    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
  },

  stopScrolling: function() {
    if(this.scrollInterval) {
      clearInterval(this.scrollInterval);
      this.scrollInterval = null;
      Draggables._lastScrollPointer = null;
    }
  },

  startScrolling: function(speed) {
    if(!(speed[0] || speed[1])) return;
    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
    this.lastScrolled = new Date();
    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
  },

  scroll: function() {
    var current = new Date();
    var delta = current - this.lastScrolled;
    this.lastScrolled = current;
    if(this.options.scroll == window) {
      with (this._getWindowScroll(this.options.scroll)) {
        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
          var d = delta / 1000;
          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
        }
      }
    } else {
      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
    }

    Position.prepare();
    Droppables.show(Draggables._lastPointer, this.element);
    Draggables.notify('onDrag', this);
    if (this._isScrollChild) {
      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
      if (Draggables._lastScrollPointer[0] < 0)
        Draggables._lastScrollPointer[0] = 0;
      if (Draggables._lastScrollPointer[1] < 0)
        Draggables._lastScrollPointer[1] = 0;
      this.draw(Draggables._lastScrollPointer);
    }

    if(this.options.change) this.options.change(this);
  },

  _getWindowScroll: function(w) {
    var T, L, W, H;
    with (w.document) {
      if (w.document.documentElement && documentElement.scrollTop) {
        T = documentElement.scrollTop;
        L = documentElement.scrollLeft;
      } else if (w.document.body) {
        T = body.scrollTop;
        L = body.scrollLeft;
      }
      if (w.innerWidth) {
        W = w.innerWidth;
        H = w.innerHeight;
      } else if (w.document.documentElement && documentElement.clientWidth) {
        W = documentElement.clientWidth;
        H = documentElement.clientHeight;
      } else {
        W = body.offsetWidth;
        H = body.offsetHeight;
      }
    }
    return { top: T, left: L, width: W, height: H };
  }
});

Draggable._dragging = { };

/*--------------------------------------------------------------------------*/

var SortableObserver = Class.create({
  initialize: function(element, observer) {
    this.element   = $(element);
    this.observer  = observer;
    this.lastValue = Sortable.serialize(this.element);
  },

  onStart: function() {
    this.lastValue = Sortable.serialize(this.element);
  },

  onEnd: function() {
    Sortable.unmark();
    if(this.lastValue != Sortable.serialize(this.element))
      this.observer(this.element)
  }
});

var Sortable = {
  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,

  sortables: { },

  _findRootElement: function(element) {
    while (element.tagName.toUpperCase() != "BODY") {
      if(element.id && Sortable.sortables[element.id]) return element;
      element = element.parentNode;
    }
  },

  options: function(element) {
    element = Sortable._findRootElement($(element));
    if(!element) return;
    return Sortable.sortables[element.id];
  },

  destroy: function(element){
    element = $(element);
    var s = Sortable.sortables[element.id];

    if(s) {
      Draggables.removeObserver(s.element);
      s.droppables.each(function(d){ Droppables.remove(d) });
      s.draggables.invoke('destroy');

      delete Sortable.sortables[s.element.id];
    }
  },

  create: function(element) {
    element = $(element);
    var options = Object.extend({
      element:     element,
      tag:         'li',       // assumes li children, override with tag: 'tagname'
      dropOnEmpty: false,
      tree:        false,
      treeTag:     'ul',
      overlap:     'vertical', // one of 'vertical', 'horizontal'
      constraint:  'vertical', // one of 'vertical', 'horizontal', false
      containment: element,    // also takes array of elements (or id's); or false
      handle:      false,      // or a CSS class
      only:        false,
      delay:       0,
      hoverclass:  null,
      ghosting:    false,
      quiet:       false,
      scroll:      false,
      scrollSensitivity: 20,
      scrollSpeed: 15,
      format:      this.SERIALIZE_RULE,

      elements:    false,
      handles:     false,

      onChange:    Prototype.emptyFunction,
      onUpdate:    Prototype.emptyFunction
    }, arguments[1] || { });

    this.destroy(element);

    var options_for_draggable = {
      revert:      true,
      quiet:       options.quiet,
      scroll:      options.scroll,
      scrollSpeed: options.scrollSpeed,
      scrollSensitivity: options.scrollSensitivity,
      delay:       options.delay,
      ghosting:    options.ghosting,
      constraint:  options.constraint,
      handle:      options.handle };

    if(options.starteffect)
      options_for_draggable.starteffect = options.starteffect;

    if(options.reverteffect)
      options_for_draggable.reverteffect = options.reverteffect;
    else
      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
        element.style.top  = 0;
        element.style.left = 0;
      };

    if(options.endeffect)
      options_for_draggable.endeffect = options.endeffect;

    if(options.zindex)
      options_for_draggable.zindex = options.zindex;

    var options_for_droppable = {
      overlap:     options.overlap,
      containment: options.containment,
      tree:        options.tree,
      hoverclass:  options.hoverclass,
      onHover:     Sortable.onHover
    };

    var options_for_tree = {
      onHover:      Sortable.onEmptyHover,
      overlap:      options.overlap,
      containment:  options.containment,
      hoverclass:   options.hoverclass
    };

    Element.cleanWhitespace(element);

    options.draggables = [];
    options.droppables = [];

    if(options.dropOnEmpty || options.tree) {
      Droppables.add(element, options_for_tree);
      options.droppables.push(element);
    }

    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
      var handle = options.handles ? $(options.handles[i]) :
        (options.handle ? $(e).select('.' + options.handle)[0] : e);
      options.draggables.push(
        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
      Droppables.add(e, options_for_droppable);
      if(options.tree) e.treeNode = element;
      options.droppables.push(e);
    });

    if(options.tree) {
      (Sortable.findTreeElements(element, options) || []).each( function(e) {
        Droppables.add(e, options_for_tree);
        e.treeNode = element;
        options.droppables.push(e);
      });
    }

    this.sortables[element.id] = options;

    Draggables.addObserver(new SortableObserver(element, options.onUpdate));

  },

  findElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.tag);
  },

  findTreeElements: function(element, options) {
    return Element.findChildren(
      element, options.only, options.tree ? true : false, options.treeTag);
  },

  onHover: function(element, dropon, overlap) {
    if(Element.isParent(dropon, element)) return;

    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
      return;
    } else if(overlap>0.5) {
      Sortable.mark(dropon, 'before');
      if(dropon.previousSibling != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, dropon);
        if(dropon.parentNode!=oldParentNode)
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    } else {
      Sortable.mark(dropon, 'after');
      var nextElement = dropon.nextSibling || null;
      if(nextElement != element) {
        var oldParentNode = element.parentNode;
        element.style.visibility = "hidden"; // fix gecko rendering
        dropon.parentNode.insertBefore(element, nextElement);
        if(dropon.parentNode!=oldParentNode)
          Sortable.options(oldParentNode).onChange(element);
        Sortable.options(dropon.parentNode).onChange(element);
      }
    }
  },

  onEmptyHover: function(element, dropon, overlap) {
    var oldParentNode = element.parentNode;
    var droponOptions = Sortable.options(dropon);

    if(!Element.isParent(dropon, element)) {
      var index;

      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
      var child = null;

      if(children) {
        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);

        for (index = 0; index < children.length; index += 1) {
          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
            offset -= Element.offsetSize (children[index], droponOptions.overlap);
          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
            child = index + 1 < children.length ? children[index + 1] : null;
            break;
          } else {
            child = children[index];
            break;
          }
        }
      }

      dropon.insertBefore(element, child);

      Sortable.options(oldParentNode).onChange(element);
      droponOptions.onChange(element);
    }
  },

  unmark: function() {
    if(Sortable._marker) Sortable._marker.hide();
  },

  mark: function(dropon, position) {
    var sortable = Sortable.options(dropon.parentNode);
    if(sortable && !sortable.ghosting) return;

    if(!Sortable._marker) {
      Sortable._marker =
        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
          hide().addClassName('dropmarker').setStyle({position:'absolute'});
      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
    }
    var offsets = Position.cumulativeOffset(dropon);
    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});

    if(position=='after')
      if(sortable.overlap == 'horizontal')
        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
      else
        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});

    Sortable._marker.show();
  },

  _tree: function(element, options, parent) {
    var children = Sortable.findElements(element, options) || [];

    for (var i = 0; i < children.length; ++i) {
      var match = children[i].id.match(options.format);

      if (!match) continue;

      var child = {
        id: encodeURIComponent(match ? match[1] : null),
        element: element,
        parent: parent,
        children: [],
        position: parent.children.length,
        container: $(children[i]).down(options.treeTag)
      };

      /* Get the element containing the children and recurse over it */
      if (child.container)
        this._tree(child.container, options, child);

      parent.children.push (child);
    }

    return parent;
  },

  tree: function(element) {
    element = $(element);
    var sortableOptions = this.options(element);
    var options = Object.extend({
      tag: sortableOptions.tag,
      treeTag: sortableOptions.treeTag,
      only: sortableOptions.only,
      name: element.id,
      format: sortableOptions.format
    }, arguments[1] || { });

    var root = {
      id: null,
      parent: null,
      children: [],
      container: element,
      position: 0
    };

    return Sortable._tree(element, options, root);
  },

  /* Construct a [i] index for a particular node */
  _constructIndex: function(node) {
    var index = '';
    do {
      if (node.id) index = '[' + node.position + ']' + index;
    } while ((node = node.parent) != null);
    return index;
  },

  sequence: function(element) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[1] || { });

    return $(this.findElements(element, options) || []).map( function(item) {
      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
    });
  },

  setSequence: function(element, new_sequence) {
    element = $(element);
    var options = Object.extend(this.options(element), arguments[2] || { });

    var nodeMap = { };
    this.findElements(element, options).each( function(n) {
        if (n.id.match(options.format))
            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
        n.parentNode.removeChild(n);
    });

    new_sequence.each(function(ident) {
      var n = nodeMap[ident];
      if (n) {
        n[1].appendChild(n[0]);
        delete nodeMap[ident];
      }
    });
  },

  serialize: function(element) {
    element = $(element);
    var options = Object.extend(Sortable.options(element), arguments[1] || { });
    var name = encodeURIComponent(
      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);

    if (options.tree) {
      return Sortable.tree(element, arguments[1]).children.map( function (item) {
        return [name + Sortable._constructIndex(item) + "[id]=" +
                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
      }).flatten().join('&');
    } else {
      return Sortable.sequence(element, arguments[1]).map( function(item) {
        return name + "[]=" + encodeURIComponent(item);
      }).join('&');
    }
  }
};

Element.isParent = function(child, element) {
  if (!child.parentNode || child == element) return false;
  if (child.parentNode == element) return true;
  return Element.isParent(child.parentNode, element);
};

Element.findChildren = function(element, only, recursive, tagName) {
  if(!element.hasChildNodes()) return null;
  tagName = tagName.toUpperCase();
  if(only) only = [only].flatten();
  var elements = [];
  $A(element.childNodes).each( function(e) {
    if(e.tagName && e.tagName.toUpperCase()==tagName &&
      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
        elements.push(e);
    if(recursive) {
      var grandchildren = Element.findChildren(e, only, recursive, tagName);
      if(grandchildren) elements.push(grandchildren);
    }
  });

  return (elements.length>0 ? elements.flatten() : []);
};

Element.offsetSize = function (element, type) {
  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
};
(function(){

  var eventMatchers = {
    'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
    'MouseEvents': /^(?:click|mouse(?:down|up|over|move|out))$/
  }
  var defaultOptions = {
    pointerX: 0,
    pointerY: 0,
    button: 0,
    ctrlKey: false,
    altKey: false,
    shiftKey: false,
    metaKey: false,
    bubbles: true,
    cancelable: true
  }

  Event.simulate = function(element, eventName) {
    var options = Object.extend(defaultOptions, arguments[2] || { });
    var oEvent, eventType = null;

    element = $(element);

    for (var name in eventMatchers) {
      if (eventMatchers[name].test(eventName)) { eventType = name; break; }
    }

    if (!eventType)
      throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');

    if (document.createEvent) {
      oEvent = document.createEvent(eventType);
      if (eventType == 'HTMLEvents') {
        oEvent.initEvent(eventName, options.bubbles, options.cancelable);
      }
      else {
        oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
          options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
          options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
      }
      element.dispatchEvent(oEvent);
    }
    else {
      options.clientX = options.pointerX;
      options.clientY = options.pointerY;
      oEvent = Object.extend(document.createEventObject(), options);
      element.fireEvent('on' + eventName, oEvent);
    }
    return element;
  }

  Element.addMethods({ simulate: Event.simulate });
})();
Element.addMethods({
  centerInWindow: function(element) {

    var window_width = document.viewport.getDimensions().width;
    var window_height = document.viewport.getDimensions().height;

    var element_width = element.getDimensions().width;
    var element_height = element.getDimensions().height;

    element.setStyle({
      top  : Math.floor((window_height - element_height) / 2) + "px",
      left : Math.floor((window_width - element_width) / 2) + "px"
    });

    return element;
  }
});
Modal = Class.create({

  initialize: function(element, dontShow) {

    this.element = $(element);

    if (!dontShow) {
      this.show();
    }


  },

  show: function() {

    this.create_overlay();
    this.updated();
    window.onresize = function() { this.element.centerInWindow(); }.bind(this);

    Modal.open_modals.push(this)
  },



  updated : function() {

    this.centerize();

    try {
      if (f = this.element.down("input[type=text]")) f.focus();
    } catch (e) {
    }

    this.element.select(".close").invoke("observe", "click", this.hide.bindAsEventListener(this));

  },

  centerize : function() {
    this.element.centerInWindow().show();
  },

  create_overlay: function() {

    this.overlay = new Element("div").addClassName("modal_overlay").setStyle({ opacity: 0.9 });

    $(document.body).insert({ bottom : this.overlay });

  },

  hide: function(evnt) {

    window.onresize = {};
    this.element.select(".close").invoke("stopObserving", "click");

    this.element.hide();
    this.overlay.remove();

    Modal.open_modals = Modal.open_modals.without(this)
  }

});

Modal.open_modals = []
Modal.hide_all = function() {
  Modal.open_modals.invoke('hide')
}

if (!Control) var Control = { };

Control.Slider = Class.create({
  initialize: function(handle, track, options) {
    var slider = this;

    if (Object.isArray(handle)) {
      this.handles = handle.collect( function(e) { return $(e) });
    } else {
      this.handles = [$(handle)];
    }

    this.track   = $(track);
    this.options = options || { };

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step || '1');
    this.range     = this.options.range || $R(0,1);

    this.value     = 0; // assure backwards compat
    this.values    = this.handles.map( function() { return 0 });
    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;

    this.alignX = parseInt(this.options.alignX || '0');
    this.alignY = parseInt(this.options.alignY || '0');

    this.trackLength = this.maximumOffset() - this.minimumOffset();

    this.handleLength = this.isVertical() ?
      (this.handles[0].offsetHeight != 0 ?
        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
        this.handles[0].style.width.replace(/px$/,""));

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    if (this.options.disabled) this.setDisabled();

    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
    if (this.allowedValues) {
      this.minimum = this.allowedValues.min();
      this.maximum = this.allowedValues.max();
    }

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);

    this.handles.each( function(h,i) {
      i = slider.handles.length-1-i;
      slider.setValue(parseFloat(
        (Object.isArray(slider.options.sliderValue) ?
          slider.options.sliderValue[i] : slider.options.sliderValue) ||
         slider.range.start), i);
      h.makePositioned().observe("mousedown", slider.eventMouseDown);
    });

    this.track.observe("mousedown", this.eventMouseDown);
    document.observe("mouseup", this.eventMouseUp);
    document.observe("mousemove", this.eventMouseMove);

    this.initialized = true;
  },
  dispose: function() {
    var slider = this;
    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    this.handles.each( function(h) {
      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
    });
  },
  setDisabled: function(){
    this.disabled = true;
  },
  setEnabled: function(){
    this.disabled = false;
  },
  getNearestValue: function(value){
    if (this.allowedValues){
      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
      if (value <= this.allowedValues.min()) return(this.allowedValues.min());

      var offset = Math.abs(this.allowedValues[0] - value);
      var newValue = this.allowedValues[0];
      this.allowedValues.each( function(v) {
        var currentOffset = Math.abs(v - value);
        if (currentOffset <= offset){
          newValue = v;
          offset = currentOffset;
        }
      });
      return newValue;
    }
    if (value > this.range.end) return this.range.end;
    if (value < this.range.start) return this.range.start;
    return value;
  },
  setValue: function(sliderValue, handleIdx){
    if (!this.active) {
      this.activeHandleIdx = handleIdx || 0;
      this.activeHandle    = this.handles[this.activeHandleIdx];
      this.updateStyles();
    }
    handleIdx = handleIdx || this.activeHandleIdx || 0;
    if (this.initialized && this.restricted) {
      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
        sliderValue = this.values[handleIdx-1];
      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
        sliderValue = this.values[handleIdx+1];
    }
    sliderValue = this.getNearestValue(sliderValue);
    this.values[handleIdx] = sliderValue;
    this.value = this.values[0]; // assure backwards compat

    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
      this.translateToPx(sliderValue);

    this.drawSpans();
    if (!this.dragging || !this.event) this.updateFinished();
  },
  setValueBy: function(delta, handleIdx) {
    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
      handleIdx || this.activeHandleIdx || 0);
  },
  translateToPx: function(value) {
    return Math.round(
      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
      (value - this.range.start)) + "px";
  },
  translateToValue: function(offset) {
    return ((offset/(this.trackLength-this.handleLength) *
      (this.range.end-this.range.start)) + this.range.start);
  },
  getRange: function(range) {
    var v = this.values.sortBy(Prototype.K);
    range = range || 0;
    return $R(v[range],v[range+1]);
  },
  minimumOffset: function(){
    return(this.isVertical() ? this.alignY : this.alignX);
  },
  maximumOffset: function(){
    return(this.isVertical() ?
      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
        this.track.style.height.replace(/px$/,"")) - this.alignY :
      (this.track.offsetWidth != 0 ? this.track.offsetWidth :
        this.track.style.width.replace(/px$/,"")) - this.alignX);
  },
  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  drawSpans: function() {
    var slider = this;
    if (this.spans)
      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
    if (this.options.startSpan)
      this.setSpan(this.options.startSpan,
        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
    if (this.options.endSpan)
      this.setSpan(this.options.endSpan,
        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  },
  setSpan: function(span, range) {
    if (this.isVertical()) {
      span.style.top = this.translateToPx(range.start);
      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
    } else {
      span.style.left = this.translateToPx(range.start);
      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
    }
  },
  updateStyles: function() {
    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
    Element.addClassName(this.activeHandle, 'selected');
  },
  startDrag: function(event) {
    if (Event.isLeftClick(event)) {
      if (!this.disabled){
        this.active = true;

        var handle = Event.element(event);
        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
        var track = handle;
        if (track==this.track) {
          var offsets  = this.track.cumulativeOffset();
          this.event = event;
          this.setValue(this.translateToValue(
                               (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
                              ));
          var offsets  = this.activeHandle.cumulativeOffset();
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        } else {
          while((this.handles.indexOf(handle) == -1) && handle.parentNode)
            handle = handle.parentNode;

          if (this.handles.indexOf(handle)!=-1) {
            this.activeHandle    = handle;
            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
            this.updateStyles();

            var offsets  = this.activeHandle.cumulativeOffset();
            this.offsetX = (pointer[0] - offsets[0]);
            this.offsetY = (pointer[1] - offsets[1]);
          }
        }
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if (this.active) {
      if (!this.dragging) this.dragging = true;
      this.draw(event);
      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
      Event.stop(event);
   }
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = this.track.cumulativeOffset();
    pointer[0] -= this.offsetX + offsets[0];
    pointer[1] -= this.offsetY + offsets[1];
    this.event = event;
    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
    if (this.initialized && this.options.onSlide)
      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  },
  endDrag: function(event) {
    if (this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },
  finishDrag: function(event, success) {
    this.active = false;
    this.dragging = false;
    this.updateFinished();
  },
  updateFinished: function() {
    if (this.initialized && this.options.onChange)
      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
    this.event = null;
  }
});
/**
 * Version: 1.0 Alpha-1
 * Build Date: 13-Nov-2007
 * Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
 * License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
 * Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
 */
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}};
Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
return-1;};Date.getDayNumberFromName=function(name){var n=Date.CultureInfo.dayNames,m=Date.CultureInfo.abbreviatedDayNames,o=Date.CultureInfo.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
return-1;};Date.isLeapYear=function(year){return(((year%4===0)&&(year%100!==0))||(year%400===0));};Date.getDaysInMonth=function(year,month){return[31,(Date.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};Date.getTimezoneOffset=function(s,dst){return(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST[s.toUpperCase()]:Date.CultureInfo.abbreviatedTimeZoneStandard[s.toUpperCase()];};Date.getTimezoneAbbreviation=function(offset,dst){var n=(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST:Date.CultureInfo.abbreviatedTimeZoneStandard,p;for(p in n){if(n[p]===offset){return p;}}
return null;};Date.prototype.clone=function(){return new Date(this.getTime());};Date.prototype.compareTo=function(date){if(isNaN(this)){throw new Error(this);}
if(date instanceof Date&&!isNaN(date)){return(this>date)?1:(this<date)?-1:0;}else{throw new TypeError(date);}};Date.prototype.equals=function(date){return(this.compareTo(date)===0);};Date.prototype.between=function(start,end){var t=this.getTime();return t>=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;}
var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);}
if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);}
if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);}
if(x.hour||x.hours){this.addHours(x.hour||x.hours);}
if(x.month||x.months){this.addMonths(x.month||x.months);}
if(x.year||x.years){this.addYears(x.year||x.years);}
if(x.day||x.days){this.addDays(x.day||x.days);}
return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(value<min||value>max){throw new RangeError(value+" is not a valid value for "+name+".");}
return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;}
if(!x.second&&x.second!==0){x.second=-1;}
if(!x.minute&&x.minute!==0){x.minute=-1;}
if(!x.hour&&x.hour!==0){x.hour=-1;}
if(!x.day&&x.day!==0){x.day=-1;}
if(!x.month&&x.month!==0){x.month=-1;}
if(!x.year&&x.year!==0){x.year=-1;}
if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());}
if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());}
if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());}
if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());}
if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());}
if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());}
if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());}
if(x.timezone){this.setTimezone(x.timezone);}
if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);}
return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;}
var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}}
return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();};
Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;}
return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i<dx.length;i++){$D[dx[i]]=$D[dx[i].substring(0,3)]=df(i);}
var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
return this.moveToMonth(n,this._orient);};};for(var j=0;j<mx.length;j++){$D[mx[j]]=$D[mx[j].substring(0,3)]=mf(j);}
var ef=function(j){return function(){if(j.substring(j.length-1)!="s"){j+="s";}
return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$D[de]=$D[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}}());Date.prototype.toJSONString=function(){return this.toString("yyyy-MM-ddThh:mm:ssZ");};Date.prototype.toShortDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortDatePattern);};Date.prototype.toLongDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.longDatePattern);};Date.prototype.toShortTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortTimePattern);};Date.prototype.toLongTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.longTimePattern);};Date.prototype.getOrdinal=function(){switch(this.getDate()){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};
(function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
break;}
return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
rx.push(r[0]);s=r[1];}
return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
try{r=(px[i].call(this,s));}catch(e){r=null;}
if(r){return r;}}
throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
rx.push(r[0]);s=r[1];}
return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
s=q[1];}
if(!r){throw new $P.Exception(s);}
if(q){throw new $P.Exception(q[1]);}
if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
if(!last&&q[1].length===0){last=true;}
if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
if(rx[1].length<best[1].length){best=rx;}
if(best[1].length===0){break;}}
if(best[0].length===0){return best;}
if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
best[1]=q[1];}
return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
return rx;};Date.Grammar={};Date.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=((s.length==3)?Date.getMonthNumberFromName(s):(Number(s)-1));};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<Date.CultureInfo.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];var now=new Date();this.year=now.getFullYear();this.month=now.getMonth();this.day=1;this.hour=0;this.minute=0;this.second=0;for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
this.hour=(this.meridian=="p"&&this.hour<13)?this.hour+12:this.hour;if(this.day>Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");}
var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
if(this.now){return new Date();}
var today=Date.today();var method=null;var expression=!!(this.days!=null||this.orient||this.operator);if(expression){var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(this.weekday){this.unit="day";gap=(Date.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
if(this.month){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
if(!this.unit){this.unit="day";}
if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
if(this.unit=="week"){this.unit="day";this.value=this.value*7;}
this[this.unit+"s"]=this.value*orient;}
return today.add(this);}else{if(this.meridian&&this.hour){this.hour=(this.hour<13&&this.meridian=="p")?this.hour+12:this.hour;}
if(this.weekday&&!this.day){this.day=(today.addDays((Date.getDayNumberFromName(this.weekday)-today.getDay()))).getDate();}
if(this.month&&!this.day){this.day=1;}
return today.set(this);}}};var _=Date.Parsing.Operators,g=Date.Grammar,t=Date.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=Date.CultureInfo.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
fn=_C[keys]=_.any.apply(null,px);}
return fn;};g.ctoken2=function(key){return _.rtoken(Date.CultureInfo.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.mm,g.ss],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^(\+|\-)?\s*\d\d\d\d?/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^(\+|\-)\s*\d\d\d\d/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[Date.CultureInfo.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw Date.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["yyyy-MM-ddTHH:mm:ss","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
return g._start.call({},s);};}());Date._parse=Date.parse;Date.parse=function(s){var r=null;if(!s){return null;}
try{r=Date.Grammar.start.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};Date.getParseFunction=function(fx){var fn=Date.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return Date.getParseFunction(fx)(s);};

var Calendar = Class.create()


Calendar.VERSION = '1.2'

Calendar.DAY_NAMES = new Array(
  'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
  'Sunday'
)

Calendar.MED_DAY_NAMES = new Array(
  'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat',
  'Sun'
)


Calendar.SHORT_DAY_NAMES = new Array(
  'S', 'M', 'T', 'W', 'T', 'F', 'S', 'S'
)

Calendar.MONTH_NAMES = new Array(
  'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
  'September', 'October', 'November', 'December'
)

Calendar.SHORT_MONTH_NAMES = new Array(
  'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov',
  'Dec'
)

Calendar.NAV_PREVIOUS_YEAR  = -2
Calendar.NAV_PREVIOUS_MONTH = -1
Calendar.NAV_TODAY          =  0
Calendar.NAV_NEXT_MONTH     =  1
Calendar.NAV_NEXT_YEAR      =  2


Calendar._checkCalendar = function(event) {
  if (!window._popupCalendar)
    return false
  if (Element.descendantOf(Event.element(event), window._popupCalendar.container))
    return
  window._popupCalendar.callCloseHandler()
  return Event.stop(event)
}


Calendar.handleMouseDownEvent = function(event)
{
  Event.observe(document, 'mouseup', Calendar.handleMouseUpEvent)
  Event.stop(event)
}

Calendar.handleMouseUpEvent = function(event)
{
  var el        = Event.element(event)
  var calendar  = el.calendar
  var isNewDate = false

  if (!calendar) return false

  if (typeof el.navAction == 'undefined')
  {
    if (calendar.currentDateElement) {
      Element.removeClassName(calendar.currentDateElement, 'selected')
      Element.addClassName(el, 'selected')
      calendar.shouldClose = (calendar.currentDateElement == el)
      if (!calendar.shouldClose) calendar.currentDateElement = el
    }
    calendar.date.setDateOnly(el.date)
    isNewDate = true
    calendar.shouldClose = true;
    var isOtherMonth     = !calendar.shouldClose
    if (el.hasClassName('otherDay')) calendar.update(calendar.date)
  }

  else
  {
    var date = new Date(calendar.date)

    if (el.navAction == Calendar.NAV_TODAY)
      date.setDateOnly(new Date())

    var year = date.getFullYear()
    var mon = date.getMonth()
    function setMonth(m) {
      var day = date.getDate()
      var max = date.getMonthDays(m)
      if (day > max) date.setDate(max)
      date.setMonth(m)
    }
    switch (el.navAction) {

      case Calendar.NAV_PREVIOUS_YEAR:
        if (year > calendar.minYear)
          date.setFullYear(year - 1)
        break

      case Calendar.NAV_PREVIOUS_MONTH:
        if (mon > 0) {
          setMonth(mon - 1)
        }
        else if (year-- > calendar.minYear) {
          date.setFullYear(year)
          setMonth(11)
        }
        break

      case Calendar.NAV_TODAY:
        break

      case Calendar.NAV_NEXT_MONTH:
        if (mon < 11) {
          setMonth(mon + 1)
        }
        else if (year < calendar.maxYear) {
          date.setFullYear(year + 1)
          setMonth(0)
        }
        break

      case Calendar.NAV_NEXT_YEAR:
        if (year < calendar.maxYear)
          date.setFullYear(year + 1)
        break

    }

    if (!date.equalsTo(calendar.date)) {
      calendar.setDate(date)
      isNewDate = true
    } else if (el.navAction == 0) {
      isNewDate = (calendar.shouldClose = true)
    }
  }

  if (isNewDate) event && calendar.callSelectHandler()
  if (calendar.shouldClose) event && calendar.callCloseHandler()

  Event.stopObserving(document, 'mouseup', Calendar.handleMouseUpEvent)

  return Event.stop(event)
}

Calendar.defaultSelectHandler = function(calendar)
{
  if (!calendar.dateField) return false

  if (calendar.dateField.tagName == 'DIV')
    Element.update(calendar.dateField, calendar.date.print(calendar.dateFormat))
  else if (calendar.dateField.tagName == 'INPUT') {
    calendar.dateField.value = calendar.date.print(calendar.dateFormat) }

  if (typeof calendar.dateField.onchange == 'function')
    calendar.dateField.onchange()

  if (calendar.shouldClose) calendar.callCloseHandler()
}

Calendar.defaultCloseHandler = function(calendar)
{
  calendar.hide()
}



Calendar.setup = function(params)
{

  function param_default(name, def) {
    if (!params[name]) params[name] = def
  }

  param_default('dateField', null)
  param_default('triggerElement', null)
  param_default('parentElement', null)
  param_default('selectHandler',  null)
  param_default('closeHandler', null)
  param_default('markFuture', false)

  if (params.parentElement)
  {
    var calendar = new Calendar(params.parentElement)
    calendar.setSelectHandler(params.selectHandler || Calendar.defaultSelectHandler)
    if (params.dateFormat)
      calendar.setDateFormat(params.dateFormat)
    if (params.dateField) {
      calendar.setDateField(params.dateField)
      calendar.parseDate(calendar.dateField.innerHTML || calendar.dateField.value)
    }
    calendar.markFuture = params.markFuture;
    calendar.show()
    return calendar
  }

  else
  {
    var triggerElement = $(params.triggerElement || params.dateField)
    triggerElement.onclick = function() {
      var calendar = new Calendar()
      calendar.markFuture = params.markFuture;
      calendar.setSelectHandler(params.selectHandler || Calendar.defaultSelectHandler)
      calendar.setCloseHandler(params.closeHandler || Calendar.defaultCloseHandler)
      if (params.dateFormat)
        calendar.setDateFormat(params.dateFormat)
      if (params.dateField) {
        calendar.setDateField(params.dateField)
        calendar.parseDate(calendar.dateField.innerHTML || calendar.dateField.value)
      }
      if (params.dateField)
        Date.parseDate(calendar.dateField.value || calendar.dateField.innerHTML, calendar.dateFormat)
      calendar.showAtElement(triggerElement)
      return calendar
    }
  }

}




Calendar.prototype = {

  container: null,

  selectHandler: null,
  closeHandler: null,

  minYear: 1900,
  maxYear: 2100,
  dateFormat: '%Y-%m-%d',

  date: new Date(),
  currentDateElement: null,

  shouldClose: false,
  isPopup: true,

  dateField: null,



  initialize: function(parent)
  {
    if (parent)
      this.create($(parent))
    else
      this.create()
  },




  update: function(date)
  {
    var calendar   = this
    var today      = new Date()
    var thisYear   = today.getFullYear()
    var thisMonth  = today.getMonth()
    var thisDay    = today.getDate()
    var month      = date.getMonth();
    var dayOfMonth = date.getDate();

    if (date.getFullYear() < this.minYear)
      date.setFullYear(this.minYear)
    else if (date.getFullYear() > this.maxYear)
      date.setFullYear(this.maxYear)

    this.date = new Date(date)

    date.setDate(1)
    date.setDate(-(date.getDay()) + 1)

    Element.getElementsBySelector(this.container, 'tbody tr').each(
      function(row, i) {
        var rowHasDays = false
        row.immediateDescendants().each(
          function(cell, j) {
            var day            = date.getDate()
            var dayOfWeek      = date.getDay()
            var isCurrentMonth = (date.getMonth() == month)

            cell.className = ''
            cell.date = new Date(date)
            cell.update(day)

            if (calendar.markFuture && cell.date > today) {
              cell.addClassName('futureDay');
            }

            if (!isCurrentMonth)
              cell.addClassName('otherDay')
            else
              rowHasDays = true

            if (isCurrentMonth && day == dayOfMonth) {
              cell.addClassName('selected')
              calendar.currentDateElement = cell
            }

            if (date.getFullYear() == thisYear && date.getMonth() == thisMonth && day == thisDay)
              cell.addClassName('today')

            if ([0, 6].indexOf(dayOfWeek) != -1)
              cell.addClassName('weekend')

            date.setDate(day + 1)
          }
        )
        !rowHasDays ? row.hide() : row.show()
      }
    )

    this.container.getElementsBySelector('td.title')[0].update(
      Calendar.MONTH_NAMES[month] + ' ' + this.date.getFullYear()
    )
  },




  create: function(parent)
  {

    if (!parent) {
      parent = document.getElementsByTagName('body')[0]
      this.isPopup = true
    } else {
      this.isPopup = false
    }

    var table = new Element('table')

    var thead = new Element('thead')
    table.appendChild(thead)

/*    var row  = new Element('tr')
    var cell = new Element('td', { colSpan: 7 } )
    cell.addClassName('title')
    row.appendChild(cell)
    thead.appendChild(row)*/

    row = new Element('tr')
    this._drawButtonCell(row, '&#x2039;', 2, Calendar.NAV_PREVIOUS_MONTH)
    c = this._drawButtonCell(row, '',    3, Calendar.NAV_TODAY);
    c.addClassName("title");

    this._drawButtonCell(row, '&#x203a;', 2, Calendar.NAV_NEXT_MONTH)
    thead.appendChild(row)

    row = new Element('tr')
    for (var i = 0; i < 7; ++i) {
      cell = new Element('th').update(Calendar.SHORT_DAY_NAMES[i])
      if (i == 0 || i == 6)
        cell.addClassName('weekend')
      row.appendChild(cell)
    }
    thead.appendChild(row)

    var tbody = table.appendChild(new Element('tbody'))
    for (i = 6; i > 0; --i) {
      row = tbody.appendChild(new Element('tr'))
      row.addClassName('days')
      for (var j = 7; j > 0; --j) {
        cell = row.appendChild(new Element('td'))
        cell.calendar = this
      }
    }

    this.container = new Element('div')
    this.container.addClassName('calendar')
    if (this.isPopup) {
      this.container.setStyle({ position: 'absolute', display: 'none' })
      this.container.addClassName('popup')
    }
    this.container.appendChild(table)

    this.update(this.date)

    Event.observe(this.container, 'mousedown', Calendar.handleMouseDownEvent)

    parent.appendChild(this.container)

  },

  _drawButtonCell: function(parent, text, colSpan, navAction)
  {
    var cell          = new Element('td')
    if (colSpan > 1) cell.colSpan = colSpan
    cell.className    = 'button'
    cell.calendar     = this
    cell.navAction    = navAction
    cell.innerHTML    = text
    cell.unselectable = 'on' // IE
    parent.appendChild(cell)
    return cell
  },




  callSelectHandler: function()
  {
    if (this.selectHandler)
      this.selectHandler(this, this.date.print(this.dateFormat))
  },

  callCloseHandler: function()
  {
    if (this.closeHandler)
      this.closeHandler(this)
  },




  show: function()
  {
    this.container.show()
    if (this.isPopup) {
      window._popupCalendar = this
      Event.observe(document, 'mousedown', Calendar._checkCalendar)
    }
  },

  showAt: function (x, y)
  {
    this.container.setStyle({ left: x + 'px', top: y + 'px' })
    this.show()
  },

  showAtElement: function(element)
  {
    var pos = element.cumulativeOffset();
    this.showAt(pos[0], pos[1])
  },

  hide: function()
  {
    this.container.hide()
    if (this.isPopup) {
      Event.stopObserving(document, 'mousedown', Calendar._checkCalendar)
    }
  },




  parseDate: function(str, format)
  {
    if (!format)
      format = this.dateFormat
    this.setDate(Date.parseDate(str, format))
  },




  setSelectHandler: function(selectHandler)
  {
    this.selectHandler = selectHandler
  },

  setCloseHandler: function(closeHandler)
  {
    this.closeHandler = closeHandler
  },

  setDate: function(date)
  {
    if (!date.equalsTo(this.date))
      this.update(date)
  },

  setDateFormat: function(format)
  {
    this.dateFormat = format
  },

  setDateField: function(field)
  {
    this.dateField = $(field)
  },

  setRange: function(minYear, maxYear)
  {
    this.minYear = minYear
    this.maxYear = maxYear
  }

}

window._popupCalendar = null






























Date.DAYS_IN_MONTH = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
Date.SECOND        = 1000 /* milliseconds */
Date.MINUTE        = 60 * Date.SECOND
Date.HOUR          = 60 * Date.MINUTE
Date.DAY           = 24 * Date.HOUR
Date.WEEK          =  7 * Date.DAY

Date.parseDate = function(str, fmt) {
  var today = new Date();
  var y     = 0;
  var m     = -1;
  var d     = 0;
  var a     = str.split(/\W+/);
  var b     = fmt.match(/%./g);
  var i     = 0, j = 0;
  var hr    = 0;
  var min   = 0;

  for (i = 0; i < a.length; ++i) {
    if (!a[i]) continue;
    switch (b[i]) {
      case "%d":
      case "%e":
        d = parseInt(a[i], 10);
        break;
      case "%m":
        m = parseInt(a[i], 10) - 1;
        break;
      case "%Y":
      case "%y":
        y = parseInt(a[i], 10);
        (y < 100) && (y += (y > 29) ? 1900 : 2000);
        break;
      case "%b":
      case "%B":
        for (j = 0; j < 12; ++j) {
          if (Calendar.MONTH_NAMES[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) {
            m = j;
            break;
          }
        }
        break;
      case "%H":
      case "%I":
      case "%k":
      case "%l":
        hr = parseInt(a[i], 10);
        break;
      case "%P":
      case "%p":
        if (/pm/i.test(a[i]) && hr < 12)
          hr += 12;
        else if (/am/i.test(a[i]) && hr >= 12)
          hr -= 12;
        break;
      case "%M":
        min = parseInt(a[i], 10);
        break;
    }
  }
  if (isNaN(y)) y = today.getFullYear();
  if (isNaN(m)) m = today.getMonth();
  if (isNaN(d)) d = today.getDate();
  if (isNaN(hr)) hr = today.getHours();
  if (isNaN(min)) min = today.getMinutes();
  if (y != 0 && m != -1 && d != 0)
    return new Date(y, m, d, hr, min, 0);
  y = 0; m = -1; d = 0;
  for (i = 0; i < a.length; ++i) {
    if (a[i].search(/[a-zA-Z]+/) != -1) {
      var t = -1;
      for (j = 0; j < 12; ++j) {
        if (Calendar.MONTH_NAMES[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
      }
      if (t != -1) {
        if (m != -1) {
          d = m+1;
        }
        m = t;
      }
    } else if (parseInt(a[i], 10) <= 12 && m == -1) {
      m = a[i]-1;
    } else if (parseInt(a[i], 10) > 31 && y == 0) {
      y = parseInt(a[i], 10);
      (y < 100) && (y += (y > 29) ? 1900 : 2000);
    } else if (d == 0) {
      d = a[i];
    }
  }
  if (y == 0)
    y = today.getFullYear();
  if (m != -1 && d != 0)
    return new Date(y, m, d, hr, min, 0);
  return today;
};

Date.prototype.getMonthDays = function(month) {
  var year = this.getFullYear()
  if (typeof month == "undefined")
    month = this.getMonth()
  if (((0 == (year % 4)) && ( (0 != (year % 100)) || (0 == (year % 400)))) && month == 1)
    return 29
  else
    return Date.DAYS_IN_MONTH[month]
};

Date.prototype.getDayOfYear = function() {
  var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
  var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
  var time = now - then;
  return Math.floor(time / Date.DAY);
};

/** Returns the number of the week in year, as defined in ISO 8601. */
Date.prototype.getWeekNumber = function() {
  var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
  var DoW = d.getDay();
  d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
  var ms = d.valueOf(); // GMT
  d.setMonth(0);
  d.setDate(4); // Thu in Week 1
  return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
};

/** Checks date and time equality */
Date.prototype.equalsTo = function(date) {
  return ((this.getFullYear() == date.getFullYear()) &&
   (this.getMonth() == date.getMonth()) &&
   (this.getDate() == date.getDate()) &&
   (this.getHours() == date.getHours()) &&
   (this.getMinutes() == date.getMinutes()));
};

/** Set only the year, month, date parts (keep existing time) */
Date.prototype.setDateOnly = function(date) {
  var tmp = new Date(date);
  this.setDate(1);
  this.setFullYear(tmp.getFullYear());
  this.setMonth(tmp.getMonth());
  this.setDate(tmp.getDate());
};

/** Prints the date in a string according to the given format. */
Date.prototype.print = function (str) {
  var m = this.getMonth();
  var d = this.getDate();
  var y = this.getFullYear();
  var wn = this.getWeekNumber();
  var w = this.getDay();
  var s = {};
  var hr = this.getHours();
  var pm = (hr >= 12);
  var ir = (pm) ? (hr - 12) : hr;
  var dy = this.getDayOfYear();
  if (ir == 0)
    ir = 12;
  var min = this.getMinutes();
  var sec = this.getSeconds();
  s["%a"] = Calendar.MED_DAY_NAMES[w]; // abbreviated weekday name [FIXME: I18N]
  s["%A"] = Calendar.DAY_NAMES[w]; // full weekday name
  s["%b"] = Calendar.SHORT_MONTH_NAMES[m]; // abbreviated month name [FIXME: I18N]
  s["%B"] = Calendar.MONTH_NAMES[m]; // full month name
  s["%C"] = 1 + Math.floor(y / 100); // the century number
  s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
  s["%e"] = d; // the day of the month (range 1 to 31)
  s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
  s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
  s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
  s["%k"] = hr;   // hour, range 0 to 23 (24h format)
  s["%l"] = ir;   // hour, range 1 to 12 (12h format)
  s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
  s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
  s["%n"] = "\n";   // a newline character
  s["%p"] = pm ? "PM" : "AM";
  s["%P"] = pm ? "pm" : "am";
  s["%s"] = Math.floor(this.getTime() / 1000);
  s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
  s["%t"] = "\t";   // a tab character
  s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
  s["%u"] = w + 1;  // the day of the week (range 1 to 7, 1 = MON)
  s["%w"] = w;    // the day of the week (range 0 to 6, 0 = SUN)
  s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
  s["%Y"] = y;    // year with the century
  s["%%"] = "%";    // a literal '%' character

  return str.gsub(/%./, function(match) { return s[match] || match });
};

Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
Date.prototype.setFullYear = function(y) {
  var d = new Date(this);
  d.__msh_oldSetFullYear(y);
  if (d.getMonth() != this.getMonth())
    this.setDate(28);
  this.__msh_oldSetFullYear(y);
}
ContactManager = Class.create({

  initialize: function(container_id, search_url, update_url) {

    this.container  = $(container_id);
    this.search_url = search_url;
    this.update_url = update_url;

    this.search_box         = this.container.down("#contact_search");
    this.results            = this.container.down("#contact_results");
    this.result_title       = this.container.down("#list_title");
    this.loading            = this.container.down("#loading");
    this.contact_edit_modal = this.container.down("#contact_edit_modal");
    this.contact_delete_modal = this.container.down("#contact_delete_modal");

    this.search_submit      = this.search_box.down("#search");
    this.search_field       = this.search_box.down("input[name=search_query]");

    this.search_submit.observe("click", this.search_clicked.bindAsEventListener(this));


    this.all_contacts_link = this.container.down("#show_all");
    this.all_contacts_link.observe("click", this.all_contacts_clicked.bindAsEventListener(this));

    this.pinned_contacts_link = this.container.down("#show_pinned");
    this.pinned_contacts_link.observe("click", this.pinned_clicked.bindAsEventListener(this));


    this.search_field.observe("keydown", function(event) {
      if (event.keyCode == Event.KEY_RETURN) this.search_clicked(event);
    }.bind(this));

  },

  search_clicked: function(event) {

    query = $F(this.search_field);

    if (!query.blank()) {
      $A([this.pinned_contacts_link, this.all_contacts_link]).invoke("addClassName", "off");
      this.pinned_contacts_link.removeClassName("off");
      this.search(query);
    } else {
      this.search_field.focus();
    }
    event.stop();
    return false;

  },

  all_contacts_clicked: function(event) {

    $A([this.pinned_contacts_link, this.all_contacts_link]).invoke("addClassName", "off");
    this.pinned_contacts_link.removeClassName("off");

    this.search("", true);

    event.stop();
    return false;

  },

  pinned_clicked: function(event) {

    this.load_pinned();

    event.stop();
    return false;

  },

  delete_contact : function(id, path) {

    this.contact_delete_modal.innerHTML = $('form_loading_modal_contents').innerHTML;
    this.delete_modal = new Modal(this.contact_delete_modal);


    new Ajax.Request(path, {
      method : "get",
      onComplete : function() {
        this.delete_modal.updated();
      }.bind(this)
    })

  },

  delete_loading : function() {
    this.contact_delete_modal.down(".buttons").hide();
    this.contact_delete_modal.down(".loading").show();
  },

  delete_success : function(contact_id) {
    this.delete_modal.hide();
    $('contact_' + contact_id).hide();
  },

  load_pinned : function() {
    $A([this.pinned_contacts_link, this.all_contacts_link]).invoke("addClassName", "off");
    this.all_contacts_link.removeClassName("off");

    this.search("", false, true);
  },

  search: function(query, all_contacts, pinned) {

    this.results.hide();
    this.result_title.hide();
    this.loading.show();

    if (all_contacts) params = {};
    else if (pinned) params = { "pinned" : true };
    else params = { "q" : query };

    this.showing_pinned = !!pinned;

    new Ajax.Request(this.search_url, {

      parameters : params,
      method     : "get",
      onSuccess  : function(transport) {

       if (all_contacts) this.result_title.update("All Contacts");
       else if (pinned) this.result_title.update("Pinned Contacts");
       else this.result_title.update("Search for \"" + query + "\"");

       this.results.update(transport.responseText);

       this.results.show();

       if (this.results.down(".contact") || !pinned) {
         this.result_title.show();
       }
       this.loading.hide();

      }.bind(this)

    });

  },

  toggle_pin : function(contact_id, remove_unpin) {

    $("contact_" + contact_id).setStyle({ opacity : 0.7 });

    var params = { "toggle_pin" : true }

    if (remove_unpin)
      params["remove_unpin"] = true

    new Ajax.Request(this.update_url.gsub("id", contact_id), {
      method     : "put",
      parameters : params,

      onSuccess : function() {

        if (remove_unpin) {
          $("contact_" + contact_id).remove();
          if (!this.results.down(".contact"))
            this.load_pinned();
        }

      }.bind(this)
    });

  },

  edit : function(contact_id) {

    this.contact_edit_modal.innerHTML = $("edit_contact_" + contact_id).innerHTML;

    this.edit_modal = new Modal(this.contact_edit_modal);

  },

  delete_click : function(contact_id) {

    this.delete_modal = new Modal(this.contact_delete_modal);

  },

  edit_loading : function() {

    this.contact_edit_modal.down(".buttons").hide();
    this.contact_edit_modal.down(".loading").show();

  },

  edit_error : function(transport) {

    this.contact_edit_modal.down(".errorExplanation").update(transport.responseText);
    this.contact_edit_modal.down(".errorExplanation").show();
    this.contact_edit_modal.down(".buttons").show();
    this.contact_edit_modal.down(".loading").hide();

  },

  close_edit : function() {

    this.edit_modal.hide();

  }

});

ClientView = Class.create({
  initialize : function(contact_id, update_url) {
    this.contact_id = contact_id;
    this.update_url = update_url;
  },

  toggle_client_view : function() {
    $("contact_" + this.contact_id).setStyle({ opacity : 0.7 });

    new Ajax.Request(this.update_url.gsub("id", this.contact_id), {
      method     : "put",
      parameters : { "toggle_client_view" : true }
    });
  }
});



ContactAdder = {

  add : function(initial_value) {

    var new_contact_modal = $("new_contact_modal");
    window.contact_modal = new Modal(new_contact_modal);

    if (initial_value) {
      new_contact_modal.down("input#contact_short_code").value = initial_value;
    }

  },

  loading : function() {

    $("new_contact_modal").down(".buttons").hide();
    $("new_contact_modal").down(".loading").show();

  },

  close : function(transport) {

    var contact = transport.responseJSON;

    window.contact_modal.hide();
    window.contacts.push(contact);

    if (window.contact_manager) window.contact_manager.search("", !window.contact_manager.showing_pinned, window.contact_manager.showing_pinned);

    if (ef = ContactAdder.entry_log_form) {
      ef.set_contact(contact);
      ContactAdder.entry_log_form = null;
    }

    $("new_contact_modal").down(".buttons").show();
    $("new_contact_modal").down(".loading").hide();

  },

  error : function(transport) {

    $("new_contact_modal").down(".errorExplanation").update(transport.responseText).show();

    $("new_contact_modal").down(".buttons").show();
    $("new_contact_modal").down(".loading").hide();

  }

};

ContactSelector = Class.create({

  MAX_CONTACTS : 10,

  initialize: function(container_id, url_pattern, options) {

    this.options = options || {};


    this.container    = $(container_id);
    this.field        = this.container.down("input[type=text]");
    this.url_template = url_pattern;

    this.template     = this.options.template || (function(contact) { return "@" + contact.short_code; });

    this.create_list();

    this.field.observe("focus", this.show_dropdown.bindAsEventListener(this));

  },

  show_dropdown: function() {

    this.list.setStyle({ top  : (this.field.cumulativeOffset().top + this.field.getDimensions().height) + "px",
                         left : (this.field.cumulativeOffset().left) + "px" });

    this.list.show();
    this.field.observe("keyup", this.keyup.bindAsEventListener(this));

    this.filter_list();

    window.document.observe("click", function(event) {

      if (!event.element().descendantOf(this.container)) {
        this.hide_dropdown();
      }


    }.bind(this));

  },

  hide_dropdown: function() {

    this.list.hide();
    this.field.stopObserving("keyup");
    window.document.stopObserving("click");

  },

  keyup: function(event) {

    if (event.keyCode == Event.KEY_RETURN) this.choose_contact(this.selected_contact);

    if (event.keyCode == Event.KEY_UP || event.keyCode == Event.KEY_DOWN) {

      var contact_index = this.contact_options.indexOf(this.selected_contact);

      (event.keyCode == Event.KEY_UP) ? contact_index-- : contact_index++;

      if (contact_index < 0) contact_index = this.contact_options.length - 1;
      if (contact_index >= this.contact_options.length) contact_index = 0;

      this.set_selected_contact(this.contact_options[contact_index]);

      this.field.getSelection().start = $F(this.field).length;

      event.stop();
      return false;

    }

    this.filter_list($F(this.field));

  },

  choose_contact: function(c) {

    window.location.href = this.url_template.gsub("-id-", c.id);

  },

  set_selected_contact: function(c) {

    this.selected_contact = c;

    this.list.select("li").invoke("removeClassName", "selected");
    this.list.down(".contact_" + c.id).addClassName("selected");

  },

  filter_list: function(query) {

    if (!query) query = "";

    if (query.startsWith("@")) query = query.substring(1);

    this.list.select("li").invoke("remove");

    var all_contacts = (this.options.contacts || window.contacts).select(function(c) {
      return query.blank() || c.short_code.startsWith(query);
    });

    this.contact_options = all_contacts.slice(0, this.MAX_CONTACTS);

    this.contact_options.each(function(c) {

      var li =  new Element("li", { "style" : "position: relative" }).addClassName("contact_" + c.id).update(this.template(c));

      if (c.pinned) li.addClassName("pinned");

      this.list.insert({
        bottom : li
      });


      li.observe("click", function() { this.choose_contact(c); }.bind(this));

      li.observe("mouseover", function() { this.set_selected_contact(c); }.bind(this));

    }.bind(this));

    this.set_selected_contact(this.contact_options.first());

  },

  create_list: function() {

    this.list  = new Element("ul", { id : "contact_selector"}).setStyle({ position : "absolute" }).hide();

    this.field.insert({ after : this.list });

  }


});
TaskManager = Class.create({

  initialize: function(container_id, search_url, update_url) {

    this.container  = $(container_id);
    this.search_url = search_url;
    this.update_url = update_url;

    this.search_box         = this.container.down("#task_search");
    this.results            = this.container.down("#task_results");
    this.result_title       = this.container.down("#list_title");
    this.loading            = this.container.down("#loading");
    this.task_edit_modal = this.container.down("#task_edit_modal");
    this.task_delete_modal = this.container.down("#task_delete_modal");

    this.search_submit      = this.search_box.down("#search");
    this.search_field       = this.search_box.down("input[name=search_query]");

    this.search_submit.observe("click", this.search_clicked.bindAsEventListener(this));


    this.all_tasks_link = this.container.down("#show_all");
    this.all_tasks_link.observe("click", this.all_tasks_clicked.bindAsEventListener(this));

    this.pinned_tasks_link = this.container.down("#show_pinned");
    this.pinned_tasks_link.observe("click", this.pinned_clicked.bindAsEventListener(this));


    this.search_field.observe("keydown", function(event) {
      if (event.keyCode == Event.KEY_RETURN) this.search_clicked(event);
    }.bind(this));

  },

  search_clicked: function(event) {

    query = $F(this.search_field);

    if (!query.blank()) {
      $A([this.pinned_tasks_link, this.all_tasks_link]).invoke("addClassName", "off");
      this.pinned_tasks_link.removeClassName("off");
      this.search(query);
    } else {
      this.search_field.focus();
    }
    event.stop();
    return false;

  },

  all_tasks_clicked: function(event) {

    $A([this.pinned_tasks_link, this.all_tasks_link]).invoke("addClassName", "off");
    this.pinned_tasks_link.removeClassName("off");

    this.search("", true);

    event.stop();
    return false;

  },

  pinned_clicked: function(event) {

    this.load_pinned();

    event.stop();
    return false;

  },

  load_pinned : function() {
    $A([this.pinned_tasks_link, this.all_tasks_link]).invoke("addClassName", "off");
    this.all_tasks_link.removeClassName("off");

    this.search("", false, true);
  },

  search: function(query, all_tasks, pinned) {

    this.results.hide();
    this.result_title.hide();
    this.loading.show();

    if (all_tasks) params = {};
    else if (pinned) params = { "pinned" : true };
    else params = { "q" : query };

    new Ajax.Request(this.search_url, {

      parameters : params,
      method     : "get",
      onSuccess  : function(transport) {

       if (all_tasks) this.result_title.update("All Tasks");
       else if (pinned) this.result_title.update("Pinned Tasks");
       else this.result_title.update("Search for \"" + query + "\"");

       this.results.update(transport.responseText);

       this.results.show();

       if (this.results.down(".task") || !pinned) {
         this.result_title.show();
       }
       this.loading.hide();

      }.bind(this)

    });

  },

  toggle_pin : function(task_id, remove_unpin) {

    $("task_" + task_id).setStyle({ opacity : 0.7 });

    var params = { "toggle_pin" : true }

    if (remove_unpin)
      params["remove_unpin"] = true

    new Ajax.Request(this.update_url.gsub("id", task_id), {
      method     : "put",
      parameters : params,

      onSuccess : function() {

        if (remove_unpin) {
          $("task_" + task_id).remove();
          if (!this.results.down(".task"))
            this.load_pinned();
        }

      }.bind(this)
    });

  },

  edit : function(task_id) {

    this.task_edit_modal.innerHTML = $("edit_task_" + task_id).innerHTML;

    this.edit_modal = new Modal(this.task_edit_modal);

  },

  edit_loading : function() {

    this.task_edit_modal.down(".buttons").hide();
    this.task_edit_modal.down(".loading").show();

  },

  edit_error : function(transport) {

    this.task_edit_modal.down(".errorExplanation").update(transport.responseText);
    this.task_edit_modal.down(".errorExplanation").show();
    this.task_edit_modal.down(".buttons").show();
    this.task_edit_modal.down(".loading").hide();

  },

  close_edit : function() {

    this.edit_modal.hide();

  },

  delete_task : function(id, path) {

    this.task_delete_modal.innerHTML = $('form_loading_modal_contents').innerHTML;
    this.delete_modal = new Modal(this.task_delete_modal);


    new Ajax.Request(path, {
      method : "get",
      onComplete : function() {
        this.delete_modal.updated();
      }.bind(this)
    })

  },

  delete_loading : function() {
    this.task_delete_modal.down(".buttons").hide();
    this.task_delete_modal.down(".loading").show();
  },

  delete_success : function(task_id) {
    this.delete_modal.hide();
    $('task_' + task_id).hide();

  }

});



TaskAdder = {

  add : function(initial_value) {

    var new_task_modal = $("new_task_modal");
    window.task_modal = new Modal(new_task_modal);

    if (initial_value) {
      new_task_modal.down("input#task_short_code").value = initial_value;
    }

  },

  loading : function() {

    $("new_task_modal").down(".buttons").hide();
    $("new_task_modal").down(".loading").show();

  },

  close : function(transport) {

    var task = transport.responseJSON;

    window.task_modal.hide();

    if (window.task_manager) window.task_manager.search("", false, true);

    if (ef = TaskAdder.entry_log_form) {
      task.new_task = true
      ef.set_task(task);
      TaskAdder.entry_log_form = null;
    }

    $("new_task_modal").down(".buttons").show();
    $("new_task_modal").down(".loading").hide();

  },

  error : function(transport) {

    $("new_task_modal").down(".errorExplanation").update(transport.responseText).show();

    $("new_task_modal").down(".buttons").show();
    $("new_task_modal").down(".loading").hide();

  }

};
Event.KEY_AT_SYMBOL = 50;
Event.KEY_SPACEBAR  = 32;

EntryLogForm = Class.create({

  MAX_CONTACTS : 10,

  initialize : function() {

    this.contacts = window.contacts;
    this.all_tasks = window.tasks;
    this.admin_features = window.current_user_is_admin;

    this.container          = $("log_entry");

    this.form               = this.container.down("form");

    this.contact_tag        = this.container.down("#contact_tag");
    this.contact_dropdown   = $("contact_dropdown");
    this.task_dropdown      = $("task_dropdown");

    this.field              = this.form.down("input.description");
    this.field.observe('focus', function() { this.field.focussed = true;  }.bindAsEventListener(this));
    this.field.observe('blur',  function() { this.field.focussed = false; }.bindAsEventListener(this));

    this.field_wrapper      = this.form.down(".field_wrapper");
    this.contact_id         = this.form.down("#entry_contact_id");

    this.timer              = this.form.down("#timer");
    this.time_display       = this.timer.down("span.time");

    this.flags              = this.form.down('#log_bar_flags');

    this.log_at = false;
    this.init_timer();

    this.init_description_field();

    this.form.observe("submit", this.submit.bindAsEventListener(this));

    this.field.observe("blur", this.field_blur.bindAsEventListener(this));


    this.init_dirty_watcher();
    this.init_tasks();

  },

  init_tasks : function() {
    this.tasks = [];

    this.form.select('input.entry_task_name').each(function(e) {
      this.set_task({ short_code : e.value });
    }.bind(this));
  },

  init_shortcuts : function() {
    ShortcutHandler.getInstance().addShortcut(ShortcutHandler.KEYS.SHIFT_EIGHT, {
      workOnInput : function() { return window.entry_log_form.field; }
    }, function(e) {

      window.entry_log_form.toggle_timer();

      e.stop();
      return false;

    }.bind(this));

    ShortcutHandler.getInstance().addShortcut(ShortcutHandler.KEYS.SHIFT_ONE,{
      workOnInput : function() { return window.entry_log_form.field; },
      handleIf      : function() {
        return !window.entry_log_form.field.focussed || (window.entry_log_form.field.value.length == 0);
      }.bind(this)
    }, function(e) {

      try {
        window.entry_log_form.field.focus();
      } catch (e) { }

      e.stop();
      return false;

    }.bind(this));

    ShortcutHandler.getInstance().addShortcut(ShortcutHandler.KEYS.SHIFT_DOT, {
      workOnInput : function() { return window.entry_log_form.field; }
    }, function(e) {
      window.entry_log_form.submit();

      e.stop();
      return false;

    }.bind(this));
  },

  init_timer : function() {

    this.timer_active = eval($F(this.form.down("#timer_active")));
    this.current_time = parseInt($F(this.form.down("#duration"))) || 0;
    this.display_current_time();

    this.timer.observe("click", this.toggle_timer_click.bindAsEventListener(this));
    this.timer.observe("dblclick", this.toggle_edit_timer.bindAsEventListener(this));
    if (this.timer_active == true) this.start_timer();
    else this.stop_timer();

    this.time_display.observe('focus', this.edit_timer_focus.bindAsEventListener(this))
    this.time_display.observe('blur', this.stop_edit_timer.bindAsEventListener(this))
    this.time_display.observe('keypress', this.edit_timer_check_input.bindAsEventListener(this))
    this.time_display.observe('selectstart', this.edit_timer_focus.bindAsEventListener(this))


  },

  init_dirty_watcher : function() {
    new PeriodicalExecuter(function(pe) {

      if (this.dirty) {
        this.send_form();
      }

    }.bind(this), 1);

    this.dirty = false;
  },

  init_description_field : function() {

    if (this.field.value.strip() == "@") this.field.value = "";

    this.field.observe("blur", this.parse_time_event.bindAsEventListener(this));
    this.field.observe("blur", this.parse_log_at_event.bindAsEventListener(this));

    this.field.observe("keyup", this.field_keyup.bindAsEventListener(this));
    this.field.observe("keydown", this.field_keydown.bindAsEventListener(this));


    if (!$F(this.contact_id).blank()) {

      var contact_id = parseInt($F(this.contact_id));

      this.set_contact(this.contacts.detect(function(c) { return c.id == contact_id }));

    }

    this.contact_tag.observe("click", function() { this.field.focus(); }.bind(this));

    this.log_at_display = $('log_at_time');
    this.log_at_display.down("a.cancel").observe("click", this.cancel_log_at_time.bindAsEventListener(this));

    if (!this.log_at_display.down("span").innerHTML.blank()) this.log_at_display.show();

    try {
       this.field.enable().focus();
    } catch (e) {
    }

    this.field.name = "entry[description]";


  },

  out_of_date : function(force) {

    this.logged = true;

    if (!force) {

      if (this.field.value == "" && this.current_time < 5 && this.tasks.size() == 0) {
        return;
      }

    }

    window.log_bar_refreshed_modal.show();

    $('logged_notification_success').hide();
    $('logged_notification_old').show();

    setTimeout(function() {
      $('logged_notification_old').hide();
      $('logged_notification_success').show();
    }, 4000)
  },

  show_timer : function() {
    if (this.timer.hasClassName("off")) {
      this.timer.removeClassName("off");
      this.timer.addClassName("paused");
    }
  },

  start_timer : function() {

    this.tick = new PeriodicalExecuter(this.do_tick.bind(this), 1);

    this.timer.addClassName("on");
    this.timer.removeClassName("off");
    this.timer.removeClassName("paused");

    if (!document.title.startsWith("* ")) {
      document.title = "* " + document.title;
    }

  },


  stop_timer : function() {

    if (this.tick) this.tick.stop();

    this.timer.addClassName("off");
    if (this.current_time > 0)  this.timer.addClassName("paused");
    else this.timer.removeClassName("paused");

    this.timer.removeClassName("on");

    if (document.title.startsWith("* ")) {
      document.title = document.title.substring(2, document.title.length);
    }

  },

  toggle_timer_click : function(event) {

    setTimeout(function() { this.toggle_timer() }.bind(this), 200)

    if (event) {
      event.stop()
      return false
    }

  },

  toggle_timer : function(event) {

    if (this.timer_editing) return

      this.timer_active = !this.timer_active;

      (this.timer_active == true) ? this.start_timer() : this.stop_timer();

      this.send_form({ "timer_state" : this.timer_active });

      this.display_current_time();

      if (event) {
        alert(event)
        event.stop();
        return false;
      }

  },

  toggle_edit_timer : function(event) {
    this.timer_editing = true

    if (this.timer_active) {
      this.stop_timer()

      this.send_form({ "timer_state" : false });
    }

    this.timer_active = false

    this.display_current_time()

    this.timer.addClassName("selected")
    this.timer.addClassName("paused")


    this.time_display.contentEditable = true

    this.time_display.focus()
  },

  edit_timer_check_input : function(e) {
    var validChars = [
      48, 49, 50, 51, 52, 53, 54, 55, 56, 57, // numbers
      45, // -
      58, // :
      46 // .
    ];
    var validKeys = [
      46, // del
      8, // backspace
      37, 38, 39, 40 // arrow keys
    ]

    if (!validChars.include(e.charCode) && !validKeys.include(e.keyCode) && !validChars.include(e.keyCode)) {

      e.stop()

      if (e.keyCode == 13) { //enter
        this.stop_edit_timer()
      }

      return false
    }
  },

  edit_timer_focus : function(e) {
    window.setTimeout(function() {

        var sel, range;
        if (window.getSelection && document.createRange) {
            range = document.createRange();
            range.selectNodeContents(this.time_display);
            range.collapse(true);
            sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (document.body.createTextRange) {
            range = document.body.createTextRange();
            range.moveToElementText(this.time_display);
            range.collapse(true);
            range.select();
        }
    }.bind(this), 1);
  },

  stop_edit_timer : function(event) {
    if (!this.timer_editing) return

    var time_regex = /^([0-9]{1,3}(\.[0-9]{1,6})?)([:\-]([0-9]{1,2}))?$/
    var time = this.time_display.innerHTML.strip();
    var matches, changed = false

    if (matches = time_regex.exec(time) ) {
      var hours = parseFloat(matches[1].replace(/^0+/, "")),
          mins  = parseInt((matches[4]||"").replace(/^0+/, ""));

      this.current_time = parseInt( (hours||0) * 60 * 60 + (mins||0) * 60 )

      this.send_form( { "entry[duration]" : this.current_time } )

      changed = true
    } else if (time == "") {
      this.current_time = 0;

      this.send_form({ "reset_timer" : true })
    }

    if (this.current_time == 0) {
      this.timer.removeClassName("paused");
    }

    this.time_display.contentEditable = false
    this.timer_editing = false

    this.timer.removeClassName("selected")

    this.display_current_time()

    try {
      this.field.focus();
    } catch(e) {}
  },

  do_tick : function() {

      this.current_time++;
      this.display_current_time();

      return;

  },

  display_current_time : function() {

    var hours = Math.floor(this.current_time / 3600);

    var mins  = Math.floor((this.current_time % 3600) / 60);

    var seconds = Math.floor((this.current_time % 60));

    if (hours < 10) hours = "0" + hours;
    if (mins  < 10) mins  = "0" + mins;
    if (seconds < 10) seconds = "0" + seconds;

    var display = hours + ":" + mins;

    if (this.timer.hasClassName("on")) {
      display += "<sup>:" + seconds + "</sup>";
    }

    this.time_display.innerHTML = display;
  },

  submit : function(event) {

    if (!this.autocomplete_on && !this.taskcomplete_on) {

      this.parse_time(true); // True doesn't send redundant request (and request that *could* get re-ordered intransit with the comit request)
      this.parse_log_at(true);

      if (!(this.contact_id.value.blank() && this.field.value.blank() && !this.current_time && !this.log_at && $F("redock_entry_id").blank())) {
        var parameters = { "log" : true }

        if (this.explicit_duration) {
          parameters["entry[additional_duration]"] = this.explicit_duration;
          this.explicit_duration = 0;
        }

        parameters["entry[log_at]"] = (this.log_at) ? this.log_at.toUTCString() : null;

        this.send_form(parameters);

        this.cancel_log_at_time();

        this.logged = true;

      } else {

        this.field.focus();

      }

    }

    if (event) {
      event.stop();
      return false;
    }

  },

  pending_dirty : function() {
    if (this.pending_dirty_timeout) {
      this.pending_dirty_timeout.stop();
    }

    var time = new Date();

    this.pending_dirty_timeout = new PeriodicalExecuter(function(pe) {

      this.dirty = true;
      pe.stop();

    }.bind(this), 0.6);


    if (this.i == undefined) this.i = 0;
    this.i++;

    this.pending_dirty_timeout.sequence = this.i;

  },

  setPendingRequest : function(val) {
    window.onbeforeunload =  !val ? null : function() {
      return "We're still saving your log bar details. Should be done in just a tick!";
    };
  },

  send_form : function(parameters) {

    this.dirty = false;

    if (this.logged) {

      return;
    }

    this.setPendingRequest(true);


    if (this.pending_dirty_timeout) this.pending_dirty_timeout.stop();

    if (!parameters) parameters = {};

    parameters["current_tab"] = window.current_tab


    if (this.tasks.size() == 0) {
      Object.extend(parameters, { "entry[task_names]" : null })
    } else {
      Object.extend(parameters, { "entry[task_names][]" : this.tasks.pluck('short_code') });
    }

    options = {

      method     : "post",
      parameters : Object.extend(this.form.serialize(true), parameters)

    };


    if (parameters && parameters.log) {
      Object.extend(options, this.loading_display());
    } else {
      Object.extend(options, {
        onComplete : function() {
          this.setPendingRequest(false)
        }.bind(this)
      });
    }

    new Ajax.Request(this.form.action, options);

  },

  loading_display : function() {

     return { onLoading  : function() {
                              this.form.down(".loading").show();
                              this.form.down("button").addClassName("on");
                            }.bind(this),

              onComplete : function() {
                              this.setPendingRequest(false);
                              this.form.down(".loading").hide();
                              this.form.down("button").removeClassName("on");
                            }.bind(this) };

  },

  focus_field : function() {
    try {
      this.field.focus();
    } catch (e) { }
  },

  field_keydown : function(e) {
    if (e.keyCode == 9) {
      if (this.autocomplete_on) {
        this.set_contact(this.selected_contact);

        e.stop();
        return false;
      } else if (this.taskcomplete_on) {
        this.set_task(this.selected_task);

        e.stop();
        return false;
      } else {
        var changed = false;
        changed = this.parse_time() || changed;
        changed = this.parse_log_at() || changed;

        if (changed) {
          e.stop();
          return false;
        }
      }
    }
  },

  field_keyup : function(event) {

    if (event.keyCode == Event.KEY_BACKSPACE && this.field.getSelection().start == 0) {

     if (!this.contact_id.value.blank()) {

        if (!this.contact_tag.hasClassName("selected")) {
          this.contact_tag.addClassName("selected");
        } else {

          this.contact_id.value = "";
          this.contact_tag.removeClassName("selected");
          this.contact_tag.hide();

          this.field.removeClassName("with_contact_tag");

          this.send_form();

        }
      } else if ((this.current_time && this.current_time != 0) || this.timer_active || this.timer.hasClassName("paused")) {

        if (!this.timer.hasClassName("selected")) {
          this.timer.addClassName("selected");
        } else {

          this.current_time = 0;
          this.stop_timer();
          this.timer_active = false;
          this.timer.removeClassName("selected");

          this.send_form({ "reset_timer" : true })

        }

      }

    } else {

      this.contact_tag.removeClassName("selected");
      this.timer.removeClassName("selected");

    }

    if (!this.taskcomplete_on && (
         (event.keyCode == Event.KEY_AT_SYMBOL && event.shiftKey)
         || $F(this.field).substr(this.field.getSelection().start-1, 1) == "@"
         ) )  {
      this.enable_autocomplete();
    }
    if (!this.autocomplete_on && (
      (event.keyCode == 51 && event.shiftKey)
      || $F(this.field).substr(this.field.getSelection().start-1, 1) == "#"
      )
    ) {
      this.enable_taskcomplete();
    }

    if (this.autocomplete_on || this.taskcomplete_on) {

      if (event.keyCode == Event.KEY_RETURN || event.keyCode == Event.KEY_SPACEBAR) {


        if (this.autocomplete_on) {
          this.set_contact(this.selected_contact);
        } else {
          this.set_task(this.selected_task);
        }

        event.stop();
        return false;

      }

      if (event.keyCode == Event.KEY_UP || event.keyCode == Event.KEY_DOWN) {

        var index;
        if (this.autocomplete_on) {
          index = this.contact_options.indexOf(this.selected_contact);
        } else if (this.taskcomplete_on) {
          index = this.task_options.indexOf(this.selected_task);
        }

        (event.keyCode == Event.KEY_UP) ? index-- : index++;
        if (this.autocomplete_on) {
          if (index < 0) index = this.contact_options.length - 1;
          if (index >= this.contact_options.length) index = 0;

          this.set_selected_contact(this.contact_options[index]);
        } else if (this.taskcomplete_on) {
          if (index < 0) index = this.task_options.length - 1;
          if (index >= this.task_options.length) index = 0;

          this.set_selected_task(this.task_options[index]);
        }


        this.field.getSelection().start = $F(this.field).length;

        event.stop();
        return false;

      }

      if (this.field.getSelection().start < this.autocomplete_start) {
        if (this.autocomplete_on) this.disable_autocomplete();
        else if (this.taskcomplete_on) this.disable_taskcomplete();

      } else {

        var field_delta = $F(this.field).length - this.last_field_length;
        this.last_field_length = $F(this.field).length;

        this.at_tag_text = $F(this.field).substr(this.autocomplete_start, this.at_tag_text.length + field_delta);

        if (this.autocomplete_on)      this.display_drop_down();
        else if (this.taskcomplete_on) this.display_task_down();

      }

    } else {

      if ( ! [Event.KEY_SHIFT, Event.KEY_RETURN].include(event.keyCode) ) {
        this.pending_dirty();
      }
    }

    if ($F(this.field).empty() && $F(this.contact_id).empty()) {

      this.field_wrapper.addClassName("empty");

    } else {

      this.field_wrapper.removeClassName("empty");

    }

  },

  parse_contact : function() {

    var contact_regexp = new RegExp(/@([-_0-9a-z]+)/ig);

    if (!this.contact_id.value) {

      if (matches = contact_regexp.exec(this.field.value)) {

        var contacts = this.contacts.select(function(contact) {
          return contact.short_code.toLowerCase() == matches[1].toLowerCase();
        });

        if (contacts.size() > 0) {
          this.field.value = this.field.value.replace(matches[0], "");
          this.set_contact(contacts[0]);
          return true;
        }
      }

    }
  },

  parse_tasks : function() {

    var tasks_regexp = new RegExp(/#([0-9a-z_-]+)\s?/);
    var searched_tasks = [];

    var fvalue = this.field.value;

    while (matches = tasks_regexp.exec(fvalue)) {

      fvalue = fvalue.replace(matches[0], "")

      var task = this.all_tasks.detect(function(t) {
        return t.short_code == matches[1];
      });

      if (task) {

        this.set_task(task)
        this.field.value = this.field.value.replace(matches[0], "")

      }
    }

  },



  field_blur : function(event) {
    if (this.autocomplete_on) {


    }

    this.parse_contact();
   if (!this.taskcomplete_on)
    this.parse_tasks();
  },

  enable_autocomplete : function() {

    var value_before_cursor = $F(this.field).substring(0,  this.field.getSelection().start);

    var frame_left = this.field.cumulativeOffset().left;
    var leftval = (frame_left + get_render_length(value_before_cursor, this.field));

    if ((leftval + this.contact_dropdown.getWidth()) > frame_left + this.field.getWidth()) leftval = frame_left + this.field.getWidth() - this.contact_dropdown.getWidth();
    else if (leftval < frame_left) leftval = frame_left;

    this.contact_dropdown.setStyle({

      left : leftval + "px",
      top  : (this.field.cumulativeOffset().top  + this.field.getHeight() - 1) + "px"

    });

    this.at_tag_text        = "";
    this.autocomplete_start = this.field.getSelection().start;
    this.last_field_length = $F(this.field).length;

    this.autocomplete_on = true;

  },

  enable_taskcomplete : function() {
    if (this.autocomplete_on) this.disable_autocomplete;

    var value_before_cursor = $F(this.field).substring(0,  this.field.getSelection().start);

    var frame_left = this.field.cumulativeOffset().left;
    var leftval = (frame_left + get_render_length(value_before_cursor, this.field));

    if ((leftval + this.task_dropdown.getWidth()) > frame_left + this.field.getWidth()) leftval = frame_left + this.field.getWidth() - this.task_dropdown.getWidth();
    else if (leftval < frame_left) leftval = frame_left;

    this.task_dropdown.setStyle({

      left : leftval + "px",
      top  : (this.field.cumulativeOffset().top  + this.field.getHeight() - 1) + "px"

    });

    this.at_tag_text        = "";
    this.autocomplete_start = this.field.getSelection().start;
    this.last_field_length  = $F(this.field).length;

    this.taskcomplete_on = true;

  },

  disable_autocomplete : function() {
    window.document.stopObserving("click");

    this.contact_dropdown.hide();
    this.autocomplete_on = false;



  },

  disable_taskcomplete : function() {
    window.document.stopObserving("click");

    this.task_dropdown.hide();
    this.taskcomplete_on = false;

  },

  display_drop_down : function() {

    this.contact_dropdown.show();

    $$("#contact_dropdown li").invoke("stopObserving", "hover");
    this.contact_dropdown.innerHTML = "";


    var all_contacts = this.contacts.select(function(contact) {

      return this.at_tag_text.blank() || contact.short_code.toLowerCase().startsWith(this.at_tag_text.toLowerCase());

    }.bind(this));

    this.contact_options = all_contacts.slice(0, this.MAX_CONTACTS);

    this.contact_options.each(function(c) {

      var li =  new Element("li", { "id" : "contact_" + c.id }).update("@" + c.short_code);

      if (c.pinned) li.addClassName("pinned");

      this.contact_dropdown.insert({
        bottom : li
      });

      li.observe("click", function() {
        this.set_contact(c);
        this.focus_field();
      }.bind(this));

      li.observe("mouseover", function() { this.set_selected_contact(c); }.bind(this));

    }.bind(this));

    var manage_text = "";
    if (window.iframe_context) {
    } else if (this.admin_features) {
      manage_text = "<span class='right'><a href='#' class='icon add' title='Add Contact'>Add</a>";
      manage_text += "<a href='/contacts' class='icon edit' title='Manage Contacts'>Manage</a></span>";
    }

    if (all_contacts.size() > 0) {
      var count_text = "<span class='left'>Showing ";
      count_text += (all_contacts.size() > this.MAX_CONTACTS) ? this.MAX_CONTACTS : all_contacts.size();
      count_text += "/" + all_contacts.size() + "</span>";
    }
    else count_text = "<span class='left'>Nothing Found</span>";

    manage_text = count_text + manage_text;

    this.contact_dropdown.insert({ bottom : new Element("li").addClassName("manage").update(manage_text) });

    var add_link = this.contact_dropdown.down("li.manage a.add");
    if (add_link) add_link.observe("click", this.add_contact.bindAsEventListener(this));

    this.set_selected_contact(this.contact_options.first());

    window.document.observe("click", function(event) {

      if (!event.element().descendantOf(this.container)) {
        if (this.selected_contact)
          this.set_contact(this.selected_contact);
        else
          this.disable_autocomplete();
      }


    }.bind(this));


  },

  display_task_down : function() {

    this.task_dropdown.show();

    $$("#task_dropdown li").invoke("stopObserving", "hover");
    this.task_dropdown.innerHTML = "";

    if (this.at_tag_text.length > 20) {
      this.disable_taskcomplete();
      return;
    }

    var show_count = 0;
    var all_tasks = this.all_tasks.select(function(task) {

      if (this.tasks.pluck('short_code').include(task.short_code))
        return false;

      if (this.at_tag_text.blank()) {
        if (task.pinned || task.entries_count > 0 || show_count < 5) {
          show_count++;
          return true;
        }
      } else {
        return task.short_code.toLowerCase().startsWith(this.at_tag_text.toLowerCase());
      }

      return false;

    }.bind(this));

    this.task_options = all_tasks.slice(0, this.MAX_CONTACTS);

    this.task_options.each(function(t) {

      var li =  new Element("li", { "id" : "task_" + t.short_code }).update("#" + t.short_code);

      if (t.pinned) li.addClassName("pinned");

      this.task_dropdown.insert({
        bottom : li
      });

      li.observe("click", function() {
        this.set_task(t);
        this.focus_field();
      }.bind(this));

      li.observe("mouseover", function() { this.set_selected_task(t); }.bind(this));

    }.bind(this));

    var task_name = this.at_tag_text.gsub("[^a-z0-9A-Z_-]", "");

    var manage_text = "";
    if (window.iframe_context) {
    } else if (this.admin_features) {
      manage_text = "<span class='right'><a href='#' class='icon add' title='Add Task'>Add</a>";
      manage_text += "<a href='/tasks' class='icon edit' title='Manage Tasks'>Manage</a></span>";
    }

    if (all_tasks.size() > 0) {
      var count_text = "<span class='left'>Showing ";
      count_text += (all_tasks.size() > this.MAX_CONTACTS) ? this.MAX_CONTACTS : all_tasks.size();
      count_text += "/" + all_tasks.size() + "</span>";
    }
    else if (task_name == "") {
      count_text = "<span class='left'>Start typing a task</span>";
    }
    else {
      count_text = "<span class='left'>No Task Found</span>";
    }

    manage_text = count_text + manage_text;

    this.task_dropdown.insert({ bottom : new Element("li").addClassName("manage").update(manage_text) });

    var add_link = this.task_dropdown.down("li.manage a.add");
    if (add_link) add_link.observe("click", this.show_add_task_modal.bindAsEventListener(this));

    this.set_selected_task(this.task_options.first());

    window.document.observe("click", function(event) {

      if (!event.element().descendantOf(this.container)) {
        if (this.selected_task)
          this.set_task(this.selected_task);
        else
          this.disable_taskcomplete();
      }


    }.bind(this));


  },

  set_contact : function(contact) {
    if (this.at_tag_text == "") {
      this.field.value += contact.short_code;
      this.at_tag_text  = contact.short_code;
    }

    this.disable_autocomplete();

    if (contact) {

      this.field.value = this.field.value.gsub("@" + this.at_tag_text + " ?", "");

      this.field.addClassName("with_contact_tag");

      this.contact_tag.show();
      this.contact_tag.update(new Element("span").update("@" + contact.short_code));
      this.contact_id.value = contact.id;

      try {
        this.field.focus();
      } catch(e) {
      }

      this.dirty = true; // save next time

      return true;
    }

    return false;

  },

  set_task : function(task) {

    this.disable_taskcomplete();

    if (task) {

        if (this.tasks.map(this.getTaskName).include(task.short_code)) {
          return false;
        }

        if (task.new_task) {
          this.all_tasks.push({ short_code : task.short_code })
        }

        this.field.value = this.field.value.gsub("#" + this.at_tag_text + " ?", "");

        this.tasks.push(task);

        var flag = new Element("div").addClassName("flag");
        var link = new Element("a").addClassName("cancel");
        link.observe('click', function(e) {
          this.remove_task(task);
          e.element().up().remove();

          e.stop();
          return false;
        }.bindAsEventListener(this));


        flag.update("<span>#" + task.short_code + "</span>").insert( { bottom : link });
        link.update("X");
        this.flags.insert( { bottom : flag });


        try {
          this.field.focus();
        } catch(e) {
        }

        this.dirty = true; // save next time

        return true;

    }

    return false;
  },

  remove_task : function(task) {
    this.tasks = this.tasks.reject(function(t) {
      return t.short_code == task.short_code;
    });
    this.dirty = true;
  },


  show_add_task_modal : function(e) {
    TaskAdder.entry_log_form = this;
    TaskAdder.add(this.at_tag_text);

    this.disable_taskcomplete();
    e.stop();
    return false;
  },

  add_contact : function(contact) {
    ContactAdder.entry_log_form = this;
    ContactAdder.add(this.at_tag_text);

    this.disable_autocomplete();
  },

  getTaskName : function(task) {
    return task.short_code;
  },

  /* choose the highlighted contact */

  set_selected_contact : function(contact) {

    this.selected_contact = contact;

    if (contact) {

      this.contact_dropdown.select("li").invoke("removeClassName", "selected");
      this.contact_dropdown.down("li#contact_" + contact.id).addClassName("selected");

    }

  },

  set_selected_task : function(task) {
    this.selected_task = task;

    if (task) {
      this.task_dropdown.select("li").invoke("removeClassName", "selected");
      this.task_dropdown.down("li#task_" + task.short_code).addClassName("selected");
    }
  },

  parse_time_event : function() {
    this.parse_time(false);
  },

  parse_time : function(send_later) {
    var matches;
    var f = $F(this.field);
    var match_re = /(?:for )?([0-9\.]{1,10})(?:\s)?(hrs?|hours?|minutes?|mins?|days?)/ig;

    var added_time = 0;

    while (matches = new RegExp(match_re).exec(f) ) {
      var whole_string  = matches[0];
      var number_string = matches[1];


      var number       = parseFloat(number_string);
      var time_type    = matches[2].toLowerCase();

      var time_in_seconds = 0;
      if (time_type.startsWith("h")) time_in_seconds = 60 * 60;     // hours
      if (time_type.startsWith("d")) time_in_seconds = 8 * 60 * 60; // days
      if (time_type.startsWith("m")) time_in_seconds = 60;          // minutes

      added_time += time_in_seconds * number;

      f = f.replace(whole_string, "");
    }

    this.field.value = f;

    if (added_time == 0)
      return;

    this.current_time += added_time;

    if (send_later) {
      this.explicit_duration = added_time;
    } else {
      this.send_form( { "entry[additional_duration]" : added_time } );
    }

    this.show_timer();
    this.display_current_time();

    return true;


  },

  parse_log_at_event : function() {
    this.parse_log_at(false);
  },

  parse_log_at : function(send_later) {
    var matches, f = $F(this.field);
    var logTime = new Date();

    var yday_re     = new RegExp(/\(yesterday\)/i);
    var last_re     = new RegExp(/\((last )?((mon|tue|wed|thu|fri|sat|sun)[^\)]*?)\)/i);
    var mon_day_re  = new RegExp(/\((([0-9]{1,2})(th|rd|nd)?\s)?((jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[^\s]*?)(\s([0-9]{1,2})(th|rd|nd)?)?\)/i);
    var explicit_re = new RegExp(/\(([0-9]{1,2})[-\/]([0-9]{1,2})([-\/]([0-9]{2}|[0-9]{4}))?\)/i);


    if (matches = yday_re.exec(f)) { // Match (yesterday)

      logTime.setDate(logTime.getDate() - 1);

    } else if (matches = last_re.exec(f)) { // Match (last friday)

      var currentWeekday = logTime.getDay();
      var setWeekday     = Date.WEEKDAYS.strIndexOfIncludes(matches[2], true);

      if (setWeekday == -1)
        return;

      if (currentWeekday > setWeekday) {
        logTime.setDate(logTime.getDate() - (currentWeekday-setWeekday));
      } else if (currentWeekday == setWeekday) {
        logTime.setDate(logTime.getDate() - 7);
      } else {
        logTime.setDate(logTime.getDate() - 7 + (setWeekday-currentWeekday));
      }

    } else if (matches = mon_day_re.exec(f)) { // Match (Oct 21st)

      var month = Date.MONTHS.strIndexOfIncludes(matches[4], true);
      var day;

      if (matches[1])
        day = matches[2];
      else if (matches[6])
        day = matches[7];
      else
        return;

      if (month == -1 || day > 31 || day < 1)
        return;

      if (month > logTime.getMonth() || month == logTime.getMonth() && day > logTime.getDate()) {
        logTime.setFullYear(logTime.getFullYear()-1)
      }

      logTime.setMonth(month);
      logTime.setDate(day);

    } else if (matches = explicit_re.exec(f)) { // Match (17/11/1999)

      var day   = matches[1];
      var month = matches[2];
      var year  = matches[4];

      if (day < 1 || day > 31 || month < 1 || month > 12)
        return;

      logTime.setMonth(month-1);
      logTime.setDate(day);
      if (year) {
        year = parseInt(year.gsub(/^0+/, ""));

        if (year < 100) {
          year = 2000 + year;

          if (year > new Date().getFullYear()) {
            year -= 100;
          }
        } else {
          if (year > new Date().getFullYear()) {
            year = new Date().getFullYear();
          }
        }

        logTime.setYear(year);
      }

    } else {
      return;
    }

    if (logTime > new Date())
      return;

    this.log_at = logTime;
    if (send_later) {
    } else {
      this.send_form( { "entry[formatted_log_at]" : logTime.toUTCString() });
    }

    this.field.value = this.field.value.replace(matches[0], "");

    var caption = Date.WEEKDAYS[this.log_at.getDay()].substring(0, 3) + ", " + this.log_at.getDate() + " " + Date.MONTHS[this.log_at.getMonth()];

    if (this.log_at.getFullYear() != new Date().getFullYear()) {
      caption += ", " + this.log_at.getFullYear();
    }

    this.log_at_display.down("span").update(caption);
    this.log_at_display.show();

    return true;

  },

  cancel_log_at_time : function(event) {

    this.log_at = null;
    this.log_at_display.hide();

    if (event) {
      this.dirty = true; // save next time

      this.send_form( { "entry[log_at]" : null });

      return false;
    }
  }


});

EntryManager = Class.create({
  initialize : function(container) {
    this.container   = $(container);
    this.just_now    = $("just_now");
    this.blank_state = $("entry_blank_slate");
    this.groups      = this.container.select(".entry_group");
  },

  notify_entry_deleted : function() {
    var are_entries = false;
    var goals = $('goals');

    if (!this.just_now.down(".entry")) {
      this.just_now.hide();
    } else {
      are_entries = true;
    }

    this.groups.each(function(group) {
      if (!group.down(".entry")) {
        group.hide();
      } else {
        are_entries = true;
      }
    });

    if (!are_entries) {
      this.blank_state.show();
      if (goals) {
        goals.hide()
      }
    } else {
      if (goals) {
        goals.show()
      }
    }
  }


});

EditableEntry = Class.create({

  initialize : function(entry_container) {

    this.container = $(entry_container);

    Calendar.setup({
      dateField      : this.container.down("input.logged_at"),
      dateFormat     : '%a, %b %e %Y',
      markFuture     : true,
      selectHandler  : function(calendar) {
        if (calendar.date > new Date()) {
          calendar.shouldClose = false;
          return false;
        }

        Calendar.defaultSelectHandler(calendar);

        this.changed = true;
      }.bind(this)
    });

    this.trigger = this.container.down(".edit_trigger");
    this.edit    = this.container.down(".edit");

    this.trigger.observe("click", this.show_edit.bindAsEventListener(this));

    this.changed = false;
    this.loading = false;

    this.init_form();

  },

  init_form: function() {

    this.form = this.container.down("form");
    this.form.observe("submit", this.submit.bindAsEventListener(this));

    this.save_button   = this.form.down("a.save");
    this.delete_button = this.form.down("a.delete");
    this.redock_button = this.form.down("a.redock");

    this.save_button.observe("click", this.submit.bindAsEventListener(this));
    this.delete_button.observe("click", this.delete_entry.bindAsEventListener(this));

    if (this.redock_button) {
      this.redock_button.observe("click", this.redock.bindAsEventListener(this));
    }

    this.container.select("input[type=text]").each(function(input) {

      input.observe("keydown", function(event) {
        if (event.keyCode == Event.KEY_RETURN) this.submit(event);
      }.bind(this));

    }.bind(this));

    this.container.select("input,select").each(function(input) {

      input.observe("change", function() {
        this.changed = true;
      }.bind(this));

    }.bind(this));

  },

  submit: function(event, redock) {

    var params = {}

    params["current_tab"] = window.current_tab

    if (redock) {
      params["redock"] = true
    }



    new Ajax.Request(this.form.action, {

      parameters   : Object.extend(this.form.serialize(true), params),
      method       : "post",
      asynchronous : true,
      evalScripts  : true,
      onLoading    : function() { this.container.select(".save,.delete,.redock").invoke("hide"); this.container.down(".loader-small").show(); this.loading = true; }.bind(this),
      onComplete   : function() { this.changed = false; this.loading = false; }.bind(this)
    });

    if (event) event.stop();
    return false;

  },

  delete_entry: function(event) {

    if (confirm("Are you sure you want to remove this entry?")) {

      new Ajax.Request(this.delete_button.href, {

        parameters : { "_method" : "delete", "authenticity_token" : this.form.down("input[name=authenticity_token]") },
        method     : "post"

      });

      new Effect.Fade(this.container);

    }

    if (event) event.stop();
    return false;

  },

  redock : function(event) {

    this.submit(event, true)

  },

  show_edit: function() {

    if (e = EditableEntry.active_entry) {
      e.hide_edit();
    }

    EditableEntry.active_entry = this;

    this.edit.show();
    this.trigger.hide();

    window.document.observe("click", this.hide_edit.bindAsEventListener(this));

  },

  hide_edit: function(event) {

    if (this.loading) return;

    if (event) {
      var clicked_on = event.element();

      if (clicked_on.descendantOf(this.container) || clicked_on.up(".calendar")) {
        return;
      }
    }

    if (this.changed) {
      this.submit();
    } else {
      this.edit.hide();
      this.trigger.show();
    }

    window.document.stopObserving("click");

  }

});

InlineValidationForm = Class.create({

  initialize: function(form_id) {

    this.form = $(form_id);

    this.form.select("input,select,textarea").each(function(f) {
      f.observe("blur", function() { this.validate(f) }.bind(this));
    }.bind(this));

    this.validations = $H({});

  },

  validates_presence: function(field, message) {

    this._set_field_validation(field,
      { type : "presence", message : (message || "This field is required") });

  },

  validates_format: function(field, format, message) {

    this._set_field_validation(field,
        { type : "format", format : format, message : (message || "This field doesn't match the required format") });

  },

  validates_length: function(field, length, message) {

    this._set_field_validation(field,
        { type : "length", required_length : length, message : (message || "This field is too short") });

  },

  validates_uniqueness: function(field, error_value, message) {

    this._set_field_validation(field,
        { type : "uniqueness", error_value : error_value, message : (message || "That value has already been taken") });

  },

  validates_must_be_checked: function(field, message) {

    this._set_field_validation(field,
        { type : "must_be_checked", message : (message || "This box must be checked") });

  },

  _set_field_validation: function(field, validation) {

    var field_validations = this.validations.get(field);
    if (!field_validations) field_validations = [];

    field_validations.push(validation);

    return this.validations.set(field, field_validations);

  },

  validate_all: function() {
    var valid = true;
    this.form.select("input,select,textarea").each(function(f) {
      valid = this.validate(f) !== false && valid;
    }.bind(this));
    return valid;
  },

  valid : function() {
    return this.validate_all();
  },

  validate: function(field) {
    validations = this.validations.get(field.id);
    if (!validations) return;


    field.removeClassName("withErrors");
    if (e = field.next(".errorExplanation")) e.remove();

    var valid = true;

    validations.each(function(v) {

      if (v.type == "presence" && $F(field).blank()) {

        this.add_error_message(field, v.message);
        valid = false;

      } else if (v.type == "format" && !v.format.test($F(field))) {

        this.add_error_message(field, v.message);
        valid = false;

      } else if (v.type == "length" && $F(field).length < v.required_length) {

        this.add_error_message(field, v.message);
        valid = false;

      } else if (v.type == "uniqueness" && $F(field) == v.error_value) {

        this.add_error_message(field, v.message);
        valid = false;

      } else if (v.type == "must_be_checked" && !field.checked) {

        this.add_error_message(field, v.message);
        valid = false;

      }

    }.bind(this));

    if (!valid) return false;

    return true;

  },

  add_error_message: function(field, message) {

    if (field.hasClassName("withErrors")) return;

    field.insert({ after :
      new Element("span", { "class" : "errorExplanation"}).update(message)
    });

    field.addClassName("withErrors");

  }



});
SelectableEntrySet = Class.create({

  collapsed_arrow : "&#9656;",
  expanded_arrow : "&#9662;",

  all_selected : false,

  initialize: function(container_id) {

    this.container          = $(container_id);
    this.toggle_el          = $(container_id + "_toggle");
    this.toggle_el_span     = this.toggle_el.down("span");
    this.select_all_el      = $(container_id + "_toggle_all");

    this.entries       = this.container.select(".selectable");

    this.toggle_el_span.insert({ top : new Element("div").addClassName("arrow").update(this.collapsed_arrow) });

    this.arrow         = this.toggle_el.down(".arrow");

    this.toggle_el.observe("click", this.toggle.bindAsEventListener(this));

    this.select_all_el.observe("mouseup", function(e) {

      if (!this.select_all_el.checked) {
        this.select_all();
      } else {
        this.unselect_all();
      }

      e.stop();
      return false;

    }.bindAsEventListener(this));


    this.entries.each(function(row){

      row.observe("mouseup", function(e) {

        this.click(e, row);

      }.bindAsEventListener(this));

    }.bind(this));

  },

  toggle: function(evt) {

    if (this.container.visible()) {
      this.arrow.update(this.collapsed_arrow);
      this.container.hide();

    } else {
      this.arrow.update(this.expanded_arrow);
      this.container.show();

    }

    return this;

  },

  click: function(event, row) {

    if (event.element().tagName == "INPUT") {

      var cbox = event.element();

    } else {

      var cbox = row.down("input[type=checkbox]");
      cbox.checked = !cbox.checked;

    }


    if (this.container.select(".selectable input[type=checkbox]").any(function(c) { return !c.checked; })) {

      this.select_all_el.checked = false;

    } else {

      this.select_all_el.checked = true;

    }

    event.stop();
    return false;

  },

  mousedown: function(event) {

    this.entries.each(function(e) {
      e.observe("mouseover", this.select_entry.bindAsEventListener(this));
    }.bind(this));

    this.select_entry(event)

    event.stop();
    return false;

  },

  mouseup: function(event) {

    this.entries.each(function(e) {
      e.stopObserving("mouseover");
    }.bind(this));

  },

  select_entry: function(event) {

    var row = (event.element().hasClassName("selectable")) ? event.element() : event.element().up("div.selectable");
    var cbox = row.down("input[type=checkbox]");

    cbox.checked = !cbox.checked;

  },

  select_all: function() {

    this.container.select("input[type=checkbox]").each(function(c) {
      c.checked = true;
    });

  },

  unselect_all : function() {

    this.container.select("input[type=checkbox]").each(function(c) {
      c.checked = false;
    });
  }

});

LineItemRow = Class.create({

  initialize: function(line_item, container, template, where) {
    this.line_item = line_item;
    this.line_item.total_dollars = parseFloat(this.line_item.total_dollars).to_currency({ unit : "" });

    if (typeof where == 'undefined') {
      where = { bottom : container }
    }

    if (where.top) {
      where.top.insert({ top : template.evaluate(this.line_item) }) ;
    } else if (where.bottom) {
      where.bottom.insert({ bottom : template.evaluate(this.line_item) });
    } else if (where.before) {
      where.before.insert({ before : template.evaluate(this.line_item) });
    } else if (where.after) {
      where.after.insert({ after : template.evaluate(this.line_item) });
    }


    this.element = container.down("tr#line_item_" + line_item.id)

    this.total                  = this.element.down(".total");
    this.duration_field         = this.element.down("input[name=formatted_duration]");
    this.dollars_per_item_field = this.element.down("input[name=dollars_per_item]");

    this.parse_duration(line_item.duration);

    this.parse_dollars_per_item(line_item.per_item);

    this.account_selector = this.element.down("select[name=xero_account_id]");
    if (this.line_item.xero_account_id) {
      if (xa = this.account_selector.down("option[value=" + this.line_item.xero_account_id + "]"))
        xa.selected = true;
    }

    this.element.select("input[type=text], select").each(function(i) {
      i.observe("change", function() { this.update_line_item(i); }.bind(this));
    }.bind(this));

    this.checkbox = this.element.down("input[type=checkbox]");
    this.selected = this.checkbox.checked;

    this.checkbox.observe("click", function() {
      this.selected = this.checkbox.checked;
      this.element.toggleClassName("selected");
    }.bind(this));

    this.duration_field.observe("focus", function() {
      if ($F(this.duration_field) == "--:--") this.duration_field.value = "";
    }.bind(this));

    this.duration_field.observe("blur", function() {
      if ($F(this.duration_field).blank()) this.duration_field.value = "--:--";
    }.bind(this));

    this.element.down(".delete").observe("click", this.remove.bindAsEventListener(this));
  },

  update_line_item: function(field) {

    var property = field.name;
    var value    = $F(field);

    eval("this.line_item." + property + "= '" + value.gsub("'", "\\'") + "'");

    if (property == "xero_account_id" && value) this.account_selector.removeClassName("withErrors"); //valid account selected
    window.line_item_manager.hide_invalid_notification();


    this.update_total();
    this.save();

  },

  update_total: function() {

    this.line_item.duration = this.parse_duration();
    this.line_item.dollars_per_item = this.parse_dollars_per_item();

    this.line_item.total_dollars = (this.line_item.duration / 3600 * this.line_item.dollars_per_item).to_currency({ unit : "" });

		this.line_item.per_item  = parseInt(this.line_item.dollars_per_item * 100);

    this.total.update(this.line_item.total_dollars);

    window.line_item_manager.update_totals();

  },

  parse_duration: function(duration) {

    if (!duration) {

      if ($F(this.duration_field).match(/[0-9]{1,9}:[0-9]{1,2}/)) {

        var parts = $F(this.duration_field).split(":", 2);

        var hours = parseInt(parts[0].gsub(/[^0-9]/, ""), 10) || 0;
        var mins  = (parts[1]) ? (parseInt(parts[1].gsub(/[^0-9]/, ""), 10) || 0) : 0;

        duration = hours * 3600 + mins * 60;

      } else {

        duration = parseFloat($F(this.duration_field)) * 3600;

        if (isNaN(duration)) duration = 0;

      }

    }

    hours = (duration / 3600).floor();
    mins  = ((duration % 3600) / 60).floor();

    if (hours == 0 && mins == 0) {
      this.duration_field.value = "--:--";
    } else {
      this.duration_field.value = hours.toPaddedString(2) + ":" + mins.toPaddedString(2);
    }

    return duration;

  },

  parse_dollars_per_item: function(per_item) {

    var dollars_per_item;

    if (!per_item) {

      dollars_per_item = $F(this.dollars_per_item_field).gsub(/[^0-9\.]/, "");

    } else {

      dollars_per_item = per_item / 100;

    }

    this.dollars_per_item_field.value = parseFloat(dollars_per_item || 0).toFixed(2) || "0.00";

    return parseFloat($F(this.dollars_per_item_field)) || 0;

  },

  loading_indicator: function(on_or_off) {

    var method = (on_or_off) ? "show" : "hide";
    eval("this.element.down('.loader-small')." + method + "();");

  },

  save: function() {

    var method = (this.line_item.new_record) ? "post" : "put";
    var url    = this.url_for(this.line_item);
    delete this.line_item.new_record;

    window.line_item_manager.saving_start();
    new Ajax.Request(url, {

      method     : method,
      parameters : this.serialize("line_item"),
      onSuccess  : function(transport) {

        this.line_item.id = eval(transport.responseJSON).id;
        this.element.id = "line_item_" + this.line_item.id

        window.line_item_manager.saving_end();
      }.bind(this)

    });

    window.line_item_manager.line_items_customised = true;

  },

  serialize : function(name) {

    var params = {
      'id' : this.line_item.id,
      'description' : this.line_item.description,
      'xero_account_id' : this.line_item.xero_account_id,
      'per_item'    : this.line_item.per_item,
      'duration' : this.line_item.duration
    };

    if (name) {
      var serialized = {};
      for (var key in params) {
        serialized[name + "[" + key + "]"] = params[key];
      }
      params = serialized;
    }

    return params;
  },

  remove: function() {

    new Effect.Fade(this.element);

    if (!this.line_item.new_record) {

      new Ajax.Request(this.url_for(this.line_item), {
        method : "delete"
      });

    }

    this.line_item.deleted = true;
    window.line_item_manager.line_items_customised = true;

    window.line_item_manager.update_totals();

  },

  url_for: function(line_item) {

    if (line_item.new_record) {

      return LineItemRow.collection_url;

    } else {

      return LineItemRow.member_url.gsub(":id", line_item.id);

    }

  },

  focus : function() {
    this.element.down("input[name='description']").focus();
  }


});

LineItemManager = Class.create({

  initialize: function(container_id, template_content) {

    this.container = $(container_id);
    this.template = new Template(template_content);

    this.subtotal  = $("subtotal");
    this.tax_total = $("tax_total");
    this.total     = $("total");

    this.add_buttons = $$(".add_line_item");
    this.add_buttons.invoke("observe", "click", this.add_line_item.bindAsEventListener(this));

    this.merge_buttons = $$(".merge_line_item");
    this.merge_buttons.invoke("observe", "click", this.merge_line_items.bindAsEventListener(this));

    this.grouping_form = $$("form.edit_invoice").first();

    this.grouping_form.select("input[type=radio]").each(function(radio) {
      radio.observe("click", this.set_grouping.bindAsEventListener(this));
    }.bind(this));

    this.notification = $("invalid_accounts_notice");

    $("next_step").observe("click", this.validate.bindAsEventListener(this));

    $("save_all").observe("click", this.save_all.bindAsEventListener(this));

    $("previous").observe("click", function(event) {

      if (this.line_items_customised &&
            !confirm("Are you sure you want to re-select time entries? This will undo any changes you've made to your line items!")) {

        event.stop();
        return false;

      }

    }.bindAsEventListener(this));


    this.set_grouping();

  },

  set_grouping: function(event) {

    if (this.line_items_customised &&
          !confirm("Are you sure you want to change your invoice grouping? This will undo any changes you've made to your line items!")) {

      event.stop();
      return false;

    }

    this.container.select("tr.item").invoke("remove");

    this.grouping_request = new Ajax.Request(this.grouping_form.action, {

      parameters : this.grouping_form.serialize(),
      onSuccess  : function(transport) {

        this.set_line_items(transport.responseJSON);
        this.toggle_loading();
        this.grouping_form.enable();

        this.line_items_customised = false;

				this.update_totals();

      }.bind(this),

      onLoading : function() {
        this.toggle_loading();
        this.grouping_form.disable();
      }.bind(this)

    });

  },

  set_line_items: function(items) {
    this.items = items.collect(function(i) {
      return new LineItemRow(i, this.container, this.template, { before: this.container.down('tbody#line_items_bottom') });
    }.bind(this));

    this.reset_sortables();

  },

  reset_sortables : function() {
    Sortable.create(this.container.id, {
      tag: 'tbody',
      only: 'sortable_line_item',
      onUpdate: this.update_positions.bind(this)
    });
  },

  update_positions : function(list) {

    var ids = list.select("tr.item").collect(function(e) {
      return e.id;
    });

    ids = ids.reject(function(e) {
      var item = this.items.find(function(i) {
        return i.element.id == e;
      });

      return !item || item.line_item == null || item.line_item.new_record || item.line_item.deleted;

    }.bind(this));


    new Ajax.Request(this.update_positions_url, {
      method: 'post',
      parameters: { ids: [ids] }
    })

  },


  toggle_loading: function() {

    $("loading_row").toggle();

  },

  save_all: function() {

    var save_items = $H({});

    this.items.reject(function(i) { return i.line_item == null || i.line_item.deleted == true || i.line_item.new_record; }).each(function(i) {
      save_items = save_items.merge(i.serialize("line_items["+i.line_item.id+"]"));
    });

    this.saving_start();
    new Ajax.Request(this.update_multiple_line_items_url, {

      method     : 'post',
      parameters : save_items,

      onSuccess: function() {
        this.saving_end();
      }.bind(this)

    });


  },

  add_line_item: function(event) {

    this.line_items_customised = true;

    var li = new LineItemRow({ id : new Date().getTime(), new_record : true }, this.container, this.template, { before: this.container.down('tbody#line_items_bottom') });
    li.update_total();

    this.items.push(li);
    li.focus();

    this.reset_sortables();

    event.stop();
    return false;

  },

  merge_line_items: function() {

    var selected = this.items.findAll(function(i) {
      return i.selected && !(i.line_item.deleted);
    });

    if (selected.size() < 2) {
      alert("Please choose two or more line items to merge");
      return;
    }

    this.line_items_customised = true;

    var new_line_item = { id : new Date().getTime(), new_record : true };

    new_line_item.duration = selected.inject(0, function(total, lir) {
      return total + lir.line_item.duration;
    });

    new_line_item.description = selected.collect(function(lir) {
      return lir.line_item.description
    }).join(", ");

    new_line_item.date             = selected.first().line_item.date;
    new_line_item.invoice_id       = selected.first().line_item.invoice_id;
    new_line_item.xero_account_id  = selected.first().line_item.xero_account_id;
    new_line_item.per_item         = selected.first().line_item.per_item;

    var first_selected_prev = this.container.down(".selected").up().previous();
    if (first_selected_prev) {
      where = { after : first_selected_prev }; // After the first item [in the merge] previous sibling (in position of first item)
    } else {
      where = { after   : this.container.down('tbody#line_items_top') }; // Top of line items
    }

    this.remove_multiple_line_items(selected);

    var li = new LineItemRow(new_line_item, this.container, this.template, where);
    li.update_total();

    this.items.push(li);
    this.update_totals();
    li.focus();
    li.save();

    this.reset_sortables();
    return;

  },

  remove_multiple_line_items: function(items) {

    items.pluck("element").compact().invoke("remove");

    saved_items = items.findAll(function(i) {
      return !i.line_item.new_record;
    });

    saved_item_ids = saved_items.collect(function(i) {
      return i.line_item.id;
    });

    if (saved_items.size() > 0) {
      new Ajax.Request(this.destroy_multiple_line_items_url, {

        method     : "delete",
        parameters : { line_item_ids : saved_item_ids.join(",") }

      });
    }

    items.each(function(i) {
      i.line_item.deleted = true;
    });

    this.line_items_customised = true;

    return;

  },

  update_totals: function() {

    var subtotal = this.items.reject(function(i) { return i.line_item.deleted; }).inject(0, function(sum, item) {
      return sum += parseFloat(item.line_item.total_dollars.gsub(/[^0-9\.]/, ""));
    });

    this.subtotal.update(this.format_currency(subtotal));

    var tax_total = this.items.reject(function(i) { return i.line_item.deleted; }).inject(0, function(sum, item) {
      return sum += Math.round(parseFloat(item.line_item.total_dollars.gsub(/[^0-9\.]/, "")) * this.tax_rate_for(item.line_item.xero_account_id) * 100) / 100;
    }.bind(this));

    this.tax_total.update(this.format_currency(tax_total));

    var total = subtotal + tax_total;

    this.total.update(this.format_currency(total));

  },

  tax_rate_for: function(account_id) {

    return this.tax_rates[account_id] || 0.0;

  },

  format_currency: function(number) {

   return parseFloat(number).to_currency({ unit : "" });

  },

  validate: function(event) {
    if ($("loading_row").visible() || this.saving_in_progress) {
      event.stop();
      return false; // don't do anything if we're loading
    }

    var valid = true;

    if (!this.ignore_line_item_account) {
      this.items.each(function(i) {
        if (i && !i.line_item.deleted && !i.line_item.xero_account_id) {

          i.account_selector.addClassName("withErrors");
          valid = false;

        }

      }.bind(this));
    }

    if (valid) {
      this.next_step();
    } else {
      this.notification.show();
      window.location.hash = "#invalid_accounts";
    }

    event.stop();
    return false;

  },

  hide_invalid_notification: function() {

    if ($$("select.withErrors").length == 0) this.notification.hide();

  },

  next_step: function() {

    var next_button = $("next_step");

    var form = new Element("form", { style : "display: none;", method : "post", action : next_button.href });

    form.insert({ bottom : new Element("input", { type : "hidden", name : "_method", value : "put" }) });
    form.insert({ bottom : new Element("input", { type : "hidden", name : "authenticity_token", value : MinuteDock.authenticity_token }) });

    $(document.body).insert({ bottom : form });

    form.submit();

  },


  saving_start : function() {
    this.saving_in_progress = true;

    $("next_step").removeClassName("green_button").addClassName("gray_button");

    $("invoice_saved").hide();
    $("invoice_saving").show();
  },

  saving_end : function() {
    this.saving_in_progress = false;

    $("next_step").removeClassName("gray_button").addClassName("green_button");

    $("invoice_saving").hide();
    $("invoice_saved").show();

    $("invoice_saved").update("<span>Saved at " + (new Date().toUTCString()) + "</span>");
  }


});
/* Manages Xero logins */
/* Xero Login Popup */

XeroLogin = Class.create({

  initialize: function(xero_access_expiry, callback, opts) {

    window.xero_login      = this;

    this.opts = opts || {};
    this.xero_login_url    = MinuteDock.xero_login_url;
    this.login_callback    = callback || function() { window.location.reload(); };

    if (xero_access_expiry != false) this.xero_access_expiry = Date.parse(xero_access_expiry)

    this.loading_window = $("xero_loading");

    this.login();

  },

  login: function() {

    this.loading_window.down(".login_text").show();
    this.loading_window.down(".progress_text").hide();

    this.show_loading_window();

    if (this.logged_in_to_xero()) this.logged_in();
    else this.show_login_window();

  },

  logged_in_to_xero: function() {

    return (this.xero_access_expiry && this.xero_access_expiry > new Date());

  },

  show_login_window: function() {

    this.loading_window.down("#reopen_or_cancel").hide();
    this.loading_window.down("#reopen_or_cancel_unauthorized").hide();

    this.loading_window.down("#info").show();

    var width  = 970;
    var height = 650;

    this.login_window = window.open(this.xero_login_url, "xero_login_popup",
      "left=\#{left},top=\#{top},width=\#{width},height=\#{height},location=yes,toolbar=0,status=0,menubar=0,scrollbars=0".interpolate({
        left:   (document.viewport.getWidth() - width) / 2,
        top:    (document.viewport.getHeight() - height) / 2,
        width:  width,
        height: height })
    );

    if (this.login_window) { // FUCKING IE8 doesn't return a value for window.open...

      if (window.focus) {this.login_window.focus()}

      this.window_poller = new PeriodicalExecuter(function(pe) {

        if (this.login_window.closed) {
          this.show_reopen_or_cancel();
          pe.stop();
        }

      }.bind(this), 1);

    }

    return;

  },

  logged_in : function() {

    d = new Date();
    this.xero_access_expiry = d.setDate(d.getMinutes() + 29);

    if (this.window_poller) this.window_poller.stop();

    if (this.login_callback.call()) this.hide_loading_window();

    this.loading_window.down(".login_text").hide();
    this.loading_window.down(".progress_text").show();

  },


  unauthorized : function() {

    if (this.window_poller) this.window_poller.stop();

    this.loading_window.down("#reopen_or_cancel_unauthorized").show();
    this.loading_window.down("#info").hide();

  },

  show_loading_window: function() {

    this.overlay = this.create_overlay();
    this.loading_window.centerInWindow().show();

    window.onresize = function() { this.loading_window.centerInWindow() }.bind(this);

  },

  hide_loading_window: function() {

    this.overlay.remove();
    this.loading_window.hide();

    window.onresize = "";

  },

  show_reopen_or_cancel: function() {

    this.loading_window.down("#reopen_or_cancel").show();
    this.loading_window.down("#info").hide();

  },

  cancel: function() {

    this.hide_loading_window();

    if (this.opts.onCancel) {
      this.opts.onCancel.call();
    }

  },

  create_overlay: function() {

    overlay = new Element("div", { "class" : "modal_overlay" }).setStyle({ opacity: 0.7 });

    $(document.body).insert({ bottom : overlay });

    return overlay;
  }


});
ReportsManager = Class.create({

  initialize : function(container, options) {
    this.container = $(container);

    this.generate_reports_url = options.generate_reports_url;
    this.reports_url = options.reports_url;
    this.report_url  = options.report_url;
    this.get_entries_reports_url = options.get_entries_reports_url;
    this.export_report_url = options.export_report_url;

    this.data_tables = {};

    this.contacts_selector = this.container.down('#contacts_selector');
    this.users_selector    = this.container.down('#users_selector');
    this.tasks_selector    = this.container.down('#tasks_selector');
    this.options_selector  = this.container.down('#select_report_options');

    if (this.users_selector) {
      this.users_selector_obj = new MultiSelector(this.users_selector);
    }

    this.tasks_selector_obj = new MultiSelector(this.tasks_selector);
    this.contacts_selector_obj = new MultiSelector(this.contacts_selector);

    if (this.options_selector)
      this.options_selector_obj = new MultiSelector(this.options_selector);

    this.report_pinned = false;
    this.pin_modal_container = $('pin_report_modal');
    this.pin_modal     = new Modal(this.pin_modal_container, true);



    this.selected_tasks    = [];
    this.selected_users    = [];
    this.selected_contacts = [];

    this.range_selector = this.container.down('#range_selector');
    this.range_custom_select = this.container.down('#range_custom_select');

    this.form = this.container.down('form');

    this.form.observe('submit', function(e) {
      e.stop();
      return false;
    });


    this.range_type_field  = this.form.down('#report_range_type');
    this.range_start_field = this.form.down('#report_formatted_range_start');
    this.range_end_field   = this.form.down('#report_formatted_range_end');
    this.report_name_field = this.form.down('#report_name');

    this.overlay        = this.container.down('#reports_overlay');
    this.regenerate_box = this.container.down('#regenerate_box');
    this.errors_box = this.container.down('#errors_box');
    this.fail_box = this.container.down('#fail_box');

    this.init_report_view();

    this.init_range_tabs();
    this.init_selectors();
    this.init_pinned_dropdown();

    this.updateLocalID();

    this.init_visible_report();

  },

  init_pinned_dropdown : function() {
    this.pinned_icon = this.container.down('#pin_report_icon');
    this.pin_toggle_link = this.container.down('#pin_report_toggle_link');

    this.pinned_dropdown = new MultiSelector(this.container.down('#select_pinned_report'), {
      widthSizeOfTitle : true,
      inactiveClass    : function() {
        return this.pinned_report ? 'green_button' : 'gray_button';
      }.bind(this),
      activeClass      : function() {
        return this.pinned_report ? 'green_button' : 'gray_button';
      }.bind(this)
    });
    this.pinned_dropdown_title = this.container.down('#select_pinned_report .title span');
  },

  init_report_view : function() {
    this.view_container    = this.container.down('#view_container');
    this.entries_container = this.container.down('#entries_container');
    this.graph_container   = this.container.down('#graph_container');

    this.data_tables = {};

    this.report_name = this.container.down('#report_name_text');

    this.graph_tab = this.container.down('#show_graph_tab');
    this.graph_tab.observe('click', this.showGraphTab.bindAsEventListener(this));
    this.entries_tab = this.container.down('#show_entries_tab');
    this.entries_tab.observe('click', this.showEntriesTab.bindAsEventListener(this));

    this.entries_loading = this.container.down('#entries_loading');

    this.contacts_chart = this.graph_container.down('#contacts_chart');
    this.users_chart = this.graph_container.down('#users_chart');
    this.tasks_chart = this.graph_container.down('#tasks_chart');
    this.time_chart = this.graph_container.down('#time_chart');
    this.line_chart = this.view_container.down('#time_line_chart');
    this.hourly_rate_chart = this.view_container.down('#hourly_rate_chart');



  },

  init_range_tabs : function() {
    this.range_selector.select('li').invoke('observe', 'click', this.clickRangeTab.bindAsEventListener(this));

    this.updateRangeType(true);

    if (!this.isCustomRange()) {
      this.range_custom_select.hide().select('input').invoke('disable');
    }

    [this.range_start_field, this.range_end_field].each(function(field) {
      Calendar.setup({
        dateField      : field,
        dateFormat     : '%b %e %Y',
        markFuture     : true,
        selectHandler  : function(calendar) {

          Calendar.defaultSelectHandler(calendar);

          window.reports_manager.updateReport();

        }
      });
    });
  },

  init_selectors : function() {
    this.contacts_selector.select('li.filter_entity, .all').invoke('observe', 'click', this.clickFilterSelector.bindAsEventListener(this));
    this.tasks_selector.select('li.filter_entity, .all, .billable_only, .unbillable_only, .include_none').invoke('observe', 'click', this.clickFilterSelector.bindAsEventListener(this));

    if (this.users_selector)
      this.users_selector.select('li.filter_entity, .all').invoke('observe', 'click', this.clickFilterSelector.bindAsEventListener(this));

    if (this.options_selector)
      this.options_selector.select('li.filter_entity, .all').invoke('observe', 'click', this.clickFilterSelector.bindAsEventListener(this));
  },

  init_visible_report : function() {
    var regexp = /#report_([0-9]+)$/
    if (matches = regexp.exec(window.location.href)) {
      this.updateReport(matches[1]);
    }
  },

  setVisibleReport : function(reportID) {
    var anchor = reportID ? "report_" + reportID : "unpinned";

    var url = window.location.href;
    url = url.replace(/#.+?$/, "");

    url += '#' + anchor;

    window.location.href = url;
  },

  updateLocalID : function() {
    this.report_name_field.value = "";
    return this.last_report_request = Math.floor(Math.random() * 10000000);
  },

  togglePinned : function() {
    if (!this.valid()) return;

    if (!this.pinned_report) {
      this.report_name_field.value = this.reportName();
      this.pin_modal.show();
    } else {

      new Ajax.Request(this.report_url.gsub(':id', this.pinned_report.id), {
        method       : 'delete',
        asynchronous : true,
        evalScripts  : true,

        onSuccess : function(transport) {
          this.setPinnedReport(null);
        }.bind(this)

      })

    }

    return false;
  },

  createReport : function() {

    this.pin_modal_container.down('.buttons').hide();
    this.pin_modal_container.down('.loading').show();


    var params = { 'current_report_id' : this.last_report_request,
                   'pin' : true,
                   'authenticity_token': MinuteDock.authenticity_token }

    new Ajax.Request(this.reports_url, {
      method       : 'post',
      parameters   : Object.extend(this.form.serialize(true), params),
      asynchronous : true,
      evalScripts  : true
    })
  },

  clickExportReport : function() {
    if (this.pinned_report) {
      window.location = this.export_report_url.gsub(":id", this.pinned_report.id);
    } else {
      this.form.submit();
    }
  },

  createdReport : function(report) {
    this.setPinnedReport(report);

    this.pin_modal.hide();
    this.pin_modal_container.down('.buttons').show();
    this.pin_modal_container.down('.loading').hide();
  },


  setPinnedReport : function(report) {
    if (report && parseInt(report.local_id) == this.last_report_request) {
      this.pinned_dropdown.clearTitleState();

      this.pinned_report = report;

      $('pin_report_toggle_link').update("Unpin Report");


      var short_name = report.name;

      if (short_name.length > 27) {
        short_name = short_name.substring(0, 27) + "...";
      }

      this.report_name.update(report.name);

      this.pinned_dropdown_title.update('▼ ' + short_name);
      this.pinned_dropdown.updateTitleState();

      this.setVisibleReport(report.id);

      this.range_type_field.value = this.pinned_report.range_type;
      this.range_selector.select('li').invoke('removeClassName', 'current');
      this.range_selector.down('#range_' + this.pinned_report.range_type).addClassName('current');

      if (this.isCustomRange()) {

        this.range_start_field.value = this.dateCaption(Date.parse(this.pinned_report.range_start.gsub(/T.+$/, "")));
        this.range_end_field.value = this.dateCaption(Date.parse(this.pinned_report.range_end.gsub(/T.+$/, "")));
      }


      var updater = function(selector, model, all_method, billable_method, unbillable_method) {
        selector.select('li.filter_entity, .filter').invoke('removeClassName', 'checked');
        if (this.pinned_report[all_method]) {
            selector.down('.all').addClassName('checked');
        } else {
          selector.down('.all').removeClassName('checked');
          this.pinned_report[model+'_ids'].each(function(id) {
            if (id) {
              var s = selector.down('li.filter_entity#'+model+'_'+id);
              if (s) s.addClassName('checked');
            } else {
              selector.down('li.filter_entity.none').addClassName('checked');
            }
          }.bind(this));
        }

        selector.select('.checked > input').map(function(e) { e.checked = true; });
        selector.select(':not(.checked) > input').map(function(e) { e.checked = false; });
      }.bind(this);

      if (this.users_selector)
        updater(this.users_selector, 'user', 'all_users');
      updater(this.tasks_selector, 'task', 'all_tasks', 'billable_tasks_only', 'unbillable_tasks_only');
      updater(this.contacts_selector, 'contact', 'all_contacts');

      this.range_custom_select[this.isCustomRange() ? 'show' : 'hide']().select('input').invoke(this.isCustomRange() ? 'enable' : 'disable');


      if (this.options_selector) {

        this.options_selector.select(".filter_entity").invoke('removeClassName', 'checked');
        if (this.pinned_report["billable_entries_only"]) {
          this.options_selector.down('#report_billable_entries_only').up('.filter_entity').addClassName('checked');
        } else if (this.pinned_report["unbillable_entries_only"]) {
          this.options_selector.down('#report_unbillable_entries_only').up('.filter_entity').addClassName('checked');
        } else {
          this.options_selector.down('.filter_entity.all_time').addClassName('checked');
        }

        this.options_selector.select('.checked > input').map(function(e) { e.checked = true; });
        this.options_selector.select(':not(.checked) > input').map(function(e) { e.checked = false; });

      }



    } else {

      this.pinned_dropdown.clearTitleState();

      this.pinned_report = null;

      this.pinned_dropdown_title.update('▼ Unpinned report');
      this.pinned_dropdown.updateTitleState();

      this.setVisibleReport(null);

      $('pin_report_toggle_link').update("Pin Report");
    }
  },

  loadPinnedReport : function(id) {
    this.updateReport(id);
    this.pinned_dropdown.hideDropdown();
  },

  reportName : function() {
    if (this.report) {
      return this.report.name;
    } else {
      var name = "Time logged ";
      if (this.isCustomRange()) {

        name += "between " + this.dateCaption(Date.parse(this.range_start_field.value)) + ' and ' + this.dateCaption(Date.parse(this.range_end_field.value));

      } else if (this.range_type_field.value == 'week') {
        name += "this week"
      } else if (this.range_type_field.value == 'month') {
        name += 'this month'
      } else if (this.range_type_field.value == 'today') {
        name += 'today'
      } else if (this.range_type_field.value == 'yesterday') {
        name += 'yesterday'
      } else if (this.range_type_field.value == 'last_week') {
        name += "last week"
      } else if (this.range_type_field.value == "last_month") {
        name += "last month"
      }

      return name;
    }
  },

  dateCaption : function(date) {
    return Date.MONTHS[date.getMonth()].substring(0, 3) + " " + date.getDate() + " " + date.getFullYear();
  },

  reportLoaded : function() {
    this.loading_report = false;
  },

  updateReport : function(load_id) {
    var reportID = this.updateLocalID(), params;

    if (!load_id) {
      this.setPinnedReport(null, true);

      if (!this.valid()) {
        this.showError();
        return;
      }

      params = this.form.serialize(true);
    } else {
      params = { 'id' : load_id }
    }

    this.showOverlay();

    if (this.update_timeout)
      clearTimeout(this.update_timeout);

    this.update_timeout = setTimeout(function() {

      var timeEntriesTab = this.entries_tab.hasClassName('selected');


      new Ajax.Request(this.generate_reports_url, {
        method     : 'post',
        parameters : Object.extend(params, { 'authenticity_token' : MinuteDock.authenticity_token, 'current_report_id' : reportID }),
        asynchronous : true,
        evalScripts  : true,
        onFailure : function() {
          this.regenerate_box.hide();
          this.fail_box.show();
        }.bind(this),

        onComplete : function() {
          if (timeEntriesTab) {

            this.showEntriesTab();

          }
        }.bind(this)
      })
    }.bind(this), 1000);

  },

  shouldRenderReport : function(id) {
    return this.last_report_request == parseInt(id);
  },

  clickRangeTab : function(e, element) {
    this.range_selector.select('li').invoke('removeClassName', 'current');

    if (!element) {
      element = e.element().match("li") ? e.element() : e.element().up("li");
    }

    element.addClassName('current');

    this.range_custom_select[this.isCustomRange() ? 'show' : 'hide']().select('input').invoke(this.isCustomRange() ? 'enable' : 'disable');
    this.updateRangeType();
  },

  updateRangeType : function(dontReload) {

    var old_value = this.range_type_field.value;

    this.range_type_field.value = this.range_selector.down('li.current').id.gsub("range_", "");

    if (!dontReload)
      this.updateReport();
  },

  isCustomRange : function() {
    return this.range_selector.down('li.current').id == "range_custom";
  },

  showGraphTab : function(e) {
    this.entries_tab.removeClassName('selected').removeClassName('current');
    this.graph_tab.addClassName('selected').addClassName('current');

    this.entries_container.hide();
    this.graph_container.show();

    if (e) e.stop();
    return false;
  },

  showEntriesTab : function(e) {
    this.graph_tab.removeClassName('selected').removeClassName('current');
    this.entries_tab.addClassName('selected').addClassName('current');

    this.graph_container.hide();
    this.entries_container.show();

    if (!this.entries_container.down('.page')) {
      this.showEntriesPage(0);
    }

    if (e) e.stop();
    return false;
  },

  showEntriesPage : function(page) {
    var callback = function() {
      this.entries_loading.hide();
      this.entries_container.down('#entries_page_'+page).show();
    }.bind(this);


    this.entries_container.select('.page').invoke('hide');

    if (!this.entries_container.down('#entries_page_' + page)) {
      var params;
      if (!this.pinned_report) {
        params = this.form.serialize(true);
      } else {
        params = { 'id' : this.pinned_report.id }
      }

      this.entries_loading.show();
      new Ajax.Request(this.get_entries_reports_url, {
        method     : 'post',
        parameters : Object.extend(params, { 'authenticity_token' : MinuteDock.authenticity_token, 'page' : page, 'current_report_id' : this.current_report_id }),
        asynchronous : true,
        evalScripts  : true,
        onComplete : callback
      });
    } else {
      callback();
    }


  },

  setData : function(grouping, data) {
    this.data_tables[grouping] = new google.visualization.DataTable(data);
  },

  displayContactsGraph : function(data) {
    new RPieChart(this.contacts_chart, data);
  },

  displayTasksGraph : function(data) {
    new RPieChart(this.tasks_chart, data);
  },

  displayUsersGraph : function(data) {
    new RPieChart(this.users_chart, data);
  },

  displayTimeGraph : function(data) {
    new RBarChart(this.time_chart, data);
  },

  displayLineGraph : function(data) {
    new RLineChart(this.line_chart, data);
  },

  displayHourlyRateGraph : function(data) {
    new RPieChart(this.hourly_rate_chart, data);
  },

  clickFilterSelector : function(e) {

    var element = e.element(), checked;

    while (!element.match("li") && !element.match('.all') && !element.match('.billable_only') && !element.match('.unbillable_only') && !element.match('.include_none'))
      element = element.up();

    var selectAll      = element.hasClassName("all"),
        billableOnly   = element.hasClassName('billable_only'),
        unbillableOnly = element.hasClassName('unbillable_only');

    element.toggleClassName("checked");
    checked = element.hasClassName("checked");

    if (selectAll || billableOnly || unbillableOnly) {

      element.up('.collapsable').select('li.filter_entity').invoke('removeClassName', 'checked');

      element.up().up().select('.checked').each(function(e) {
        if (e != element && (selectAll || !e.match('.include_none'))) {
          e.removeClassName('checked');
        }
      })

    } else if (element.match(".exclusive")) {

      element.up('.collapsable').select('.filter_entity').each(function(e) {
        if (e != element) {
          e.removeClassName('checked');
        }
      })

    } else {

      var all = element.up('.collapsable').down('.all_selector');

      if (all) all.select('.filter').invoke('removeClassName', 'checked');

    }

    element.up('.collapsable').select('.checked > input').map(function(e) { e.checked = true; })
    element.up('.collapsable').select(':not(.checked) > input').map(function(e) { e.checked = false; })

    this.updateReport();
  },


  showOverlay : function(showErrors) {

    if (!this.overlay.visible()) {
      var height = (this.view_container && this.view_container.getHeight()) || 500; // Sometimes rendering engine hasn't rendered fully yet


      this.overlay.setStyle({ width: '930px', height: (height+50) + 'px' });
      this.overlay.setOpacity(0);
      this.overlay.show();

      new Effect.Opacity(this.overlay , { from: 0.0, to: 0.8, duration: 0.5 });
    }

    if (!showErrors) {
      if (!this.regenerate_box.visible()) {
        this.errors_box.hide();
        this.fail_box.hide();

        this.regenerate_box.setOpacity(0);
        this.regenerate_box.show();

        new Effect.Opacity(this.regenerate_box , { from: 0.0, to: 1.0, duration: 0.7 });
      }
    } else {
      var errors = this.getErrors();
      var ul = new Element("ul");
      errors.each(function(e) {
        ul.insert({ bottom : new Element("li").update(e) });
      });

      this.errors_box.down("ul").replace(ul);

      if (!this.errors_box.visible()) {

        this.regenerate_box.hide();

        this.errors_box.setOpacity(0);
        this.errors_box.show();

        new Effect.Opacity(this.errors_box , { from: 0.0, to: 1.0, duration: 0.7 });
      }

    }

  },

  showError : function() {
    this.showOverlay(true);
  },

  hideOverlay : function() {
    this.regenerate_box.hide();
    this.overlay.hide();
  },

  valid : function() {
    return this.getErrors().length == 0;
  },

  getErrors : function() {
    var errors = [];

    if (this.users_selector) {
      if (!this.users_selector.down('.checked')) {
        errors.push("You must select an option from the users list.");
      }
    }

    if (!this.tasks_selector.down('.checked')) {
      errors.push("You must select an option from the tasks list.");
    }

    if (!this.contacts_selector.down('.checked')) {
      errors.push("You must select an option from the contacts list.");
    }

    if (this.isCustomRange()) {
      var start = Date.parse(this.range_start_field.value),
          end   = Date.parse(this.range_end_field.value);

      if (start > end) {
        errors.push("You must select a custom start date earlier than the end date.");
      }
    }

    return errors;
  }

});


SignupController = Class.create({

  initialize : function(form, opts) {
    this.form = $(form)


    this.current_user_fields = this.form.down('#current_user_fields')
    this.new_user_fields     = this.form.down('#new_user_fields')

    this.account_xero_input = this.form.down('input[name="link_xero_account"]')

    this.account_source = this.form.down('#save_account_company_source')
    this.account_manual = this.form.down('#save_account_company_fields_manual')
    this.account_xero   = this.form.down('#save_account_company_xero')

    this.submit_buttons = this.form.down('#submit_buttons')

    this.enable_disable_fields()

    this.org_details_url = opts['org_details_url']

    this.xero_details = this.form.down('#xero_info')
    this.xero_loading = this.form.down('#xero_info_loading')
  },

  submit : function() {
    this.form.submit()
  },

  show_sources : function() {
    this.account_manual.hide()
    this.account_xero.hide()
    this.submit_buttons.hide()

    this.account_source.show()
  },

  connect_with_xero : function() {

    this.show_sources()

    new XeroLogin(false, function() {

      this.account_manual.hide()
      this.account_xero_input.enable()
      this.account_xero.show()
      this.account_source.hide()
      this.submit_buttons.show()

      this.get_organization_info()

      return true;

    }.bind(this), { onCancel : null });
  },

  get_organization_info : function() {
    this.xero_details.hide()
    this.xero_loading.show()
    this.submit_buttons.hide()

    new Ajax.Request(this.org_details_url, {

      parameters : { authenticity_token : MinuteDock.authenticity_token },

      onSuccess : function(t) {
        var org = t.responseJSON;

        this.xero_details.down('#xero_info_name').update(org['name'])
        this.xero_details.down('#xero_info_currency').update(org['base_currency_code'])

        this.xero_loading.hide()
        this.xero_details.show()

        this.submit_buttons.show()
      }.bind(this)
    })
  },

  save_with_different_user : function() {
    this.current_user_fields.hide()
    this.new_user_fields.show()
    this.enable_disable_fields()
  },

  enter_account_info : function() {
    this.account_source.hide()
    this.account_xero_input.disable()
    this.account_manual.show()
    this.submit_buttons.show()
  },

  enable_disable_fields : function() {
    var enable = [], disable = []

    if (this.current_user_fields) {
      if (this.current_user_fields.visible()) {
        enable.push(this.current_user_fields)
      } else {
        disable.push(this.current_user_fields)
      }
    }

    if (this.new_user_fields.visible()) {
      enable.push(this.new_user_fields)
    } else {
      disable.push(this.new_user_fields)
    }

    enable.map(function(c) {
      c.select('input').invoke('enable')
    })

    disable.map(function(c) {
      c.select('input').invoke('disable')
    })

  }


})
UserSlider = Class.create({

  UNLIMITED_USER_COUNT : 13,
  default_options : {
    slider_width: 500,
    initial_value: 0
  },
  /**
   * Options
   *   slider_width
   *   initial_value
   *   pricing_plan_data
   */
  initialize: function(slider_id, options) {

    this.options = Object.extend({}, this.default_options);
    this.options = Object.extend(this.options, options||{});

    this.container = $(slider_id);

    var handle = this.container.down("img.handle");
    var track  = this.container;

    handle.setStyle({ width: "20px" });
    track.setStyle({ width: (this.options.slider_width) + 'px' });

    this.slider = new Control.Slider(handle, track, {

      onSlide : this.onSlide.bind(this),
      onChange : this.onSlide.bind(this),
      range: $R(this.options.minimum || 1, this.UNLIMITED_USER_COUNT),
      sliderValue: this.options.initial_value,
      values: $R(this.options.minimum || 1, this.UNLIMITED_USER_COUNT)

    });

    this.pricing_plans = this.options.pricing_plan_data;

    if (this.pricing_plans) {

      this.period_hidden_field = this.options.period_hidden_field;
      this.updatePricing();

    }

    this.tooltip = this.container.next(".tooltip").hide();
    this.updateToolTip();

    new PeriodicalExecuter(function(pe) {
      this.updateToolTip();
    }.bind(this), 0.1);

  },

  onSlide : function(value) {

    this.updateToolTip();
    this.updatePricing();

    $("user_count").value = this.slider.value;

    var prorate_notification = $("prorate_notification");


    if (prorate_notification && this.slider.value > this.options.initial_value) {
      if (!this.prorate_animating && !prorate_notification.visible()) {

        new Effect.Appear(prorate_notification, {

          afterFinish : function() {
            this.prorate_animating = false;
          }.bind(this)

        });

        this.prorate_animating = true;

      }

    } else if (prorate_notification) {

      if (!this.prorate_animating && prorate_notification.visible()) {

        new Effect.Fade(prorate_notification, {

          afterFinish : function() {
            this.prorate_animating = false;
          }.bind(this)

        });

        this.prorate_animating = true;

      }

    }

  },

  updateToolTip : function() {
    var handlePosition = this.slider.handles.first().positionedOffset();
    var sliderPosition = this.slider.handles.first().up().positionedOffset();

    handlePosition.top += sliderPosition.top;
    handlePosition.left += sliderPosition.left;

    this.tooltip.show();
    this.tooltip.setStyle({ position : "absolute",
                            left     : (handlePosition.left - (this.tooltip.getWidth() / 2) + 10) + "px",
                            top      : (handlePosition.top + 22) + "px",
                            'min-width' : '65px' /*chrome fail*/,
                            'text-align': 'center' });

    if (this.slider.value == this.UNLIMITED_USER_COUNT) {
      this.tooltip.down("span").innerHTML = "No limit!"
    } else {
      this.tooltip.down("span").innerHTML = this.slider.value + " " + ((this.slider.value == 1) ? "user" : "users");
    }

  },

  updatePricing : function() {
    (this.options.updatePricingDisplay || this.updatePricingDisplay)(this);
  },

  updatePricingDisplay : function(slider) {

    (function() {

      $$(".price").invoke("hide");

      var current_period = $F(this.period_hidden_field) || "monthly";

      var currency = "nzd";

      var plan = this.pricing_plans.find(function(p) {

        if (p.period == current_period) {

          if (this.slider.value == this.UNLIMITED_USER_COUNT && !p.priced_per_user) return true;
          else if (this.slider.value < this.UNLIMITED_USER_COUNT) return true;

        }

        return false;

      }.bind(this));

      if (plan.priced_per_user == true) {
        var price = plan.base_price + plan.price_per_user * this.slider.value;
      } else {
        var price = plan.base_price;
      }

      if (this.options.affiliate_discount_percent) {

        var affiliate_price = price - (price * (this.options.affiliate_discount_percent / 100))

      }

      $$(".price." + current_period).each(function(price_container) {

        price_container.show();

        var container_price = (price_container.hasClassName("affiliate")) ? affiliate_price : price;
        price_container.down("span").innerHTML = (container_price / 100).to_currency({ unit : "" });

      });


    }.bind(slider))();

  },

  currentPricingPlan : function() {



  }


});
ToggleButton = Class.create({

  initialize: function(el_id, options) {

    this.el           = $(el_id);

    this.hidden_field = $(this.el.id.gsub("_toggle_button", ""));

    options = options || {};
    this.callback = options.callback;

    this.options = this.el.select("a");

    this.options.invoke("observe", "click", this.toggle.bindAsEventListener(this));

    if (!$F(this.hidden_field).blank()) {

      this.setSelected(this.el.down("a[rel=" + $F(this.hidden_field) + "]"));

    } else {

      this.setSelected(this.options.first());

    }

  },

  toggle: function(event) {

    if (this.selected_option == this.options.first()) this.setSelected(this.options.last());
    else (this.setSelected(this.options.first()));

    if (this.callback) this.callback.call(null, $F(this.hidden_field));

    if (event) {
      event.stop();
    }

    return false;

  },

  setSelected: function(option) {

    this.options.invoke("removeClassName", "on"); // turn everything off
    option.addClassName("on");

    this.hidden_field.value = option.rel;

    this.selected_option = option;

  }


});

SimpleCarousel = Class.create({

  default_options : $H({ window_size : 3, scroll_increment : 1, autoscroll : false, autoscroll_period : 5, change_arrow_titles : true }),

  initialize: function(container, options) {

    this.container = $(container);
    this.clip      = new Element("div", { "class" : "clip" });

    this.list  = this.container.down("ul");
    this.list.wrap(this.clip);

    this.items     = this.list.select("li.step");

    if (this.items.size() == 0) return;

/*    nested_items = this.list.select("ul > li");
    this.items   = this.items.filter(function(e) {
      return (nested_items.indexOf(e) == -1);
    });*/

    this.prev_arrows = this.container.select(".arrow.prev");
    this.next_arrows = this.container.select(".arrow.next");

    this.arrows = this.prev_arrows.concat(this.next_arrows);

    this.options = this.default_options.merge(options);

    this.init_styles();

    this.current_index = 0;

    if (initial = this.options.get("initial_position")) this.scroll_to_position(initial, false);

    this.update_arrows();

    this.arrows.each(function(arrow) {

      arrow.observe("click", this.scroll.bindAsEventListener(this));

    }.bind(this));

    if (this.options.get("hide_arrows") && this.options.get("window_size") >= this.items.size()) {
      this.arrows.invoke("hide");
    }

    if (this.options.get("autoscroll")) {
      this.init_autoscroll();
    }
  },

  init_autoscroll : function(period) {
    this.autoscroll_timeout = new PeriodicalExecuter(this.autoscroll.bind(this), this.options.get("autoscroll_period"));
  },

  autoscroll : function() {
    var position = (this.current_index + 1) % this.items.size();

    if (this.moving) return;
    this.moving = true;
    this.scroll_to_position(position, true);

  },

  init_styles: function() {

     this.clip.setStyle({ position : "relative", "overflow" : "hidden", width: "100%" });

     this.list.setStyle({ position : "relative", "overflow" : "hidden",
                          width    : "99999px",  left         : "0px" });


    var padding = parseInt(this.items.first().getStyle("padding-left")) +
                    parseInt(this.items.first().getStyle("padding-right"));

    var margins = parseInt(this.items.first().getStyle("margin-left")||0) +
                      parseInt(this.items.first().getStyle("margin-right")||0);

    var item_width = Math.floor(this.clip.getWidth() / this.options.get("window_size"));

    var inner_margins = Math.floor(margins * (this.options.get("window_size") - 1)) / this.options.get("window_size");

    this.scroll_width = item_width + margins - inner_margins;
    this.item_width   = item_width - padding - inner_margins;

    this.items.each(function(s) {
      $(s).setStyle({ 'width' : this.item_width + "px", cssFloat : "left" });
    }.bind(this));
    /*this.items.invoke("setStyle", { 'width' : this.item_width + "px", 'float' : "left" });*/

    return;

  },

  update_arrows: function() {

    var prev = (this.current_index == 0) ? "addClassName" : "removeClassName";
    var next = (this.current_index + this.options.get("window_size") >= this.items.size()) ? "addClassName" : "removeClassName";

    eval("this.prev_arrows.invoke('" + prev + "', 'disabled');");
    eval("this.next_arrows.invoke('" + next + "', 'disabled');");

    if (this.options.get("change_arrow_titles")) {

      if (this.current_index == 0) {
        this.prev_arrows.invoke("update", "Step 1");
        this.next_arrows.invoke("update", "Step 2");
      } else if (this.current_index == this.items.size() - 1) {
        this.prev_arrows.invoke("update", "Step " + (this.items.size() - 1));
        this.next_arrows.invoke("update", "Step " + (this.items.size()));
      } else {
        this.prev_arrows.invoke("update", "Step " + (this.current_index));
        this.next_arrows.invoke("update", "Step " + (this.current_index + 2));
      }

    }

    return;

  },

  scroll: function(event) {

    if (this.autoscroll_timeout) this.autoscroll_timeout.stop();

    if (this.moving) return;
    this.moving = true;

    var direction = (event.element().hasClassName("prev")) ? -1 : 1;

    var new_position  = this.current_index + (direction * this.options.get("scroll_increment"));

    this.scroll_to_position(new_position, true);

    event.stop();
    return false;

  },

  scroll_to_position: function(new_position, animate) {

    var last_pos      = this.items.size() - this.options.get("window_size");

    if (new_position > last_pos) new_position = last_pos;
    if (new_position < 0) new_position = 0;

    if (new_position == this.current_index) {
      this.moving = false;
      return;
    }

    var offset = -1 * (this.scroll_width * (new_position - this.current_index));

    if (animate) {

      new Effect.Move(this.list, { x : offset, duration : 0.5,

                                  afterFinish : function() {

                                    this.undim();
                                    this.moving = false;

                                  }.bind(this) });

      this.dim();

    } else {

      var current_left = parseInt(this.list.getStyle("left"));

      this.list.setStyle({ left : (offset + current_left) + "px" });
      this.moving = false;

    }

    this.current_index = new_position;
    this.update_arrows();

    return;

  },

  dim: function() {
    this.items.invoke("setOpacity", 1);
  },

  undim: function() {
    this.items.invoke("setOpacity", 1);
  }


});
/*
 * Raphael 1.3.2 - JavaScript Vector Library
 *
 * Copyright (c) 2009 Dmitry Baranovskiy (http://raphaeljs.com)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 */
Raphael=(function(){var a=/[, ]+/,aY=/^(circle|rect|path|ellipse|text|image)$/,a9="prototype",W="hasOwnProperty",P=document,aB=window,m={was:Object[a9][W].call(aB,"Raphael"),is:aB.Raphael},au=function(){if(au.is(arguments[0],"array")){var d=arguments[0],e=z[a7](au,d.splice(0,3+au.is(d[0],aq))),S=e.set();for(var R=0,bc=d[n];R<bc;R++){var E=d[R]||{};aY.test(E.type)&&S[f](e[E.type]().attr(E));}return S;}return z[a7](au,arguments);},a4=function(){},aU="appendChild",a7="apply",a2="concat",aA="",at=" ",C="split",J="click dblclick mousedown mousemove mouseout mouseover mouseup"[C](at),aH="join",n="length",bb=String[a9].toLowerCase,af=Math,h=af.max,aR=af.min,aq="number",aJ="toString",aE=Object[a9][aJ],a0={},aV=af.pow,f="push",a5=/^(?=[\da-f]$)/,c=/^url\(['"]?([^\)]+?)['"]?\)$/i,A=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgb\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|rgb\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\)|hs[bl]\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|hs[bl]\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\))\s*$/i,U=af.round,y="setAttribute",aa=parseFloat,K=parseInt,aX=String[a9].toUpperCase,k={blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/",opacity:1,path:"M0,0",r:0,rotation:0,rx:0,ry:0,scale:"1 1",src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",translation:"0 0",width:0,x:0,y:0},ad={along:"along",blur:aq,"clip-rect":"csv",cx:aq,cy:aq,fill:"colour","fill-opacity":aq,"font-size":aq,height:aq,opacity:aq,path:"path",r:aq,rotation:"csv",rx:aq,ry:aq,scale:"csv",stroke:"colour","stroke-opacity":aq,"stroke-width":aq,translation:"csv",width:aq,x:aq,y:aq},aZ="replace";au.version="1.3.2";au.type=(aB.SVGAngle||P.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML");if(au.type=="VML"){var ak=P.createElement("div");ak.innerHTML="<!--[if vml]><br><br><![endif]-->";if(ak.childNodes[n]!=2){return au.type=null;}ak=null;}au.svg=!(au.vml=au.type=="VML");a4[a9]=au[a9];au._id=0;au._oid=0;au.fn={};au.is=function(e,d){d=bb.call(d);return((d=="object"||d=="undefined")&&typeof e==d)||(e==null&&d=="null")||bb.call(aE.call(e).slice(8,-1))==d;};au.setWindow=function(d){aB=d;P=aB.document;};var aM=function(e){if(au.vml){var d=/^\s+|\s+$/g;aM=an(function(R){var S;R=(R+aA)[aZ](d,aA);try{var bc=new aB.ActiveXObject("htmlfile");bc.write("<body>");bc.close();S=bc.body;}catch(be){S=aB.createPopup().document.body;}var i=S.createTextRange();try{S.style.color=R;var bd=i.queryCommandValue("ForeColor");bd=((bd&255)<<16)|(bd&65280)|((bd&16711680)>>>16);return"#"+("000000"+bd[aJ](16)).slice(-6);}catch(be){return"none";}});}else{var E=P.createElement("i");E.title="Rapha\xebl Colour Picker";E.style.display="none";P.body[aU](E);aM=an(function(i){E.style.color=i;return P.defaultView.getComputedStyle(E,aA).getPropertyValue("color");});}return aM(e);};var ao=function(){return"hsb("+[this.h,this.s,this.b]+")";},w=function(){return this.hex;};au.hsb2rgb=an(function(bf,bd,bj){if(au.is(bf,"object")&&"h" in bf&&"s" in bf&&"b" in bf){bj=bf.b;bd=bf.s;bf=bf.h;}var R,S,bk;if(bj==0){return{r:0,g:0,b:0,hex:"#000"};}if(bf>1||bd>1||bj>1){bf/=255;bd/=255;bj/=255;}var bc=~~(bf*6),bg=(bf*6)-bc,E=bj*(1-bd),e=bj*(1-(bd*bg)),bl=bj*(1-(bd*(1-bg)));R=[bj,e,E,E,bl,bj,bj][bc];S=[bl,bj,bj,e,E,E,bl][bc];bk=[E,E,bl,bj,bj,e,E][bc];R*=255;S*=255;bk*=255;var bh={r:R,g:S,b:bk,toString:w},d=(~~R)[aJ](16),be=(~~S)[aJ](16),bi=(~~bk)[aJ](16);d=d[aZ](a5,"0");be=be[aZ](a5,"0");bi=bi[aZ](a5,"0");bh.hex="#"+d+be+bi;return bh;},au);au.rgb2hsb=an(function(d,e,bd){if(au.is(d,"object")&&"r" in d&&"g" in d&&"b" in d){bd=d.b;e=d.g;d=d.r;}if(au.is(d,"string")){var bf=au.getRGB(d);d=bf.r;e=bf.g;bd=bf.b;}if(d>1||e>1||bd>1){d/=255;e/=255;bd/=255;}var bc=h(d,e,bd),i=aR(d,e,bd),R,E,S=bc;if(i==bc){return{h:0,s:0,b:bc};}else{var be=(bc-i);E=be/bc;if(d==bc){R=(e-bd)/be;}else{if(e==bc){R=2+((bd-d)/be);}else{R=4+((d-e)/be);}}R/=6;R<0&&R++;R>1&&R--;}return{h:R,s:E,b:S,toString:ao};},au);var aN=/,?([achlmqrstvxz]),?/gi;au._path2string=function(){return this.join(",")[aZ](aN,"$1");};function an(E,e,d){function i(){var R=Array[a9].slice.call(arguments,0),bc=R[aH]("\u25ba"),S=i.cache=i.cache||{},bd=i.count=i.count||[];if(S[W](bc)){return d?d(S[bc]):S[bc];}bd[n]>=1000&&delete S[bd.shift()];bd[f](bc);S[bc]=E[a7](e,R);return d?d(S[bc]):S[bc];}return i;}au.getRGB=an(function(d){if(!d||!!((d=d+aA).indexOf("-")+1)){return{r:-1,g:-1,b:-1,hex:"none",error:1};}if(d=="none"){return{r:-1,g:-1,b:-1,hex:"none"};}!(({hs:1,rg:1})[W](d.substring(0,2))||d.charAt()=="#")&&(d=aM(d));var S,i,E,be,bf,bc=d.match(A);if(bc){if(bc[2]){be=K(bc[2].substring(5),16);E=K(bc[2].substring(3,5),16);i=K(bc[2].substring(1,3),16);}if(bc[3]){be=K((bf=bc[3].charAt(3))+bf,16);E=K((bf=bc[3].charAt(2))+bf,16);i=K((bf=bc[3].charAt(1))+bf,16);}if(bc[4]){bc=bc[4][C](/\s*,\s*/);i=aa(bc[0]);E=aa(bc[1]);be=aa(bc[2]);}if(bc[5]){bc=bc[5][C](/\s*,\s*/);i=aa(bc[0])*2.55;E=aa(bc[1])*2.55;be=aa(bc[2])*2.55;}if(bc[6]){bc=bc[6][C](/\s*,\s*/);i=aa(bc[0]);E=aa(bc[1]);be=aa(bc[2]);return au.hsb2rgb(i,E,be);}if(bc[7]){bc=bc[7][C](/\s*,\s*/);i=aa(bc[0])*2.55;E=aa(bc[1])*2.55;be=aa(bc[2])*2.55;return au.hsb2rgb(i,E,be);}bc={r:i,g:E,b:be};var e=(~~i)[aJ](16),R=(~~E)[aJ](16),bd=(~~be)[aJ](16);e=e[aZ](a5,"0");R=R[aZ](a5,"0");bd=bd[aZ](a5,"0");bc.hex="#"+e+R+bd;return bc;}return{r:-1,g:-1,b:-1,hex:"none",error:1};},au);au.getColor=function(e){var i=this.getColor.start=this.getColor.start||{h:0,s:1,b:e||0.75},d=this.hsb2rgb(i.h,i.s,i.b);i.h+=0.075;if(i.h>1){i.h=0;i.s-=0.2;i.s<=0&&(this.getColor.start={h:0,s:1,b:i.b});}return d.hex;};au.getColor.reset=function(){delete this.start;};var aC=/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,ar=/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig;au.parsePathString=an(function(d){if(!d){return null;}var i={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},e=[];if(au.is(d,"array")&&au.is(d[0],"array")){e=aD(d);}if(!e[n]){(d+aA)[aZ](aC,function(R,E,bd){var bc=[],S=bb.call(E);bd[aZ](ar,function(bf,be){be&&bc[f](+be);});if(S=="m"&&bc[n]>2){e[f]([E][a2](bc.splice(0,2)));S="l";E=E=="m"?"l":"L";}while(bc[n]>=i[S]){e[f]([E][a2](bc.splice(0,i[S])));if(!i[S]){break;}}});}e[aJ]=au._path2string;return e;});au.findDotsAtSegment=function(e,d,bq,bo,bc,R,be,bd,bk){var bi=1-bk,bh=aV(bi,3)*e+aV(bi,2)*3*bk*bq+bi*3*bk*bk*bc+aV(bk,3)*be,bf=aV(bi,3)*d+aV(bi,2)*3*bk*bo+bi*3*bk*bk*R+aV(bk,3)*bd,bm=e+2*bk*(bq-e)+bk*bk*(bc-2*bq+e),bl=d+2*bk*(bo-d)+bk*bk*(R-2*bo+d),bp=bq+2*bk*(bc-bq)+bk*bk*(be-2*bc+bq),bn=bo+2*bk*(R-bo)+bk*bk*(bd-2*R+bo),bj=(1-bk)*e+bk*bq,bg=(1-bk)*d+bk*bo,E=(1-bk)*bc+bk*be,i=(1-bk)*R+bk*bd,S=(90-af.atan((bm-bp)/(bl-bn))*180/af.PI);(bm>bp||bl<bn)&&(S+=180);return{x:bh,y:bf,m:{x:bm,y:bl},n:{x:bp,y:bn},start:{x:bj,y:bg},end:{x:E,y:i},alpha:S};};var Y=an(function(bh){if(!bh){return{x:0,y:0,width:0,height:0};}bh=L(bh);var be=0,bd=0,R=[],e=[],E;for(var S=0,bg=bh[n];S<bg;S++){E=bh[S];if(E[0]=="M"){be=E[1];bd=E[2];R[f](be);e[f](bd);}else{var bc=aL(be,bd,E[1],E[2],E[3],E[4],E[5],E[6]);R=R[a2](bc.min.x,bc.max.x);e=e[a2](bc.min.y,bc.max.y);be=E[5];bd=E[6];}}var d=aR[a7](0,R),bf=aR[a7](0,e);return{x:d,y:bf,width:h[a7](0,R)-d,height:h[a7](0,e)-bf};}),aD=function(bc){var E=[];if(!au.is(bc,"array")||!au.is(bc&&bc[0],"array")){bc=au.parsePathString(bc);}for(var e=0,R=bc[n];e<R;e++){E[e]=[];for(var d=0,S=bc[e][n];d<S;d++){E[e][d]=bc[e][d];}}E[aJ]=au._path2string;return E;},ah=an(function(R){if(!au.is(R,"array")||!au.is(R&&R[0],"array")){R=au.parsePathString(R);}var bg=[],bi=0,bh=0,bl=0,bk=0,E=0;if(R[0][0]=="M"){bi=R[0][1];bh=R[0][2];bl=bi;bk=bh;E++;bg[f](["M",bi,bh]);}for(var bd=E,bm=R[n];bd<bm;bd++){var d=bg[bd]=[],bj=R[bd];if(bj[0]!=bb.call(bj[0])){d[0]=bb.call(bj[0]);switch(d[0]){case"a":d[1]=bj[1];d[2]=bj[2];d[3]=bj[3];d[4]=bj[4];d[5]=bj[5];d[6]=+(bj[6]-bi).toFixed(3);d[7]=+(bj[7]-bh).toFixed(3);break;case"v":d[1]=+(bj[1]-bh).toFixed(3);break;case"m":bl=bj[1];bk=bj[2];default:for(var bc=1,be=bj[n];bc<be;bc++){d[bc]=+(bj[bc]-((bc%2)?bi:bh)).toFixed(3);}}}else{d=bg[bd]=[];if(bj[0]=="m"){bl=bj[1]+bi;bk=bj[2]+bh;}for(var S=0,e=bj[n];S<e;S++){bg[bd][S]=bj[S];}}var bf=bg[bd][n];switch(bg[bd][0]){case"z":bi=bl;bh=bk;break;case"h":bi+=+bg[bd][bf-1];break;case"v":bh+=+bg[bd][bf-1];break;default:bi+=+bg[bd][bf-2];bh+=+bg[bd][bf-1];}}bg[aJ]=au._path2string;return bg;},0,aD),t=an(function(R){if(!au.is(R,"array")||!au.is(R&&R[0],"array")){R=au.parsePathString(R);}var bf=[],bh=0,bg=0,bk=0,bj=0,E=0;if(R[0][0]=="M"){bh=+R[0][1];bg=+R[0][2];bk=bh;bj=bg;E++;bf[0]=["M",bh,bg];}for(var bd=E,bl=R[n];bd<bl;bd++){var d=bf[bd]=[],bi=R[bd];if(bi[0]!=aX.call(bi[0])){d[0]=aX.call(bi[0]);switch(d[0]){case"A":d[1]=bi[1];d[2]=bi[2];d[3]=bi[3];d[4]=bi[4];d[5]=bi[5];d[6]=+(bi[6]+bh);d[7]=+(bi[7]+bg);break;case"V":d[1]=+bi[1]+bg;break;case"H":d[1]=+bi[1]+bh;break;case"M":bk=+bi[1]+bh;bj=+bi[2]+bg;default:for(var bc=1,be=bi[n];bc<be;bc++){d[bc]=+bi[bc]+((bc%2)?bh:bg);}}}else{for(var S=0,e=bi[n];S<e;S++){bf[bd][S]=bi[S];}}switch(d[0]){case"Z":bh=bk;bg=bj;break;case"H":bh=d[1];break;case"V":bg=d[1];break;default:bh=bf[bd][bf[bd][n]-2];bg=bf[bd][bf[bd][n]-1];}}bf[aJ]=au._path2string;return bf;},null,aD),a8=function(e,E,d,i){return[e,E,d,i,d,i];},aT=function(e,E,bc,R,d,i){var S=1/3,bd=2/3;return[S*e+bd*bc,S*E+bd*R,S*d+bd*bc,S*i+bd*R,d,i];},O=function(bl,bQ,bu,bs,bm,bg,S,bk,bP,bn){var R=af.PI,br=R*120/180,d=R/180*(+bm||0),by=[],bv,bM=an(function(bR,bU,i){var bT=bR*af.cos(i)-bU*af.sin(i),bS=bR*af.sin(i)+bU*af.cos(i);return{x:bT,y:bS};});if(!bn){bv=bM(bl,bQ,-d);bl=bv.x;bQ=bv.y;bv=bM(bk,bP,-d);bk=bv.x;bP=bv.y;var e=af.cos(R/180*bm),bi=af.sin(R/180*bm),bA=(bl-bk)/2,bz=(bQ-bP)/2;var bK=(bA*bA)/(bu*bu)+(bz*bz)/(bs*bs);if(bK>1){bK=af.sqrt(bK);bu=bK*bu;bs=bK*bs;}var E=bu*bu,bD=bs*bs,bF=(bg==S?-1:1)*af.sqrt(af.abs((E*bD-E*bz*bz-bD*bA*bA)/(E*bz*bz+bD*bA*bA))),bp=bF*bu*bz/bs+(bl+bk)/2,bo=bF*-bs*bA/bu+(bQ+bP)/2,bf=af.asin(((bQ-bo)/bs).toFixed(7)),be=af.asin(((bP-bo)/bs).toFixed(7));bf=bl<bp?R-bf:bf;be=bk<bp?R-be:be;bf<0&&(bf=R*2+bf);be<0&&(be=R*2+be);if(S&&bf>be){bf=bf-R*2;}if(!S&&be>bf){be=be-R*2;}}else{bf=bn[0];be=bn[1];bp=bn[2];bo=bn[3];}var bj=be-bf;if(af.abs(bj)>br){var bq=be,bt=bk,bh=bP;be=bf+br*(S&&be>bf?1:-1);bk=bp+bu*af.cos(be);bP=bo+bs*af.sin(be);by=O(bk,bP,bu,bs,bm,0,S,bt,bh,[be,bq,bp,bo]);}bj=be-bf;var bd=af.cos(bf),bO=af.sin(bf),bc=af.cos(be),bN=af.sin(be),bB=af.tan(bj/4),bE=4/3*bu*bB,bC=4/3*bs*bB,bL=[bl,bQ],bJ=[bl+bE*bO,bQ-bC*bd],bI=[bk+bE*bN,bP-bC*bc],bG=[bk,bP];bJ[0]=2*bL[0]-bJ[0];bJ[1]=2*bL[1]-bJ[1];if(bn){return[bJ,bI,bG][a2](by);}else{by=[bJ,bI,bG][a2](by)[aH]()[C](",");var bw=[];for(var bH=0,bx=by[n];bH<bx;bH++){bw[bH]=bH%2?bM(by[bH-1],by[bH],d).y:bM(by[bH],by[bH+1],d).x;}return bw;}},T=function(e,d,E,i,be,bd,bc,S,bf){var R=1-bf;return{x:aV(R,3)*e+aV(R,2)*3*bf*E+R*3*bf*bf*be+aV(bf,3)*bc,y:aV(R,3)*d+aV(R,2)*3*bf*i+R*3*bf*bf*bd+aV(bf,3)*S};},aL=an(function(i,d,R,E,bl,bk,bh,be){var bj=(bl-2*R+i)-(bh-2*bl+R),bg=2*(R-i)-2*(bl-R),bd=i-R,bc=(-bg+af.sqrt(bg*bg-4*bj*bd))/2/bj,S=(-bg-af.sqrt(bg*bg-4*bj*bd))/2/bj,bf=[d,be],bi=[i,bh],e;af.abs(bc)>1000000000000&&(bc=0.5);af.abs(S)>1000000000000&&(S=0.5);if(bc>0&&bc<1){e=T(i,d,R,E,bl,bk,bh,be,bc);bi[f](e.x);bf[f](e.y);}if(S>0&&S<1){e=T(i,d,R,E,bl,bk,bh,be,S);bi[f](e.x);bf[f](e.y);}bj=(bk-2*E+d)-(be-2*bk+E);bg=2*(E-d)-2*(bk-E);bd=d-E;bc=(-bg+af.sqrt(bg*bg-4*bj*bd))/2/bj;S=(-bg-af.sqrt(bg*bg-4*bj*bd))/2/bj;af.abs(bc)>1000000000000&&(bc=0.5);af.abs(S)>1000000000000&&(S=0.5);if(bc>0&&bc<1){e=T(i,d,R,E,bl,bk,bh,be,bc);bi[f](e.x);bf[f](e.y);}if(S>0&&S<1){e=T(i,d,R,E,bl,bk,bh,be,S);bi[f](e.x);bf[f](e.y);}return{min:{x:aR[a7](0,bi),y:aR[a7](0,bf)},max:{x:h[a7](0,bi),y:h[a7](0,bf)}};}),L=an(function(bl,bg){var R=t(bl),bh=bg&&t(bg),bi={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},d={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},bc=function(bm,bn){var i,bo;if(!bm){return["C",bn.x,bn.y,bn.x,bn.y,bn.x,bn.y];}!(bm[0] in {T:1,Q:1})&&(bn.qx=bn.qy=null);switch(bm[0]){case"M":bn.X=bm[1];bn.Y=bm[2];break;case"A":bm=["C"][a2](O[a7](0,[bn.x,bn.y][a2](bm.slice(1))));break;case"S":i=bn.x+(bn.x-(bn.bx||bn.x));bo=bn.y+(bn.y-(bn.by||bn.y));bm=["C",i,bo][a2](bm.slice(1));break;case"T":bn.qx=bn.x+(bn.x-(bn.qx||bn.x));bn.qy=bn.y+(bn.y-(bn.qy||bn.y));bm=["C"][a2](aT(bn.x,bn.y,bn.qx,bn.qy,bm[1],bm[2]));break;case"Q":bn.qx=bm[1];bn.qy=bm[2];bm=["C"][a2](aT(bn.x,bn.y,bm[1],bm[2],bm[3],bm[4]));break;case"L":bm=["C"][a2](a8(bn.x,bn.y,bm[1],bm[2]));break;case"H":bm=["C"][a2](a8(bn.x,bn.y,bm[1],bn.y));break;case"V":bm=["C"][a2](a8(bn.x,bn.y,bn.x,bm[1]));break;case"Z":bm=["C"][a2](a8(bn.x,bn.y,bn.X,bn.Y));break;}return bm;},e=function(bm,bn){if(bm[bn][n]>7){bm[bn].shift();var bo=bm[bn];while(bo[n]){bm.splice(bn++,0,["C"][a2](bo.splice(0,6)));}bm.splice(bn,1);bj=h(R[n],bh&&bh[n]||0);}},E=function(bq,bp,bn,bm,bo){if(bq&&bp&&bq[bo][0]=="M"&&bp[bo][0]!="M"){bp.splice(bo,0,["M",bm.x,bm.y]);bn.bx=0;bn.by=0;bn.x=bq[bo][1];bn.y=bq[bo][2];bj=h(R[n],bh&&bh[n]||0);}};for(var be=0,bj=h(R[n],bh&&bh[n]||0);be<bj;be++){R[be]=bc(R[be],bi);e(R,be);bh&&(bh[be]=bc(bh[be],d));bh&&e(bh,be);E(R,bh,bi,d,be);E(bh,R,d,bi,be);var bd=R[be],bk=bh&&bh[be],S=bd[n],bf=bh&&bk[n];bi.x=bd[S-2];bi.y=bd[S-1];bi.bx=aa(bd[S-4])||bi.x;bi.by=aa(bd[S-3])||bi.y;d.bx=bh&&(aa(bk[bf-4])||d.x);d.by=bh&&(aa(bk[bf-3])||d.y);d.x=bh&&bk[bf-2];d.y=bh&&bk[bf-1];}return bh?[R,bh]:R;},null,aD),r=an(function(bg){var bf=[];for(var bc=0,bh=bg[n];bc<bh;bc++){var e={},be=bg[bc].match(/^([^:]*):?([\d\.]*)/);e.color=au.getRGB(be[1]);if(e.color.error){return null;}e.color=e.color.hex;be[2]&&(e.offset=be[2]+"%");bf[f](e);}for(bc=1,bh=bf[n]-1;bc<bh;bc++){if(!bf[bc].offset){var E=aa(bf[bc-1].offset||0),R=0;for(var S=bc+1;S<bh;S++){if(bf[S].offset){R=bf[S].offset;break;}}if(!R){R=100;S=bh;}R=aa(R);var bd=(R-E)/(S-bc+1);for(;bc<S;bc++){E+=bd;bf[bc].offset=E+"%";}}}return bf;}),av=function(d,R,i,E){var e;if(au.is(d,"string")||au.is(d,"object")){e=au.is(d,"string")?P.getElementById(d):d;if(e.tagName){if(R==null){return{container:e,width:e.style.pixelWidth||e.offsetWidth,height:e.style.pixelHeight||e.offsetHeight};}else{return{container:e,width:R,height:i};}}}else{if(au.is(d,aq)&&E!=null){return{container:1,x:d,y:R,width:i,height:E};}}},aP=function(d,i){var e=this;for(var E in i){if(i[W](E)&&!(E in d)){switch(typeof i[E]){case"function":(function(R){d[E]=d===e?R:function(){return R[a7](e,arguments);};})(i[E]);break;case"object":d[E]=d[E]||{};aP.call(this,d[E],i[E]);break;default:d[E]=i[E];break;}}}},ap=function(d,e){d==e.top&&(e.top=d.prev);d==e.bottom&&(e.bottom=d.next);d.next&&(d.next.prev=d.prev);d.prev&&(d.prev.next=d.next);},ac=function(d,e){if(e.top===d){return;}ap(d,e);d.next=null;d.prev=e.top;e.top.next=d;e.top=d;},l=function(d,e){if(e.bottom===d){return;}ap(d,e);d.next=e.bottom;d.prev=null;e.bottom.prev=d;e.bottom=d;},D=function(e,d,i){ap(e,i);d==i.top&&(i.top=e);d.next&&(d.next.prev=e);e.next=d.next;e.prev=d;d.next=e;},ax=function(e,d,i){ap(e,i);d==i.bottom&&(i.bottom=e);d.prev&&(d.prev.next=e);e.prev=d.prev;d.prev=e;e.next=d;},u=function(d){return function(){throw new Error("Rapha\xebl: you are calling to method \u201c"+d+"\u201d of removed object");};},az=/^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/;if(au.svg){a4[a9].svgns="http://www.w3.org/2000/svg";a4[a9].xlink="http://www.w3.org/1999/xlink";U=function(d){return +d+(~~d===d)*0.5;};var Z=function(S){for(var e=0,E=S[n];e<E;e++){if(bb.call(S[e][0])!="a"){for(var d=1,R=S[e][n];d<R;d++){S[e][d]=U(S[e][d]);}}else{S[e][6]=U(S[e][6]);S[e][7]=U(S[e][7]);}}return S;},aS=function(i,d){if(d){for(var e in d){if(d[W](e)){i[y](e,d[e]+aA);}}}else{return P.createElementNS(a4[a9].svgns,i);}};au[aJ]=function(){return"Your browser supports SVG.\nYou are running Rapha\xebl "+this.version;};var s=function(d,E){var e=aS("path");E.canvas&&E.canvas[aU](e);var i=new aF(e,E);i.type="path";ae(i,{fill:"none",stroke:"#000",path:d});return i;};var b=function(R,bk,d){var bh="linear",be=0.5,bc=0.5,bm=R.style;bk=(bk+aA)[aZ](az,function(bo,i,bp){bh="radial";if(i&&bp){be=aa(i);bc=aa(bp);var bn=((bc>0.5)*2-1);aV(be-0.5,2)+aV(bc-0.5,2)>0.25&&(bc=af.sqrt(0.25-aV(be-0.5,2))*bn+0.5)&&bc!=0.5&&(bc=bc.toFixed(5)-0.00001*bn);}return aA;});bk=bk[C](/\s*\-\s*/);if(bh=="linear"){var bd=bk.shift();bd=-aa(bd);if(isNaN(bd)){return null;}var S=[0,0,af.cos(bd*af.PI/180),af.sin(bd*af.PI/180)],bj=1/(h(af.abs(S[2]),af.abs(S[3]))||1);S[2]*=bj;S[3]*=bj;if(S[2]<0){S[0]=-S[2];S[2]=0;}if(S[3]<0){S[1]=-S[3];S[3]=0;}}var bg=r(bk);if(!bg){return null;}var e=R.getAttribute("fill");e=e.match(/^url\(#(.*)\)$/);e&&d.defs.removeChild(P.getElementById(e[1]));var E=aS(bh+"Gradient");E.id="r"+(au._id++)[aJ](36);aS(E,bh=="radial"?{fx:be,fy:bc}:{x1:S[0],y1:S[1],x2:S[2],y2:S[3]});d.defs[aU](E);for(var bf=0,bl=bg[n];bf<bl;bf++){var bi=aS("stop");aS(bi,{offset:bg[bf].offset?bg[bf].offset:!bf?"0%":"100%","stop-color":bg[bf].color||"#fff"});E[aU](bi);}aS(R,{fill:"url(#"+E.id+")",opacity:1,"fill-opacity":1});bm.fill=aA;bm.opacity=1;bm.fillOpacity=1;return 1;};var Q=function(e){var d=e.getBBox();aS(e.pattern,{patternTransform:au.format("translate({0},{1})",d.x,d.y)});};var ae=function(bi,br){var bl={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},bn=bi.node,bj=bi.attrs,bf=bi.rotate(),S=function(by,bx){bx=bl[bb.call(bx)];if(bx){var bv=by.attrs["stroke-width"]||"1",bt={round:bv,square:bv,butt:0}[by.attrs["stroke-linecap"]||br["stroke-linecap"]]||0,bw=[];var bu=bx[n];while(bu--){bw[bu]=bx[bu]*bv+((bu%2)?1:-1)*bt;}aS(bn,{"stroke-dasharray":bw[aH](",")});}};br[W]("rotation")&&(bf=br.rotation);var be=(bf+aA)[C](a);if(!(be.length-1)){be=null;}else{be[1]=+be[1];be[2]=+be[2];}aa(bf)&&bi.rotate(0,true);for(var bm in br){if(br[W](bm)){if(!k[W](bm)){continue;}var bk=br[bm];bj[bm]=bk;switch(bm){case"blur":bi.blur(bk);break;case"rotation":bi.rotate(bk,true);break;case"href":case"title":case"target":var bp=bn.parentNode;if(bb.call(bp.tagName)!="a"){var E=aS("a");bp.insertBefore(E,bn);E[aU](bn);bp=E;}bp.setAttributeNS(bi.paper.xlink,bm,bk);break;case"cursor":bn.style.cursor=bk;break;case"clip-rect":var e=(bk+aA)[C](a);if(e[n]==4){bi.clip&&bi.clip.parentNode.parentNode.removeChild(bi.clip.parentNode);var i=aS("clipPath"),bo=aS("rect");i.id="r"+(au._id++)[aJ](36);aS(bo,{x:e[0],y:e[1],width:e[2],height:e[3]});i[aU](bo);bi.paper.defs[aU](i);aS(bn,{"clip-path":"url(#"+i.id+")"});bi.clip=bo;}if(!bk){var bq=P.getElementById(bn.getAttribute("clip-path")[aZ](/(^url\(#|\)$)/g,aA));bq&&bq.parentNode.removeChild(bq);aS(bn,{"clip-path":aA});delete bi.clip;}break;case"path":if(bi.type=="path"){aS(bn,{d:bk?bj.path=Z(t(bk)):"M0,0"});}break;case"width":bn[y](bm,bk);if(bj.fx){bm="x";bk=bj.x;}else{break;}case"x":if(bj.fx){bk=-bj.x-(bj.width||0);}case"rx":if(bm=="rx"&&bi.type=="rect"){break;}case"cx":be&&(bm=="x"||bm=="cx")&&(be[1]+=bk-bj[bm]);bn[y](bm,U(bk));bi.pattern&&Q(bi);break;case"height":bn[y](bm,bk);if(bj.fy){bm="y";bk=bj.y;}else{break;}case"y":if(bj.fy){bk=-bj.y-(bj.height||0);}case"ry":if(bm=="ry"&&bi.type=="rect"){break;}case"cy":be&&(bm=="y"||bm=="cy")&&(be[2]+=bk-bj[bm]);bn[y](bm,U(bk));bi.pattern&&Q(bi);break;case"r":if(bi.type=="rect"){aS(bn,{rx:bk,ry:bk});}else{bn[y](bm,bk);}break;case"src":if(bi.type=="image"){bn.setAttributeNS(bi.paper.xlink,"href",bk);}break;case"stroke-width":bn.style.strokeWidth=bk;bn[y](bm,bk);if(bj["stroke-dasharray"]){S(bi,bj["stroke-dasharray"]);}break;case"stroke-dasharray":S(bi,bk);break;case"translation":var bc=(bk+aA)[C](a);bc[0]=+bc[0]||0;bc[1]=+bc[1]||0;if(be){be[1]+=bc[0];be[2]+=bc[1];}v.call(bi,bc[0],bc[1]);break;case"scale":bc=(bk+aA)[C](a);bi.scale(+bc[0]||1,+bc[1]||+bc[0]||1,isNaN(aa(bc[2]))?null:+bc[2],isNaN(aa(bc[3]))?null:+bc[3]);break;case"fill":var R=(bk+aA).match(c);if(R){i=aS("pattern");var bh=aS("image");i.id="r"+(au._id++)[aJ](36);aS(i,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1});aS(bh,{x:0,y:0});bh.setAttributeNS(bi.paper.xlink,"href",R[1]);i[aU](bh);var bs=P.createElement("img");bs.style.cssText="position:absolute;left:-9999em;top-9999em";bs.onload=function(){aS(i,{width:this.offsetWidth,height:this.offsetHeight});aS(bh,{width:this.offsetWidth,height:this.offsetHeight});P.body.removeChild(this);bi.paper.safari();};P.body[aU](bs);bs.src=R[1];bi.paper.defs[aU](i);bn.style.fill="url(#"+i.id+")";aS(bn,{fill:"url(#"+i.id+")"});bi.pattern=i;bi.pattern&&Q(bi);break;}if(!au.getRGB(bk).error){delete br.gradient;delete bj.gradient;!au.is(bj.opacity,"undefined")&&au.is(br.opacity,"undefined")&&aS(bn,{opacity:bj.opacity});!au.is(bj["fill-opacity"],"undefined")&&au.is(br["fill-opacity"],"undefined")&&aS(bn,{"fill-opacity":bj["fill-opacity"]});}else{if((({circle:1,ellipse:1})[W](bi.type)||(bk+aA).charAt()!="r")&&b(bn,bk,bi.paper)){bj.gradient=bk;bj.fill="none";break;}}case"stroke":bn[y](bm,au.getRGB(bk).hex);break;case"gradient":(({circle:1,ellipse:1})[W](bi.type)||(bk+aA).charAt()!="r")&&b(bn,bk,bi.paper);break;case"opacity":case"fill-opacity":if(bj.gradient){var d=P.getElementById(bn.getAttribute("fill")[aZ](/^url\(#|\)$/g,aA));if(d){var bd=d.getElementsByTagName("stop");bd[bd[n]-1][y]("stop-opacity",bk);}break;}default:bm=="font-size"&&(bk=K(bk,10)+"px");var bg=bm[aZ](/(\-.)/g,function(bt){return aX.call(bt.substring(1));});bn.style[bg]=bk;bn[y](bm,bk);break;}}}I(bi,br);if(be){bi.rotate(be.join(at));}else{aa(bf)&&bi.rotate(bf,true);}};var j=1.2,I=function(d,R){if(d.type!="text"||!(R[W]("text")||R[W]("font")||R[W]("font-size")||R[W]("x")||R[W]("y"))){return;}var bf=d.attrs,e=d.node,bh=e.firstChild?K(P.defaultView.getComputedStyle(e.firstChild,aA).getPropertyValue("font-size"),10):10;if(R[W]("text")){bf.text=R.text;while(e.firstChild){e.removeChild(e.firstChild);}var E=(R.text+aA)[C]("\n");for(var S=0,bg=E[n];S<bg;S++){if(E[S]){var bd=aS("tspan");S&&aS(bd,{dy:bh*j,x:bf.x});bd[aU](P.createTextNode(E[S]));e[aU](bd);}}}else{E=e.getElementsByTagName("tspan");for(S=0,bg=E[n];S<bg;S++){S&&aS(E[S],{dy:bh*j,x:bf.x});}}aS(e,{y:bf.y});var bc=d.getBBox(),be=bf.y-(bc.y+bc.height/2);be&&isFinite(be)&&aS(e,{y:bf.y+be});},aF=function(e,d){var E=0,i=0;this[0]=e;this.id=au._oid++;this.node=e;e.raphael=this;this.paper=d;this.attrs=this.attrs||{};this.transformations=[];this._={tx:0,ty:0,rt:{deg:0,cx:0,cy:0},sx:1,sy:1};!d.bottom&&(d.bottom=this);this.prev=d.top;d.top&&(d.top.next=this);d.top=this;this.next=null;};aF[a9].rotate=function(e,d,E){if(this.removed){return this;}if(e==null){if(this._.rt.cx){return[this._.rt.deg,this._.rt.cx,this._.rt.cy][aH](at);}return this._.rt.deg;}var i=this.getBBox();e=(e+aA)[C](a);if(e[n]-1){d=aa(e[1]);E=aa(e[2]);}e=aa(e[0]);if(d!=null){this._.rt.deg=e;}else{this._.rt.deg+=e;}(E==null)&&(d=null);this._.rt.cx=d;this._.rt.cy=E;d=d==null?i.x+i.width/2:d;E=E==null?i.y+i.height/2:E;if(this._.rt.deg){this.transformations[0]=au.format("rotate({0} {1} {2})",this._.rt.deg,d,E);this.clip&&aS(this.clip,{transform:au.format("rotate({0} {1} {2})",-this._.rt.deg,d,E)});}else{this.transformations[0]=aA;this.clip&&aS(this.clip,{transform:aA});}aS(this.node,{transform:this.transformations[aH](at)});return this;};aF[a9].hide=function(){!this.removed&&(this.node.style.display="none");return this;};aF[a9].show=function(){!this.removed&&(this.node.style.display="");return this;};aF[a9].remove=function(){if(this.removed){return;}ap(this,this.paper);this.node.parentNode.removeChild(this.node);for(var d in this){delete this[d];}this.removed=true;};aF[a9].getBBox=function(){if(this.removed){return this;}if(this.type=="path"){return Y(this.attrs.path);}if(this.node.style.display=="none"){this.show();var E=true;}var bd={};try{bd=this.node.getBBox();}catch(S){}finally{bd=bd||{};}if(this.type=="text"){bd={x:bd.x,y:Infinity,width:0,height:0};for(var d=0,R=this.node.getNumberOfChars();d<R;d++){var bc=this.node.getExtentOfChar(d);(bc.y<bd.y)&&(bd.y=bc.y);(bc.y+bc.height-bd.y>bd.height)&&(bd.height=bc.y+bc.height-bd.y);(bc.x+bc.width-bd.x>bd.width)&&(bd.width=bc.x+bc.width-bd.x);}}E&&this.hide();return bd;};aF[a9].attr=function(E,bd){if(this.removed){return this;}if(E==null){var S={};for(var R in this.attrs){if(this.attrs[W](R)){S[R]=this.attrs[R];}}this._.rt.deg&&(S.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(S.scale=this.scale());S.gradient&&S.fill=="none"&&(S.fill=S.gradient)&&delete S.gradient;return S;}if(bd==null&&au.is(E,"string")){if(E=="translation"){return v.call(this);}if(E=="rotation"){return this.rotate();}if(E=="scale"){return this.scale();}if(E=="fill"&&this.attrs.fill=="none"&&this.attrs.gradient){return this.attrs.gradient;}return this.attrs[E];}if(bd==null&&au.is(E,"array")){var d={};for(var e=0,bc=E.length;e<bc;e++){d[E[e]]=this.attr(E[e]);}return d;}if(bd!=null){var be={};be[E]=bd;ae(this,be);}else{if(E!=null&&au.is(E,"object")){ae(this,E);}}return this;};aF[a9].toFront=function(){if(this.removed){return this;}this.node.parentNode[aU](this.node);var d=this.paper;d.top!=this&&ac(this,d);return this;};aF[a9].toBack=function(){if(this.removed){return this;}if(this.node.parentNode.firstChild!=this.node){this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild);l(this,this.paper);var d=this.paper;}return this;};aF[a9].insertAfter=function(d){if(this.removed){return this;}var e=d.node;if(e.nextSibling){e.parentNode.insertBefore(this.node,e.nextSibling);}else{e.parentNode[aU](this.node);}D(this,d,this.paper);return this;};aF[a9].insertBefore=function(d){if(this.removed){return this;}var e=d.node;e.parentNode.insertBefore(this.node,e);ax(this,d,this.paper);return this;};aF[a9].blur=function(e){var d=this;if(+e!==0){var i=aS("filter"),E=aS("feGaussianBlur");d.attrs.blur=e;i.id="r"+(au._id++)[aJ](36);aS(E,{stdDeviation:+e||1.5});i.appendChild(E);d.paper.defs.appendChild(i);d._blur=i;aS(d.node,{filter:"url(#"+i.id+")"});}else{if(d._blur){d._blur.parentNode.removeChild(d._blur);delete d._blur;delete d.attrs.blur;}d.node.removeAttribute("filter");}};var V=function(e,d,S,R){d=U(d);S=U(S);var E=aS("circle");e.canvas&&e.canvas[aU](E);var i=new aF(E,e);i.attrs={cx:d,cy:S,r:R,fill:"none",stroke:"#000"};i.type="circle";aS(E,i.attrs);return i;};var aO=function(i,d,bd,e,S,bc){d=U(d);bd=U(bd);var R=aS("rect");i.canvas&&i.canvas[aU](R);var E=new aF(R,i);E.attrs={x:d,y:bd,width:e,height:S,r:bc||0,rx:bc||0,ry:bc||0,fill:"none",stroke:"#000"};E.type="rect";aS(R,E.attrs);return E;};var am=function(e,d,bc,S,R){d=U(d);bc=U(bc);var E=aS("ellipse");e.canvas&&e.canvas[aU](E);var i=new aF(E,e);i.attrs={cx:d,cy:bc,rx:S,ry:R,fill:"none",stroke:"#000"};i.type="ellipse";aS(E,i.attrs);return i;};var q=function(i,bc,d,bd,e,S){var R=aS("image");aS(R,{x:d,y:bd,width:e,height:S,preserveAspectRatio:"none"});R.setAttributeNS(i.xlink,"href",bc);i.canvas&&i.canvas[aU](R);var E=new aF(R,i);E.attrs={x:d,y:bd,width:e,height:S,src:bc};E.type="image";return E;};var ab=function(e,d,S,R){var E=aS("text");aS(E,{x:d,y:S,"text-anchor":"middle"});e.canvas&&e.canvas[aU](E);var i=new aF(E,e);i.attrs={x:d,y:S,"text-anchor":"middle",text:R,font:k.font,stroke:"none",fill:"#000"};i.type="text";ae(i,i.attrs);return i;};var a6=function(e,d){this.width=e||this.width;this.height=d||this.height;this.canvas[y]("width",this.width);this.canvas[y]("height",this.height);return this;};var z=function(){var E=av[a7](0,arguments),i=E&&E.container,e=E.x,bc=E.y,R=E.width,d=E.height;if(!i){throw new Error("SVG container not found.");}var S=aS("svg");R=R||512;d=d||342;aS(S,{xmlns:"http://www.w3.org/2000/svg",version:1.1,width:R,height:d});if(i==1){S.style.cssText="position:absolute;left:"+e+"px;top:"+bc+"px";P.body[aU](S);}else{if(i.firstChild){i.insertBefore(S,i.firstChild);}else{i[aU](S);}}i=new a4;i.width=R;i.height=d;i.canvas=S;aP.call(i,i,au.fn);i.clear();return i;};a4[a9].clear=function(){var d=this.canvas;while(d.firstChild){d.removeChild(d.firstChild);}this.bottom=this.top=null;(this.desc=aS("desc"))[aU](P.createTextNode("Created with Rapha\xebl"));d[aU](this.desc);d[aU](this.defs=aS("defs"));};a4[a9].remove=function(){this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var d in this){this[d]=u(d);}};}if(au.vml){var H={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},ay=/([clmz]),?([^clmz]*)/gi,ba=/-?[^,\s-]+/g,aI=1000+at+1000,p=10,aQ=function(bh){var be=/[ahqstv]/ig,E=t;(bh+aA).match(be)&&(E=L);be=/[clmz]/g;if(E==t&&!(bh+aA).match(be)){var bd=(bh+aA)[aZ](ay,function(bk,bm,bi){var bl=[],i=bb.call(bm)=="m",bj=H[bm];bi[aZ](ba,function(bn){if(i&&bl[n]==2){bj+=bl+H[bm=="m"?"l":"L"];bl=[];}bl[f](U(bn*p));});return bj+bl;});return bd;}var bf=E(bh),e,d;bd=[];for(var S=0,bg=bf[n];S<bg;S++){e=bf[S];d=bb.call(bf[S][0]);d=="z"&&(d="x");for(var R=1,bc=e[n];R<bc;R++){d+=U(e[R]*p)+(R!=bc-1?",":aA);}bd[f](d);}return bd[aH](at);};au[aJ]=function(){return"Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl "+this.version;};s=function(i,e){var S=al("group");S.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";S.coordsize=e.coordsize;S.coordorigin=e.coordorigin;var R=al("shape"),E=R.style;E.width=e.width+"px";E.height=e.height+"px";R.coordsize=aI;R.coordorigin=e.coordorigin;S[aU](R);var bc=new aF(R,S,e),d={fill:"none",stroke:"#000"};i&&(d.path=i);bc.isAbsolute=true;bc.type="path";bc.path=[];bc.Path=aA;ae(bc,d);e.canvas[aU](S);return bc;};ae=function(bf,bk){bf.attrs=bf.attrs||{};var bi=bf.node,bl=bf.attrs,bc=bi.style,E,bp=bf;for(var bd in bk){if(bk[W](bd)){bl[bd]=bk[bd];}}bk.href&&(bi.href=bk.href);bk.title&&(bi.title=bk.title);bk.target&&(bi.target=bk.target);bk.cursor&&(bc.cursor=bk.cursor);"blur" in bk&&bf.blur(bk.blur);if(bk.path&&bf.type=="path"){bl.path=bk.path;bi.path=aQ(bl.path);}if(bk.rotation!=null){bf.rotate(bk.rotation,true);}if(bk.translation){E=(bk.translation+aA)[C](a);v.call(bf,E[0],E[1]);if(bf._.rt.cx!=null){bf._.rt.cx+=+E[0];bf._.rt.cy+=+E[1];bf.setBox(bf.attrs,E[0],E[1]);}}if(bk.scale){E=(bk.scale+aA)[C](a);bf.scale(+E[0]||1,+E[1]||+E[0]||1,+E[2]||null,+E[3]||null);}if("clip-rect" in bk){var d=(bk["clip-rect"]+aA)[C](a);if(d[n]==4){d[2]=+d[2]+(+d[0]);d[3]=+d[3]+(+d[1]);var be=bi.clipRect||P.createElement("div"),bo=be.style,S=bi.parentNode;bo.clip=au.format("rect({1}px {2}px {3}px {0}px)",d);if(!bi.clipRect){bo.position="absolute";bo.top=0;bo.left=0;bo.width=bf.paper.width+"px";bo.height=bf.paper.height+"px";S.parentNode.insertBefore(be,S);be[aU](S);bi.clipRect=be;}}if(!bk["clip-rect"]){bi.clipRect&&(bi.clipRect.style.clip=aA);}}if(bf.type=="image"&&bk.src){bi.src=bk.src;}if(bf.type=="image"&&bk.opacity){bi.filterOpacity=" progid:DXImageTransform.Microsoft.Alpha(opacity="+(bk.opacity*100)+")";bc.filter=(bi.filterMatrix||aA)+(bi.filterOpacity||aA);}bk.font&&(bc.font=bk.font);bk["font-family"]&&(bc.fontFamily='"'+bk["font-family"][C](",")[0][aZ](/^['"]+|['"]+$/g,aA)+'"');bk["font-size"]&&(bc.fontSize=bk["font-size"]);bk["font-weight"]&&(bc.fontWeight=bk["font-weight"]);bk["font-style"]&&(bc.fontStyle=bk["font-style"]);if(bk.opacity!=null||bk["stroke-width"]!=null||bk.fill!=null||bk.stroke!=null||bk["stroke-width"]!=null||bk["stroke-opacity"]!=null||bk["fill-opacity"]!=null||bk["stroke-dasharray"]!=null||bk["stroke-miterlimit"]!=null||bk["stroke-linejoin"]!=null||bk["stroke-linecap"]!=null){bi=bf.shape||bi;var bj=(bi.getElementsByTagName("fill")&&bi.getElementsByTagName("fill")[0]),bm=false;!bj&&(bm=bj=al("fill"));if("fill-opacity" in bk||"opacity" in bk){var e=((+bl["fill-opacity"]+1||2)-1)*((+bl.opacity+1||2)-1);e<0&&(e=0);e>1&&(e=1);bj.opacity=e;}bk.fill&&(bj.on=true);if(bj.on==null||bk.fill=="none"){bj.on=false;}if(bj.on&&bk.fill){var i=bk.fill.match(c);if(i){bj.src=i[1];bj.type="tile";}else{bj.color=au.getRGB(bk.fill).hex;bj.src=aA;bj.type="solid";if(au.getRGB(bk.fill).error&&(bp.type in {circle:1,ellipse:1}||(bk.fill+aA).charAt()!="r")&&b(bp,bk.fill)){bl.fill="none";bl.gradient=bk.fill;}}}bm&&bi[aU](bj);var R=(bi.getElementsByTagName("stroke")&&bi.getElementsByTagName("stroke")[0]),bn=false;!R&&(bn=R=al("stroke"));if((bk.stroke&&bk.stroke!="none")||bk["stroke-width"]||bk["stroke-opacity"]!=null||bk["stroke-dasharray"]||bk["stroke-miterlimit"]||bk["stroke-linejoin"]||bk["stroke-linecap"]){R.on=true;}(bk.stroke=="none"||R.on==null||bk.stroke==0||bk["stroke-width"]==0)&&(R.on=false);R.on&&bk.stroke&&(R.color=au.getRGB(bk.stroke).hex);e=((+bl["stroke-opacity"]+1||2)-1)*((+bl.opacity+1||2)-1);var bg=(aa(bk["stroke-width"])||1)*0.75;e<0&&(e=0);e>1&&(e=1);bk["stroke-width"]==null&&(bg=bl["stroke-width"]);bk["stroke-width"]&&(R.weight=bg);bg&&bg<1&&(e*=bg)&&(R.weight=1);R.opacity=e;bk["stroke-linejoin"]&&(R.joinstyle=bk["stroke-linejoin"]||"miter");R.miterlimit=bk["stroke-miterlimit"]||8;bk["stroke-linecap"]&&(R.endcap=bk["stroke-linecap"]=="butt"?"flat":bk["stroke-linecap"]=="square"?"square":"round");if(bk["stroke-dasharray"]){var bh={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};R.dashstyle=bh[W](bk["stroke-dasharray"])?bh[bk["stroke-dasharray"]]:aA;}bn&&bi[aU](R);}if(bp.type=="text"){bc=bp.paper.span.style;bl.font&&(bc.font=bl.font);bl["font-family"]&&(bc.fontFamily=bl["font-family"]);bl["font-size"]&&(bc.fontSize=bl["font-size"]);bl["font-weight"]&&(bc.fontWeight=bl["font-weight"]);bl["font-style"]&&(bc.fontStyle=bl["font-style"]);bp.node.string&&(bp.paper.span.innerHTML=(bp.node.string+aA)[aZ](/</g,"&#60;")[aZ](/&/g,"&#38;")[aZ](/\n/g,"<br>"));bp.W=bl.w=bp.paper.span.offsetWidth;bp.H=bl.h=bp.paper.span.offsetHeight;bp.X=bl.x;bp.Y=bl.y+U(bp.H/2);switch(bl["text-anchor"]){case"start":bp.node.style["v-text-align"]="left";bp.bbx=U(bp.W/2);break;case"end":bp.node.style["v-text-align"]="right";bp.bbx=-U(bp.W/2);break;default:bp.node.style["v-text-align"]="center";break;}}};b=function(d,bd){d.attrs=d.attrs||{};var be=d.attrs,bg=d.node.getElementsByTagName("fill"),S="linear",bc=".5 .5";d.attrs.gradient=bd;bd=(bd+aA)[aZ](az,function(bi,bj,i){S="radial";if(bj&&i){bj=aa(bj);i=aa(i);aV(bj-0.5,2)+aV(i-0.5,2)>0.25&&(i=af.sqrt(0.25-aV(bj-0.5,2))*((i>0.5)*2-1)+0.5);bc=bj+at+i;}return aA;});bd=bd[C](/\s*\-\s*/);if(S=="linear"){var e=bd.shift();e=-aa(e);if(isNaN(e)){return null;}}var R=r(bd);if(!R){return null;}d=d.shape||d.node;bg=bg[0]||al("fill");if(R[n]){bg.on=true;bg.method="none";bg.type=(S=="radial")?"gradientradial":"gradient";bg.color=R[0].color;bg.color2=R[R[n]-1].color;var bh=[];for(var E=0,bf=R[n];E<bf;E++){R[E].offset&&bh[f](R[E].offset+at+R[E].color);}bg.colors&&(bg.colors.value=bh[n]?bh[aH](","):"0% "+bg.color);if(S=="radial"){bg.focus="100%";bg.focussize=bc;bg.focusposition=bc;}else{bg.angle=(270-e)%360;}}return 1;};aF=function(R,bc,d){var S=0,i=0,e=0,E=1;this[0]=R;this.id=au._oid++;this.node=R;R.raphael=this;this.X=0;this.Y=0;this.attrs={};this.Group=bc;this.paper=d;this._={tx:0,ty:0,rt:{deg:0},sx:1,sy:1};!d.bottom&&(d.bottom=this);this.prev=d.top;d.top&&(d.top.next=this);d.top=this;this.next=null;};aF[a9].rotate=function(e,d,i){if(this.removed){return this;}if(e==null){if(this._.rt.cx){return[this._.rt.deg,this._.rt.cx,this._.rt.cy][aH](at);}return this._.rt.deg;}e=(e+aA)[C](a);if(e[n]-1){d=aa(e[1]);i=aa(e[2]);}e=aa(e[0]);if(d!=null){this._.rt.deg=e;}else{this._.rt.deg+=e;}i==null&&(d=null);this._.rt.cx=d;this._.rt.cy=i;this.setBox(this.attrs,d,i);this.Group.style.rotation=this._.rt.deg;return this;};aF[a9].setBox=function(bo,e,d){if(this.removed){return this;}var bi=this.Group.style,R=(this.shape&&this.shape.style)||this.node.style;bo=bo||{};for(var bm in bo){if(bo[W](bm)){this.attrs[bm]=bo[bm];}}e=e||this._.rt.cx;d=d||this._.rt.cy;var bk=this.attrs,bd,bc,be,bn;switch(this.type){case"circle":bd=bk.cx-bk.r;bc=bk.cy-bk.r;be=bn=bk.r*2;break;case"ellipse":bd=bk.cx-bk.rx;bc=bk.cy-bk.ry;be=bk.rx*2;bn=bk.ry*2;break;case"rect":case"image":bd=+bk.x;bc=+bk.y;be=bk.width||0;bn=bk.height||0;break;case"text":this.textpath.v=["m",U(bk.x),", ",U(bk.y-2),"l",U(bk.x)+1,", ",U(bk.y-2)][aH](aA);bd=bk.x-U(this.W/2);bc=bk.y-this.H/2;be=this.W;bn=this.H;break;case"path":if(!this.attrs.path){bd=0;bc=0;be=this.paper.width;bn=this.paper.height;}else{var bl=Y(this.attrs.path);bd=bl.x;bc=bl.y;be=bl.width;bn=bl.height;}break;default:bd=0;bc=0;be=this.paper.width;bn=this.paper.height;break;}e=(e==null)?bd+be/2:e;d=(d==null)?bc+bn/2:d;var E=e-this.paper.width/2,bh=d-this.paper.height/2,bg;bi.left!=(bg=E+"px")&&(bi.left=bg);bi.top!=(bg=bh+"px")&&(bi.top=bg);this.X=this.type=="path"?-E:bd;this.Y=this.type=="path"?-bh:bc;this.W=be;this.H=bn;if(this.type=="path"){R.left!=(bg=-E*p+"px")&&(R.left=bg);R.top!=(bg=-bh*p+"px")&&(R.top=bg);}else{if(this.type=="text"){R.left!=(bg=-E+"px")&&(R.left=bg);R.top!=(bg=-bh+"px")&&(R.top=bg);}else{bi.width!=(bg=this.paper.width+"px")&&(bi.width=bg);bi.height!=(bg=this.paper.height+"px")&&(bi.height=bg);R.left!=(bg=bd-E+"px")&&(R.left=bg);R.top!=(bg=bc-bh+"px")&&(R.top=bg);R.width!=(bg=be+"px")&&(R.width=bg);R.height!=(bg=bn+"px")&&(R.height=bg);var S=(+bo.r||0)/aR(be,bn);if(this.type=="rect"&&this.arcsize.toFixed(4)!=S.toFixed(4)&&(S||this.arcsize)){var bj=al("roundrect"),bp={},bf=this.events&&this.events[n];bm=0;bj.arcsize=S;bj.raphael=this;this.Group[aU](bj);this.Group.removeChild(this.node);this[0]=this.node=bj;this.arcsize=S;for(bm in bk){bp[bm]=bk[bm];}delete bp.scale;this.attr(bp);if(this.events){for(;bm<bf;bm++){this.events[bm].unbind=ai(this.node,this.events[bm].name,this.events[bm].f,this);}}}}}};aF[a9].hide=function(){!this.removed&&(this.Group.style.display="none");return this;};aF[a9].show=function(){!this.removed&&(this.Group.style.display="block");return this;};aF[a9].getBBox=function(){if(this.removed){return this;}if(this.type=="path"){return Y(this.attrs.path);}return{x:this.X+(this.bbx||0),y:this.Y,width:this.W,height:this.H};};aF[a9].remove=function(){if(this.removed){return;}ap(this,this.paper);this.node.parentNode.removeChild(this.node);this.Group.parentNode.removeChild(this.Group);this.shape&&this.shape.parentNode.removeChild(this.shape);for(var d in this){delete this[d];}this.removed=true;};aF[a9].attr=function(e,bc){if(this.removed){return this;}if(e==null){var R={};for(var E in this.attrs){if(this.attrs[W](E)){R[E]=this.attrs[E];}}this._.rt.deg&&(R.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(R.scale=this.scale());R.gradient&&R.fill=="none"&&(R.fill=R.gradient)&&delete R.gradient;return R;}if(bc==null&&au.is(e,"string")){if(e=="translation"){return v.call(this);}if(e=="rotation"){return this.rotate();}if(e=="scale"){return this.scale();}if(e=="fill"&&this.attrs.fill=="none"&&this.attrs.gradient){return this.attrs.gradient;}return this.attrs[e];}if(this.attrs&&bc==null&&au.is(e,"array")){var S,d={};for(E=0,S=e[n];E<S;E++){d[e[E]]=this.attr(e[E]);}return d;}var bd;if(bc!=null){bd={};bd[e]=bc;}bc==null&&au.is(e,"object")&&(bd=e);if(bd){if(bd.text&&this.type=="text"){this.node.string=bd.text;}ae(this,bd);if(bd.gradient&&(({circle:1,ellipse:1})[W](this.type)||(bd.gradient+aA).charAt()!="r")){b(this,bd.gradient);}(this.type!="path"||this._.rt.deg)&&this.setBox(this.attrs);}return this;};aF[a9].toFront=function(){!this.removed&&this.Group.parentNode[aU](this.Group);this.paper.top!=this&&ac(this,this.paper);return this;};aF[a9].toBack=function(){if(this.removed){return this;}if(this.Group.parentNode.firstChild!=this.Group){this.Group.parentNode.insertBefore(this.Group,this.Group.parentNode.firstChild);l(this,this.paper);}return this;};aF[a9].insertAfter=function(d){if(this.removed){return this;}if(d.Group.nextSibling){d.Group.parentNode.insertBefore(this.Group,d.Group.nextSibling);}else{d.Group.parentNode[aU](this.Group);}D(this,d,this.paper);return this;};aF[a9].insertBefore=function(d){if(this.removed){return this;}d.Group.parentNode.insertBefore(this.Group,d.Group);ax(this,d,this.paper);return this;};var a3=/ progid:\S+Blur\([^\)]+\)/g;aF[a9].blur=function(d){var e=this.node.style,i=e.filter;i=i.replace(a3,"");if(+d!==0){this.attrs.blur=d;e.filter=i+" progid:DXImageTransform.Microsoft.Blur(pixelradius="+(+d||1.5)+")";e.margin=Raphael.format("-{0}px 0 0 -{0}px",Math.round(+d||1.5));}else{e.filter=i;e.margin=0;delete this.attrs.blur;}};V=function(e,d,bd,S){var R=al("group"),bc=al("oval"),i=bc.style;R.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";R.coordsize=aI;R.coordorigin=e.coordorigin;R[aU](bc);var E=new aF(bc,R,e);E.type="circle";ae(E,{stroke:"#000",fill:"none"});E.attrs.cx=d;E.attrs.cy=bd;E.attrs.r=S;E.setBox({x:d-S,y:bd-S,width:S*2,height:S*2});e.canvas[aU](R);return E;};aO=function(e,bd,bc,be,E,d){var R=al("group"),i=al("roundrect"),bf=(+d||0)/(aR(be,E));R.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";R.coordsize=aI;R.coordorigin=e.coordorigin;R[aU](i);i.arcsize=bf;var S=new aF(i,R,e);S.type="rect";ae(S,{stroke:"#000"});S.arcsize=bf;S.setBox({x:bd,y:bc,width:be,height:E,r:d});e.canvas[aU](R);return S;};am=function(d,be,bd,i,e){var R=al("group"),E=al("oval"),bc=E.style;R.style.cssText="position:absolute;left:0;top:0;width:"+d.width+"px;height:"+d.height+"px";R.coordsize=aI;R.coordorigin=d.coordorigin;R[aU](E);var S=new aF(E,R,d);S.type="ellipse";ae(S,{stroke:"#000"});S.attrs.cx=be;S.attrs.cy=bd;S.attrs.rx=i;S.attrs.ry=e;S.setBox({x:be-i,y:bd-e,width:i*2,height:e*2});d.canvas[aU](R);return S;};q=function(e,d,be,bd,bf,E){var R=al("group"),i=al("image"),bc=i.style;R.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";R.coordsize=aI;R.coordorigin=e.coordorigin;i.src=d;R[aU](i);var S=new aF(i,R,e);S.type="image";S.attrs.src=d;S.attrs.x=be;S.attrs.y=bd;S.attrs.w=bf;S.attrs.h=E;S.setBox({x:be,y:bd,width:bf,height:E});e.canvas[aU](R);return S;};ab=function(e,be,bd,bf){var R=al("group"),E=al("shape"),bc=E.style,bg=al("path"),d=bg.style,i=al("textpath");R.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";R.coordsize=aI;R.coordorigin=e.coordorigin;bg.v=au.format("m{0},{1}l{2},{1}",U(be*10),U(bd*10),U(be*10)+1);bg.textpathok=true;bc.width=e.width;bc.height=e.height;i.string=bf+aA;i.on=true;E[aU](i);E[aU](bg);R[aU](E);var S=new aF(i,R,e);S.shape=E;S.textpath=bg;S.type="text";S.attrs.text=bf;S.attrs.x=be;S.attrs.y=bd;S.attrs.w=1;S.attrs.h=1;ae(S,{font:k.font,stroke:"none",fill:"#000"});S.setBox();e.canvas[aU](R);return S;};a6=function(i,d){var e=this.canvas.style;i==+i&&(i+="px");d==+d&&(d+="px");e.width=i;e.height=d;e.clip="rect(0 "+i+" "+d+" 0)";return this;};var al;P.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!P.namespaces.rvml&&P.namespaces.add("rvml","urn:schemas-microsoft-com:vml");al=function(d){return P.createElement("<rvml:"+d+' class="rvml">');};}catch(aj){al=function(d){return P.createElement("<"+d+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');};}z=function(){var i=av[a7](0,arguments),d=i.container,be=i.height,bf,e=i.width,bd=i.x,bc=i.y;if(!d){throw new Error("VML container not found.");}var R=new a4,S=R.canvas=P.createElement("div"),E=S.style;e=e||512;be=be||342;e==+e&&(e+="px");be==+be&&(be+="px");R.width=1000;R.height=1000;R.coordsize=p*1000+at+p*1000;R.coordorigin="0 0";R.span=P.createElement("span");R.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";S[aU](R.span);E.cssText=au.format("width:{0};height:{1};position:absolute;clip:rect(0 {0} {1} 0);overflow:hidden",e,be);if(d==1){P.body[aU](S);E.left=bd+"px";E.top=bc+"px";}else{d.style.width=e;d.style.height=be;if(d.firstChild){d.insertBefore(S,d.firstChild);}else{d[aU](S);}}aP.call(R,R,au.fn);return R;};a4[a9].clear=function(){this.canvas.innerHTML=aA;this.span=P.createElement("span");this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";this.canvas[aU](this.span);this.bottom=this.top=null;};a4[a9].remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var d in this){this[d]=u(d);}return true;};}if((/^Apple|^Google/).test(aB.navigator.vendor)&&!(aB.navigator.userAgent.indexOf("Version/4.0")+1)){a4[a9].safari=function(){var d=this.rect(-99,-99,this.width+99,this.height+99);aB.setTimeout(function(){d.remove();});};}else{a4[a9].safari=function(){};}var ai=(function(){if(P.addEventListener){return function(R,i,e,d){var E=function(S){return e.call(d,S);};R.addEventListener(i,E,false);return function(){R.removeEventListener(i,E,false);return true;};};}else{if(P.attachEvent){return function(S,E,i,e){var R=function(bc){return i.call(e,bc||aB.event);};S.attachEvent("on"+E,R);var d=function(){S.detachEvent("on"+E,R);return true;};return d;};}}})();for(var ag=J[n];ag--;){(function(d){aF[a9][d]=function(e){if(au.is(e,"function")){this.events=this.events||[];this.events.push({name:d,f:e,unbind:ai(this.shape||this.node,d,e,this)});}return this;};aF[a9]["un"+d]=function(E){var i=this.events,e=i[n];while(e--){if(i[e].name==d&&i[e].f==E){i[e].unbind();i.splice(e,1);!i.length&&delete this.events;return this;}}return this;};})(J[ag]);}aF[a9].hover=function(e,d){return this.mouseover(e).mouseout(d);};aF[a9].unhover=function(e,d){return this.unmouseover(e).unmouseout(d);};a4[a9].circle=function(d,i,e){return V(this,d||0,i||0,e||0);};a4[a9].rect=function(d,R,e,i,E){return aO(this,d||0,R||0,e||0,i||0,E||0);};a4[a9].ellipse=function(d,E,i,e){return am(this,d||0,E||0,i||0,e||0);};a4[a9].path=function(d){d&&!au.is(d,"string")&&!au.is(d[0],"array")&&(d+=aA);return s(au.format[a7](au,arguments),this);};a4[a9].image=function(E,d,R,e,i){return q(this,E||"about:blank",d||0,R||0,e||0,i||0);};a4[a9].text=function(d,i,e){return ab(this,d||0,i||0,e||aA);};a4[a9].set=function(d){arguments[n]>1&&(d=Array[a9].splice.call(arguments,0,arguments[n]));return new X(d);};a4[a9].setSize=a6;a4[a9].top=a4[a9].bottom=null;a4[a9].raphael=au;function x(){return this.x+at+this.y;}aF[a9].scale=function(bi,bh,E,e){if(bi==null&&bh==null){return{x:this._.sx,y:this._.sy,toString:x};}bh=bh||bi;!+bh&&(bh=bi);var bm,bk,bl,bj,by=this.attrs;if(bi!=0){var bg=this.getBBox(),bd=bg.x+bg.width/2,R=bg.y+bg.height/2,bx=bi/this._.sx,bw=bh/this._.sy;E=(+E||E==0)?E:bd;e=(+e||e==0)?e:R;var bf=~~(bi/af.abs(bi)),bc=~~(bh/af.abs(bh)),bp=this.node.style,bA=E+(bd-E)*bx,bz=e+(R-e)*bw;switch(this.type){case"rect":case"image":var be=by.width*bf*bx,bo=by.height*bc*bw;this.attr({height:bo,r:by.r*aR(bf*bx,bc*bw),width:be,x:bA-be/2,y:bz-bo/2});break;case"circle":case"ellipse":this.attr({rx:by.rx*bf*bx,ry:by.ry*bc*bw,r:by.r*aR(bf*bx,bc*bw),cx:bA,cy:bz});break;case"path":var br=ah(by.path),bs=true;for(var bu=0,bn=br[n];bu<bn;bu++){var bq=br[bu],S=aX.call(bq[0]);if(S=="M"&&bs){continue;}else{bs=false;}if(S=="A"){bq[br[bu][n]-2]*=bx;bq[br[bu][n]-1]*=bw;bq[1]*=bf*bx;bq[2]*=bc*bw;bq[5]=+!(bf+bc?!+bq[5]:+bq[5]);}else{if(S=="H"){for(var bt=1,bv=bq[n];bt<bv;bt++){bq[bt]*=bx;}}else{if(S=="V"){for(bt=1,bv=bq[n];bt<bv;bt++){bq[bt]*=bw;}}else{for(bt=1,bv=bq[n];bt<bv;bt++){bq[bt]*=(bt%2)?bx:bw;}}}}}var d=Y(br);bm=bA-d.x-d.width/2;bk=bz-d.y-d.height/2;br[0][1]+=bm;br[0][2]+=bk;this.attr({path:br});break;}if(this.type in {text:1,image:1}&&(bf!=1||bc!=1)){if(this.transformations){this.transformations[2]="scale("[a2](bf,",",bc,")");this.node[y]("transform",this.transformations[aH](at));bm=(bf==-1)?-by.x-(be||0):by.x;bk=(bc==-1)?-by.y-(bo||0):by.y;this.attr({x:bm,y:bk});by.fx=bf-1;by.fy=bc-1;}else{this.node.filterMatrix=" progid:DXImageTransform.Microsoft.Matrix(M11="[a2](bf,", M12=0, M21=0, M22=",bc,", Dx=0, Dy=0, sizingmethod='auto expand', filtertype='bilinear')");bp.filter=(this.node.filterMatrix||aA)+(this.node.filterOpacity||aA);}}else{if(this.transformations){this.transformations[2]=aA;this.node[y]("transform",this.transformations[aH](at));by.fx=0;by.fy=0;}else{this.node.filterMatrix=aA;bp.filter=(this.node.filterMatrix||aA)+(this.node.filterOpacity||aA);}}by.scale=[bi,bh,E,e][aH](at);this._.sx=bi;this._.sy=bh;}return this;};aF[a9].clone=function(){var d=this.attr();delete d.scale;delete d.translation;return this.paper[this.type]().attr(d);};var g=an(function(E,d,bd,bc,bj,bi,bh,bg,R){var bf=0,S;for(var be=0;be<1.001;be+=0.001){var e=au.findDotsAtSegment(E,d,bd,bc,bj,bi,bh,bg,be);be&&(bf+=aV(aV(S.x-e.x,2)+aV(S.y-e.y,2),0.5));if(bf>=R){return e;}S=e;}}),aK=function(d,e){return function(bl,S,bc){bl=L(bl);var bh,bg,E,bd,R="",bk={},bi,bf=0;for(var be=0,bj=bl.length;be<bj;be++){E=bl[be];if(E[0]=="M"){bh=+E[1];bg=+E[2];}else{bd=o(bh,bg,E[1],E[2],E[3],E[4],E[5],E[6]);if(bf+bd>S){if(e&&!bk.start){bi=g(bh,bg,E[1],E[2],E[3],E[4],E[5],E[6],S-bf);R+=["C",bi.start.x,bi.start.y,bi.m.x,bi.m.y,bi.x,bi.y];if(bc){return R;}bk.start=R;R=["M",bi.x,bi.y+"C",bi.n.x,bi.n.y,bi.end.x,bi.end.y,E[5],E[6]][aH]();bf+=bd;bh=+E[5];bg=+E[6];continue;}if(!d&&!e){bi=g(bh,bg,E[1],E[2],E[3],E[4],E[5],E[6],S-bf);return{x:bi.x,y:bi.y,alpha:bi.alpha};}}bf+=bd;bh=+E[5];bg=+E[6];}R+=E;}bk.end=R;bi=d?bf:e?bk:au.findDotsAtSegment(bh,bg,E[1],E[2],E[3],E[4],E[5],E[6],1);bi.alpha&&(bi={x:bi.x,y:bi.y,alpha:bi.alpha});return bi;};},o=an(function(E,d,bc,S,bi,bh,bg,bf){var R={x:0,y:0},be=0;for(var bd=0;bd<1.01;bd+=0.01){var e=T(E,d,bc,S,bi,bh,bg,bf,bd);bd&&(be+=aV(aV(R.x-e.x,2)+aV(R.y-e.y,2),0.5));R=e;}return be;});var aw=aK(1),G=aK(),N=aK(0,1);aF[a9].getTotalLength=function(){if(this.type!="path"){return;}return aw(this.attrs.path);};aF[a9].getPointAtLength=function(d){if(this.type!="path"){return;}return G(this.attrs.path,d);};aF[a9].getSubpath=function(i,e){if(this.type!="path"){return;}if(af.abs(this.getTotalLength()-e)<0.000001){return N(this.attrs.path,i).end;}var d=N(this.attrs.path,e,1);return i?N(d,i).end:d;};au.easing_formulas={linear:function(d){return d;},"<":function(d){return aV(d,3);},">":function(d){return aV(d-1,3)+1;},"<>":function(d){d=d*2;if(d<1){return aV(d,3)/2;}d-=2;return(aV(d,3)+2)/2;},backIn:function(e){var d=1.70158;return e*e*((d+1)*e-d);},backOut:function(e){e=e-1;var d=1.70158;return e*e*((d+1)*e+d)+1;},elastic:function(i){if(i==0||i==1){return i;}var e=0.3,d=e/4;return aV(2,-10*i)*af.sin((i-d)*(2*af.PI)/e)+1;},bounce:function(E){var e=7.5625,i=2.75,d;if(E<(1/i)){d=e*E*E;}else{if(E<(2/i)){E-=(1.5/i);d=e*E*E+0.75;}else{if(E<(2.5/i)){E-=(2.25/i);d=e*E*E+0.9375;}else{E-=(2.625/i);d=e*E*E+0.984375;}}}return d;}};var M={length:0},a1=function(){var be=+new Date;for(var bq in M){if(bq!="length"&&M[W](bq)){var bv=M[bq];if(bv.stop||bv.el.removed){delete M[bq];M[n]--;continue;}var bc=be-bv.start,bn=bv.ms,bm=bv.easing,br=bv.from,bj=bv.diff,E=bv.to,bi=bv.t,bl=bv.prev||0,bd=bv.el,R=bv.callback,bk={},d;if(bc<bn){var S=au.easing_formulas[bm]?au.easing_formulas[bm](bc/bn):bc/bn;for(var bo in br){if(br[W](bo)){switch(ad[bo]){case"along":d=S*bn*bj[bo];E.back&&(d=E.len-d);var bp=G(E[bo],d);bd.translate(bj.sx-bj.x||0,bj.sy-bj.y||0);bj.x=bp.x;bj.y=bp.y;bd.translate(bp.x-bj.sx,bp.y-bj.sy);E.rot&&bd.rotate(bj.r+bp.alpha,bp.x,bp.y);break;case"number":d=+br[bo]+S*bn*bj[bo];break;case"colour":d="rgb("+[F(U(br[bo].r+S*bn*bj[bo].r)),F(U(br[bo].g+S*bn*bj[bo].g)),F(U(br[bo].b+S*bn*bj[bo].b))][aH](",")+")";break;case"path":d=[];for(var bt=0,bh=br[bo][n];bt<bh;bt++){d[bt]=[br[bo][bt][0]];for(var bs=1,bu=br[bo][bt][n];bs<bu;bs++){d[bt][bs]=+br[bo][bt][bs]+S*bn*bj[bo][bt][bs];}d[bt]=d[bt][aH](at);}d=d[aH](at);break;case"csv":switch(bo){case"translation":var bg=bj[bo][0]*(bc-bl),bf=bj[bo][1]*(bc-bl);bi.x+=bg;bi.y+=bf;d=bg+at+bf;break;case"rotation":d=+br[bo][0]+S*bn*bj[bo][0];br[bo][1]&&(d+=","+br[bo][1]+","+br[bo][2]);break;case"scale":d=[+br[bo][0]+S*bn*bj[bo][0],+br[bo][1]+S*bn*bj[bo][1],(2 in E[bo]?E[bo][2]:aA),(3 in E[bo]?E[bo][3]:aA)][aH](at);break;case"clip-rect":d=[];bt=4;while(bt--){d[bt]=+br[bo][bt]+S*bn*bj[bo][bt];}break;}break;}bk[bo]=d;}}bd.attr(bk);bd._run&&bd._run.call(bd);}else{if(E.along){bp=G(E.along,E.len*!E.back);bd.translate(bj.sx-(bj.x||0)+bp.x-bj.sx,bj.sy-(bj.y||0)+bp.y-bj.sy);E.rot&&bd.rotate(bj.r+bp.alpha,bp.x,bp.y);}(bi.x||bi.y)&&bd.translate(-bi.x,-bi.y);E.scale&&(E.scale=E.scale+aA);bd.attr(E);delete M[bq];M[n]--;bd.in_animation=null;au.is(R,"function")&&R.call(bd);}bv.prev=bc;}}au.svg&&bd&&bd.paper.safari();M[n]&&aB.setTimeout(a1);},F=function(d){return d>255?255:(d<0?0:d);},v=function(d,i){if(d==null){return{x:this._.tx,y:this._.ty,toString:x};}this._.tx+=+d;this._.ty+=+i;switch(this.type){case"circle":case"ellipse":this.attr({cx:+d+this.attrs.cx,cy:+i+this.attrs.cy});break;case"rect":case"image":case"text":this.attr({x:+d+this.attrs.x,y:+i+this.attrs.y});break;case"path":var e=ah(this.attrs.path);e[0][1]+=+d;e[0][2]+=+i;this.attr({path:e});break;}return this;};aF[a9].animateWith=function(e,i,d,R,E){M[e.id]&&(i.start=M[e.id].start);return this.animate(i,d,R,E);};aF[a9].animateAlong=aG();aF[a9].animateAlongBack=aG(1);function aG(d){return function(E,i,e,S){var R={back:d};au.is(e,"function")?(S=e):(R.rot=e);E&&E.constructor==aF&&(E=E.attrs.path);E&&(R.along=E);return this.animate(R,i,S);};}aF[a9].onAnimation=function(d){this._run=d||0;return this;};aF[a9].animate=function(bq,bh,bg,E){if(au.is(bg,"function")||!bg){E=bg||null;}var bl={},e={},be={};for(var bi in bq){if(bq[W](bi)){if(ad[W](bi)){bl[bi]=this.attr(bi);(bl[bi]==null)&&(bl[bi]=k[bi]);e[bi]=bq[bi];switch(ad[bi]){case"along":var bo=aw(bq[bi]),bj=G(bq[bi],bo*!!bq.back),R=this.getBBox();be[bi]=bo/bh;be.tx=R.x;be.ty=R.y;be.sx=bj.x;be.sy=bj.y;e.rot=bq.rot;e.back=bq.back;e.len=bo;bq.rot&&(be.r=aa(this.rotate())||0);break;case"number":be[bi]=(e[bi]-bl[bi])/bh;break;case"colour":bl[bi]=au.getRGB(bl[bi]);var bk=au.getRGB(e[bi]);be[bi]={r:(bk.r-bl[bi].r)/bh,g:(bk.g-bl[bi].g)/bh,b:(bk.b-bl[bi].b)/bh};break;case"path":var S=L(bl[bi],e[bi]);bl[bi]=S[0];var bf=S[1];be[bi]=[];for(var bn=0,bd=bl[bi][n];bn<bd;bn++){be[bi][bn]=[0];for(var bm=1,bp=bl[bi][bn][n];bm<bp;bm++){be[bi][bn][bm]=(bf[bn][bm]-bl[bi][bn][bm])/bh;}}break;case"csv":var d=(bq[bi]+aA)[C](a),bc=(bl[bi]+aA)[C](a);switch(bi){case"translation":bl[bi]=[0,0];be[bi]=[d[0]/bh,d[1]/bh];break;case"rotation":bl[bi]=(bc[1]==d[1]&&bc[2]==d[2])?bc:[0,d[1],d[2]];be[bi]=[(d[0]-bl[bi][0])/bh,0,0];break;case"scale":bq[bi]=d;bl[bi]=(bl[bi]+aA)[C](a);be[bi]=[(d[0]-bl[bi][0])/bh,(d[1]-bl[bi][1])/bh,0,0];break;case"clip-rect":bl[bi]=(bl[bi]+aA)[C](a);be[bi]=[];bn=4;while(bn--){be[bi][bn]=(d[bn]-bl[bi][bn])/bh;}break;}e[bi]=d;}}}}this.stop();this.in_animation=1;M[this.id]={start:bq.start||+new Date,ms:bh,easing:bg,from:bl,diff:be,to:e,el:this,callback:E,t:{x:0,y:0}};++M[n]==1&&a1();return this;};aF[a9].stop=function(){M[this.id]&&M[n]--;delete M[this.id];return this;};aF[a9].translate=function(d,e){return this.attr({translation:d+" "+e});};aF[a9][aJ]=function(){return"Rapha\xebl\u2019s object";};au.ae=M;var X=function(d){this.items=[];this[n]=0;if(d){for(var e=0,E=d[n];e<E;e++){if(d[e]&&(d[e].constructor==aF||d[e].constructor==X)){this[this.items[n]]=this.items[this.items[n]]=d[e];this[n]++;}}}};X[a9][f]=function(){var R,d;for(var e=0,E=arguments[n];e<E;e++){R=arguments[e];if(R&&(R.constructor==aF||R.constructor==X)){d=this.items[n];this[d]=this.items[d]=R;this[n]++;}}return this;};X[a9].pop=function(){delete this[this[n]--];return this.items.pop();};for(var B in aF[a9]){if(aF[a9][W](B)){X[a9][B]=(function(d){return function(){for(var e=0,E=this.items[n];e<E;e++){this.items[e][d][a7](this.items[e],arguments);}return this;};})(B);}}X[a9].attr=function(e,bc){if(e&&au.is(e,"array")&&au.is(e[0],"object")){for(var d=0,S=e[n];d<S;d++){this.items[d].attr(e[d]);}}else{for(var E=0,R=this.items[n];E<R;E++){this.items[E].attr(e,bc);}}return this;};X[a9].animate=function(S,e,be,bd){(au.is(be,"function")||!be)&&(bd=be||null);var d=this.items[n],E=d,bc=this,R;bd&&(R=function(){!--d&&bd.call(bc);});this.items[--E].animate(S,e,be||R,R);while(E--){this.items[E].animateWith(this.items[d-1],S,e,be||R,R);}return this;};X[a9].insertAfter=function(e){var d=this.items[n];while(d--){this.items[d].insertAfter(e);}return this;};X[a9].getBBox=function(){var d=[],bc=[],e=[],R=[];for(var E=this.items[n];E--;){var S=this.items[E].getBBox();d[f](S.x);bc[f](S.y);e[f](S.x+S.width);R[f](S.y+S.height);}d=aR[a7](0,d);bc=aR[a7](0,bc);return{x:d,y:bc,width:h[a7](0,e)-d,height:h[a7](0,R)-bc};};X[a9].clone=function(E){E=new X;for(var d=0,e=this.items[n];d<e;d++){E[f](this.items[d].clone());}return E;};au.registerFont=function(e){if(!e.face){return e;}this.fonts=this.fonts||{};var E={w:e.w,face:{},glyphs:{}},i=e.face["font-family"];for(var bc in e.face){if(e.face[W](bc)){E.face[bc]=e.face[bc];}}if(this.fonts[i]){this.fonts[i][f](E);}else{this.fonts[i]=[E];}if(!e.svg){E.face["units-per-em"]=K(e.face["units-per-em"],10);for(var R in e.glyphs){if(e.glyphs[W](R)){var S=e.glyphs[R];E.glyphs[R]={w:S.w,k:{},d:S.d&&"M"+S.d[aZ](/[mlcxtrv]/g,function(bd){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[bd]||"M";})+"z"};if(S.k){for(var d in S.k){if(S[W](d)){E.glyphs[R].k[d]=S.k[d];}}}}}}return e;};a4[a9].getFont=function(be,bf,e,R){R=R||"normal";e=e||"normal";bf=+bf||{normal:400,bold:700,lighter:300,bolder:800}[bf]||400;var S=au.fonts[be];if(!S){var E=new RegExp("(^|\\s)"+be[aZ](/[^\w\d\s+!~.:_-]/g,aA)+"(\\s|$)","i");for(var d in au.fonts){if(au.fonts[W](d)){if(E.test(d)){S=au.fonts[d];break;}}}}var bc;if(S){for(var bd=0,bg=S[n];bd<bg;bd++){bc=S[bd];if(bc.face["font-weight"]==bf&&(bc.face["font-style"]==e||!bc.face["font-style"])&&bc.face["font-stretch"]==R){break;}}}return bc;};a4[a9].print=function(R,E,d,bd,be,bn){bn=bn||"middle";var bj=this.set(),bm=(d+aA)[C](aA),bk=0,bg=aA,bo;au.is(bd,"string")&&(bd=this.getFont(bd));if(bd){bo=(be||16)/bd.face["units-per-em"];var e=bd.face.bbox.split(a),bc=+e[0],bf=+e[1]+(bn=="baseline"?e[3]-e[1]+(+bd.face.descent):(e[3]-e[1])/2);for(var bi=0,S=bm[n];bi<S;bi++){var bh=bi&&bd.glyphs[bm[bi-1]]||{},bl=bd.glyphs[bm[bi]];bk+=bi?(bh.w||bd.w)+(bh.k&&bh.k[bm[bi]]||0):0;bl&&bl.d&&bj[f](this.path(bl.d).attr({fill:"#000",stroke:"none",translation:[bk,0]}));}bj.scale(bo,bo,bc,bf).translate(R-bc,E-bf);}return bj;};var aW=/\{(\d+)\}/g;au.format=function(e,i){var d=au.is(i,"array")?[0][a2](i):arguments;e&&au.is(e,"string")&&d[n]-1&&(e=e[aZ](aW,function(R,E){return d[++E]==null?aA:d[E];}));return e||aA;};au.ninja=function(){m.was?(Raphael=m.is):delete Raphael;return au;};au.el=aF[a9];return au;})();
/*
 * g.Raphael 0.4 - Charting library, based on Raphaël
 *
 * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 */
(function(){Raphael.fn.g=Raphael.fn.g||{};Raphael.fn.g.markers={disc:"disc",o:"disc",flower:"flower",f:"flower",diamond:"diamond",d:"diamond",square:"square",s:"square",triangle:"triangle",t:"triangle",star:"star","*":"star",cross:"cross",x:"cross",plus:"plus","+":"plus",arrow:"arrow","->":"arrow"};Raphael.fn.g.shim={stroke:"none",fill:"#000","fill-opacity":0};Raphael.fn.g.txtattr={font:"12px Arial, sans-serif"};Raphael.fn.g.colors=[];var b=[0.6,0.2,0.05,0.1333,0.75,0];for(var a=0;a<10;a++){if(a<b.length){Raphael.fn.g.colors.push("hsb("+b[a]+", .75, .75)");}else{Raphael.fn.g.colors.push("hsb("+b[a-b.length]+", 1, .5)");}}Raphael.fn.g.text=function(c,f,e){return this.text(c,f,e).attr(this.g.txtattr);};Raphael.fn.g.labelise=function(c,f,e){if(c){return(c+"").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g,function(g,i,h){if(i){return(+f).toFixed(i.replace(/^#+\.?/g,"").length);}if(h){return(f*100/e).toFixed(h.replace(/^%+\.?/g,"").length)+"%";}});}else{return(+f).toFixed(0);}};Raphael.fn.g.finger=function(j,i,e,k,f,g,h){if((f&&!k)||(!f&&!e)){return h?"":this.path();}g={square:"square",sharp:"sharp",soft:"soft"}[g]||"round";var m;k=Math.round(k);e=Math.round(e);j=Math.round(j);i=Math.round(i);switch(g){case"round":if(!f){var c=Math.floor(k/2);if(e<c){c=e;m=["M",j+0.5,i+0.5-Math.floor(k/2),"l",0,0,"a",c,Math.floor(k/2),0,0,1,0,k,"l",0,0,"z"];}else{m=["M",j+0.5,i+0.5-c,"l",e-c,0,"a",c,c,0,1,1,0,k,"l",c-e,0,"z"];}}else{var c=Math.floor(e/2);if(k<c){c=k;m=["M",j-Math.floor(e/2),i,"l",0,0,"a",Math.floor(e/2),c,0,0,1,e,0,"l",0,0,"z"];}else{m=["M",j-c,i,"l",0,c-k,"a",c,c,0,1,1,e,0,"l",0,k-c,"z"];}}break;case"sharp":if(!f){var l=Math.floor(k/2);m=["M",j,i+l,"l",0,-k,Math.max(e-l,0),0,Math.min(l,e),l,-Math.min(l,e),l+(l*2<k),"z"];}else{var l=Math.floor(e/2);m=["M",j+l,i,"l",-e,0,0,-Math.max(k-l,0),l,-Math.min(l,k),l,Math.min(l,k),l,"z"];}break;case"square":if(!f){m=["M",j,i+Math.floor(k/2),"l",0,-k,e,0,0,k,"z"];}else{m=["M",j+Math.floor(e/2),i,"l",1-e,0,0,-k,e-1,0,"z"];}break;case"soft":var c;if(!f){c=Math.min(e,Math.round(k/5));m=["M",j+0.5,i+0.5-Math.floor(k/2),"l",e-c,0,"a",c,c,0,0,1,c,c,"l",0,k-c*2,"a",c,c,0,0,1,-c,c,"l",c-e,0,"z"];}else{c=Math.min(Math.round(e/5),k);m=["M",j-Math.floor(e/2),i,"l",0,c-k,"a",c,c,0,0,1,c,-c,"l",e-2*c,0,"a",c,c,0,0,1,c,c,"l",0,k-c,"z"];}}if(h){return m.join(",");}else{return this.path(m);}};Raphael.fn.g.disc=function(c,f,e){return this.circle(c,f,e);};Raphael.fn.g.line=function(c,f,e){return this.rect(c-e,f-e/5,2*e,2*e/5);};Raphael.fn.g.square=function(c,f,e){e=e*0.7;return this.rect(c-e,f-e,2*e,2*e);};Raphael.fn.g.triangle=function(c,f,e){e*=1.75;return this.path("M".concat(c,",",f,"m0-",e*0.58,"l",e*0.5,",",e*0.87,"-",e,",0z"));};Raphael.fn.g.diamond=function(c,f,e){return this.path(["M",c,f-e,"l",e,e,-e,e,-e,-e,e,-e,"z"]);};Raphael.fn.g.flower=function(g,f,c,e){c=c*1.25;var l=c,k=l*0.5;e=+e<3||!e?5:e;var m=["M",g,f+k,"Q"],j;for(var h=1;h<e*2+1;h++){j=h%2?l:k;m=m.concat([+(g+j*Math.sin(h*Math.PI/e)).toFixed(3),+(f+j*Math.cos(h*Math.PI/e)).toFixed(3)]);}m.push("z");return this.path(m.join(","));};Raphael.fn.g.star=function(c,k,j,e){e=e||j*0.5;var h=["M",c,k+e,"L"],g;for(var f=1;f<10;f++){g=f%2?j:e;h=h.concat([(c+g*Math.sin(f*Math.PI*0.2)).toFixed(3),(k+g*Math.cos(f*Math.PI*0.2)).toFixed(3)]);}h.push("z");return this.path(h.join(","));};Raphael.fn.g.cross=function(c,f,e){e=e/2.5;return this.path("M".concat(c-e,",",f,"l",[-e,-e,e,-e,e,e,e,-e,e,e,-e,e,e,e,-e,e,-e,-e,-e,e,-e,-e,"z"]));};Raphael.fn.g.plus=function(c,f,e){e=e/2;return this.path("M".concat(c-e/2,",",f-e/2,"l",[0,-e,e,0,0,e,e,0,0,e,-e,0,0,e,-e,0,0,-e,-e,0,0,-e,"z"]));};Raphael.fn.g.arrow=function(c,f,e){return this.path("M".concat(c-e*0.7,",",f-e*0.4,"l",[e*0.6,0,0,-e*0.4,e,e*0.8,-e,e*0.8,0,-e*0.4,-e*0.6,0],"z"));};Raphael.fn.g.tag=function(c,k,j,i,g){i=i||0;g=g==null?5:g;j=j==null?"$9.99":j;var f=0.5522*g,e=this.set(),h=3;e.push(this.path().attr({fill:"#000",stroke:"none"}));e.push(this.text(c,k,j).attr(this.g.txtattr).attr({fill:"#fff"}));e.update=function(){this.rotate(0,c,k);var m=this[1].getBBox();if(m.height>=g*2){this[0].attr({path:["M",c,k+g,"a",g,g,0,1,1,0,-g*2,g,g,0,1,1,0,g*2,"m",0,-g*2-h,"a",g+h,g+h,0,1,0,0,(g+h)*2,"L",c+g+h,k+m.height/2+h,"l",m.width+2*h,0,0,-m.height-2*h,-m.width-2*h,0,"L",c,k-g-h].join(",")});}else{var l=Math.sqrt(Math.pow(g+h,2)-Math.pow(m.height/2+h,2));this[0].attr({path:["M",c,k+g,"c",-f,0,-g,f-g,-g,-g,0,-f,g-f,-g,g,-g,f,0,g,g-f,g,g,0,f,f-g,g,-g,g,"M",c+l,k-m.height/2-h,"a",g+h,g+h,0,1,0,0,m.height+2*h,"l",g+h-l+m.width+2*h,0,0,-m.height-2*h,"L",c+l,k-m.height/2-h].join(",")});}this[1].attr({x:c+g+h+m.width/2,y:k});i=(360-i)%360;this.rotate(i,c,k);i>90&&i<270&&this[1].attr({x:c-g-h-m.width/2,y:k,rotation:[180+i,c,k]});return this;};e.update();return e;};Raphael.fn.g.popupit=function(j,i,k,e,q){e=e==null?2:e;q=q||5;j=Math.round(j)+0.5;i=Math.round(i)+0.5;var g=k.getBBox(),l=Math.round(g.width/2),f=Math.round(g.height/2),o=[0,l+q*2,0,-l-q*2],m=[-f*2-q*3,-f-q,0,-f-q],c=["M",j-o[e],i-m[e],"l",-q,(e==2)*-q,-Math.max(l-q,0),0,"a",q,q,0,0,1,-q,-q,"l",0,-Math.max(f-q,0),(e==3)*-q,-q,(e==3)*q,-q,0,-Math.max(f-q,0),"a",q,q,0,0,1,q,-q,"l",Math.max(l-q,0),0,q,!e*-q,q,!e*q,Math.max(l-q,0),0,"a",q,q,0,0,1,q,q,"l",0,Math.max(f-q,0),(e==1)*q,q,(e==1)*-q,q,0,Math.max(f-q,0),"a",q,q,0,0,1,-q,q,"l",-Math.max(l-q,0),0,"z"].join(","),n=[{x:j,y:i+q*2+f},{x:j-q*2-l,y:i},{x:j,y:i-q*2-f},{x:j+q*2+l,y:i}][e];k.translate(n.x-l-g.x,n.y-f-g.y);return this.path(c).attr({fill:"#000",stroke:"none"}).insertBefore(k.node?k:k[0]);};Raphael.fn.g.popup=function(c,j,i,e,g){e=e==null?2:e;g=g||5;i=i||"$9.99";var f=this.set(),h=3;f.push(this.path().attr({fill:"#000",stroke:"none"}));f.push(this.text(c,j,i).attr(this.g.txtattr).attr({fill:"#fff"}));f.update=function(m,l,n){m=m||c;l=l||j;var q=this[1].getBBox(),s=q.width/2,o=q.height/2,v=[0,s+g*2,0,-s-g*2],t=[-o*2-g*3,-o-g,0,-o-g],k=["M",m-v[e],l-t[e],"l",-g,(e==2)*-g,-Math.max(s-g,0),0,"a",g,g,0,0,1,-g,-g,"l",0,-Math.max(o-g,0),(e==3)*-g,-g,(e==3)*g,-g,0,-Math.max(o-g,0),"a",g,g,0,0,1,g,-g,"l",Math.max(s-g,0),0,g,!e*-g,g,!e*g,Math.max(s-g,0),0,"a",g,g,0,0,1,g,g,"l",0,Math.max(o-g,0),(e==1)*g,g,(e==1)*-g,g,0,Math.max(o-g,0),"a",g,g,0,0,1,-g,g,"l",-Math.max(s-g,0),0,"z"].join(","),u=[{x:m,y:l+g*2+o},{x:m-g*2-s,y:l},{x:m,y:l-g*2-o},{x:m+g*2+s,y:l}][e];if(n){this[0].animate({path:k},500,">");this[1].animate(u,500,">");}else{this[0].attr({path:k});this[1].attr(u);}return this;};return f.update(c,j);};Raphael.fn.g.flag=function(c,i,h,g){g=g||0;h=h||"$9.99";var e=this.set(),f=3;e.push(this.path().attr({fill:"#000",stroke:"none"}));e.push(this.text(c,i,h).attr(this.g.txtattr).attr({fill:"#fff"}));e.update=function(j,m){this.rotate(0,j,m);var l=this[1].getBBox(),k=l.height/2;this[0].attr({path:["M",j,m,"l",k+f,-k-f,l.width+2*f,0,0,l.height+2*f,-l.width-2*f,0,"z"].join(",")});this[1].attr({x:j+k+f+l.width/2,y:m});g=360-g;this.rotate(g,j,m);g>90&&g<270&&this[1].attr({x:j-r-f-l.width/2,y:m,rotation:[180+g,j,m]});return this;};return e.update(c,i);};Raphael.fn.g.label=function(c,g,f){var e=this.set();e.push(this.rect(c,g,10,10).attr({stroke:"none",fill:"#000"}));e.push(this.text(c,g,f).attr(this.g.txtattr).attr({fill:"#fff"}));e.update=function(){var i=this[1].getBBox(),h=Math.min(i.width+10,i.height+10)/2;this[0].attr({x:i.x-h/2,y:i.y-h/2,width:i.width+h,height:i.height+h,r:h});};e.update();return e;};Raphael.fn.g.labelit=function(f){var e=f.getBBox(),c=Math.min(20,e.width+10,e.height+10)/2;return this.rect(e.x-c/2,e.y-c/2,e.width+c,e.height+c,c).attr({stroke:"none",fill:"#000"}).insertBefore(f[0]);};Raphael.fn.g.drop=function(c,i,h,f,g){f=f||30;g=g||0;var e=this.set();e.push(this.path(["M",c,i,"l",f,0,"A",f*0.4,f*0.4,0,1,0,c+f*0.7,i-f*0.7,"z"]).attr({fill:"#000",stroke:"none",rotation:[22.5-g,c,i]}));g=(g+90)*Math.PI/180;e.push(this.text(c+f*Math.sin(g),i+f*Math.cos(g),h).attr(this.g.txtattr).attr({"font-size":f*12/30,fill:"#fff"}));e.drop=e[0];e.text=e[1];return e;};Raphael.fn.g.blob=function(e,k,j,i,g){i=(+i+1?i:45)+90;g=g||12;var c=Math.PI/180,h=g*12/12;var f=this.set();f.push(this.path().attr({fill:"#000",stroke:"none"}));f.push(this.text(e+g*Math.sin((i)*c),k+g*Math.cos((i)*c)-h/2,j).attr(this.g.txtattr).attr({"font-size":h,fill:"#fff"}));f.update=function(q,p,v){q=q||e;p=p||k;var y=this[1].getBBox(),B=Math.max(y.width+h,g*25/12),x=Math.max(y.height+h,g*25/12),m=q+g*Math.sin((i-22.5)*c),z=p+g*Math.cos((i-22.5)*c),o=q+g*Math.sin((i+22.5)*c),A=p+g*Math.cos((i+22.5)*c),D=(o-m)/2,C=(A-z)/2,n=B/2,l=x/2,u=-Math.sqrt(Math.abs(n*n*l*l-n*n*C*C-l*l*D*D)/(n*n*C*C+l*l*D*D)),t=u*n*C/l+(o+m)/2,s=u*-l*D/n+(A+z)/2;if(v){this.animate({x:t,y:s,path:["M",e,k,"L",o,A,"A",n,l,0,1,1,m,z,"z"].join(",")},500,">");}else{this.attr({x:t,y:s,path:["M",e,k,"L",o,A,"A",n,l,0,1,1,m,z,"z"].join(",")});}return this;};f.update(e,k);return f;};Raphael.fn.g.colorValue=function(g,f,e,c){return"hsb("+[Math.min((1-g/f)*0.4,1),e||0.75,c||0.75]+")";};Raphael.fn.g.snapEnds=function(l,m,k){var h=l,n=m;if(h==n){return{from:h,to:n,power:0};}function o(f){return Math.abs(f-0.5)<0.25?Math.floor(f)+0.5:Math.round(f);}var j=(n-h)/k,c=Math.floor(j),g=c,e=0;if(c){while(g){e--;g=Math.floor(j*Math.pow(10,e))/Math.pow(10,e);}e++;}else{while(!c){e=e||1;c=Math.floor(j*Math.pow(10,e))/Math.pow(10,e);e++;}e&&e--;}var n=o(m*Math.pow(10,e))/Math.pow(10,e);if(n<m){n=o((m+0.5)*Math.pow(10,e))/Math.pow(10,e);}var h=o((l-(e>0?0:0.5))*Math.pow(10,e))/Math.pow(10,e);return{from:h,to:n,power:e};};Raphael.fn.g.axis=function(s,q,m,E,h,H,k,J,l,c){c=c==null?2:c;l=l||"t";H=H||10;var D=l=="|"||l==" "?["M",s+0.5,q,"l",0,0.001]:k==1||k==3?["M",s+0.5,q,"l",0,-m]:["M",s,q+0.5,"l",m,0],v=this.g.snapEnds(E,h,H),I=v.from,z=v.to,G=v.power,F=0,A=this.set();d=(z-I)/H;var p=I,o=G>0?G:0;u=m/H;if(+k==1||+k==3){var e=q,w=(k-1?1:-1)*(c+3+!!(k-1));while(e>=q-m){l!="-"&&l!=" "&&(D=D.concat(["M",s-(l=="+"||l=="|"?c:!(k-1)*c*2),e+0.5,"l",c*2+1,0]));A.push(this.text(s+w,e,(J&&J[F++])||(Math.round(p)==p?p:+p.toFixed(o))).attr(this.g.txtattr).attr({"text-anchor":k-1?"start":"end"}));p+=d;e-=u;}if(Math.round(e+u-(q-m))){l!="-"&&l!=" "&&(D=D.concat(["M",s-(l=="+"||l=="|"?c:!(k-1)*c*2),q-m+0.5,"l",c*2+1,0]));A.push(this.text(s+w,q-m,(J&&J[F])||(Math.round(p)==p?p:+p.toFixed(o))).attr(this.g.txtattr).attr({"text-anchor":k-1?"start":"end"}));}}else{var g=s,p=I,o=G>0?G:0,w=(k?-1:1)*(c+9+!k),u=m/H,B=0,C=0;while(g<=s+m){l!="-"&&l!=" "&&(D=D.concat(["M",g+0.5,q-(l=="+"?c:!!k*c*2),"l",0,c*2+1]));A.push(B=this.text(g,q+w,(J&&J[F++])||(Math.round(p)==p?p:+p.toFixed(o))).attr(this.g.txtattr));var n=B.getBBox();if(C>=n.x-5){A.pop(A.length-1).remove();}else{C=n.x+n.width;}p+=d;g+=u;}if(Math.round(g-u-s-m)){l!="-"&&l!=" "&&(D=D.concat(["M",s+m+0.5,q-(l=="+"?c:!!k*c*2),"l",0,c*2+1]));A.push(this.text(s+m,q+w,(J&&J[F])||(Math.round(p)==p?p:+p.toFixed(o))).attr(this.g.txtattr));}}var K=this.path(D);K.text=A;K.all=this.set([K,A]);K.remove=function(){this.text.remove();this.constructor.prototype.remove.call(this);};return K;};Raphael.el.lighter=function(e){e=e||2;var c=[this.attrs.fill,this.attrs.stroke];this.fs=this.fs||[c[0],c[1]];c[0]=Raphael.rgb2hsb(Raphael.getRGB(c[0]).hex);c[1]=Raphael.rgb2hsb(Raphael.getRGB(c[1]).hex);c[0].b=Math.min(c[0].b*e,1);c[0].s=c[0].s/e;c[1].b=Math.min(c[1].b*e,1);c[1].s=c[1].s/e;this.attr({fill:"hsb("+[c[0].h,c[0].s,c[0].b]+")",stroke:"hsb("+[c[1].h,c[1].s,c[1].b]+")"});};Raphael.el.darker=function(e){e=e||2;var c=[this.attrs.fill,this.attrs.stroke];this.fs=this.fs||[c[0],c[1]];c[0]=Raphael.rgb2hsb(Raphael.getRGB(c[0]).hex);c[1]=Raphael.rgb2hsb(Raphael.getRGB(c[1]).hex);c[0].s=Math.min(c[0].s*e,1);c[0].b=c[0].b/e;c[1].s=Math.min(c[1].s*e,1);c[1].b=c[1].b/e;this.attr({fill:"hsb("+[c[0].h,c[0].s,c[0].b]+")",stroke:"hsb("+[c[1].h,c[1].s,c[1].b]+")"});};Raphael.el.original=function(){if(this.fs){this.attr({fill:this.fs[0],stroke:this.fs[1]});delete this.fs;}};})();
/*
 * g.Raphael 0.4 - Charting library, based on Raphaël
 *
 * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 */
Raphael.fn.g.linechart = function (x, y, width, height, valuesx, valuesy, opts) {
    function shrink(values, dim) {
        var k = values.length / dim,
            j = 0,
            l = k,
            sum = 0,
            res = [];
        while (j < values.length) {
            l--;
            if (l < 0) {
                sum += values[j] * (1 + l);
                res.push(sum / k);
                sum = values[j++] * -l;
                l += k;
            } else {
                sum += values[j++];
            }
        }
        return res;
    }
    opts = opts || {};
    if (!this.raphael.is(valuesx[0], "array")) {
        valuesx = [valuesx];
    }
    if (!this.raphael.is(valuesy[0], "array")) {
        valuesy = [valuesy];
    }
    var allx = Array.prototype.concat.apply([], valuesx),
        ally = Array.prototype.concat.apply([], valuesy),
        xdim = this.g.snapEnds(Math.min.apply(Math, allx), Math.max.apply(Math, allx), valuesx[0].length - 1),
        minx = xdim.from,
        maxx = xdim.to,
        gutter = opts.gutter || 10,
        kx = (width - gutter * 2) / (maxx - minx),
        ydim = this.g.snapEnds(Math.min.apply(Math, ally), Math.max.apply(Math, ally), valuesy[0].length - 1),
        miny = ydim.from,
        maxy = ydim.to,
        ky = (height - gutter * 2) / (maxy - miny),
        len = Math.max(valuesx[0].length, valuesy[0].length),
        symbol = opts.symbol || "",
        colors = opts.colors || Raphael.fn.g.colors,
        that = this,
        columns = null,
        dots = null,
        chart = this.set(),
        path = [];

    for (var i = 0, ii = valuesy.length; i < ii; i++) {
        len = Math.max(len, valuesy[i].length);
    }
    var shades = this.set();
    for (var i = 0, ii = valuesy.length; i < ii; i++) {
        if (opts.shade) {
            shades.push(this.path().attr({stroke: "none", fill: colors[i], opacity: opts.nostroke ? 1 : .3}));
        }
        if (valuesy[i].length > width - 2 * gutter) {
            valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
            len = width - 2 * gutter;
        }
        if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
            valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
        }
    }
    var axis = this.set();
    if (opts.axis) {
        var ax = (opts.axis + "").split(/[,\s]+/);
        +ax[0] && axis.push(this.g.axis(x + gutter, y + gutter, width - 2 * gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 20), 2));
        +ax[1] && axis.push(this.g.axis(x + width - gutter, y + height - gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 2 * gutter) / 20), 3));
        +ax[2] && axis.push(this.g.axis(x + gutter, y + height - gutter, width - 2 * gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 20), 0));
        +ax[3] && axis.push(this.g.axis(x + gutter, y + height - gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 2 * gutter) / 20), 1));
    }
    var lines = this.set(),
        symbols = this.set(),
        line;
    for (var i = 0, ii = valuesy.length; i < ii; i++) {
        if (!opts.nostroke) {
            lines.push(line = this.path().attr({
                stroke: colors[i],
                "stroke-width": opts.width || 2,
                "stroke-linejoin": "round",
                "stroke-linecap": "round",
                "stroke-dasharray": opts.dash || ""
            }));
        }
        var sym = this.raphael.is(symbol, "array") ? symbol[i] : symbol,
            symset = this.set();
        path = [];
        for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
            var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx;
            var Y = y + height - gutter - (valuesy[i][j] - miny) * ky;
            (Raphael.is(sym, "array") ? sym[j] : sym) && symset.push(this.g[Raphael.fn.g.markers[this.raphael.is(sym, "array") ? sym[j] : sym]](X, Y, (opts.width || 2) * 3).attr({fill: colors[i], stroke: "none"}));
            path = path.concat([j ? "L" : "M", X, Y]);
        }
        symbols.push(symset);
        if (opts.shade) {
            shades[i].attr({path: path.concat(["L", X, y + height - gutter, "L",  x + gutter + ((valuesx[i] || valuesx[0])[0] - minx) * kx, y + height - gutter, "z"]).join(",")});
        }
        !opts.nostroke && line.attr({path: path.join(",")});
    }
    function createColumns(f) {
        var Xs = [];
        for (var i = 0, ii = valuesx.length; i < ii; i++) {
            Xs = Xs.concat(valuesx[i]);
        }
        Xs.sort();
        var Xs2 = [],
            xs = [];
        for (var i = 0, ii = Xs.length; i < ii; i++) {
            Xs[i] != Xs[i - 1] && Xs2.push(Xs[i]) && xs.push(x + gutter + (Xs[i] - minx) * kx);
        }
        Xs = Xs2;
        ii = Xs.length;
        var cvrs = f || that.set();
        for (var i = 0; i < ii; i++) {
            var X = xs[i] - (xs[i] - (xs[i - 1] || x)) / 2,
                w = ((xs[i + 1] || x + width) - xs[i]) / 2 + (xs[i] - (xs[i - 1] || x)) / 2,
                C;
            f ? (C = {}) : cvrs.push(C = that.rect(X - 1, y, Math.max(w + 1, 1), height).attr({stroke: "none", fill: "#000", opacity: 0}));
            C.values = [];
            C.symbols = that.set();
            C.y = [];
            C.x = xs[i];
            C.axis = Xs[i];
            for (var j = 0, jj = valuesy.length; j < jj; j++) {
                Xs2 = valuesx[j] || valuesx[0];
                for (var k = 0, kk = Xs2.length; k < kk; k++) {
                    if (Xs2[k] == Xs[i]) {
                        C.values.push(valuesy[j][k]);
                        C.y.push(y + height - gutter - (valuesy[j][k] - miny) * ky);
                        C.symbols.push(chart.symbols[j][k]);
                    }
                }
            }
            f && f.call(C);
        }
        !f && (columns = cvrs);
    }
    function createDots(f) {
        var cvrs = f || that.set(),
            C;
        for (var i = 0, ii = valuesy.length; i < ii; i++) {
            for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
                var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx,
                    nearX = x + gutter + ((valuesx[i] || valuesx[0])[j ? j - 1 : 1] - minx) * kx,
                    Y = y + height - gutter - (valuesy[i][j] - miny) * ky;
                f ? (C = {}) : cvrs.push(C = that.circle(X, Y, Math.abs(nearX - X) / 2).attr({stroke: "none", fill: "#000", opacity: 0}));
                C.x = X;
                C.y = Y;
                C.value = valuesy[i][j];
                C.line = chart.lines[i];
                C.shade = chart.shades[i];
                C.symbol = chart.symbols[i][j];
                C.symbols = chart.symbols[i];
                C.axis = (valuesx[i] || valuesx[0])[j];
                f && f.call(C);
            }
        }
        !f && (dots = cvrs);
    }
    chart.push(lines, shades, symbols, axis, columns, dots);
    chart.lines = lines;
    chart.shades = shades;
    chart.symbols = symbols;
    chart.axis = axis;
    chart.hoverColumn = function (fin, fout) {
        !columns && createColumns();
        columns.mouseover(fin).mouseout(fout);
        return this;
    };
    chart.clickColumn = function (f) {
        !columns && createColumns();
        columns.click(f);
        return this;
    };
    chart.hrefColumn = function (cols) {
        var hrefs = that.raphael.is(arguments[0], "array") ? arguments[0] : arguments;
        if (!(arguments.length - 1) && typeof cols == "object") {
            for (var x in cols) {
                for (var i = 0, ii = columns.length; i < ii; i++) if (columns[i].axis == x) {
                    columns[i].attr("href", cols[x]);
                }
            }
        }
        !columns && createColumns();
        for (var i = 0, ii = hrefs.length; i < ii; i++) {
            columns[i] && columns[i].attr("href", hrefs[i]);
        }
        return this;
    };
    chart.hover = function (fin, fout) {
        !dots && createDots();
        dots.mouseover(fin).mouseout(fout);
        return this;
    };
    chart.click = function (f) {
        !dots && createDots();
        dots.click(f);
        return this;
    };
    chart.each = function (f) {
        createDots(f);
        return this;
    };
    chart.eachColumn = function (f) {
        createColumns(f);
        return this;
    };
    return chart;
};
/*
 * g.Raphael 0.4 - Charting library, based on Raphaël
 *
 * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 */
Raphael.fn.g.piechart = function (cx, cy, r, values, opts) {
    opts = opts || {};
    var paper = this,
        sectors = [],
        covers = this.set(),
        chart = this.set(),
        series = this.set(),
        order = [],
        len = values.length,
        angle = 0,
        total = 0,
        others = 0,
        cut = 9,
        defcut = true;
    chart.covers = covers;
    if (len == 1) {
        series.push(this.circle(cx, cy, r).attr({fill: this.g.colors[0], stroke: opts.stroke || "#fff", "stroke-width": opts.strokewidth == null ? 1 : opts.strokewidth}));
        covers.push(this.circle(cx, cy, r).attr(this.g.shim));
        total = values[0];
        values[0] = {value: values[0], order: 0, valueOf: function () { return this.value; }};
        series[0].middle = {x: cx, y: cy};
        series[0].mangle = 180;
        series[0].value = values[0];
    } else {
        function sector(cx, cy, r, startAngle, endAngle, fill) {
            var rad = Math.PI / 180,
                x1 = cx + r * Math.cos(-startAngle * rad),
                x2 = cx + r * Math.cos(-endAngle * rad),
                xm = cx + r / 2 * Math.cos(-(startAngle + (endAngle - startAngle) / 2) * rad),
                y1 = cy + r * Math.sin(-startAngle * rad),
                y2 = cy + r * Math.sin(-endAngle * rad),
                ym = cy + r / 2 * Math.sin(-(startAngle + (endAngle - startAngle) / 2) * rad),
                res = ["M", cx, cy, "L", x1, y1, "A", r, r, 0, +(Math.abs(endAngle - startAngle) > 180), 1, x2, y2, "z"];
            res.middle = {x: xm, y: ym};
            return res;
        }
        for (var i = 0; i < len; i++) {
            total += values[i];
            values[i] = {value: values[i], order: i, valueOf: function () { return this.value; }};
        }
        values.sort(function (a, b) {
            return b.value - a.value;
        });
        for (var i = 0; i < len; i++) {
            if (defcut && values[i] * 360 / total <= 1.5) {
                cut = i;
                defcut = false;
            }
            if (i > cut) {
                defcut = false;
                values[cut].value += values[i];
                values[cut].others = true;
                others = values[cut].value;
            }
        }
        len = Math.min(cut + 1, values.length);
        others && values.splice(len) && (values[cut].others = true);
        for (var i = 0; i < len; i++) {
            var mangle = angle - 360 * values[i] / total / 2;
            if (!i) {
                angle = 90 - mangle;
                mangle = angle - 360 * values[i] / total / 2;
            }
            if (opts.init) {
                var ipath = sector(cx, cy, 1, angle, angle - 360 * values[i] / total).join(",");
            }
            var path = sector(cx, cy, r, angle, angle -= 360 * values[i] / total);
            var p = this.path(opts.init ? ipath : path).attr({fill: opts.colors && opts.colors[i] || this.g.colors[i] || "#666", stroke: opts.stroke || "#fff", "stroke-width": (opts.strokewidth == null ? 1 : opts.strokewidth), "stroke-linejoin": "round"});
            p.value = values[i];
            p.middle = path.middle;
            p.mangle = mangle;
            sectors.push(p);
            series.push(p);
            opts.init && p.animate({path: path.join(",")}, (+opts.init - 1) || 1000, ">");
        }
        for (var i = 0; i < len; i++) {
            var p = paper.path(sectors[i].attr("path")).attr(this.g.shim);
            opts.href && opts.href[i] && p.attr({href: opts.href[i]});
            p.attr = function () {};
            covers.push(p);
            series.push(p);
        }
    }

    chart.hover = function (fin, fout) {
        fout = fout || function () {};
        var that = this;
        for (var i = 0; i < len; i++) {
            (function (sector, cover, j) {
                var o = {
                    sector: sector,
                    cover: cover,
                    cx: cx,
                    cy: cy,
                    mx: sector.middle.x,
                    my: sector.middle.y,
                    mangle: sector.mangle,
                    r: r,
                    value: values[j],
                    total: total,
                    label: that.labels && that.labels[j]
                };
                cover.mouseover(function () {
                    fin.call(o);
                }).mouseout(function () {
                    fout.call(o);
                });
            })(series[i], covers[i], i);
        }
        return this;
    };
    chart.each = function (f) {
        var that = this;
        for (var i = 0; i < len; i++) {
            (function (sector, cover, j) {
                var o = {
                    sector: sector,
                    cover: cover,
                    cx: cx,
                    cy: cy,
                    x: sector.middle.x,
                    y: sector.middle.y,
                    mangle: sector.mangle,
                    r: r,
                    value: values[j],
                    total: total,
                    label: that.labels && that.labels[j]
                };
                f.call(o);
            })(series[i], covers[i], i);
        }
        return this;
    };
    chart.click = function (f) {
        var that = this;
        for (var i = 0; i < len; i++) {
            (function (sector, cover, j) {
                var o = {
                    sector: sector,
                    cover: cover,
                    cx: cx,
                    cy: cy,
                    mx: sector.middle.x,
                    my: sector.middle.y,
                    mangle: sector.mangle,
                    r: r,
                    value: values[j],
                    total: total,
                    label: that.labels && that.labels[j]
                };
                cover.click(function () { f.call(o); });
            })(series[i], covers[i], i);
        }
        return this;
    };
    chart.inject = function (element) {
        element.insertBefore(covers[0]);
    };
    var legend = function (labels, otherslabel, mark, dir) {
        var x = cx + r + r / 5,
            y = cy,
            h = y + 10;
        labels = labels || [];
        dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "east";
        mark = paper.g.markers[mark && mark.toLowerCase()] || "disc";
        chart.labels = paper.set();
        for (var i = 0; i < len; i++) {
            var clr = series[i].attr("fill"),
                j = values[i].order,
                txt;
            values[i].others && (labels[j] = otherslabel || "Others");
            labels[j] = paper.g.labelise(labels[j], values[i], total);
            chart.labels.push(paper.set());
            chart.labels[i].push(paper.g[mark](x + 5, h, 5).attr({fill: clr, stroke: "none"}));
            chart.labels[i].push(txt = paper.text(x + 20, h, labels[j] || values[j]).attr(paper.g.txtattr).attr({fill: opts.legendcolor || "#000", "text-anchor": "start"}));
            covers[i].label = chart.labels[i];
            h += txt.getBBox().height * 1.2;
        }
        var bb = chart.labels.getBBox(),
            tr = {
                east: [0, -bb.height / 2],
                west: [-bb.width - 2 * r - 20, -bb.height / 2],
                north: [-r - bb.width / 2, -r - bb.height - 10],
                south: [-r - bb.width / 2, r + 10]
            }[dir];
        chart.labels.translate.apply(chart.labels, tr);
        chart.push(chart.labels);
    };
    if (opts.legend) {
        legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
    }
    chart.push(series, covers);
    chart.series = series;
    chart.covers = covers;
    return chart;
};
/*
 * g.Raphael 0.4 - Charting library, based on Raphaël
 *
 * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 */
Raphael.fn.g.barchart = function (x, y, width, height, values, opts) {
    opts = opts || {};
    var type = {round: "round", sharp: "sharp", soft: "soft"}[opts.type] || "square",
        gutter = parseFloat(opts.gutter || "20%"),
        chart = this.set(),
        bars = this.set(),
        covers = this.set(),
        covers2 = this.set(),
        total = Math.max.apply(Math, values),
        stacktotal = [],
        paper = this,
        multi = 0,
        colors = opts.colors || this.g.colors,
        len = values.length;
    if (this.raphael.is(values[0], "array")) {
        total = [];
        multi = len;
        len = 0;
        for (var i = values.length; i--;) {
            bars.push(this.set());
            total.push(Math.max.apply(Math, values[i]));
            len = Math.max(len, values[i].length);
        }
        if (opts.stacked) {
            for (var i = len; i--;) {
                var tot = 0;
                for (var j = values.length; j--;) {
                    tot +=+ values[j][i] || 0;
                }
                stacktotal.push(tot);
            }
        }
        for (var i = values.length; i--;) {
            if (values[i].length < len) {
                for (var j = len; j--;) {
                    values[i].push(0);
                }
            }
        }
        total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
    }

    total = (opts.to) || total;
    var barwidth = width / (len * (100 + gutter) + gutter) * 100,
        barhgutter = barwidth * gutter / 100,
        barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
        stack = [],
        X = x + barhgutter,
        Y = (height - 2 * barvgutter) / total;
    if (!opts.stretch) {
        barhgutter = Math.round(barhgutter);
        barwidth = Math.floor(barwidth);
    }
    !opts.stacked && (barwidth /= multi || 1);
    for (var i = 0; i < len; i++) {
        stack = [];
        for (var j = 0; j < (multi || 1); j++) {
            var h = Math.round((multi ? values[j][i] : values[i]) * Y) || 1,
                top = y + height - barvgutter - h,
                bar = this.g.finger(Math.round(X + barwidth / 2), top + h, barwidth, h, true, type).attr({stroke: colors[multi ? j : i], fill: colors[multi ? j : i]});
            if (multi) {
                bars[j].push(bar);
            } else {
                bars.push(bar);
            }
            bar.y = top;
            bar.x = Math.round(X + barwidth / 2);
            bar.w = barwidth;
            bar.h = h;
            bar.i = i;
            bar.value = multi ? values[j][i] : values[i];
            if (!opts.stacked) {
                X += barwidth;
            } else {
                stack.push(bar);
            }
        }
        if (opts.stacked) {
            var cvr;
            covers2.push(cvr = this.rect(stack[0].x - stack[0].w / 2, y, barwidth, height).attr(this.g.shim));
            cvr.bars = this.set();
            var size = 0;
            for (var s = stack.length; s--;) {
                stack[s].toFront();
            }
            for (var s = 0, ss = stack.length; s < ss; s++) {
                var bar = stack[s],
                    cover,
                    h = (size + bar.value) * Y,
                    path = this.g.finger(bar.x, y + height - barvgutter - !!size * .5, barwidth, h, true, type, 1);
                cvr.bars.push(bar);
                size && bar.attr({path: path});
                bar.h = h;
                bar.y = y + height - barvgutter - !!size * .5 - h;
                covers.push(cover = this.rect(bar.x - bar.w / 2, bar.y, barwidth, bar.value * Y).attr(this.g.shim));
                cover.bar = bar;
                cover.value = bar.value;
                size += bar.value;
            }
            X += barwidth;
        }
        X += barhgutter;
    }
    covers2.toFront();
    X = x + barhgutter;
    if (!opts.stacked) {
        for (var i = 0; i < len; i++) {
            for (var j = 0; j < (multi || 1); j++) {
                var cover;
                covers.push(cover = this.rect(Math.round(X), y + barvgutter, barwidth, height - barvgutter).attr(this.g.shim));
                cover.bar = multi ? bars[j][i] : bars[i];
                cover.value = cover.bar.value;
                X += barwidth;
            }
            X += barhgutter;
        }
    }
    chart.label = function (labels, isBottom) {
        labels = labels || [];
        this.labels = paper.set();
        var L, l = -Infinity;
        if (opts.stacked) {
            for (var i = 0; i < len; i++) {
                var tot = 0;
                for (var j = 0; j < (multi || 1); j++) {
                    tot += multi ? values[j][i] : values[i];
                    if (j == multi - 1) {
                        var label = paper.g.labelise(labels[i], tot, total);
                        L = paper.g.text(bars[i * (multi || 1) + j].x, y + height - barvgutter / 2, label).insertBefore(covers[i * (multi || 1) + j]);
                        var bb = L.getBBox();
                        if (bb.x - 7 < l) {
                            L.remove();
                        } else {
                            this.labels.push(L);
                            l = bb.x + bb.width;
                        }
                    }
                }
            }
        } else {
            for (var i = 0; i < len; i++) {
                for (var j = 0; j < (multi || 1); j++) {
                    var label = paper.g.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
                    L = paper.g.text(bars[i * (multi || 1) + j].x, isBottom ? y + height - barvgutter / 2 : bars[i * (multi || 1) + j].y - 10, label).insertBefore(covers[i * (multi || 1) + j]);
                    var bb = L.getBBox();
                    if (bb.x - 7 < l) {
                        L.remove();
                    } else {
                        this.labels.push(L);
                        l = bb.x + bb.width;
                    }
                }
            }
        }
        return this;
    };
    chart.hover = function (fin, fout) {
        covers2.hide();
        covers.show();
        covers.mouseover(fin).mouseout(fout);
        return this;
    };
    chart.hoverColumn = function (fin, fout) {
        covers.hide();
        covers2.show();
        fout = fout || function () {};
        covers2.mouseover(fin).mouseout(fout);
        return this;
    };
    chart.click = function (f) {
        covers2.hide();
        covers.show();
        covers.click(f);
        return this;
    };
    chart.each = function (f) {
        if (!Raphael.is(f, "function")) {
            return this;
        }
        for (var i = covers.length; i--;) {
            f.call(covers[i]);
        }
        return this;
    };
    chart.eachColumn = function (f) {
        if (!Raphael.is(f, "function")) {
            return this;
        }
        for (var i = covers2.length; i--;) {
            f.call(covers2[i]);
        }
        return this;
    };
    chart.clickColumn = function (f) {
        covers.hide();
        covers2.show();
        covers2.click(f);
        return this;
    };
    chart.push(bars, covers, covers2);
    chart.bars = bars;
    chart.covers = covers;
    return chart;
};
Raphael.fn.g.hbarchart = function (x, y, width, height, values, opts) {
    opts = opts || {};
    var type = {round: "round", sharp: "sharp", soft: "soft"}[opts.type] || "square",
        gutter = parseFloat(opts.gutter || "20%"),
        chart = this.set(),
        bars = this.set(),
        covers = this.set(),
        covers2 = this.set(),
        total = Math.max.apply(Math, values),
        stacktotal = [],
        paper = this,
        multi = 0,
        colors = opts.colors || this.g.colors,
        len = values.length;
    if (this.raphael.is(values[0], "array")) {
        total = [];
        multi = len;
        len = 0;
        for (var i = values.length; i--;) {
            bars.push(this.set());
            total.push(Math.max.apply(Math, values[i]));
            len = Math.max(len, values[i].length);
        }
        if (opts.stacked) {
            for (var i = len; i--;) {
                var tot = 0;
                for (var j = values.length; j--;) {
                    tot +=+ values[j][i] || 0;
                }
                stacktotal.push(tot);
            }
        }
        for (var i = values.length; i--;) {
            if (values[i].length < len) {
                for (var j = len; j--;) {
                    values[i].push(0);
                }
            }
        }
        total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
    }

    total = (opts.to) || total;
    var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
        bargutter = Math.floor(barheight * gutter / 100),
        stack = [],
        Y = y + bargutter,
        X = (width - 1) / total;
    !opts.stacked && (barheight /= multi || 1);
    for (var i = 0; i < len; i++) {
        stack = [];
        for (var j = 0; j < (multi || 1); j++) {
            var val = multi ? values[j][i] : values[i],
                bar = this.g.finger(x, Y + barheight / 2, Math.round(val * X), barheight - 1, false, type).attr({stroke: colors[multi ? j : i], fill: colors[multi ? j : i]});

            bar.i = i;
            if (multi) {
                bars[j].push(bar);
            } else {
                bars.push(bar);
            }
            bar.x = x + Math.round(val * X);
            bar.y = Y + barheight / 2;
            bar.w = Math.round(val * X);
            bar.h = barheight;
            bar.value = +val;
            if (!opts.stacked) {
                Y += barheight;
            } else {
                stack.push(bar);
            }
        }
        if (opts.stacked) {
            var cvr = this.rect(x, stack[0].y - stack[0].h / 2, width, barheight).attr(this.g.shim);
            covers2.push(cvr);
            cvr.bars = this.set();
            var size = 0;
            for (var s = stack.length; s--;) {
                stack[s].toFront();
            }
            for (var s = 0, ss = stack.length; s < ss; s++) {
                var bar = stack[s],
                    cover,
                    val = Math.round((size + bar.value) * X),
                    path = this.g.finger(x, bar.y, val, barheight - 1, false, type, 1);
                cvr.bars.push(bar);
                size && bar.attr({path: path});
                bar.w = val;
                bar.x = x + val;
                covers.push(cover = this.rect(x + size * X, bar.y - bar.h / 2, bar.value * X, barheight).attr(this.g.shim));
                cover.bar = bar;
                size += bar.value;
            }
            Y += barheight;
        }
        Y += bargutter;
    }
    covers2.toFront();
    Y = y + bargutter;
    if (!opts.stacked) {
        for (var i = 0; i < len; i++) {
            for (var j = 0; j < multi; j++) {
                var cover = this.rect(x, Y, width, barheight).attr(this.g.shim);
                covers.push(cover);
                cover.bar = bars[j][i];
                Y += barheight;
            }
            Y += bargutter;
        }
    }
    chart.label = function (labels, isRight) {
        labels = labels || [];
        this.labels = paper.set();
        for (var i = 0; i < len; i++) {
            for (var j = 0; j < multi; j++) {
                var  label = paper.g.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
                var X = isRight ? bars[i * (multi || 1) + j].x - barheight / 2 + 3 : x + 5,
                    A = isRight ? "end" : "start",
                    L;
                this.labels.push(L = paper.g.text(X, bars[i * (multi || 1) + j].y, label).attr({"text-anchor": A}).insertBefore(covers[0]));
                if (L.getBBox().x < x + 5) {
                    L.attr({x: x + 5, "text-anchor": "start"});
                } else {
                    bars[i * (multi || 1) + j].label = L;
                }
            }
        }
        return this;
    };
    chart.hover = function (fin, fout) {
        covers2.hide();
        covers.show();
        fout = fout || function () {};
        covers.mouseover(fin).mouseout(fout);
        return this;
    };
    chart.hoverColumn = function (fin, fout) {
        covers.hide();
        covers2.show();
        fout = fout || function () {};
        covers2.mouseover(fin).mouseout(fout);
        return this;
    };
    chart.each = function (f) {
        if (!Raphael.is(f, "function")) {
            return this;
        }
        for (var i = covers.length; i--;) {
            f.call(covers[i]);
        }
        return this;
    };
    chart.eachColumn = function (f) {
        if (!Raphael.is(f, "function")) {
            return this;
        }
        for (var i = covers2.length; i--;) {
            f.call(covers2[i]);
        }
        return this;
    };
    chart.click = function (f) {
        covers2.hide();
        covers.show();
        covers.click(f);
        return this;
    };
    chart.clickColumn = function (f) {
        covers.hide();
        covers2.show();
        covers2.click(f);
        return this;
    };
    chart.push(bars, covers, covers2);
    chart.bars = bars;
    chart.covers = covers;
    return chart;
};
RChart = Class.create({
  initialize : function(container, googleDataJSON) {
    this.container = $(container);
    this.raphael = Raphael(container);
    this.raphael.g.txtattr.font = "10px 'Fontin Sans', Fontin-Sans, sans-serif";

    this.data = googleDataJSON;

    this.draw();
  },

  getValues : function() {
    return this.data.rows.map(function(r) { return r.c[1].v; });
  },

  getLabels : function() {
    return this.data.rows.map(function(r) { return r.c[0].v; });
  },

  format_number : function(nStr) {
    nStr += '';
    x = nStr.split('.');
    x1 = x[0];
    x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
      x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1 + x2;
  }
});


RPieChart = Class.create(RChart, {

  draw : function() {
    this.pie = this.raphael.g.piechart(140, 125, 100, this.getValues(), {
      legend: this.getLabels(),
      legendpos: "east"
    });

    this.pie.hover(this.sectorMouseOver(), this.sectorMouseOut());
  },

  getLabels : function($super) {
    var labels = $super();

    return labels.map(function(l) { return l + " (%%)"; });
  },

  sectorMouseOver : function () {
    var self = this;

    return function() {
      this.sector.stop();

      this.sector.scale(1.08, 1.08, this.cx, this.cy);

      if (this.label) {
         this.label[0].stop();
         this.label[0].scale(1.5);
      }

      var label = self.format_number(this.sector.value || "0") + " hours";
      this.flag = self.raphael.g.popup(this.sector.middle.x, this.sector.middle.y, label);
    }
  },

  sectorMouseOut : function() {
    var self = this;

    return function() {
      this.sector.animate({scale: [1, 1, this.cx, this.cy]}, 500, "bounce");

      if (this.label) {
         this.label[0].animate({scale: 1}, 500, "bounce");
      }

      if (this.flag) this.flag.animate({ opacity: 0 }, 300, function () {
        /* fails in safari: this.remove(); */
      });

      this.flag = null;
    }
  }
});


RBarChart = Class.create(RChart, {
  draw : function() {
    var maxy   = this.getValues().max(),
        x      = 50,
        y      = 10,
        width  = 340,
        height = 200,
        gutter = 10;

    this.chart = this.raphael.g.barchart(x, y, width, height, [this.getValues()], { type: "soft", gutter : 10 }).hover(this.barMouseOver(), this.barMouseOut());

    var axis = this.raphael.set();
    axis.push(this.raphael.g.axis(x - gutter, y + height - 2 * gutter, height - 4 * gutter, 0, maxy, Math.floor((height - 2 * gutter) / 20), 1));

    this.chart.push(axis);
    this.chart.axis = axis;


    var labels = this.raphael.set();
    var allLabels = this.getLabels();


    for (var i = 0; i < this.chart.bars[0].length; i++) {
      var bar = this.chart.bars[0][i];

      var rotate, tx, ty = bar.y+bar.h+2*gutter, l = allLabels[bar.i];

      if (bar.w > l.length * 5) {
        tx = bar.x;
      } else {
        tx = bar.x-bar.w/2;
        rotate = true;
      }

      var t = this.raphael.text(tx, ty, l);
      if (rotate) t.rotate(-20);
      labels.push(t);
    }

    this.chart.push(labels);



  },

  barMouseOver:  function () {
    var self = this;
    return function() {
      var tooltip = self.getLabels()[this.bar.i] + " (" + self.format_number(this.bar.value || "0") + " hours)";
      this.flag = self.raphael.g.popup(this.bar.x, this.bar.y, tooltip).insertBefore(this);
    }
  },

  barMouseOut : function () {
    var self = this;

    return function() {
      if (this.flag) this.flag.animate({ opacity: 0 }, 300, function () {
        /* fails in safari: this.remove(); */
      });

      this.flag = null
    }
  }
});


RLineChart = Class.create(RChart, {
  draw : function() {

    var width = this.container.getWidth(), height = this.container.getHeight();
    this.chart = this.raphael.g.linechart(0, 0, width, height, this.getLabels(), this.getValues(),  {
      nostroke: false,
      shade : true,
      gutter: 0
    });
  },

  getLabels : function($super) {
    var labels = $super();

    var xv = [];
    for (var i = 0; i < labels.length; i++)
      xv.push(i);

    return xv;
  },

  columnMouseOver : function() {
    var self = this;
    return function () {
      this.tags = self.raphael.set();
      for (var i = 0, ii = this.y.length; i < ii; i++) {
        this.tags.push(self.raphael.g.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{fill: "#DDE3E9"}, {fill: this.symbols[i].attr("fill")}]));
      }
    }
  },

  columnMouseOut : function() {
    return function () {
      this.tags && this.tags.remove();
    }
  }
});

MultiSelector = Class.create({
  defaultOptions : {
    activeClass   : 'gray_button',
    inactiveClass : 'green_button',
    widthCallback : null,
    handleInputs  : false ,
    onChange      : null
  },

  initialize : function(container, options) {
    this.container = $(container);
    this.header    = this.container.down(".title");
    this.entries   = this.container.down(".collapsable");

    this.options = Object.extend({}, this.defaultOptions);
    this.options = Object.extend(this.options, options||{});

    this.header.observe('click', this.toggleDropdown.bindAsEventListener(this));

    if (this.options.handleInputs) {

      this.entries.select(".filter").invoke('observe', 'click', this.clickFilter.bindAsEventListener(this))

    }
  },

  clickFilter : function(e) {

    var element = e.element()
    while (!element.match('.filter'))
      element = element.up()

    var checked = !element.hasClassName('checked')
    checked ? element.addClassName('checked') : element.removeClassName('checked')

    if (element.match('.exclusive')) {
      this.entries.select('.filter.checked').reject(function(e) { return e == element }).invoke('removeClassName', 'checked')
    } else if (element.up('.exclusive')) {

      element.up('.exclusive').siblings().each(function(g) {
        g.select('.filter.checked').invoke('removeClassName', 'checked')
      })

    }

    this.entries.select('.filter.checked > input').map(function(e) { e.checked = true; })
    this.entries.select('.filter:not(.checked) > input').map(function(e) { e.checked = false; })

    if (this.options.onChange)
      this.options.onChange()
  },

  toggleDropdown : function(e) {
    if (this.entries.visible()) {
      this.hideDropdown();
    } else {
      this.showDropdown();
    }

    if (e) e.stop();
    return false;
  },

  prepareScrollbar : function() {
    if (this.entries.select('li').length > 10) {
      this.entries.addClassName('scrollable');
    } else {
      this.entries.removeClassName('scrollable');
    }
  },

  showDropdown : function(e) {
    if (MultiSelector.selected_dropdown)
      MultiSelector.selected_dropdown.hideDropdown();

    MultiSelector.selected_dropdown = this;

    this.container.addClassName('active')

    this.prepareScrollbar();

    if (this.options.widthSizeOfTitle) {
      var width = this.header.getWidth();
      if (width > 200) {
        this.entries.select('li').map(function(li) {
          li.setStyle({'width' : width + 'px'});
        });
      }
    }

    this.entries.show();
    this.updateTitleState();

    $(window.document).observe('click', this.clickOffDropdown.bindAsEventListener(this));

    if (e) e.stop();
    return false;
  },

  inactiveClass : function() {
    var name = this.options['inactiveClass'];

    if (typeof name == 'function') {
      return name();
    }

    return name;
  },

  activeClass : function() {
    var name = this.options['activeClass'];

    if (typeof name == 'function') {
      return name();
    }

    return name;
  },

  updateTitleState : function() {
    this.clearTitleState();
    if (this.entries.visible()) {
      this.header.addClassName(this.activeClass());
    } else {
      this.header.addClassName(this.inactiveClass());
    }
  },

  clearTitleState : function() {
    this.header.removeClassName(this.inactiveClass());
    this.header.removeClassName(this.activeClass());
  },

  hideDropdown : function() {
    this.entries.hide();
    this.updateTitleState();

    this.container.removeClassName('active')

    $(window.document).stopObserving('click');

    if (MultiSelector.selected_dropdown == this)
      MultiSelector.selected_dropdown = null;
  },

  clickOffDropdown : function(event) {
    if (!event.element().descendantOf(this.container)) {
        this.hideDropdown();
    }
  }
});
GoalsManager = Class.create({

  initialize : function(goals_container, options) {

    this.container = $(goals_container)

    if (options.expandable) {
      this.goals = this.container.select('.goal').map(function(goal) {
        return new ExpandableGoal(goal)
      })
    } else {


      if (!this.container) return

      this.container.select('.goal').each(function(e) {
        e.observe('click', function(event) {
          document.location = e.down('a').href;
          event.stop();
          return false;
        })
      })

    }

    this.urls = options.urls
  },

  about_to_expand_a_goal : function() {
    this.goals.reject(function(g) { return !g.is_expanded; }).invoke('collapse', true)
  },

  rebind_goal : function(relative_goal_id) {
    if (this.goals) {

      this.goals.detect(function(g) {
        return g.dom_id == relative_goal_id
      }).bindToDom()

    }
  },

  pulse : function(relative_goal_id) {


    var bars = $(relative_goal_id).select('.progress_bar')

    bars.invoke('addClassName', 'pulse')

    setTimeout(function() {
      bars.invoke('removeClassName', "pulse")
    }, 3000)
  },

  expand_goal : function(goal_id) {
    var goal = this.goals.detect(function(g) {
      return g.id == goal_id
    })

    if (goal) {
      goal.expand()
    }


  }

});

ExpandableGoal = Class.create({

  initialize : function(dom) {
    this.dom_id = $(dom).id
    this.id     = this.dom_id.split("_").last()

    this.bindToDom()

    this.is_expanded = this.expanded && this.expanded.visible()

  },

  bindToDom : function() {

    var oldElement = this.element

    this.element  = $(this.dom_id)

    this.brief    = $("brief_"    + this.dom_id)
    this.expanded = $("expanded_" + this.dom_id)
    this.pin      = this.element.down('.icon.pin')
    this.archive  = this.element.down('.delete')

    if (this.expanded) {
      this.edit = this.expanded.down('.edit')
      this.edit.observe('click', this.toggleEdit.bindAsEventListener(this))
    }

    this.element.select('.hoverTooltip').each(Tooltip.attach)

    if (oldElement != this.element) {
      this.element.observe('click', this.toggle.bindAsEventListener(this))

      if (this.pin) {
        this.pin.observe('click', this.togglePin.bindAsEventListener(this))
      }

      if (this.archive) {
        this.archive.observe('click', this.toggleArchive.bindAsEventListener(this))
      }

      if (oldElement) {
        if (this.is_expanded) {
          this.expand()
        }
      }

    }

  },

  toggleEdit : function(e) {
    document.location = this.edit.href

    e.stop()
    return false
  },

  toggle : function() {

    if (ExpandableGoal.expanding_mutex) return

    if (this.brief.visible()) {

      if (window.goals_manager)
        window.goals_manager.about_to_expand_a_goal()

      this.expand(true)


    } else {

      this.collapse(true)

    }
  },

  togglePin : function(e) {

    var params = {
      "toggle_pin" : true,
      "element_id" : this.dom_id
    }

    new Ajax.Request(window.goals_manager.urls.goal_path.gsub("id", this.id), {

      method : "put",
      parameters: params,
      onComplete : function() {
        this.bindToDom()
      }.bind(this)

    })

    e.stop()
    return false

  },

  toggleArchive : function(e) {

    if (!confirm("Are you sure you want to archive this goal? This can't be undone")) {
      e.stop()
      return false
    }

    var params = {
      "archive" : true
    }

    new Ajax.Request(window.goals_manager.urls.goal_path.gsub("id", this.id), {

      method : "put",
      parameters: params,
      onComplete : function() {
        this.bindToDom()
      }.bind(this)

    })

    e.stop()
    return false

  },

  animation : function(old_height, mutex, callback) {
    this.element.setStyle({ 'height' : 'auto' })
    var new_height = this.element.getHeight()
    var padding = parseInt(this.element.getStyle('padding-top'), 10) + parseInt(this.element.getStyle('padding-bottom'), 10);

    this.element.setStyle({ height : (old_height-padding) + 'px', overflow : 'hidden' })


    ExpandableGoal[mutex] = true
    new Effect.Morph(this.element, {
      style : {
        height : (new_height-padding) + "px"
      },
      duration: 0.3,

      afterFinish : callback
    })

  },


  collapse : function(animate) {
    var old_height = this.element.getHeight()

    this.element.removeClassName('expanded')
    this.element.addClassName('collapsed')

    makeCollapsed.call(this)

    if (animate === true) {

      this.animation(old_height, 'collapsing_mutex', makeCollapsed.bind(this))

    }

    function makeCollapsed() {


      this.expanded.hide()
      this.brief.show()
      this.is_expanded = false

      this.element.setStyle({ 'height' : 'auto' })

      ExpandableGoal.collapsing_mutex = false
    }

  },

  expand : function(animate) {

    var old_height = this.element.getHeight()

    this.element.removeClassName('collapsed')
    this.element.addClassName('expanded')

    makeExpanded.call(this)

    if (animate === true) {

      this.animation(old_height, 'expanding_mutex', makeExpanded.bind(this))

    }

    function makeExpanded() {
      this.brief.hide()
      this.expanded.show()
      this.is_expanded = true

      this.element.setStyle({ 'height' : 'auto' })

      ExpandableGoal.expanding_mutex = false
    }

  }

});



GoalsForm = Class.create({

  initialize : function(form, opts) {

    this.options = opts
    this.form = $(form)

    new ToggleButton("goal_target_type_toggle_button", {

       callback : this.updateSummary.bind(this)

     });

     Calendar.setup({
       dateField      : $('report_formatted_range_end'),
       dateFormat     : '%b %e %Y',
       markFuture     : false
     });


     Calendar.setup({
        dateField      : $('report_formatted_range_start'),
        dateFormat     : '%b %e %Y',
        markFuture     : false
      });


     if ($('users_selector')) {
        new MultiSelector("users_selector",    { handleInputs : true, onChange : this.updateSummary.bind(this) })
     }

     new MultiSelector("contacts_selector", { handleInputs : true, onChange : this.updateSummary.bind(this) })
     new MultiSelector("tasks_selector",    { handleInputs : true, onChange : this.updateSummary.bind(this) })

     $('goal_target_hours').observe('change', this.updateSummary.bindAsEventListener(this))


     this.updateSummary()

  },

  updateSummary : function() {
     var amt = parseInt( $('goal_target_hours').value, 10 )

     var advice = ""
     if ( $('goal_target_type').value == "at_least" ) {
       advice = "At least "
     } else {
       advice = "At most "
     }


     $('goal_summary_hours').update(advice + amt + " hour" + (amt != 1 ? "s" : ""))


     var users    = $('goal_summary_users'),
         tasks    = $('goal_summary_tasks'),
         contacts = $('goal_summary_contacts')

     var scontacts = $$("#contacts_selector ul .filter.checked span").map(collectNames),
         susers    = $$("#users_selector ul .filter.checked span").map(collectNames),
         stasks    = $$("#tasks_selector ul .filter.checked span").map(collectNames)

     if (susers.length == 0) {
       users.down('.list').update('Anyone')
     } else {
       users.down('.list').update(susers.join(', '))
     }

     if (scontacts.length == 0) {
       contacts.down('.list').update('Any contact')
     } else {
       contacts.down('.list').update(scontacts.join(', '))
     }

     if (stasks.length == 0) {
       tasks.down('.list').update('Any task')
     } else {
       tasks.down('.list').update(stasks.join(', '))
     }


     function collectNames(e) {
       return e.innerHTML.stripTags().strip()
     }
  }

})
Tooltip = Class.create({

  initialize : function(elem, show_now) {

    this.tip     = this.getTip()
    this.element = $(elem)
    this.content = this.element.title

    this.element.tooltipAttached = true

    this.element.title = null

    if (show_now)
      this.show()
  },

  show : function() {
    this.tip.down('.content span').update(this.content)
    this.positionTipOnElement()
    this.tip.show()
  },

  hide : function() {
    this.tip.hide()
  },

  positionTipOnElement : function() {
    var offset = this.element.cumulativeOffset();
    this.tip.show();
    var toffset = parseInt(this.tip.getWidth() / 2);
    var eoffset = parseInt(this.element.getWidth() / 2);

    var theight = this.tip.getHeight();

    this.tip.setStyle({
      position : 'absolute',
      top      : (offset.top - theight - 2) + 'px',
      left     : (offset.left - toffset + eoffset) + 'px'
    });
  },

  getTip : function() {

    if (!Tooltip.tip) {

      Tooltip.tip = new Element('div').addClassName('tooltip').addClassName('upper').hide()

      var content = new Element('div').addClassName('content')
      content.update(new Element("span"))
      Tooltip.tip.update(content)

      document.body.appendChild(Tooltip.tip)

    }

    return Tooltip.tip

  }

})

Tooltip.tip = null
Tooltip.currentTip = null
Tooltip.create = function(element) {
  Tooltip.currentTip = new Tooltip(element)
}
Tooltip.hide = function(element) {
  if (Tooltip.currentTip)
    Tooltip.currentTip.hide()
}
Tooltip.attach = function(element) {

  if (element.tooltipAttached) return;

  var tip = new Tooltip(element, false)

  element.observe('mouseover', function() {
    Tooltip.hide()
    tip.show()
  })

  element.observe('mouseout', function() {
    tip.hide()
  })
}


document.observe("dom:loaded", function() {
  if ($("log_entry")) init_entry_form();

  $$(".editable_entry").each(function(entry_container) {
    new EditableEntry(entry_container);
  });

  var help_tab = $("help_tab");
  if (help_tab) {
    window.help_modal = new HelpModal(help_tab);
  }


  if (account_swap = $("account_swap_link")) {
    var switch_form = $("account_switcher");

    account_swap.observe("click", function(event) {
      account_swap.hide();
      switch_form.show();

      try {
        switch_form.down('select').focus();
      } catch(e) {}

      event.stop();
      return false;
    });

    switch_form.down("select").observe("change", function() {
      switch_form.down("form").submit();
      switch_form.down("select").disable();
    });

    switch_form.down("select").observe('blur', function() {
      switch_form.hide()
      account_swap.show()
    });







  }

});

function init_entry_form() {
  var first_load = !window.entry_log_form;

  window.entry_log_form = new EntryLogForm();

  if (first_load) {
    window.entry_log_form.init_shortcuts();
  }
}

/* toggles the log entry section */
EntryToggler = Class.create({

  initialize : function(url) {

    this.container = $("log_entry");
    this.trigger   = $("log_tab");
    this.image     = this.trigger.down("img");

    this.url = url;

    this.start();

  },

  start : function() {

    this.trigger.observe("click", this.toggle.bindAsEventListener(this));

  },

  stop : function() {

    this.trigger.stopObserving("click");

  },

  toggle : function() {

    if (Prototype.Browser.IE) {
      if (this.container.visible()) {
        this.container.hide();
      } else {
        this.container.show();
      }
      this.save_state(this.container.visible());
      return;
    }

    var effect_name = (this.container.visible()) ? "BlindUp" : "BlindDown";
    var options     = {
                        duration : 0.3,
                        onStart : this.stop.bind(this),
                        afterFinish : function() {
                          this.trigger.toggleClassName("off");
                          this.start.bind(this);
                        }.bind(this)
                      };

    eval("new Effect." + effect_name + "(this.container, options)");

    this.save_state(!this.container.visible());

  },

  save_state : function(state) {

    new Ajax.Request(this.url,
      {
        method     : "put",
        parameters : { "visible" : state }
      }
    );

  }

});

Object.extend(Number.prototype, {

  to_currency: function (options) {
    try {
      var options   = options || {};
      var precision = options["precision"] || 2;
      var unit      = (typeof options["unit"] != undefined) ? options["unit"] : "$";
      var separator = precision > 0 ? options["separator"] || "." : "";
      var delimiter = options["delimiter"] || ",";

      var parts = parseFloat(this).toFixed(precision).split('.');
      return unit + parseFloat(parts[0]).with_delimiter(delimiter) + separator + parts[1].toString();
    } catch(e) {
     return this;
    }
  },

  with_delimiter: function (delimiter, separator) {
    try {
      var delimiter = delimiter || ",";
      var separator = separator || ".";

      var parts = this.toString().split('.');
      parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + delimiter);
      return parts.join(separator);
    } catch(e) {
     return this;
    }
  }
});

Object.extend(Array.prototype, {
  /**
   * Find the [first] indexOf a specified string (str) in the array, where the indexOf of that string
   * is the element index in the array where that string is present at the start of the element.
   *
   * Returns -1 when there is no matching element.
   */
  strIndexOfIncludes : function(str, ignoreCase) {
    for (var i = 0; i < this.length; i++) {
      if ((ignoreCase && this[i].toLowerCase().startsWith(str)) || this[i].startsWith(str))
        return i;
    }
    return -1;
  }
});

Element.addMethods({

  getSelection: function(element) {
          if ("selectionStart" in element) {
                  return {
                          start: element.selectionStart,
                          end: element.selectionEnd
                  }
          }

          var bookmark = document.selection.createRange().getBookmark()
          var selection = element.createTextRange()
          selection.moveToBookmark(bookmark)

          var before = element.createTextRange()
          before.collapse(true)
          before.setEndPoint("EndToStart", selection)

          var beforeLength = before.text.length
          var selLength = selection.text.length

          return {
                  start: beforeLength,
                  end: beforeLength + selLength
          }
  }

});

HelpModal = Class.create(Modal, {

  initialize: function($super, trigger) {

    this.help_request_url = MinuteDock.help_request_url;

    this.element = $("help_tab_modal");

    this.element.select(".close").invoke("observe", "click", this.hide.bindAsEventListener(this));

    $(trigger).observe("click", this.show.bindAsEventListener(this));


  },

  show: function($super) {

    this.element.down(".thanks").hide();
    this.element.down("form").show().reset();

    this.element.down("form").observe("submit", this.send_help_request.bindAsEventListener(this));

    $super();

  },

  send_help_request: function(event) {

    this.element.down(".thanks").show();
    this.element.down("form").hide();

    new Ajax.Request(this.help_request_url, {

      parameters : this.element.down("form").serialize()

    });

    this.element.down("form").stopObserving("submit");

    event.stop();
    return false;

  }


});

DemoLogoutModal = Class.create(Modal, {

  initialize: function($super, logout_link) {

    this.element = $("demo_logout_modal");

    $(logout_link).observe('click', this.logout_clicked.bindAsEventListener(this));

    this.element.select(".close").invoke("observe", "click", this.hide.bindAsEventListener(this));

  },

  logout_clicked : function(e) {
    e.stop();
    this.show();


    return false;
  }

});

WhyXeroConnectModal = Class.create(Modal, {
  initialize : function($super) {
    this.element = $('why_xero_connect_modal');
  },

  execute_xero_login : function() {
    this.sleep();

    new XeroLogin(false, function() {

      if (this.afterConnect) this.afterConnect();

      return true;

    }.bind(this), { onCancel : this.wake.bind(this) });
  },

  sleep : Modal.prototype.hide,
  wake  : Modal.prototype.show

});


SaveAccountModal = Class.create(Modal, {
  initialize : function($super) {

    this.element = $("save_account_modal");

    this.message = this.element.down('.message');

    this.bind_to_actions();
  },

  setMessage : function(message) {
    if (message) this.message.update(message);
  },

  send_mixpanel : function(uri) {
    new Ajax.Request(uri);
  },

  show_from_modal : function(modal, skip_intro, mixpanel) {
    modal.hide();
    skip_intro ? this.show_without_intro() : this.show();

    if (mixpanel)
      this.send_mixpanel(mixpanel);
  },

  show_without_intro : function() {
    document.location = '/signup/save'
  },


  bind_to_actions : function() {
    var default_message = null;

    this._bind_to_submit('form.edit_user input[name="commit"]', default_message);

    this._bind_to_link('#add_user_link', default_message);
    this._bind_to_link('.payment_button', default_message);

    this._bind_to_submit('form.edit_account input[name="commit"]', default_message);

    this._bind_to_function(window, 'sync_with_xero', default_message);
    this._bind_to_function(window, 'send_invoice_to_xero', default_message);
  },


  _bind_to_link  : function(link_selector, message) {
    var links = $$(link_selector);

    links.each(function(link) {
      this._bind_to(link, 'click', message);
    }.bind(this))
  },

  _bind_to_function : function(obj, name, message) {
    obj[name] = function() {
      this.setMessage(message);

      this.show();
      this.message.show();
      return false;
    }.bind(this);
  },

  _bind_to_submit : function(submit_button, message) {
    var buttons = $$(submit_button);

    buttons.each(function(button) {
      var form   = button ? button.up('form') : null;

      this._bind_to(button, 'click',  message);
      if (form) this._bind_to(form, 'submit', message);
    }.bind(this));
  },

  _bind_to : function(obj, event, message) {
    obj.stopObserving(event);
    obj.observe(event, function(e) {
      this.setMessage(message);

      this.show();
      this.message.show();
      e.stop();
      return false;
    }.bindAsEventListener(this));
  }

});





AccountCreationModal = Class.create(Modal, {

  descriptions: $A(["Reticulating splines", "Engaging hyperdrive", "Playing cards",
                    "Twiddling thumbs", "Watching the clouds", "Grabbing a coffee",
                      "Knitting a sweater", "Folding paper airplanes", "Prodding developers"]),

  initialize: function($super) {

    $super("account_being_created");

    new PeriodicalExecuter(this.change_description.bind(this), 2);
    this.change_description();

    new PeriodicalExecuter(this.poll.bind(this), 2);

  },

  change_description: function() {

    var rand = Math.floor(Math.random() * this.descriptions.size());

    this.element.down(".status").update(this.descriptions[rand] + "...");

  },

  poll: function() {

    var rand = Math.floor(Math.random() * 1000000)

    new Ajax.Request("/account/is_pending_creation.json", {

      method    : "get",
      onSuccess : function(transport) {

        if (transport.responseJSON) {
        } else {
          window.location.reload();
        }

      }.bind(this)

    });

  }

});


ClientViewNotificationModal = Class.create(Modal, {
  initialize : function($super, url) {
    this.send_email_url = url;
  },

  redom : function() {
    this.element = $("client_view_notification_modal");
  },

  show : function($super) {
    this.redom();

    this.element.down("form").reset();
    this.element.down("form").observe("submit", this.send_notification.bindAsEventListener(this));
    $super();
  },

  send_notification : function(event) {

    this.element.down("input[type=submit]").disable().value = "Sending...";

    new Ajax.Request(this.send_email_url, {

      parameters : this.element.down("form").serialize(),

      onSuccess  : function() {

        this.element.down("input[name='commit']").enable();
        this.element.down("input[name='commit']").value = "Send";

        this.hide();

        $("email_display").update($F(this.element.down("input[name=email]")));

        new Modal("sent_modal");

      }.bind(this),

      onFailure  : function(transport) {

        this.element.down("input[type=submit]").enable().value = "Send";
        this.element.down(".errorExplanation").show().update(transport.responseText);

      }.bind(this)

    });

    event.stop();
    return false;
  }


});




ShortcutHandler = Class.create({
  KEYS : {
    SHIFT_EIGHT : 42,
    SHIFT_DOT   : 62,
    SHIFT_ONE   : 33,

    ONE   : 49,
    EIGHT : 56,
    DOT   : 190
  },

  shortcuts : [],
  keydowns : [],

  initialize : function() {
    var self = this;
    Event.observe(window, 'load', function() {
      Event.observe(document, 'keydown', function(e) {
        return self.handleEvent(e, 'keydown');
      })

      Event.observe(document, 'keypress', function(e) {
        return self.handleEvent(e, 'keypress');
      })

    })
  },

  handleEvent : function(e, eventType) {

    var shouldInfo = this.shouldHandle(e);
    var shouldHandle = shouldInfo[0], input = shouldInfo[1];

    var code;
    if (e.keyCode) code = e.keyCode;
    else if (e.which) code = e.which;

    if (!code) return;

    var shortcuts = eventType == 'keypress' ? this.shortcuts : this.keydowns;

    shortcuts.each(function(shortcut) {
      var key = shortcut[0], opts = shortcut[1], handler = shortcut[2];

      if (code == key) {

        var workOnInput = opts.workOnInput;

        if (workOnInput && typeof workOnInput == 'function')
          workOnInput = workOnInput();

        if (shouldHandle || opts.always || (input && workOnInput && workOnInput == input)) {

          if (!opts.handleIf || opts.handleIf()) {
            if ((!opts.ctrl || e.ctrlKey) && (!opts.alt || e.altKey) && (!opts.shift || e.shiftKey)) {
              return handler(e);
            }
          }
        }
      }

    });

  },

  shouldHandle : function(e) {
    var element;
		if (e.target) {
		  element = e.target;
		} else if (e.srcElement) {
		  element = e.srcElement;
		}

		if (element) {
		  if(element.nodeType == 3)
		    element=element.parentNode;   // If element is textNode

		  if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') {
		    return [false, element];
		  }
		}

		return [true, null];
  },

  addShortcut : function(key, opts, handler, eventType) {

    eventType = eventType || 'keypress'

    if (typeof opts == 'function' && !handler) {
      handler = opts;
      opts = {};
    } else if (!handler) {
      handler = function() {};
    }

    if (eventType == 'keypress') this.shortcuts.push([key, opts||{}, handler]);
    else this.keydowns.push([key, opts||{}, handler])
  }

});
ShortcutHandler.getInstance = function() {
  if (!ShortcutHandler.instance) {
    ShortcutHandler.instance = new ShortcutHandler();
  }
  return ShortcutHandler.instance;
}
ShortcutHandler.KEYS = ShortcutHandler.getInstance().KEYS;
ShortcutHandler.getInstance(); // Create instance

ShortcutHandler.getInstance().addShortcut(27, { always : true }, Modal.hide_all, 'keydown');


function get_render_length(string, element) {

  x = new Element("div").update(string);

  x.setStyle({

    position      : "absolute",
    left          : "-9999px",
    fontSize      : element.getStyle('fontSize'),
    fontFamily    : element.getStyle('fontFamily'),
    letterSpacing : element.getStyle('letterSpacing')

  });

  $(document.body).insert({ bottom : x });

  var w = x.getWidth();
  x.remove();

  return w;

}




Object.extend(Date, {
  WEEKDAYS : ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
  MONTHS   : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
});



/**
 * Define console sinks.
 */
if (typeof console == 'undefined') {
	console = {};
}

['log',      'debug', 'info',    'warn',    'error', 'groupCollapsed',
 'assert',   'dir',   'dirxml',  'trace',   'group', 'count',
 'groupEnd', 'time',  'timeEnd', 'profile', 'profileEnd'].each(function(method) {
	if (!console[method]) {
		console[method] = function() {  };
	}
});


